Repository: kbsali/php-redmine-api (php)
Issue: Unassigne user from issue
Hi,

I would like to unassigne a user from an issue but without assigning the issue to an another user.

It is possible to do it with a PUT request to `issues/_issue_id_.json` endpint with the following body:
```
{
    "issue": {
        "assigned_to_id": ""
    }
}
```
I tried the same with this client using the following code snippet:
```
$redmineClient->issue->update($issueId, [
    'assigned_to_id' => ''
]);
```
But the `sanitizeParams()` method eliminates the empty `assigned_to_id` field.

So, my question is: how can I unassigne a user from an issue and leave the issue unassigned?
Pull request description: Allow unassign user from an issue
This PR should fix #308. A user could be unassign from an issue with this code:

```php
$client->issue->update($issueId, [
    'assigned_to_id' => ''
]);
```

However there is a bug in Redmine when the REST API is accessed in XML format. See https://www.redmine.org/issues/33744

So this bugfix will only work after the bug in Redmine is fixed. A temporary workaround is posted here: https://github.com/kbsali/php-redmine-api/issues/308#issuecomment-1048732062

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e27bb54..84705df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ## [Unreleased](https://github.com/kbsali/php-redmine-api/compare/v2.1.0...v2.x)
 
+### Fixed
+
+- Allow unassign user from an issue
+
 ## [v2.1.1](https://github.com/kbsali/php-redmine-api/compare/v2.1.0...v2.1.1) - 2022-01-15
 
 ### Fixed
diff --git a/src/Redmine/Api/Issue.php b/src/Redmine/Api/Issue.php
index 7841878..c52393c 100644
--- a/src/Redmine/Api/Issue.php
+++ b/src/Redmine/Api/Issue.php
@@ -160,9 +160,14 @@ class Issue extends AbstractApi
             'due_date' => null,
         ];
         $params = $this->cleanParams($params);
-        $params = $this->sanitizeParams($defaults, $params);
+        $sanitizedParams = $this->sanitizeParams($defaults, $params);
 
-        $xml = $this->buildXML($params);
+        // Allow assigned_to_id to be `` (empty string) to unassign a user from an issue
+        if (array_key_exists('assigned_to_id', $params) && $params['assigned_to_id'] === '') {
+            $sanitizedParams['assigned_to_id'] = '';
+        }
+
+        $xml = $this->buildXML($sanitizedParams);
 
         return $this->put('/issues/'.$id.'.xml', $xml->asXML());
     }


Test patch:
diff --git a/tests/Unit/Api/IssueTest.php b/tests/Unit/Api/IssueTest.php
index 2c6a89d..50274df 100644
--- a/tests/Unit/Api/IssueTest.php
+++ b/tests/Unit/Api/IssueTest.php
@@ -887,4 +887,70 @@ class IssueTest extends TestCase
         // Perform the tests
         $api->create($parameters);
     }
+
+    /**
+     * Test assign an user to an issue
+     *
+     * @test
+     */
+    public function testAssignUserToAnIssue()
+    {
+        // Test values
+        $parameters = [
+            'assigned_to_id' => 5,
+        ];
+
+        // Create the used mock objects
+        $client = $this->createMock(Client::class);
+        $client->expects($this->once())
+            ->method('requestPut')
+            ->with(
+                '/issues/5.xml',
+                $this->logicalAnd(
+                    $this->stringStartsWith('<?xml version="1.0"?>'."\n".'<issue>'),
+                    $this->stringContains('<assigned_to_id>5</assigned_to_id>'),
+                    $this->stringEndsWith('</issue>'."\n"),
+
+                )
+            );
+
+        // Create the object under test
+        $api = new Issue($client);
+
+        // Perform the tests
+        $api->update(5, $parameters);
+    }
+
+    /**
+     * Test unassign an user from an issue
+     *
+     * @test
+     */
+    public function testUnassignUserFromAnIssue()
+    {
+        // Test values
+        $parameters = [
+            'assigned_to_id' => "",
+        ];
+
+        // Create the used mock objects
+        $client = $this->createMock(Client::class);
+        $client->expects($this->once())
+            ->method('requestPut')
+            ->with(
+                '/issues/5.xml',
+                $this->logicalAnd(
+                    $this->stringStartsWith('<?xml version="1.0"?>'."\n".'<issue>'),
+                    $this->stringContains('<assigned_to_id></assigned_to_id>'),
+                    $this->stringEndsWith('</issue>'."\n"),
+
+                )
+            );
+
+        // Create the object under test
+        $api = new Issue($client);
+
+        // Perform the tests
+        $api->update(5, $parameters);
+    }
 }