├── src ├── Project │ ├── Component.php │ ├── ProjectType.php │ └── Project.php ├── User │ ├── User.php │ └── UserService.php ├── GreenHopperTrait.php ├── Epic │ ├── EpicColor.php │ ├── Epic.php │ └── EpicService.php ├── ServiceDesk │ ├── DataObjectTrait.php │ ├── Customer │ │ ├── CustomerLinks.php │ │ ├── Customer.php │ │ └── CustomerService.php │ ├── Organisation │ │ ├── Organisation.php │ │ └── OrganisationService.php │ ├── Request │ │ ├── RequestStatus.php │ │ └── Request.php │ ├── Comment │ │ ├── Comment.php │ │ └── CommentService.php │ ├── ServiceDeskClient.php │ ├── Attachment │ │ └── AttachmentService.php │ └── Participant │ │ └── ParticipantService.php ├── JsonSerializableTrait.php ├── Issue │ ├── Statuscategory.php │ ├── AgileIssue.php │ ├── AgileIssueFields.php │ ├── TransitionTo.php │ ├── Component.php │ ├── ContentField.php │ ├── Comments.php │ ├── SecurityScheme.php │ ├── History.php │ ├── ChangeLog.php │ ├── Priority.php │ ├── IssueStatus.php │ ├── IssueType.php │ ├── RemoteObject.php │ ├── VersionUnresolvedCount.php │ ├── Attachment.php │ ├── Visibility.php │ ├── Issue.php │ ├── VisibilityTrait.php │ ├── CustomFieldUsage.php │ ├── AgileIssueService.php │ ├── RemoteIssueLink.php │ ├── Comment.php │ ├── IssueBulkResult.php │ ├── VersionIssueCounts.php │ ├── IssueSearchResult.php │ ├── PaginatedWorklog.php │ ├── Transition.php │ ├── Reporter.php │ ├── Notify.php │ ├── Version.php │ ├── TimeTracking.php │ └── Worklog.php ├── Board │ ├── BoardColumnConfig.php │ ├── BoardColumn.php │ ├── Board.php │ ├── Location.php │ └── BoardService.php ├── Role │ └── Role.php ├── ServiceDeskTrait.php ├── NoOperationMonologHandlerV3.php ├── Group │ ├── GroupUser.php │ ├── GroupSearchResult.php │ ├── Group.php │ └── GroupService.php ├── IssueLink │ ├── LinkedIssue.php │ ├── IssueLinkType.php │ ├── IssueLink.php │ └── IssueLinkService.php ├── Status │ ├── Status.php │ └── StatusService.php ├── NoOperationMonologHandler.php ├── AssigneeTypeEnum.php ├── Auth │ ├── SessionInfo.php │ ├── AuthSession.php │ ├── CurrentUser.php │ ├── LoginInfo.php │ └── AuthService.php ├── Request │ ├── Author.php │ ├── RequestComment.php │ └── RequestService.php ├── ADF │ ├── ADFListItemTypes.php │ ├── ADFMarkType.php │ └── AtlassianDocumentFormat.php ├── JiraException.php ├── Field │ ├── Schema.php │ ├── Field.php │ └── FieldService.php ├── IssueType │ └── IssueTypeService.php ├── JiraCloudApiServiceProvider.php ├── Dumper.php ├── StatusCategory │ └── StatusCategoryService.php ├── JsonMapperHelper.php ├── RapidCharts │ └── ScopeChangeBurnDownChartService.php ├── Configuration │ ├── ArrayConfiguration.php │ ├── ConfigurationInterface.php │ ├── DotEnvConfiguration.php │ └── AbstractConfiguration.php ├── Priority │ └── PriorityService.php ├── ClassSerialize.php ├── DynamicPropertiesTrait.php ├── Sprint │ ├── Sprint.php │ ├── SprintSearchResult.php │ └── SprintService.php ├── Attachment │ └── AttachmentService.php ├── Component │ ├── Component.php │ └── ComponentService.php └── Version │ └── VersionService.php ├── phpstan.neon.dist ├── LICENSE ├── composer.json ├── test-data ├── comment.json ├── issueFieldV3.json └── issueField.json └── apiary.apib /src/Project/Component.php: -------------------------------------------------------------------------------- 1 | setAPIUri('/rest/greenhopper/'.$version); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Epic/EpicColor.php: -------------------------------------------------------------------------------- 1 | setAPIUri($uri); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Board/BoardColumn.php: -------------------------------------------------------------------------------- 1 | name = $name; 13 | } 14 | 15 | #[\ReturnTypeWillChange] 16 | public function jsonSerialize() 17 | { 18 | return array_filter(get_object_vars($this)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ServiceDesk/Customer/CustomerLinks.php: -------------------------------------------------------------------------------- 1 | content = []; 14 | } 15 | 16 | #[\ReturnTypeWillChange] 17 | public function jsonSerialize() 18 | { 19 | return array_filter(get_object_vars($this)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/NoOperationMonologHandler.php: -------------------------------------------------------------------------------- 1 | 'PROJECT_LEAD', 15 | AssigneeTypeEnum::COMPONENT_LEAD => 'COMPONENT_LEAD', 16 | AssigneeTypeEnum::UNASSIGNED => 'UNASSIGNED', 17 | }; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Auth/SessionInfo.php: -------------------------------------------------------------------------------- 1 | _links = $links; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Issue/ChangeLog.php: -------------------------------------------------------------------------------- 1 | statusDate = new DateTime($statusDate['iso8601']); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 KwangSeob Jeong(lesstif@gmail.com) 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/Issue/Priority.php: -------------------------------------------------------------------------------- 1 | 'orderedList', 24 | ADFListItemTypes::BULLET_LIST => 'bulletList', 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Issue/RemoteObject.php: -------------------------------------------------------------------------------- 1 | self = $self; 20 | 21 | return $this; 22 | } 23 | 24 | public function setIssuesUnresolvedCount(int $issuesUnresolvedCount): static 25 | { 26 | $this->issuesUnresolvedCount = $issuesUnresolvedCount; 27 | 28 | return $this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/JiraException.php: -------------------------------------------------------------------------------- 1 | response = $response; 25 | } 26 | 27 | /** 28 | * Get error response. 29 | */ 30 | public function getResponse(): ?string 31 | { 32 | return $this->response; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Status/StatusService.php: -------------------------------------------------------------------------------- 1 | exec($this->uri.'/', null); 23 | $this->log->info("Result=\n".$ret); 24 | 25 | return $this->json_mapper->mapArray( 26 | json_decode($ret, false), 27 | new \ArrayObject(), 28 | \JiraCloud\Status\Status::class 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Issue/Attachment.php: -------------------------------------------------------------------------------- 1 | exec($this->uri.'/', null); 24 | $this->log->info("Result=\n".$ret); 25 | 26 | return $this->json_mapper->mapArray( 27 | json_decode($ret, false), 28 | new \ArrayObject(), 29 | IssueType::class 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Issue/Visibility.php: -------------------------------------------------------------------------------- 1 | type = $type; 17 | } 18 | 19 | public function setValue(string $value) 20 | { 21 | $this->value = $value; 22 | } 23 | 24 | public function getType(): string 25 | { 26 | return $this->type; 27 | } 28 | 29 | public function getValue(): string 30 | { 31 | return $this->value; 32 | } 33 | 34 | #[\ReturnTypeWillChange] 35 | public function jsonSerialize() 36 | { 37 | return array_filter(get_object_vars($this)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/JiraCloudApiServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind(ConfigurationInterface::class, function () { 31 | return new DotEnvConfiguration(base_path()); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Dumper.php: -------------------------------------------------------------------------------- 1 | dump((new VarCloner())->cloneVar($value)); 21 | } else { 22 | var_dump($value); 23 | } 24 | } 25 | 26 | public static function dd($x) 27 | { 28 | array_map(function ($x) { 29 | (new self())->dump($x); 30 | }, func_get_args()); 31 | exit(1); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Request/RequestComment.php: -------------------------------------------------------------------------------- 1 | body = $body; 22 | 23 | return $this; 24 | } 25 | 26 | public function setIsPublic(bool $public): static 27 | { 28 | $this->public = $public; 29 | 30 | return $this; 31 | } 32 | 33 | #[\ReturnTypeWillChange] 34 | public function jsonSerialize(): array 35 | { 36 | return array_filter(get_object_vars($this), function ($var) { 37 | return $var !== null; 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/StatusCategory/StatusCategoryService.php: -------------------------------------------------------------------------------- 1 | exec($this->uri.'/', null); 24 | $this->log->info("Result=\n".$ret); 25 | 26 | return $this->json_mapper->mapArray( 27 | json_decode($ret, false), 28 | new \ArrayObject(), 29 | \JiraCloud\Issue\Statuscategory::class 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Issue/Issue.php: -------------------------------------------------------------------------------- 1 | 'code', 24 | ADFMarkType::strong => 'strong', 25 | ADFMarkType::em => 'em', 26 | ADFMarkType::link => 'link', 27 | ADFMarkType::strike => 'strike', 28 | ADFMarkType::subsup => 'subsup', 29 | ADFMarkType::textColor => 'textColor', 30 | ADFMarkType::underline => 'underline', 31 | }; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Group/Group.php: -------------------------------------------------------------------------------- 1 | name = $name; 48 | 49 | return $this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Issue/VisibilityTrait.php: -------------------------------------------------------------------------------- 1 | visibility = $type; 10 | 11 | return $this; 12 | } 13 | 14 | public function setVisibilityAsArray(array $array): static 15 | { 16 | if (is_null($this->visibility)) { 17 | $this->visibility = new Visibility(); 18 | } 19 | 20 | $this->visibility->setType($array['type']); 21 | $this->visibility->setValue($array['value']); 22 | 23 | return $this; 24 | } 25 | 26 | public function setVisibilityAsString(string $type, string $value): static 27 | { 28 | if (is_null($this->visibility)) { 29 | $this->visibility = new Visibility(); 30 | } 31 | 32 | $this->visibility->setType($type); 33 | $this->visibility->setValue($value); 34 | 35 | return $this; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/JsonMapperHelper.php: -------------------------------------------------------------------------------- 1 | {$propName} = $jsonValue; 22 | $object->customFields[$propName] = $jsonValue; 23 | } 24 | } elseif (isset($object->{$propName})) { 25 | $object->{$propName} = $jsonValue; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/RapidCharts/ScopeChangeBurnDownChartService.php: -------------------------------------------------------------------------------- 1 | setupAPIUri(); 19 | } 20 | 21 | public function getBurnDownChartData($rapidViewId, $sprintId, $paramArray = []) 22 | { 23 | $paramArray['rapidViewId'] = $rapidViewId; 24 | $paramArray['sprintId'] = $sprintId; 25 | $json = $this->exec($this->uri.'/'.$this->toHttpQueryParameter($paramArray), null); 26 | $burnDownChart = json_decode($json); 27 | 28 | return $burnDownChart; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ADF/AtlassianDocumentFormat.php: -------------------------------------------------------------------------------- 1 | document = (new Document()) 25 | ->paragraph() 26 | ->text($document) 27 | ->end(); 28 | 29 | return; 30 | } 31 | 32 | $this->document = $document; 33 | } 34 | 35 | #[\ReturnTypeWillChange] 36 | public function jsonSerialize(): array 37 | { 38 | return $this->document->jsonSerialize(); 39 | } 40 | 41 | public function setDocument(Document|Node $document) 42 | { 43 | $this->document = $document; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ServiceDesk/Customer/Customer.php: -------------------------------------------------------------------------------- 1 | jiraRest = $data['jiraRest']; 37 | $links->avatarUrls = $data['avatarUrls']; 38 | } 39 | 40 | $this->_links = $links; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Issue/CustomFieldUsage.php: -------------------------------------------------------------------------------- 1 | fieldName = $fieldName; 29 | 30 | return $this; 31 | } 32 | 33 | public function setCustomFieldId($customFieldId) 34 | { 35 | $this->customFieldId = $customFieldId; 36 | 37 | return $this; 38 | } 39 | 40 | public function setIssueCountWithVersionInCustomField($issueCountWithVersionInCustomField) 41 | { 42 | $this->issueCountWithVersionInCustomField = $issueCountWithVersionInCustomField; 43 | 44 | return $this; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Issue/AgileIssueService.php: -------------------------------------------------------------------------------- 1 | exec($this->uri.'/'.$issueIdOrKey.$this->toHttpQueryParameter($paramArray), null); 20 | 21 | try { 22 | return $this->json_mapper->map( 23 | json_decode($response, false, 512, $this->getJsonOptions()), 24 | new AgileIssue() 25 | ); 26 | } catch (\JsonException $exception) { 27 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 28 | 29 | return null; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Configuration/ArrayConfiguration.php: -------------------------------------------------------------------------------- 1 | jiraLogEnabled = true; 13 | $this->jiraLogFile = 'jira-rest-client.log'; 14 | $this->jiraLogLevel = 'WARNING'; 15 | $this->curlOptSslVerifyHost = false; 16 | $this->curlOptSslVerifyPeer = false; 17 | $this->curlOptSslCert = ''; 18 | $this->curlOptSslCertPassword = ''; 19 | $this->curlOptSslKey = ''; 20 | $this->curlOptSslKeyPassword = ''; 21 | $this->curlOptVerbose = false; 22 | $this->cookieAuthEnabled = false; 23 | $this->cookieFile = 'jira-cookie.txt'; 24 | $this->curlOptUserAgent = $this->getDefaultUserAgentString(); 25 | $this->serviceDeskId = null; 26 | 27 | $this->personalAccessToken = ''; 28 | 29 | foreach ($configuration as $key => $value) { 30 | if (property_exists($this, $key)) { 31 | $this->$key = $value; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Issue/RemoteIssueLink.php: -------------------------------------------------------------------------------- 1 | object = new RemoteObject(); 28 | } 29 | 30 | public function setUrl(string $url): static 31 | { 32 | $this->globalId = $url; 33 | $this->object->url = $url; 34 | 35 | return $this; 36 | } 37 | 38 | public function setTitle($title): static 39 | { 40 | $this->object->title = $title; 41 | 42 | return $this; 43 | } 44 | 45 | public function setSummary($summary): static 46 | { 47 | $this->object->summary = $summary; 48 | 49 | return $this; 50 | } 51 | 52 | public function setRelationship($relationship): static 53 | { 54 | $this->relationship = $relationship; 55 | 56 | return $this; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/ServiceDesk/Comment/Comment.php: -------------------------------------------------------------------------------- 1 | id = (int) $id; 29 | } 30 | 31 | public function setAuthor(array $author): void 32 | { 33 | $this->author = new Reporter(); 34 | foreach ($author as $key => $value) { 35 | if (property_exists($this->author, $key)) { 36 | $this->author->$key = $value; 37 | } 38 | } 39 | } 40 | 41 | public function setCreated(array $created): void 42 | { 43 | $this->created = new DateTime($created['iso8601']); 44 | } 45 | 46 | public function setLinks(array $links): void 47 | { 48 | $this->_links = $links; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lesstif/jira-cloud-restapi", 3 | "description": "JIRA Cloud REST API", 4 | "type": "library", 5 | "keywords": ["jira", "rest", "jira-php", "jira-rest", "jira-cloud"], 6 | "require": { 7 | "php": "^8.1", 8 | "ext-curl": "*", 9 | "ext-json": "*", 10 | "netresearch/jsonmapper": "^3.0|^4.0|^5.0", 11 | "monolog/monolog": "^2.0|^3.0", 12 | "vlucas/phpdotenv": "^5.0|^6.0", 13 | "damienharper/adf-tools": "^1.0" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "^9.0|^10.0", 17 | "mockery/mockery": "^1.0|^2.0", 18 | "symfony/var-dumper": "^5.0|^6.0|^7.0", 19 | "phpstan/phpstan": "^1.0|^2.0" 20 | }, 21 | "license": "Apache-2.0", 22 | "authors": [ 23 | { 24 | "name": "KwangSeob Jeong", 25 | "email": "lesstif@gmail.com", 26 | "homepage": "https://lesstif.atlassian.net/" 27 | } 28 | ], 29 | "autoload": { 30 | "psr-4" : { 31 | "JiraCloud\\" : "src" 32 | } 33 | }, 34 | "autoload-dev": { 35 | "psr-4" : { 36 | "JiraCloud\\Test\\" : "tests" 37 | } 38 | }, 39 | "extra": { 40 | "laravel": { 41 | "providers": [ 42 | "JiraCloud\\JiraCloudApiServiceProvider" 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Issue/Comment.php: -------------------------------------------------------------------------------- 1 | body = json_decode(json_encode($body), true); 43 | 44 | return $this; 45 | } 46 | 47 | public function setBodyByAtlassianDocumentFormat(Document|Node $body): static 48 | { 49 | $this->body = $body->jsonSerialize(); 50 | 51 | return $this; 52 | } 53 | 54 | #[\ReturnTypeWillChange] 55 | public function jsonSerialize(): array 56 | { 57 | return array_filter(get_object_vars($this), function ($var) { 58 | return !is_null($var); 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Priority/PriorityService.php: -------------------------------------------------------------------------------- 1 | exec($this->uri, null); 25 | 26 | $this->log->info("Result=\n".$ret); 27 | 28 | $priorityData = json_decode($ret); 29 | $priorities = []; 30 | 31 | foreach ($priorityData as $priority) { 32 | $priorities[] = $this->json_mapper->map($priority, new Priority()); 33 | } 34 | 35 | return $priorities; 36 | } 37 | 38 | /** 39 | * get specific priority info. 40 | * 41 | * @param string|int $priorityId priority id 42 | * 43 | * @throws \JsonMapper_Exception 44 | * @throws \JiraCloud\JiraException 45 | * 46 | * @return \JiraCloud\Issue\Priority 47 | */ 48 | public function get($priorityId) 49 | { 50 | $ret = $this->exec($this->uri."/$priorityId", null); 51 | 52 | $this->log->info("Result=\n".$ret); 53 | 54 | $priority = $this->json_mapper->map(json_decode($ret), new Priority()); 55 | 56 | return $priority; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Board/Board.php: -------------------------------------------------------------------------------- 1 | id; 36 | } 37 | 38 | /** 39 | * Get board url. 40 | */ 41 | public function getSelf() 42 | { 43 | return $this->self; 44 | } 45 | 46 | /** 47 | * Get board name. 48 | */ 49 | public function getName() 50 | { 51 | return $this->name; 52 | } 53 | 54 | /** 55 | * Get board type. 56 | */ 57 | public function getType() 58 | { 59 | return $this->type; 60 | } 61 | 62 | /** 63 | * Get location. 64 | */ 65 | public function getLocation() 66 | { 67 | return $this->location; 68 | } 69 | 70 | #[\ReturnTypeWillChange] 71 | public function jsonSerialize() 72 | { 73 | return array_filter(get_object_vars($this), function ($var) { 74 | return !is_null($var); 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ClassSerialize.php: -------------------------------------------------------------------------------- 1 | $value) { 21 | if ($excludeMode === true) { 22 | if (!in_array($key, $ignoreProperties)) { 23 | $retAr[$key] = $value; 24 | } 25 | } else { // include mode 26 | if (in_array($key, $ignoreProperties)) { 27 | $retAr[$key] = $value; 28 | } 29 | } 30 | } 31 | 32 | return $retAr; 33 | } 34 | 35 | /** 36 | * class property to String. 37 | * 38 | * @param array $ignoreProperties this properties to be excluded from String. 39 | * @param bool $excludeMode 40 | * 41 | * @return string 42 | */ 43 | public function toString(array $ignoreProperties = [], bool $excludeMode = true): string 44 | { 45 | $ar = $this->toArray($ignoreProperties, $excludeMode); 46 | 47 | return json_encode($ar, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Issue/IssueBulkResult.php: -------------------------------------------------------------------------------- 1 | issueErrors; 31 | } 32 | 33 | /** 34 | * @param array $issueErrors 35 | */ 36 | public function setIssueErrors($issueErrors) 37 | { 38 | $this->issueErrors = $issueErrors; 39 | } 40 | 41 | /** 42 | * @return Issue[] 43 | */ 44 | public function getIssues() 45 | { 46 | return $this->issues; 47 | } 48 | 49 | /** 50 | * @param Issue[] $issues 51 | */ 52 | public function setIssues($issues) 53 | { 54 | $this->issues = $issues; 55 | } 56 | 57 | /** 58 | * @param int $ndx 59 | * 60 | * @return Issue 61 | */ 62 | public function getIssue($ndx) 63 | { 64 | return $this->issues[$ndx]; 65 | } 66 | 67 | /** 68 | * @return string 69 | */ 70 | public function getExpand() 71 | { 72 | return $this->expand; 73 | } 74 | 75 | /** 76 | * @param string $expand 77 | */ 78 | public function setExpand($expand) 79 | { 80 | $this->expand = $expand; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/ServiceDesk/ServiceDeskClient.php: -------------------------------------------------------------------------------- 1 | json_mapper->bEnforceMapType = false; 22 | } 23 | 24 | /** 25 | * @inheritDoc 26 | */ 27 | protected function createUrlByContext(string $context): string 28 | { 29 | $host = $this->getConfiguration()->getJiraHost(); 30 | 31 | return sprintf('%s/rest/servicedeskapi/%s', $host, preg_replace('/\//', '', $context, 1)); 32 | } 33 | 34 | public function getLogger(): LoggerInterface 35 | { 36 | return $this->log; 37 | } 38 | 39 | public function getMapper(): JsonMapper 40 | { 41 | return $this->json_mapper; 42 | } 43 | 44 | public function createUrl(string $format, array $parameters, array $urlParameters = []): string 45 | { 46 | if (count($urlParameters) > 0) { 47 | $format .= '?%s'; 48 | $parameters[] = http_build_query($urlParameters); 49 | } 50 | 51 | array_unshift($parameters, $format); 52 | 53 | return call_user_func_array('sprintf', $parameters); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Issue/VersionIssueCounts.php: -------------------------------------------------------------------------------- 1 | self = $self; 31 | 32 | return $this; 33 | } 34 | 35 | public function setIssuesFixedCount(int $issuesFixedCount): static 36 | { 37 | $this->issuesFixedCount = $issuesFixedCount; 38 | 39 | return $this; 40 | } 41 | 42 | public function setIssuesAffectedCount(int $issuesAffectedCount): static 43 | { 44 | $this->issuesAffectedCount = $issuesAffectedCount; 45 | 46 | return $this; 47 | } 48 | 49 | public function setIssueCountWithCustomFieldsShowingVersion(int $issueCountWithCustomFieldsShowingVersion): static 50 | { 51 | $this->issueCountWithCustomFieldsShowingVersion = $issueCountWithCustomFieldsShowingVersion; 52 | 53 | return $this; 54 | } 55 | 56 | public function setCustomFieldUsage(array $customFieldUsage): static 57 | { 58 | $this->customFieldUsage = $customFieldUsage; 59 | 60 | return $this; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/DynamicPropertiesTrait.php: -------------------------------------------------------------------------------- 1 | */ 13 | protected array $dynamicProperties = []; 14 | 15 | /** 16 | * Attempts to retrieve a dynamic property from {@link static::$dynamicProperties}. 17 | * 18 | * @param string $name 19 | * 20 | * @return mixed The requested value if found, `null` otherwise. 21 | */ 22 | public function __get(string $name): mixed 23 | { 24 | return $this->dynamicProperties[$name] ?? null; 25 | } 26 | 27 | /** 28 | * Returns whether the dynamic property `$name` is set (not `null`!) in {@link static::$dynamicProperties}. 29 | * 30 | * @param string $name 31 | * 32 | * @return bool 33 | */ 34 | public function __isset(string $name): bool 35 | { 36 | return isset($this->dynamicProperties[$name]); 37 | } 38 | 39 | /** 40 | * Applies `$value` using `$name` as key on {@link static::$dynamicProperties}. 41 | * 42 | * @param string $name 43 | * @param mixed $value 44 | * 45 | * @return void 46 | */ 47 | public function __set(string $name, mixed $value): void 48 | { 49 | $this->dynamicProperties[$name] = $value; 50 | } 51 | 52 | /** 53 | * Accessor for {@link static::$dynamicProperties}. 54 | * 55 | * @return array 56 | */ 57 | public function getDynamicProperties(): array 58 | { 59 | return $this->dynamicProperties; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Issue/IssueSearchResult.php: -------------------------------------------------------------------------------- 1 | isLast; 36 | } 37 | 38 | /** 39 | * @param bool $isLast 40 | */ 41 | public function setIsLast($isLast) 42 | { 43 | return $this->isLast; 44 | } 45 | 46 | /** 47 | * @return string 48 | * @return string|null 49 | */ 50 | public function getNextPageToken() 51 | { 52 | return $this->nextPageToken; 53 | } 54 | 55 | /** 56 | * @param string $nextPageToken 57 | */ 58 | public function setNextPageToken($nextPageToken) 59 | { 60 | $this->nextPageToken = $nextPageToken; 61 | } 62 | 63 | /** 64 | * @return Issue[] 65 | */ 66 | public function getIssues() 67 | { 68 | return $this->issues; 69 | } 70 | 71 | /** 72 | * @param Issue[] $issues 73 | */ 74 | public function setIssues($issues) 75 | { 76 | $this->issues = $issues; 77 | } 78 | 79 | /** 80 | * @param int $ndx 81 | * 82 | * @return Issue 83 | */ 84 | public function getIssue($ndx) 85 | { 86 | return $this->issues[$ndx]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test-data/comment.json: -------------------------------------------------------------------------------- 1 | { 2 | "self": "https://jira.example.com/rest/api/2/issue/13332/comment/12312", 3 | "id": "12312", 4 | "author": { 5 | "self": "https://jira.example.com/rest/api/2/user?username=lesstif", 6 | "name": "John Doe", 7 | "key": "lesstif", 8 | "emailAddress": "johndoe@example.com", 9 | "avatarUrls": { 10 | "48x48": "https://jira.example.com/secure/useravatar?avatarId=10122", 11 | "24x24": "https://jira.example.com/secure/useravatar?size=small&avatarId=10122", 12 | "16x16": "https://jira.example.com/secure/useravatar?size=xsmall&avatarId=10122", 13 | "32x32": "https://jira.example.com/secure/useravatar?size=medium&avatarId=10122" 14 | }, 15 | "displayName": "John Doe", 16 | "active": true, 17 | "timeZone": "ROK" 18 | }, 19 | "body": "Adds a new comment to an issue.\\r\\n* Bullet 1\\r\\n* Bullet 2\\r\\n** sub Bullet 1\\r\\n** sub Bullet 2", 20 | "updateAuthor": { 21 | "self": "https://jira.example.com/rest/api/2/user?username=lesstif", 22 | "name": "KwangSeob Jeong", 23 | "key": "lesstif", 24 | "emailAddress": "lesstif@gmail.com", 25 | "avatarUrls": { 26 | "48x48": "https://jira.example.com/secure/useravatar?avatarId=10122", 27 | "24x24": "https://jira.example.com/secure/useravatar?size=small&avatarId=10122", 28 | "16x16": "https://jira.example.com/secure/useravatar?size=xsmall&avatarId=10122", 29 | "32x32": "https://jira.example.com/secure/useravatar?size=medium&avatarId=10122" 30 | }, 31 | "displayName": "정광섭", 32 | "active": true, 33 | "timeZone": "ROK" 34 | }, 35 | "created": "2017-12-20T09:00:05.752+0900", 36 | "updated": "2017-12-20T09:00:05.752+0900", 37 | "visibility": { 38 | "type": "role", 39 | "value": "Users" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Board/Location.php: -------------------------------------------------------------------------------- 1 | projectId; 34 | } 35 | 36 | /** 37 | * Get project id. 38 | */ 39 | public function getDisplayName() 40 | { 41 | return $this->displayName; 42 | } 43 | 44 | /** 45 | * Get project name. 46 | */ 47 | public function getProjectName() 48 | { 49 | return $this->projectName; 50 | } 51 | 52 | /** 53 | * Get project key. 54 | */ 55 | public function getProjectKey() 56 | { 57 | return $this->projectKey; 58 | } 59 | 60 | /** 61 | * Get project type key. 62 | */ 63 | public function getProjectTypeKey() 64 | { 65 | return $this->projectTypeKey; 66 | } 67 | 68 | /** 69 | * Get avatar uri. 70 | */ 71 | public function getAvatarUri() 72 | { 73 | return $this->avatarUri; 74 | } 75 | 76 | /** 77 | * Get name. 78 | */ 79 | public function getName() 80 | { 81 | return $this->name; 82 | } 83 | 84 | /** 85 | * {@inheritdoc} 86 | */ 87 | #[\ReturnTypeWillChange] 88 | public function jsonSerialize() 89 | { 90 | return array_filter(get_object_vars($this), function ($var) { 91 | return !is_null($var); 92 | }); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/Issue/PaginatedWorklog.php: -------------------------------------------------------------------------------- 1 | startAt; 40 | } 41 | 42 | /** 43 | * @param int $startAt 44 | */ 45 | public function setStartAt($startAt) 46 | { 47 | $this->startAt = $startAt; 48 | } 49 | 50 | /** 51 | * @return int 52 | */ 53 | public function getMaxResults() 54 | { 55 | return $this->maxResults; 56 | } 57 | 58 | /** 59 | * @param int $maxResults 60 | */ 61 | public function setMaxResults($maxResults) 62 | { 63 | $this->maxResults = $maxResults; 64 | } 65 | 66 | /** 67 | * @return int 68 | */ 69 | public function getTotal() 70 | { 71 | return $this->total; 72 | } 73 | 74 | /** 75 | * @param int $total 76 | */ 77 | public function setTotal($total) 78 | { 79 | $this->total = $total; 80 | } 81 | 82 | /** 83 | * @return \JiraCloud\Issue\Worklog[] Worklogs 84 | */ 85 | public function getWorklogs() 86 | { 87 | return $this->worklogs; 88 | } 89 | 90 | /** 91 | * @param \JiraCloud\Issue\Worklog[] $worklogs 92 | */ 93 | public function setWorklogs($worklogs) 94 | { 95 | $this->worklogs = $worklogs; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Epic/EpicService.php: -------------------------------------------------------------------------------- 1 | setAPIUri('/rest/agile/'.$this->version); 18 | } 19 | 20 | public function getEpic($id, $paramArray = []): ?Epic 21 | { 22 | $response = $this->exec($this->uri.'/'.$id.$this->toHttpQueryParameter($paramArray), null); 23 | 24 | try { 25 | return $this->json_mapper->map( 26 | json_decode($response, false, 512, $this->getJsonOptions()), 27 | new Epic() 28 | ); 29 | } catch (\JsonException $exception) { 30 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 31 | 32 | return null; 33 | } 34 | } 35 | 36 | /** 37 | * @return \ArrayObject|AgileIssue[]|null 38 | */ 39 | public function getEpicIssues($id, $paramArray = []): ?\ArrayObject 40 | { 41 | $response = $this->exec($this->uri.'/'.$id.'/issue'.$this->toHttpQueryParameter($paramArray), null); 42 | 43 | try { 44 | return $this->json_mapper->mapArray( 45 | json_decode($response, false, 512, $this->getJsonOptions())->issues, 46 | new \ArrayObject(), 47 | AgileIssue::class 48 | ); 49 | } catch (\JsonException $exception) { 50 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 51 | 52 | return null; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Issue/Transition.php: -------------------------------------------------------------------------------- 1 | transition)) { 36 | $this->transition = []; 37 | } 38 | 39 | $this->transition['name'] = $name; 40 | } 41 | 42 | /** 43 | * set none translated transition name. 44 | */ 45 | public function setUntranslatedName(string $untranslatedName): void 46 | { 47 | if (is_null($this->transition)) { 48 | $this->transition = []; 49 | } 50 | 51 | $this->transition['untranslatedName'] = $untranslatedName; 52 | } 53 | 54 | public function setTransitionId(string $id): void 55 | { 56 | if (is_null($this->transition)) { 57 | $this->transition = []; 58 | } 59 | 60 | $this->transition['id'] = $id; 61 | } 62 | 63 | public function setCommentBody(AtlassianDocumentFormat $commentBody): void 64 | { 65 | if (is_null($this->update)) { 66 | $this->update = []; 67 | $this->update['comment'] = []; 68 | } 69 | 70 | $ar = []; 71 | $ar['add']['body'] = $commentBody->jsonSerialize(); 72 | 73 | array_push($this->update['comment'], $ar); 74 | } 75 | 76 | #[\ReturnTypeWillChange] 77 | public function jsonSerialize() 78 | { 79 | return array_filter(get_object_vars($this)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Request/RequestService.php: -------------------------------------------------------------------------------- 1 | setupAPIUri(); 30 | } 31 | 32 | /** 33 | * Add the given comment to the specified request based on the provided $issueIdOrKey value. Returns a new 34 | * RequestComment with the response. 35 | * 36 | * @param string|int $issueIdOrKey 37 | * @param RequestComment $requestComment 38 | * 39 | * @throws JiraException 40 | * @throws \JsonMapper_Exception 41 | * 42 | * @return RequestComment 43 | */ 44 | public function addComment($issueIdOrKey, RequestComment $requestComment): RequestComment 45 | { 46 | $this->log->info("addComment=\n"); 47 | 48 | if (empty($requestComment->body)) { 49 | throw new JiraException('comment param must be an instance of RequestComment and have body text.'); 50 | } 51 | 52 | $data = json_encode($requestComment); 53 | 54 | $ret = $this->exec($this->uri."/$issueIdOrKey/comment", $data); 55 | 56 | $this->log->debug('add comment result='.var_export($ret, true)); 57 | $requestComment = $this->json_mapper->map( 58 | json_decode($ret), 59 | new RequestComment() 60 | ); 61 | 62 | return $requestComment; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/IssueLink/IssueLink.php: -------------------------------------------------------------------------------- 1 | type['name'] = $typeName; 39 | 40 | return $this; 41 | } 42 | 43 | /** 44 | * @param string $issueKey inward issue key or id 45 | * 46 | * @return $this 47 | */ 48 | public function setInwardIssueByKey(string $issueKey): static 49 | { 50 | if ($this->inwardIssue === null) { 51 | $this->inwardIssue = new LinkedIssue(); 52 | } 53 | $this->inwardIssue->key = $issueKey; 54 | 55 | return $this; 56 | } 57 | 58 | /** 59 | * @param string $issueKey out ward issue key or id 60 | * 61 | * @return $this 62 | */ 63 | public function setOutwardIssueByKey(string $issueKey): static 64 | { 65 | if ($this->outwardIssue === null) { 66 | $this->outwardIssue = new LinkedIssue(); 67 | } 68 | 69 | $this->outwardIssue->key = $issueKey; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * @param AtlassianDocumentFormat $comment string or \JiraCloud\Issue\Comment instance 76 | * 77 | * @return $this 78 | */ 79 | public function setCommentAsADF(?AtlassianDocumentFormat $comment): static 80 | { 81 | if (!empty($comment)) { 82 | $this->comment['body'] = $comment; 83 | } 84 | 85 | return $this; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Issue/Reporter.php: -------------------------------------------------------------------------------- 1 | $value) { 44 | if ($key === 'name' && ($this->isWantUnassigned() === true)) { 45 | continue; 46 | } elseif ($key === 'wantUnassigned') { 47 | unset($vars[$key]); 48 | } elseif (is_null($value) || $value === '') { 49 | unset($vars[$key]); 50 | } 51 | } 52 | 53 | if (empty($vars)) { 54 | return null; 55 | } 56 | 57 | return $vars; 58 | } 59 | 60 | /** 61 | * determine class has value for effective json serialize. 62 | * 63 | * @see https://github.com/lesstif/php-jira-rest-client/issues/126 64 | * 65 | * @return bool 66 | */ 67 | public function isEmpty() 68 | { 69 | if (empty($this->name) && empty($this->self)) { 70 | return true; 71 | } 72 | 73 | return false; 74 | } 75 | 76 | /** 77 | * @return bool 78 | */ 79 | public function isWantUnassigned() 80 | { 81 | if ($this->wantUnassigned) { 82 | return true; 83 | } 84 | 85 | return false; 86 | } 87 | 88 | /** 89 | * @param bool $param boolean 90 | */ 91 | public function setWantUnassigned(bool $param) 92 | { 93 | $this->wantUnassigned = $param; 94 | $this->name = null; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/Sprint/Sprint.php: -------------------------------------------------------------------------------- 1 | name = $sprintName; 43 | 44 | return $this; 45 | } 46 | 47 | public function setGoalAsString(string $sprintGoal): self 48 | { 49 | $this->goal = $sprintGoal; 50 | 51 | return $this; 52 | } 53 | 54 | public function setOriginBoardIdAsStringOrInt(string|int $originBoardId): self 55 | { 56 | $this->originBoardId = $originBoardId; 57 | 58 | return $this; 59 | } 60 | 61 | public function setStartDateAsDateTime(\DateTimeInterface $startDate, string $format = 'Y-m-d'): static 62 | { 63 | $this->startDate = $startDate->format($format); 64 | 65 | return $this; 66 | } 67 | 68 | public function setStartDateAsString(string $startDate): static 69 | { 70 | $this->startDate = $startDate; 71 | 72 | return $this; 73 | } 74 | 75 | public function setEndDateAsDateTime(\DateTimeInterface $endDate, string $format = 'Y-m-d'): static 76 | { 77 | $this->endDate = $endDate->format($format); 78 | 79 | return $this; 80 | } 81 | 82 | public function setEndDateAsString(string $endDate): static 83 | { 84 | $this->endDate = $endDate; 85 | 86 | return $this; 87 | } 88 | 89 | public function setMoveIssues(array $issues): static 90 | { 91 | $this->issues = $issues; 92 | 93 | return $this; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/IssueLink/IssueLinkService.php: -------------------------------------------------------------------------------- 1 | log->info("addIssueLink=\n"); 21 | 22 | $data = json_encode($issueLink); 23 | 24 | $this->log->debug("Create IssueLink=\n".$data); 25 | 26 | $url = $this->uri.'/issueLink'; 27 | $type = 'POST'; 28 | 29 | $ret = $this->exec($url, $data, $type); 30 | 31 | return $ret; 32 | } 33 | 34 | /** 35 | * @throws \JiraCloud\JiraException 36 | * 37 | * @return IssueLinkType[] 38 | * 39 | * @phpstan-return ArrayObject 40 | */ 41 | public function getIssueLinkTypes(): ArrayObject 42 | { 43 | $this->log->info("getIssueLinkTYpes=\n"); 44 | 45 | $url = $this->uri.'/issueLinkType'; 46 | 47 | $ret = $this->exec($url); 48 | 49 | $data = json_encode(json_decode($ret)->issueLinkTypes); 50 | 51 | $linkTypes = $this->json_mapper->mapArray( 52 | json_decode($data, false), 53 | new \ArrayObject(), 54 | \JiraCloud\IssueLink\IssueLinkType::class 55 | ); 56 | 57 | return $linkTypes; 58 | } 59 | 60 | /** 61 | * @param string $linkId 62 | * 63 | * @throws \JiraCloud\JiraException 64 | * @throws \JsonMapper_Exception 65 | * 66 | * @return IssueLink 67 | */ 68 | public function getIssueLink(string $linkId): IssueLink 69 | { 70 | $this->log->info("getIssueLink=\n"); 71 | 72 | $url = $this->uri.'/issueLink/'.$linkId; 73 | 74 | $ret = $this->exec($url); 75 | 76 | return $this->json_mapper->map( 77 | json_decode($ret), 78 | new IssueLink() 79 | ); 80 | } 81 | 82 | public function deleteIssueLink(string $linkId): bool 83 | { 84 | $this->log->info("deleteIssueLink=\n"); 85 | 86 | $url = $this->uri.'/issueLink/'.$linkId; 87 | 88 | $ret = $this->exec($url, '', 'DELETE'); 89 | 90 | return $ret; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /apiary.apib: -------------------------------------------------------------------------------- 1 | FORMAT: 1A 2 | HOST: http://polls.apiblueprint.org/ 3 | 4 | # jira 5 | 6 | Polls is a simple API allowing consumers to view polls and vote in them. 7 | 8 | ## Questions Collection [/questions] 9 | 10 | ### List All Questions [GET] 11 | 12 | + Response 200 (application/json) 13 | 14 | [ 15 | { 16 | "question": "Favourite programming language?", 17 | "published_at": "2015-08-05T08:40:51.620Z", 18 | "choices": [ 19 | { 20 | "choice": "Swift", 21 | "votes": 2048 22 | }, { 23 | "choice": "Python", 24 | "votes": 1024 25 | }, { 26 | "choice": "Objective-C", 27 | "votes": 512 28 | }, { 29 | "choice": "Ruby", 30 | "votes": 256 31 | } 32 | ] 33 | } 34 | ] 35 | 36 | ### Create a New Question [POST] 37 | 38 | You may create your own question using this action. It takes a JSON 39 | object containing a question and a collection of answers in the 40 | form of choices. 41 | 42 | + Request (application/json) 43 | 44 | { 45 | "question": "Favourite programming language?", 46 | "choices": [ 47 | "Swift", 48 | "Python", 49 | "Objective-C", 50 | "Ruby" 51 | ] 52 | } 53 | 54 | + Response 201 (application/json) 55 | 56 | + Headers 57 | 58 | Location: /questions/2 59 | 60 | + Body 61 | 62 | { 63 | "question": "Favourite programming language?", 64 | "published_at": "2015-08-05T08:40:51.620Z", 65 | "choices": [ 66 | { 67 | "choice": "Swift", 68 | "votes": 0 69 | }, { 70 | "choice": "Python", 71 | "votes": 0 72 | }, { 73 | "choice": "Objective-C", 74 | "votes": 0 75 | }, { 76 | "choice": "Ruby", 77 | "votes": 0 78 | } 79 | ] 80 | } -------------------------------------------------------------------------------- /src/Attachment/AttachmentService.php: -------------------------------------------------------------------------------- 1 | exec($this->uri.$id, null); 36 | 37 | $this->log->info("Result=\n".$ret); 38 | 39 | $attachment = $this->json_mapper->map( 40 | json_decode($ret), 41 | new Attachment() 42 | ); 43 | 44 | if ($outDir == null) { 45 | return $attachment; 46 | } 47 | 48 | // download contents 49 | if (!file_exists($outDir)) { 50 | mkdir($outDir, $mode, $recursive); 51 | } 52 | 53 | // extract filename 54 | $file = substr(strrchr($attachment->content, '/'), 1); 55 | 56 | if (file_exists($outDir.DIRECTORY_SEPARATOR.$file) && $overwrite == false) { 57 | return $attachment; 58 | } 59 | 60 | $this->download($attachment->content, $outDir, $file); 61 | 62 | return $attachment; 63 | } 64 | 65 | /** 66 | * Remove an attachment from an issue. 67 | * 68 | * @param int|string $id attachment id 69 | * 70 | * @throws JiraException 71 | * 72 | * @return string 73 | */ 74 | public function remove(int|string $id): string 75 | { 76 | $ret = $this->exec($this->uri.$id, null, 'DELETE'); 77 | 78 | $this->log->info("Result=\n".$ret); 79 | 80 | return $ret; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Component/Component.php: -------------------------------------------------------------------------------- 1 | name; 43 | } 44 | 45 | public function setName(string $name): static 46 | { 47 | $this->name = $name; 48 | 49 | return $this; 50 | } 51 | 52 | public function setDescription($description): static 53 | { 54 | $this->description = $description; 55 | 56 | return $this; 57 | } 58 | 59 | public function setLeadUserName(string $leadUserName): static 60 | { 61 | $this->leadUserName = $leadUserName; 62 | 63 | return $this; 64 | } 65 | 66 | public function setAssigneeType(string $assigneeType): static 67 | { 68 | $this->assigneeType = $assigneeType; 69 | 70 | return $this; 71 | } 72 | 73 | public function setAssigneeTypeAsEnum(AssigneeTypeEnum $assigneeType): static 74 | { 75 | $this->assigneeType = $assigneeType->type(); 76 | 77 | return $this; 78 | } 79 | 80 | public function setProjectKey(string $projectKey): static 81 | { 82 | $this->project = $projectKey; 83 | 84 | return $this; 85 | } 86 | 87 | public function setProject(string $project): static 88 | { 89 | $this->project = $project; 90 | 91 | return $this; 92 | } 93 | 94 | #[\ReturnTypeWillChange] 95 | public function jsonSerialize() 96 | { 97 | $vars = array_filter(get_object_vars($this), function ($var) { 98 | return !is_null($var); 99 | }); 100 | 101 | return $vars; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /test-data/issueFieldV3.json: -------------------------------------------------------------------------------- 1 | { 2 | "update": { 3 | "worklog": [ 4 | { 5 | "add": { 6 | "timeSpent": "60m", 7 | "started": "2011-07-05T11:05:00.000+0000" 8 | } 9 | } 10 | ] 11 | }, 12 | "fields": { 13 | "summary": "something's wrong", 14 | "issuetype": { 15 | "id": "10000" 16 | }, 17 | "components": [ 18 | { 19 | "id": "10000" 20 | } 21 | ], 22 | "customfield_20000": "06/Jul/11 3:25 PM", 23 | "customfield_40000": "this is a text field", 24 | "customfield_70000": [ 25 | "jira-administrators", 26 | "jira-software-users" 27 | ], 28 | "project": { 29 | "id": "10000" 30 | }, 31 | "description": { 32 | "type": "doc", 33 | "version": 1, 34 | "content": [ 35 | { 36 | "type": "paragraph", 37 | "content": [ 38 | { 39 | "text": "description", 40 | "type": "text" 41 | } 42 | ] 43 | } 44 | ] 45 | }, 46 | "reporter": { 47 | "id": "557058:d6b5955a-e193-41e1-b051-79cdb0755d68" 48 | }, 49 | "fixVersions": [ 50 | { 51 | "id": "10001" 52 | } 53 | ], 54 | "customfield_10000": "09/Jun/81", 55 | "priority": { 56 | "id": "20000" 57 | }, 58 | "labels": [ 59 | "bugfix", 60 | "blitz_test" 61 | ], 62 | "timetracking": { 63 | "remainingEstimate": "5", 64 | "originalEstimate": "10" 65 | }, 66 | "customfield_30000": [ 67 | "10000", 68 | "10002" 69 | ], 70 | "customfield_80000": { 71 | "value": "red" 72 | }, 73 | "security": { 74 | "id": "10000" 75 | }, 76 | "environment": { 77 | "type": "doc", 78 | "version": 1, 79 | "content": [ 80 | { 81 | "type": "paragraph", 82 | "content": [ 83 | { 84 | "text": "environment", 85 | "type": "text" 86 | } 87 | ] 88 | } 89 | ] 90 | }, 91 | "versions": [ 92 | { 93 | "id": "10000" 94 | } 95 | ], 96 | "duedate": "2011-03-11T00:00:00.000Z", 97 | "customfield_60000": "jira-software-users", 98 | "customfield_50000": "this is a text area. big text.", 99 | "assignee": { 100 | "id": "5b10a2844c20165700ede21f" 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Sprint/SprintSearchResult.php: -------------------------------------------------------------------------------- 1 | startAt; 48 | } 49 | 50 | /** 51 | * @param int $startAt 52 | */ 53 | public function setStartAt($startAt) 54 | { 55 | $this->startAt = $startAt; 56 | } 57 | 58 | /** 59 | * @return int 60 | */ 61 | public function getMaxResults() 62 | { 63 | return $this->maxResults; 64 | } 65 | 66 | /** 67 | * @param int $maxResults 68 | */ 69 | public function setMaxResults($maxResults) 70 | { 71 | $this->maxResults = $maxResults; 72 | } 73 | 74 | /** 75 | * @return int 76 | */ 77 | public function getTotal() 78 | { 79 | return $this->total; 80 | } 81 | 82 | /** 83 | * @param int $total 84 | */ 85 | public function setTotal($total) 86 | { 87 | $this->total = $total; 88 | } 89 | 90 | /** 91 | * @return Sprint[] 92 | */ 93 | public function getSprints() 94 | { 95 | return $this->issues; 96 | } 97 | 98 | /** 99 | * @param Sprint[] $issues 100 | */ 101 | public function setSprints($issues) 102 | { 103 | $this->issues = $issues; 104 | } 105 | 106 | /** 107 | * @param int $ndx 108 | * 109 | * @return object 110 | */ 111 | public function getSprint($ndx) 112 | { 113 | return $this->issues[$ndx]; 114 | } 115 | 116 | /** 117 | * @return string 118 | */ 119 | public function getExpand() 120 | { 121 | return $this->expand; 122 | } 123 | 124 | /** 125 | * @param string $expand 126 | */ 127 | public function setExpand($expand) 128 | { 129 | $this->expand = $expand; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/Field/Field.php: -------------------------------------------------------------------------------- 1 | name = $name; 34 | 35 | return $this; 36 | } 37 | 38 | public function setDescription($description) 39 | { 40 | $this->description = $description; 41 | 42 | return $this; 43 | } 44 | 45 | /** 46 | * set custom field type. 47 | * 48 | * @see https://confluence.atlassian.com/jira064/changing-custom-field-types-720415917.html 49 | * 50 | * @param string $type 51 | * 52 | * @return $this 53 | */ 54 | public function setType(string $type) 55 | { 56 | $this->type = $type; 57 | 58 | return $this; 59 | } 60 | 61 | /** 62 | * atlassian supplied poor documentation. 63 | * 64 | * @param string $searcherKey 65 | * 66 | * @return $this 67 | */ 68 | public function setSearcherKey(string $searcherKey) 69 | { 70 | $this->searcherKey = $searcherKey; 71 | 72 | return $this; 73 | } 74 | 75 | /* @var string */ 76 | public $id; 77 | 78 | /* @var string */ 79 | public $name; 80 | 81 | /* @var string */ 82 | public $description; 83 | 84 | /* @var string */ 85 | public $type; 86 | 87 | /* @var boolean */ 88 | public $custom; 89 | 90 | /* @var boolean */ 91 | public $orderable; 92 | 93 | /* @var boolean */ 94 | public $navigable; 95 | 96 | /* @var boolean */ 97 | public $searchable; 98 | 99 | /** @var string */ 100 | public $searcherKey; 101 | 102 | /** 103 | * if field is custom, array has two element. first is custom field number represented in bracket with cf prefix, second element is field name. 104 | * 105 | * Ex: [0 => "cf[10201]", 1 => "My Check Box"] 106 | * 107 | * @var array 108 | */ 109 | public $clauseNames; 110 | 111 | /* @var Schema */ 112 | public $schema; 113 | 114 | #[\ReturnTypeWillChange] 115 | public function jsonSerialize() 116 | { 117 | return array_filter(get_object_vars($this)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Component/ComponentService.php: -------------------------------------------------------------------------------- 1 | log->info("Create Component=\n".$data); 26 | 27 | $ret = $this->exec($this->uri, $data, 'POST'); 28 | 29 | return $this->json_mapper->map( 30 | json_decode($ret), 31 | new Component() 32 | ); 33 | } 34 | 35 | /** 36 | * get component. 37 | * 38 | * @param string|int $id component id 39 | * 40 | * @return Component 41 | */ 42 | public function get($id) 43 | { 44 | $ret = $this->exec($this->uri.'/'.$id); 45 | 46 | $this->log->info('Result='.$ret); 47 | 48 | return $this->json_mapper->map( 49 | json_decode($ret), 50 | new Component() 51 | ); 52 | } 53 | 54 | /** 55 | * @param Component $component 56 | * 57 | * @throws JiraException 58 | * 59 | * @return Component 60 | */ 61 | public function update(Component $component) 62 | { 63 | if (!$component->id || !is_numeric($component->id)) { 64 | throw new JiraException($component->id.' is not a valid component id.'); 65 | } 66 | 67 | $data = json_encode($component); 68 | $ret = $this->exec($this->uri.'/'.$component->id, $data, 'PUT'); 69 | 70 | return $this->json_mapper->map( 71 | json_decode($ret), 72 | new Component() 73 | ); 74 | } 75 | 76 | /** 77 | * @param Component $component 78 | * @param Component|false $moveIssuesTo 79 | * 80 | * @throws JiraException 81 | * 82 | * @return string 83 | */ 84 | public function delete(Component $component, $moveIssuesTo = false) 85 | { 86 | if (!$component->id || !is_numeric($component->id)) { 87 | throw new JiraException($component->id.' is not a valid component id.'); 88 | } 89 | 90 | $data = []; 91 | $paramArray = []; 92 | 93 | if ($moveIssuesTo && $moveIssuesTo instanceof Component) { 94 | $paramArray['moveIssuesTo'] = $moveIssuesTo->id; 95 | } 96 | 97 | $ret = $this->exec($this->uri.'/'.$component->id.$this->toHttpQueryParameter($paramArray), json_encode($data), 'DELETE'); 98 | 99 | return $ret; 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Configuration/ConfigurationInterface.php: -------------------------------------------------------------------------------- 1 | client = $client; 22 | $this->jsonMapper = $client->getMapper(); 23 | } 24 | 25 | /** 26 | * @param Attachment[] $attachments 27 | * 28 | * @throws JiraException 29 | * 30 | * @return string[] 31 | */ 32 | public function createTemporaryFiles(array $attachments, int $serviceDeskId): array 33 | { 34 | $fileNames = $this->getFilenamesFromAttachments($attachments); 35 | 36 | return $this->client->upload( 37 | $this->client->createUrl('/servicedesk/%d/attachTemporaryFile', [$serviceDeskId]), 38 | $fileNames 39 | ); 40 | } 41 | 42 | /** 43 | * @throws JiraException|JsonException|JsonMapper_Exception 44 | * 45 | * @return Attachment[] 46 | */ 47 | public function addAttachmentToRequest(int $requestId, array $temporaryFiles): array 48 | { 49 | $attachment_ids = array_map(static function (string $upload) { 50 | $upload = json_decode($upload, true, 512, JSON_THROW_ON_ERROR); 51 | 52 | return $upload['temporaryAttachments'][0]['temporaryAttachmentId']; 53 | }, $temporaryFiles); 54 | 55 | $parameters = [ 56 | 'temporaryAttachmentIds' => $attachment_ids, 57 | 'public' => false, 58 | ]; 59 | 60 | $result = $this->client->exec( 61 | $this->client->createUrl('/request/%d/attachment', [$requestId]), 62 | json_encode($parameters, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE), 63 | 'POST' 64 | ); 65 | 66 | return $this->createAttachmentsFromJson($result); 67 | } 68 | 69 | /** 70 | * @param Attachment[] $attachments 71 | * 72 | * @return string[] 73 | */ 74 | private function getFilenamesFromAttachments(array $attachments): array 75 | { 76 | return array_map(static function (Attachment $attachment) { 77 | return $attachment->filename; 78 | }, $attachments); 79 | } 80 | 81 | /** 82 | * @throws JsonMapper_Exception|JsonException 83 | * 84 | * @return Attachment[] 85 | */ 86 | private function createAttachmentsFromJson(string $result): array 87 | { 88 | $attachmentData = json_decode($result, false, 512, JSON_THROW_ON_ERROR); 89 | 90 | $attachments = []; 91 | foreach ($attachmentData as $attachment) { 92 | $attachments[] = $this->jsonMapper->map($attachment, new Attachment()); 93 | } 94 | 95 | return $attachments; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Issue/Notify.php: -------------------------------------------------------------------------------- 1 | to = []; 28 | $this->to['users'] = []; 29 | $this->to['groups'] = []; 30 | 31 | $this->restrict = []; 32 | $this->restrict['groups'] = []; 33 | $this->restrict['permissions'] = []; 34 | 35 | $this->to['reporter'] = true; 36 | $this->to['assignee'] = true; 37 | $this->to['watchers'] = true; 38 | $this->to['voters'] = true; 39 | } 40 | 41 | public function setSubject($subject) 42 | { 43 | $this->subject = $subject; 44 | 45 | return $this; 46 | } 47 | 48 | public function setTextBody($textBody) 49 | { 50 | $this->textBody = $textBody; 51 | 52 | return $this; 53 | } 54 | 55 | public function setHtmlBody($htmlBody) 56 | { 57 | $this->htmlBody = $htmlBody; 58 | 59 | return $this; 60 | } 61 | 62 | public function sendToReporter($bool) 63 | { 64 | $this->to['reporter'] = $bool; 65 | 66 | return $this; 67 | } 68 | 69 | public function sendToAssignee($bool) 70 | { 71 | $this->to['assignee'] = $bool; 72 | 73 | return $this; 74 | } 75 | 76 | public function sendToWatchers($bool) 77 | { 78 | $this->to['watchers'] = $bool; 79 | 80 | return $this; 81 | } 82 | 83 | public function sendToVoters($bool) 84 | { 85 | $this->to['voters'] = $bool; 86 | 87 | return $this; 88 | } 89 | 90 | public function sendToUser($name, $active) 91 | { 92 | $user['name'] = $name; 93 | $user['active'] = $active; 94 | 95 | array_push($this->to['users'], $user); 96 | 97 | return $this; 98 | } 99 | 100 | public function sendToGroup($groupName) 101 | { 102 | $group['name'] = $groupName; 103 | 104 | array_push($this->to['groups'], $group); 105 | 106 | return $this; 107 | } 108 | 109 | public function setRestrictGroup($groupName) 110 | { 111 | $group['name'] = $groupName; 112 | 113 | array_push($this->restrict['groups'], $group); 114 | 115 | return $this; 116 | } 117 | 118 | public function setRestrictPermission($id, $key) 119 | { 120 | $perm['id'] = $id; 121 | $perm['key'] = $key; 122 | array_push($this->restrict['permissions'], $perm); 123 | 124 | return $this; 125 | } 126 | 127 | #[\ReturnTypeWillChange] 128 | public function jsonSerialize() 129 | { 130 | return array_filter(get_object_vars($this)); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Issue/Version.php: -------------------------------------------------------------------------------- 1 | name = $name; 37 | } 38 | 39 | #[\ReturnTypeWillChange] 40 | public function jsonSerialize() 41 | { 42 | return array_filter(get_object_vars($this)); 43 | } 44 | 45 | public function setProjectId(string $id): static 46 | { 47 | $this->projectId = $id; 48 | 49 | return $this; 50 | } 51 | 52 | public function setName(string $name): static 53 | { 54 | $this->name = $name; 55 | 56 | return $this; 57 | } 58 | 59 | public function setDescription(string $description): static 60 | { 61 | $this->description = $description; 62 | 63 | return $this; 64 | } 65 | 66 | public function setArchived(bool $archived): static 67 | { 68 | $this->archived = $archived; 69 | 70 | return $this; 71 | } 72 | 73 | public function setReleased(bool $released): static 74 | { 75 | $this->released = $released; 76 | 77 | return $this; 78 | } 79 | 80 | public function setReleaseDateAsDateTime(DateTimeInterface $releaseDate, string $format = 'Y-m-d'): static 81 | { 82 | $this->releaseDate = $releaseDate->format($format); 83 | 84 | return $this; 85 | } 86 | 87 | public function setReleaseDateAsString(string $releaseDate): static 88 | { 89 | $this->releaseDate = $releaseDate; 90 | 91 | return $this; 92 | } 93 | 94 | public function setUserReleaseDate(string $userReleaseDate): static 95 | { 96 | $this->userReleaseDate = $userReleaseDate; 97 | 98 | return $this; 99 | } 100 | 101 | public function setStartDateAsDateTime(\DateTimeInterface $startDate, string $format = 'Y-m-d'): static 102 | { 103 | $this->startDate = $startDate->format($format); 104 | 105 | return $this; 106 | } 107 | 108 | public function setStartDateAsString(?string $startDate): static 109 | { 110 | $this->startDate = $startDate; 111 | 112 | return $this; 113 | } 114 | 115 | public function setUserStartDateAsDateTime(\DateTimeInterface $userStartDate, string $format = 'Y-m-d'): static 116 | { 117 | $this->userStartDate = $userStartDate->format($format); 118 | 119 | return $this; 120 | } 121 | 122 | public function setUserStartDateAsString(?string $userStartDate): static 123 | { 124 | $this->userStartDate = $userStartDate; 125 | 126 | return $this; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/Sprint/SprintService.php: -------------------------------------------------------------------------------- 1 | setAPIUri('/rest/agile/1.0'); 21 | } 22 | 23 | /** 24 | * @param object $json JSON object structure from json_decode 25 | * 26 | * @throws \JsonMapper_Exception 27 | * 28 | * @return Sprint 29 | */ 30 | public function getSprintFromJSON(object $json): Sprint 31 | { 32 | $sprint = $this->json_mapper->map( 33 | $json, 34 | new Sprint() 35 | ); 36 | 37 | return $sprint; 38 | } 39 | 40 | public function getSprint(string|int $sprintId): Sprint 41 | { 42 | $ret = $this->exec($this->uri.'/'.$sprintId, null); 43 | 44 | $this->log->info("Result=\n".$ret); 45 | 46 | return $this->json_mapper->map( 47 | json_decode($ret), 48 | new Sprint() 49 | ); 50 | } 51 | 52 | /** 53 | * @throws JiraException 54 | * @throws \JsonMapper_Exception 55 | * 56 | * @return Issue[] array of Issue 57 | * 58 | * @see https://developer.atlassian.com/cloud/jira/software/rest/api-group-sprint/#api-rest-agile-1-0-sprint-sprintid-get 59 | */ 60 | public function getSprintIssues(string|int $sprintId, array $paramArray = []) 61 | { 62 | $json = $this->exec($this->uri.'/'.$sprintId.'/issue'.$this->toHttpQueryParameter($paramArray), null); 63 | 64 | $issues = $this->json_mapper->mapArray( 65 | json_decode($json)->issues, 66 | new \ArrayObject(), 67 | Issue::class 68 | ); 69 | 70 | return $issues; 71 | } 72 | 73 | /** 74 | * @see https://developer.atlassian.com/cloud/jira/software/rest/api-group-sprint/#api-rest-agile-1-0-sprint-post 75 | */ 76 | public function createSprint(Sprint $sprint): Sprint 77 | { 78 | $data = json_encode($sprint); 79 | 80 | $ret = $this->exec($this->uri, $data); 81 | 82 | $this->log->debug('createSprint result='.var_export($ret, true)); 83 | 84 | return $this->json_mapper->map( 85 | json_decode($ret), 86 | new Sprint() 87 | ); 88 | } 89 | 90 | /** 91 | * @param int $sprintId 92 | * @param Sprint $sprint 93 | * 94 | * @throws JiraException 95 | * 96 | * @return bool 97 | * 98 | * @see https://developer.atlassian.com/cloud/jira/software/rest/api-group-sprint/#api-rest-agile-1-0-sprint-sprintid-issue-post 99 | */ 100 | public function moveIssues2Sprint(int $sprintId, Sprint $sprint): bool 101 | { 102 | $data = json_encode($sprint); 103 | 104 | $ret = $this->exec($this->uri.'/'.$sprintId.'/issue', $data); 105 | 106 | $this->log->debug('moveIssues2Sprint result='.var_export($ret, true)); 107 | 108 | return $ret; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Issue/TimeTracking.php: -------------------------------------------------------------------------------- 1 | originalEstimate. 32 | */ 33 | public int $originalEstimateSeconds; 34 | 35 | /** 36 | * Remaining estimate in seconds, generated in jira 37 | * for create/update issue set $this->remainingEstimate. 38 | */ 39 | public int $remainingEstimateSeconds; 40 | 41 | /** 42 | * Time spent in seconds, generated in jira 43 | * for create/update issue set $this->timeSpent. 44 | */ 45 | public int $timeSpentSeconds; 46 | 47 | public function getOriginalEstimate(): string 48 | { 49 | return $this->originalEstimate; 50 | } 51 | 52 | public function setOriginalEstimate(string $originalEstimate): void 53 | { 54 | $this->originalEstimate = $originalEstimate; 55 | } 56 | 57 | public function getRemainingEstimate(): string 58 | { 59 | return $this->remainingEstimate; 60 | } 61 | 62 | public function setRemainingEstimate(string $remainingEstimate) 63 | { 64 | $this->remainingEstimate = $remainingEstimate; 65 | } 66 | 67 | public function getTimeSpent(): string 68 | { 69 | return $this->timeSpent; 70 | } 71 | 72 | public function setTimeSpent(string $timeSpent) 73 | { 74 | $this->timeSpent = $timeSpent; 75 | } 76 | 77 | public function getOriginalEstimateSeconds(): int 78 | { 79 | return $this->originalEstimateSeconds; 80 | } 81 | 82 | public function setOriginalEstimateSeconds(int $originalEstimateSeconds) 83 | { 84 | $this->originalEstimateSeconds = $originalEstimateSeconds; 85 | } 86 | 87 | public function getRemainingEstimateSeconds(): int 88 | { 89 | return $this->remainingEstimateSeconds; 90 | } 91 | 92 | public function setRemainingEstimateSeconds(int $remainingEstimateSeconds) 93 | { 94 | $this->remainingEstimateSeconds = $remainingEstimateSeconds; 95 | } 96 | 97 | public function getTimeSpentSeconds(): int 98 | { 99 | return $this->timeSpentSeconds; 100 | } 101 | 102 | public function setTimeSpentSeconds(int $timeSpentSeconds) 103 | { 104 | $this->timeSpentSeconds = $timeSpentSeconds; 105 | } 106 | 107 | /** 108 | * Specify data which should be serialized to JSON. 109 | * 110 | * @link http://php.net/manual/en/jsonserializable.jsonserialize.php 111 | * 112 | * @return mixed data which can be serialized by json_encode, 113 | * which is a value of any type other than a resource. 114 | */ 115 | #[\ReturnTypeWillChange] 116 | public function jsonSerialize() 117 | { 118 | return array_filter(get_object_vars($this)); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Issue/Worklog.php: -------------------------------------------------------------------------------- 1 | comment = $comment; 53 | 54 | return $this; 55 | } 56 | 57 | /** 58 | * Note that in the docblock below, you cannot replace `mixed` by `\DateTimeInterface|string` because JsonMapper doesn't support that, 59 | * see . 60 | * 61 | * @param DateTimeInterface|string $started started time value(\DateTimeInterface|string) e.g. - new \DateTime("2016-03-17 11:15:34") or "2016-03-17 11:15:34" 62 | * 63 | * @throws JiraException 64 | * 65 | * @return $this 66 | */ 67 | public function setStarted(mixed $started): static 68 | { 69 | if (is_string($started)) { 70 | $dt = new \DateTime($started); 71 | } elseif ($started instanceof \DateTimeInterface) { 72 | $dt = $started; 73 | } else { 74 | throw new JiraException('field only accept date string or DateTimeInterface object.'.get_class($started)); 75 | } 76 | 77 | // workround micro second 78 | $this->started = $dt->format("Y-m-d\TH:i:s").'.000'.$dt->format('O'); 79 | 80 | return $this; 81 | } 82 | 83 | /** 84 | * Function to set start time of worklog. 85 | * 86 | * @param \DateTimeInterface $started e.g. - new \DateTime("2014-04-05 16:00:00") 87 | * 88 | * @return Worklog 89 | */ 90 | public function setStartedDateTime(DateTimeInterface $started): static 91 | { 92 | // workround micro second 93 | $this->started = $started->format("Y-m-d\TH:i:s").'.000'.$started->format('O'); 94 | 95 | return $this; 96 | } 97 | 98 | /** 99 | * Function to set worklog time in string. 100 | * 101 | * @param string $timeSpent 102 | */ 103 | public function setTimeSpent(string $timeSpent): static 104 | { 105 | $this->timeSpent = $timeSpent; 106 | 107 | return $this; 108 | } 109 | 110 | /** 111 | * Function to set worklog time in seconds. 112 | * 113 | * @param int $timeSpentSeconds 114 | * 115 | * @return Worklog 116 | */ 117 | public function setTimeSpentSeconds(int $timeSpentSeconds): static 118 | { 119 | $this->timeSpentSeconds = $timeSpentSeconds; 120 | 121 | return $this; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Group/GroupService.php: -------------------------------------------------------------------------------- 1 | exec($this->uri.$queryParam, null); 28 | 29 | $this->log->info("Result=\n".$ret); 30 | 31 | return $this->json_mapper->map( 32 | json_decode($ret), 33 | new Group() 34 | ); 35 | } 36 | 37 | /** 38 | * Get users from group. 39 | * 40 | * @param array $paramArray groupname, includeInactiveUsers, startAt, maxResults 41 | * 42 | * @throws \JiraCloud\JiraException 43 | * @throws \JsonMapper_Exception 44 | * 45 | * @return GroupSearchResult 46 | */ 47 | public function getMembers($paramArray) 48 | { 49 | $queryParam = '?'.http_build_query($paramArray); 50 | 51 | $ret = $this->exec($this->uri.'/member'.$queryParam, null); 52 | 53 | $this->log->info("Result=\n".$ret); 54 | 55 | $userData = json_decode($ret); 56 | 57 | $res = $this->json_mapper->map($userData, new GroupSearchResult()); 58 | 59 | return $res; 60 | } 61 | 62 | /** 63 | * Creates a group by given group parameter. 64 | * 65 | * @param \JiraCloud\Group\Group $group 66 | * 67 | * @throws \JiraCloud\JiraException 68 | * @throws \JsonMapper_Exception 69 | * 70 | * @return Group 71 | */ 72 | public function createGroup(Group $group) 73 | { 74 | $data = json_encode($group); 75 | 76 | $ret = $this->exec($this->uri, $data); 77 | 78 | $this->log->info("Result=\n".$ret); 79 | 80 | $group = $this->json_mapper->map( 81 | json_decode($ret), 82 | new Group() 83 | ); 84 | 85 | return $group; 86 | } 87 | 88 | /** 89 | * Adds given user to a group. 90 | * 91 | * @param string $groupName 92 | * @param string $userName 93 | * 94 | * @throws \JiraCloud\JiraException 95 | * @throws \JsonMapper_Exception 96 | * 97 | * @return Group Returns the current state of the group. 98 | */ 99 | public function addUserToGroup(string $groupName, string $userName) 100 | { 101 | $data = json_encode(['name' => $userName]); 102 | 103 | $ret = $this->exec($this->uri.'/user?groupname='.urlencode($groupName), $data); 104 | 105 | $this->log->info("Result=\n".$ret); 106 | 107 | $group = $this->json_mapper->map( 108 | json_decode($ret), 109 | new Group() 110 | ); 111 | 112 | return $group; 113 | } 114 | 115 | /** 116 | * Removes given user from a group. 117 | * 118 | * @param string $groupName 119 | * @param string $userName 120 | * 121 | * @throws \JiraCloud\JiraException 122 | * 123 | * @return string|null Returns no content 124 | */ 125 | public function removeUserFromGroup(string $groupName, string $userName) 126 | { 127 | $param = http_build_query(['groupname' => $groupName, 'username' => $userName]); 128 | 129 | $ret = $this->exec($this->uri.'/user?'.$param, [], 'DELETE'); 130 | 131 | $this->log->info("Result=\n".$ret); 132 | 133 | return $ret; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/ServiceDesk/Request/Request.php: -------------------------------------------------------------------------------- 1 | requestTypeId = $requestTypeId; 37 | 38 | return $this; 39 | } 40 | 41 | public function setCreatedDate(object $createdDate): void 42 | { 43 | if (!$createdDate instanceof DateTimeInterface) { 44 | $createdDate = new DateTime($createdDate->iso8601); 45 | } 46 | 47 | $this->createdDate = $createdDate; 48 | } 49 | 50 | public function setReporter(object $reporter): self 51 | { 52 | if (!$reporter instanceof Customer) { 53 | $reporter = $this->map($reporter, new Customer()); 54 | } 55 | 56 | $this->reporter = $reporter; 57 | 58 | return $this; 59 | } 60 | 61 | public function setSummary(string $summary): self 62 | { 63 | $this->requestFieldValues['summary'] = $summary; 64 | 65 | return $this; 66 | } 67 | 68 | public function setDescription(string $description): self 69 | { 70 | $this->requestFieldValues['description'] = $description; 71 | 72 | return $this; 73 | } 74 | 75 | public function addCustomField(string $key, $value): self 76 | { 77 | $this->requestFieldValues[$key] = $value; 78 | 79 | return $this; 80 | } 81 | 82 | public function setCurrentStatus(object $currentStatus): void 83 | { 84 | $this->currentStatus = $this->map($currentStatus, new RequestStatus()); 85 | } 86 | 87 | public function setLinks(object $links): void 88 | { 89 | $this->_links = $links; 90 | } 91 | 92 | /** 93 | * @param Customer[] $requestParticipants 94 | */ 95 | public function setRequestParticipants(array $requestParticipants): self 96 | { 97 | $this->requestParticipants = $requestParticipants; 98 | 99 | return $this; 100 | } 101 | 102 | public function jsonSerialize(): array 103 | { 104 | $data = get_object_vars($this); 105 | if ($this->reporter) { 106 | $data['raiseOnBehalfOf'] = $this->reporter->accountId ?? $this->reporter->emailAddress; 107 | } 108 | unset($data['reporter']); 109 | 110 | $data['requestParticipants'] = array_map(static function (Customer $customer): string { 111 | return $customer->accountId ?? $customer->emailAddress; 112 | }, $this->requestParticipants); 113 | 114 | return array_filter($data); 115 | } 116 | 117 | private function map(object $data, object $target) 118 | { 119 | $mapper = new JsonMapper(); 120 | 121 | // Adjust settings for JsonMapper v5.0 BC 122 | if (property_exists($mapper, 'bStrictNullTypesInArrays')) { 123 | $mapper->bStrictNullTypesInArrays = false; // if you want to allow nulls in arrays 124 | } 125 | $mapper->bStrictNullTypes = false; // if you want to allow nulls 126 | $mapper->bStrictObjectTypeChecking = false; // if you want to disable strict type checking 127 | 128 | return $mapper->map( 129 | $data, 130 | $target 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/ServiceDesk/Comment/CommentService.php: -------------------------------------------------------------------------------- 1 | client = $client; 25 | $this->logger = $client->getLogger(); 26 | $this->jsonMapper = $client->getMapper(); 27 | } 28 | 29 | /** 30 | * @throws JiraException|JsonMapper_Exception|JsonException 31 | */ 32 | public function addComment(string $issueId, Comment $comment): Comment 33 | { 34 | $this->logger->info("addComment=\n"); 35 | 36 | if (empty($comment->body)) { 37 | throw new JiraException('comment param must have body text.'); 38 | } 39 | 40 | $data = json_encode($comment, JSON_THROW_ON_ERROR); 41 | 42 | $result = $this->client->exec( 43 | $this->client->createUrl('%s/%d/comment', [$this->uri, $issueId]), 44 | $data 45 | ); 46 | 47 | $this->logger->debug('add comment result='.var_export($result, true)); 48 | 49 | return $this->jsonMapper->map( 50 | json_decode($result, false, 512, JSON_THROW_ON_ERROR), 51 | new Comment() 52 | ); 53 | } 54 | 55 | /** 56 | * @throws JiraException|JsonMapper_Exception|JsonException 57 | */ 58 | public function getComment(string $issueId, int $commentId): Comment 59 | { 60 | $this->logger->info("getComment=\n"); 61 | 62 | $result = $this->client->exec( 63 | $this->client->createUrl('%s/%d/comment/%d', [$this->uri, $issueId, $commentId]) 64 | ); 65 | 66 | $this->logger->debug('get comment result='.var_export($result, true)); 67 | 68 | return $this->jsonMapper->map( 69 | json_decode($result, false, 512, JSON_THROW_ON_ERROR), 70 | new Comment() 71 | ); 72 | } 73 | 74 | /** 75 | * @throws JiraException|JsonMapper_Exception|InvalidArgumentException|JsonException 76 | * 77 | * @return Comment[] 78 | * 79 | * @see https://docs.atlassian.com/jira-servicedesk/REST/3.6.2/#servicedeskapi/request/{issueIdOrKey}/comment-getRequestComments 80 | */ 81 | public function getCommentsForRequest(string $issueId, bool $showPublicComments = true, bool $showInternalComments = true, int $startIndex = 0, int $amountOfItems = 50): array 82 | { 83 | $this->logger->info("getComments for request=\n"); 84 | 85 | $searchParameters = $this->getRequestSearchParameters( 86 | $showPublicComments, 87 | $showInternalComments, 88 | $startIndex, 89 | $amountOfItems 90 | ); 91 | 92 | $result = $this->client->exec( 93 | $this->client->createUrl('%s/%d/comment', [$this->uri, $issueId], $searchParameters) 94 | ); 95 | 96 | $this->logger->debug('get comments result='.var_export($result, true)); 97 | 98 | $commentData = json_decode($result, false, 512, JSON_THROW_ON_ERROR); 99 | 100 | $comments = []; 101 | foreach ($commentData as $comment) { 102 | $comments[] = $this->jsonMapper->map( 103 | $comment, 104 | new Comment() 105 | ); 106 | } 107 | 108 | return $comments; 109 | } 110 | 111 | /** 112 | * @throws InvalidArgumentException 113 | */ 114 | private function getRequestSearchParameters(bool $showPublicComments, bool $showInternalComments, int $startIndex, int $amountOfItems): array 115 | { 116 | if ($startIndex < 0) { 117 | throw new InvalidArgumentException('Start index can not be lower then 0.'); 118 | } 119 | if ($amountOfItems < 1) { 120 | throw new InvalidArgumentException('Amount of items can not be lower then 1.'); 121 | } 122 | 123 | return [ 124 | 'public' => $showPublicComments, 125 | 'internal' => $showInternalComments, 126 | 'start' => $startIndex, 127 | 'limit' => $amountOfItems, 128 | ]; 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/ServiceDesk/Participant/ParticipantService.php: -------------------------------------------------------------------------------- 1 | client = $client; 24 | $this->logger = $client->getLogger(); 25 | $this->mapper = $client->getMapper(); 26 | } 27 | 28 | /** 29 | * @see https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-participant-get 30 | * 31 | * @throws JiraException|JsonMapper_Exception|JsonException 32 | * 33 | * @return Customer[] The participants of the customer request, at the specified page of the results. 34 | */ 35 | public function getParticipantOfRequest(string $issueIdOrKey, int $start = 0, int $limit = 50): array 36 | { 37 | $this->logger->debug("getParticipant=\n"); 38 | 39 | $result = $this->client->exec( 40 | $this->client->createUrl('/request/%s/participant?start=%d&limit=%d', [$issueIdOrKey, $start, $limit]) 41 | ); 42 | 43 | $this->logger->debug('get participant result='.var_export($result, true)); 44 | 45 | return $this->mapResult($result); 46 | } 47 | 48 | /** 49 | * @see https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-participant-post 50 | * 51 | * @param Customer[] $participants 52 | * 53 | * @throws JiraException|JsonMapper_Exception|JsonException 54 | * 55 | * @return Customer[] The participants of the customer request. 56 | */ 57 | public function addParticipantToRequest(string $issueIdOrKey, array $participants): array 58 | { 59 | $this->logger->debug("addParticipant=\n"); 60 | 61 | $result = $this->client->exec( 62 | $this->client->createUrl('/request/%s/participant', [$issueIdOrKey]), 63 | $this->encodeParticipants($participants) 64 | ); 65 | 66 | $this->logger->debug('add participant result='.var_export($result, true)); 67 | 68 | return $this->mapResult($result); 69 | } 70 | 71 | /** 72 | * @see https://developer.atlassian.com/cloud/jira/service-desk/rest/api-group-request/#api-rest-servicedeskapi-request-issueidorkey-participant-delete 73 | * 74 | * @param Customer[] $participants 75 | * 76 | * @throws JiraException|JsonException|JsonMapper_Exception 77 | * 78 | * @return Customer[] The first page of participants of the customer request after removing the specified users. 79 | */ 80 | public function removeParticipantFromRequest(string $issueIdOrKey, array $participants): array 81 | { 82 | $this->logger->debug("removeParticipant=\n"); 83 | 84 | $result = $this->client->exec( 85 | $this->client->createUrl('/request/%s/participant', [$issueIdOrKey]), 86 | $this->encodeParticipants($participants), 87 | 'DELETE' 88 | ); 89 | 90 | $this->logger->debug('remove participant result='.var_export($result, true)); 91 | 92 | return $this->mapResult($result); 93 | } 94 | 95 | /** 96 | * @param Customer[] $participants 97 | * 98 | * @throws JsonException 99 | */ 100 | private function encodeParticipants(array $participants): string 101 | { 102 | return json_encode([ 103 | 'accountIds' => array_map(static function (Customer $participant): string { 104 | return $participant->accountId ?? $participant->emailAddress; 105 | }, $participants), 106 | ], JSON_THROW_ON_ERROR); 107 | } 108 | 109 | /** 110 | * @throws JsonMapper_Exception|JsonException 111 | * 112 | * @return Customer[] 113 | */ 114 | private function mapResult(string $result): array 115 | { 116 | $userData = json_decode($result, false, 512, JSON_THROW_ON_ERROR); 117 | 118 | return array_map(function (object $user): Customer { 119 | return $this->mapper->map( 120 | $user, 121 | new Customer() 122 | ); 123 | }, $userData->values ?? []); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/Configuration/DotEnvConfiguration.php: -------------------------------------------------------------------------------- 1 | loadDotEnv($path); 20 | 21 | $this->jiraHost = $this->env('JIRAAPI_V3_HOST'); 22 | $this->jiraUser = $this->env('JIRAAPI_V3_USER'); 23 | 24 | $this->oauthAccessToken = $this->env('JIRAAPI_V3_OAUTH_ACCESS_TOKEN'); 25 | $this->cookieAuthEnabled = $this->env('JIRAAPI_V3_COOKIE_AUTH_ENABLED', false); 26 | $this->cookieFile = $this->env('JIRAAPI_V3_COOKIE_FILE', 'jira-cookie.txt'); 27 | $this->jiraLogEnabled = $this->env('JIRAAPI_V3_LOG_ENABLED', true); 28 | $this->jiraLogFile = $this->env('JIRAAPI_V3_LOG_FILE', 'jira-rest-client.log'); 29 | $this->jiraLogLevel = $this->env('JIRAAPI_V3_LOG_LEVEL', 'WARNING'); 30 | $this->curlOptSslVerifyHost = $this->env('JIRAAPI_V3_CURLOPT_SSL_VERIFYHOST', false); 31 | $this->curlOptSslVerifyPeer = $this->env('JIRAAPI_V3_CURLOPT_SSL_VERIFYPEER', false); 32 | $this->curlOptSslCert = $this->env('JIRAAPI_V3_CURLOPT_SSL_CERT'); 33 | $this->curlOptSslCertPassword = $this->env('JIRAAPI_V3_CURLOPT_SSL_CERT_PASSWORD'); 34 | $this->curlOptSslKey = $this->env('JIRAAPI_V3_CURLOPT_SSL_KEY'); 35 | $this->curlOptSslKeyPassword = $this->env('JIRAAPI_V3_CURLOPT_SSL_KEY_PASSWORD'); 36 | $this->curlOptUserAgent = $this->env('JIRAAPI_V3_CURLOPT_USERAGENT', $this->getDefaultUserAgentString()); 37 | $this->curlOptVerbose = $this->env('JIRAAPI_V3_CURLOPT_VERBOSE', false); 38 | $this->proxyServer = $this->env('JIRAAPI_V3_PROXY_SERVER'); 39 | $this->proxyPort = $this->env('JIRAAPI_V3_PROXY_PORT'); 40 | $this->proxyUser = $this->env('JIRAAPI_V3_PROXY_USER'); 41 | $this->proxyPassword = $this->env('JIRAAPI_V3_PROXY_PASSWORD'); 42 | 43 | $this->timeout = $this->env('JIRAAPI_V3_TIMEOUT', 30); 44 | 45 | $this->personalAccessToken = $this->env('JIRAAPI_V3_PERSONAL_ACCESS_TOKEN', false); 46 | $this->serviceDeskId = $this->env('JIRAAPI_V3_SERVICE_DESK_ID', null); 47 | } 48 | 49 | /** 50 | * Gets the value of an environment variable. Supports boolean, empty and null. 51 | */ 52 | private function env(string $key, mixed $default = null): mixed 53 | { 54 | $value = $_ENV[$key] ?? null; 55 | 56 | if ($value === null) { 57 | return $default; 58 | } 59 | 60 | switch (strtolower($value)) { 61 | case 'true': 62 | case '(true)': 63 | return true; 64 | 65 | case 'false': 66 | case '(false)': 67 | return false; 68 | 69 | case 'empty': 70 | case '(empty)': 71 | return ''; 72 | 73 | case 'null': 74 | case '(null)': 75 | return null; 76 | } 77 | 78 | if ($this->startsWith($value, '"') && $this->endsWith($value, '"')) { 79 | return substr($value, 1, -1); 80 | } 81 | 82 | return $value; 83 | } 84 | 85 | /** 86 | * Determine if a given string starts with a given substring. 87 | */ 88 | public function startsWith(string $haystack, array|string $needles): bool 89 | { 90 | foreach ((array) $needles as $needle) { 91 | if ($needle != '' && strpos($haystack, $needle) === 0) { 92 | return true; 93 | } 94 | } 95 | 96 | return false; 97 | } 98 | 99 | /** 100 | * Determine if a given string ends with a given substring. 101 | */ 102 | public function endsWith(string $haystack, array|string $needles): bool 103 | { 104 | foreach ((array) $needles as $needle) { 105 | if ((string) $needle === substr($haystack, -strlen($needle))) { 106 | return true; 107 | } 108 | } 109 | 110 | return false; 111 | } 112 | 113 | /** 114 | * load dotenv. 115 | */ 116 | private function loadDotEnv(string $path) 117 | { 118 | $requireParam = [ 119 | 'JIRAAPI_V3_HOST', 'JIRAAPI_V3_USER', 'JIRAAPI_V3_PERSONAL_ACCESS_TOKEN', 120 | ]; 121 | 122 | // support for dotenv 1.x and 2.x. see also https://github.com/lesstif/php-jira-rest-client/issues/102 123 | //if (method_exists('\Dotenv\Dotenv', 'createImmutable')) { // v4 or above 124 | $dotenv = \Dotenv\Dotenv::createImmutable($path); 125 | 126 | $dotenv->safeLoad(); 127 | $dotenv->required($requireParam); 128 | //} 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/Auth/AuthService.php: -------------------------------------------------------------------------------- 1 | _sessionCookieName) && !empty($this->_sessionCookieValue); 32 | } 33 | 34 | /** 35 | * used to prevent infinite recursion when cookie authorization requested and performed. 36 | * 37 | * @return bool 38 | */ 39 | public function isAuthInProgress() 40 | { 41 | return $this->_authInProgress; 42 | } 43 | 44 | /** 45 | * For internal usage. Performs login and saves session information for far cookie session authorization. 46 | * 47 | * @param null|string $username 48 | * @param null|string $password 49 | * 50 | * @throws \JiraCloud\JiraException 51 | * @throws \JsonMapper_Exception 52 | */ 53 | public function authorizeWithCookie($username = null, $password = null) 54 | { 55 | $this->_authInProgress = true; 56 | $session = $this->login($username, $password); 57 | 58 | $this->_sessionCookieName = $session->session->name; 59 | $this->_sessionCookieValue = $session->session->value; 60 | $this->_authInProgress = false; 61 | } 62 | 63 | public function getSessionCookieName() 64 | { 65 | return $this->_sessionCookieName; 66 | } 67 | 68 | public function getSessionCookieValue() 69 | { 70 | return $this->_sessionCookieValue; 71 | } 72 | 73 | /** 74 | * AuthService constructor. 75 | * 76 | * @param ConfigurationInterface|null $configuration 77 | * @param \Psr\Log\LoggerInterface|null $logger 78 | * @param string $path 79 | * 80 | * @throws \Exception 81 | * @throws \JiraCloud\JiraException 82 | */ 83 | public function __construct(?ConfigurationInterface $configuration = null, ?LoggerInterface $logger = null, $path = './') 84 | { 85 | parent::__construct($configuration, $logger, $path); 86 | $this->setAPIUri($this->auth_api_uri); 87 | } 88 | 89 | /** 90 | * Returns information about the currently authenticated user's session. 91 | * If the caller is not authenticated they will get a 401 Unauthorized status code. 92 | * 93 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/latest/#auth/1/session-currentUser Jira Reference 94 | * 95 | * @throws \JiraCloud\JiraException 96 | * @throws \JsonMapper_Exception 97 | * 98 | * @return CurrentUser 99 | */ 100 | public function getCurrentUser() 101 | { 102 | $ret = $this->exec($this->uri); 103 | 104 | $user = $this->json_mapper->map( 105 | json_decode($ret), 106 | new CurrentUser() 107 | ); 108 | 109 | return $user; 110 | } 111 | 112 | /** 113 | * Logs the current user out of JIRA, destroying the existing session, if any. 114 | * 115 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/latest/#auth/1/session-logout Jira Reference 116 | * 117 | * @throws \JiraCloud\JiraException 118 | * @throws \Exception 119 | * 120 | * @return bool 121 | */ 122 | public function logout() 123 | { 124 | $this->exec($this->uri, '', 'DELETE'); 125 | 126 | $this->_sessionCookieName = null; 127 | $this->_sessionCookieValue = null; 128 | 129 | return true; 130 | } 131 | 132 | /** 133 | * Logs the current user out of JIRA, destroying the existing session, if any. 134 | * 135 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/latest/#auth/1/session-logout Jira Reference 136 | * 137 | * @param string|null $username If null - takes username from configuration. 138 | * @param string|null $password If null - takes password from configuration. 139 | * 140 | * @throws \JiraCloud\JiraException 141 | * @throws \JsonMapper_Exception 142 | * 143 | * @return AuthSession 144 | */ 145 | public function login($username = null, $password = null) 146 | { 147 | if (!$username) { 148 | $username = $this->getConfiguration()->getJiraUser(); 149 | } 150 | 151 | if (!$password) { 152 | $password = $this->getConfiguration()->getJiraPassword(); 153 | } 154 | 155 | $ret = $this->exec($this->uri, json_encode([ 156 | 'username' => $username, 157 | 'password' => $password, 158 | ]), 'POST'); 159 | 160 | $session = $this->json_mapper->map( 161 | json_decode($ret), 162 | new AuthSession() 163 | ); 164 | 165 | return $session; 166 | } 167 | 168 | /** 169 | * This method invalidates the any current WebSudo session. 170 | * 171 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/latest/#auth/1/websudo-release Jira Reference 172 | * 173 | * @throws \JiraCloud\JiraException 174 | * 175 | * @return bool 176 | */ 177 | public function release() 178 | { 179 | $this->exec('websudo', '', 'DELETE'); 180 | 181 | return true; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Version/VersionService.php: -------------------------------------------------------------------------------- 1 | log->info("Create Version=\n".$data); 23 | 24 | $ret = $this->exec($this->uri, $data, 'POST'); 25 | 26 | return $this->json_mapper->map( 27 | json_decode($ret), 28 | new Version() 29 | ); 30 | } 31 | 32 | /** 33 | * Modify a version's sequence within a project. 34 | * 35 | * @param Version $version 36 | * 37 | * @throws JiraException 38 | */ 39 | public function move(Version $version) 40 | { 41 | throw new JiraException('move version not yet implemented'); 42 | } 43 | 44 | /** 45 | * get project version. 46 | * 47 | * @param string $id version id 48 | * 49 | * @throws JiraException 50 | * @throws \JsonMapper_Exception 51 | * 52 | * @return Version 53 | * 54 | * @see ProjectService::getVersions() 55 | */ 56 | public function get(string $id): Version 57 | { 58 | $ret = $this->exec($this->uri.'/'.$id); 59 | 60 | $this->log->info('Result='.$ret); 61 | 62 | return $this->json_mapper->map( 63 | json_decode($ret), 64 | new Version() 65 | ); 66 | } 67 | 68 | /** 69 | * @author Martijn Smidt 70 | * 71 | * @param Version $version 72 | * 73 | * @throws JiraException 74 | * 75 | * @return Version 76 | */ 77 | public function update(Version $version): Version 78 | { 79 | if (!$version->id || !is_numeric($version->id)) { 80 | throw new JiraException($version->id.' is not a valid version id.'); 81 | } 82 | 83 | //Only one of 'releaseDate' and 'userReleaseDate' can be specified when editing a version." 84 | $version->userReleaseDate = null; 85 | $version->userStartDate = null; 86 | 87 | $data = json_encode($version); 88 | $ret = $this->exec($this->uri.'/'.$version->id, $data, 'PUT'); 89 | 90 | return $this->json_mapper->map( 91 | json_decode($ret), 92 | new Version() 93 | ); 94 | } 95 | 96 | /** 97 | * @author Martijn Smidt 98 | * 99 | * @param Version $version 100 | * @param Version|bool $moveAffectedIssuesTo 101 | * @param Version|bool $moveFixIssuesTo 102 | * 103 | * @throws JiraException 104 | * 105 | * @return string 106 | */ 107 | public function delete(Version $version, $moveAffectedIssuesTo = false, $moveFixIssuesTo = false): string 108 | { 109 | if (!$version->id || !is_numeric($version->id)) { 110 | throw new JiraException($version->id.' is not a valid version id.'); 111 | } 112 | 113 | $data = []; 114 | 115 | if ($moveAffectedIssuesTo && $moveAffectedIssuesTo instanceof Version) { 116 | $data['moveAffectedIssuesTo'] = $moveAffectedIssuesTo->name; 117 | } 118 | 119 | if ($moveFixIssuesTo && $moveFixIssuesTo instanceof Version) { 120 | $data['moveFixIssuesTo'] = $moveFixIssuesTo->name; 121 | } 122 | 123 | $ret = $this->exec($this->uri.'/'.$version->id, json_encode($data), 'DELETE'); 124 | 125 | return $ret; 126 | } 127 | 128 | public function merge($ver) 129 | { 130 | throw new JiraException('merge version not yet implemented'); 131 | } 132 | 133 | /** 134 | * Returns a bean containing the number of fixed in and affected issues for the given version. 135 | * 136 | * @param Version $version 137 | * 138 | * @throws JiraException 139 | * 140 | * @see https://docs.atlassian.com/jira/REST/server/#api/2/version-getVersionRelatedIssues 141 | */ 142 | public function getRelatedIssues(Version $version): VersionIssueCounts 143 | { 144 | if (!$version->id || !is_numeric($version->id)) { 145 | throw new JiraException($version->id.' is not a valid version id.'); 146 | } 147 | 148 | $ret = $this->exec($this->uri.'/'.$version->id.'/relatedIssueCounts'); 149 | 150 | return $this->json_mapper->map( 151 | json_decode($ret), 152 | new VersionIssueCounts() 153 | ); 154 | } 155 | 156 | /** 157 | * Returns a bean containing the number of unresolved issues for the given version. 158 | * 159 | * @param Version $version 160 | * 161 | * @throws JiraException 162 | * 163 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/version-getVersionUnresolvedIssues 164 | * 165 | * @return VersionUnresolvedCount 166 | */ 167 | public function getUnresolvedIssues(Version $version): VersionUnresolvedCount 168 | { 169 | if (!$version->id || !is_numeric($version->id)) { 170 | throw new JiraException($version->id.' is not a valid version id.'); 171 | } 172 | 173 | $ret = $this->exec($this->uri.'/'.$version->id.'/unresolvedIssueCount'); 174 | 175 | return $this->json_mapper->map( 176 | json_decode($ret), 177 | new VersionUnresolvedCount() 178 | ); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/Configuration/AbstractConfiguration.php: -------------------------------------------------------------------------------- 1 | jiraHost; 94 | } 95 | 96 | public function getJiraUser(): string 97 | { 98 | return $this->jiraUser; 99 | } 100 | 101 | public function getJiraPassword(): string 102 | { 103 | return $this->jiraPassword; 104 | } 105 | 106 | public function getJiraLogEnabled(): bool 107 | { 108 | return $this->jiraLogEnabled; 109 | } 110 | 111 | public function getJiraLogFile(): string 112 | { 113 | return $this->jiraLogFile; 114 | } 115 | 116 | public function getJiraLogLevel(): string 117 | { 118 | return $this->jiraLogLevel; 119 | } 120 | 121 | public function isCurlOptSslVerifyHost(): bool 122 | { 123 | return $this->curlOptSslVerifyHost; 124 | } 125 | 126 | public function isCurlOptSslVerifyPeer(): bool 127 | { 128 | return $this->curlOptSslVerifyPeer; 129 | } 130 | 131 | public function isCurlOptSslCert(): ?string 132 | { 133 | return $this->curlOptSslCert; 134 | } 135 | 136 | public function isCurlOptSslCertPassword(): ?string 137 | { 138 | return $this->curlOptSslCertPassword; 139 | } 140 | 141 | public function isCurlOptSslKey(): ?string 142 | { 143 | return $this->curlOptSslKey; 144 | } 145 | 146 | public function isCurlOptSslKeyPassword(): ?string 147 | { 148 | return $this->curlOptSslKeyPassword; 149 | } 150 | 151 | public function isCurlOptVerbose(): bool 152 | { 153 | return $this->curlOptVerbose; 154 | } 155 | 156 | /** 157 | * Get curl option CURLOPT_USERAGENT. 158 | */ 159 | public function getCurlOptUserAgent(): ?string 160 | { 161 | return $this->curlOptUserAgent; 162 | } 163 | 164 | public function getCurlOptSslVerifyHostValue(): int 165 | { 166 | return [ 167 | false => 0, 168 | // See https://www.php.net/manual/en/function.curl-setopt.php for information why 2 needs to be here. 169 | true => 2, 170 | ][$this->isCurlOptSslVerifyHost()]; 171 | } 172 | 173 | public function getOAuthAccessToken(): string 174 | { 175 | return $this->oauthAccessToken; 176 | } 177 | 178 | public function isCookieAuthorizationEnabled(): bool 179 | { 180 | return $this->cookieAuthEnabled; 181 | } 182 | 183 | /** 184 | * get default User-Agent String. 185 | */ 186 | public function getDefaultUserAgentString(): string 187 | { 188 | $curlVersion = curl_version(); 189 | 190 | return sprintf('curl/%s (%s)', $curlVersion['version'], $curlVersion['host']); 191 | } 192 | 193 | public function getCookieFile(): ?string 194 | { 195 | return $this->cookieFile; 196 | } 197 | 198 | public function getProxyServer(): ?string 199 | { 200 | return $this->proxyServer; 201 | } 202 | 203 | public function getProxyPort(): ?string 204 | { 205 | return $this->proxyPort; 206 | } 207 | 208 | public function getProxyUser(): ?string 209 | { 210 | return $this->proxyUser; 211 | } 212 | 213 | public function getProxyPassword(): ?string 214 | { 215 | return $this->proxyPassword; 216 | } 217 | 218 | public function getTimeout(): int 219 | { 220 | return $this->timeout; 221 | } 222 | 223 | public function getPersonalAccessToken(): string 224 | { 225 | return $this->personalAccessToken; 226 | } 227 | 228 | public function getServiceDeskId(): ?int 229 | { 230 | return $this->serviceDeskId; 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/ServiceDesk/Customer/CustomerService.php: -------------------------------------------------------------------------------- 1 | client = $client; 30 | $this->userService = $userService; 31 | $this->logger = $client->getLogger(); 32 | $this->jsonMapper = $client->getMapper(); 33 | } 34 | 35 | /** 36 | * Creates a new customer. 37 | * 38 | * @throws JsonMapper_Exception|JiraException|JsonException 39 | * 40 | * @see https://docs.atlassian.com/jira-servicedesk/REST/3.6.2/#servicedeskapi/customer 41 | */ 42 | public function create(array|string $data): Customer 43 | { 44 | if (is_array($data)) { 45 | $data = json_encode($data, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE); 46 | } 47 | 48 | $this->logger->info("Create Customer=\n".$data); 49 | 50 | $result = $this->client->exec($this->uri, $data, 'POST'); 51 | 52 | return $this->jsonMapper->map( 53 | json_decode($result, false, 512, JSON_THROW_ON_ERROR), 54 | new Customer() 55 | ); 56 | } 57 | 58 | /** 59 | * Creates a new customer. 60 | * 61 | * @throws JsonMapper_Exception|JiraException|JsonException 62 | * 63 | * @see https://docs.atlassian.com/jira-servicedesk/REST/3.6.2/#servicedeskapi/customer 64 | */ 65 | public function createFromCustomer(Customer $customer): Customer 66 | { 67 | $data = json_encode($customer, JSON_THROW_ON_ERROR); 68 | 69 | return $this->create($data); 70 | } 71 | 72 | /** 73 | * Function to get customer. 74 | * 75 | * @param array $parameters Possible values for $paramArray 'username', 'key'. 76 | * "Either the 'username' or the 'key' query parameters need to be provided". 77 | * 78 | * @throws JsonMapper_Exception|JiraException 79 | */ 80 | public function get(array $parameters): Customer 81 | { 82 | return $this->userToCustomer( 83 | $this->userService->get($parameters) 84 | ); 85 | } 86 | 87 | /** 88 | * Returns a list of customers that match the search string and/or property. 89 | * 90 | * @param array $parameters 91 | * 92 | * @throws JsonMapper_Exception|JiraException|RuntimeException 93 | * 94 | * @return Customer[] 95 | */ 96 | public function findCustomers(array $parameters): array 97 | { 98 | return $this->usersToCustomers( 99 | $this->userService->findUsers($parameters) 100 | ); 101 | } 102 | 103 | /** 104 | * Returns a list of users that match with a specific query. 105 | * 106 | * @throws JsonMapper_Exception|JiraException|RuntimeException 107 | * 108 | * @return Customer[] 109 | * 110 | * @see https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-query-get 111 | */ 112 | public function findCustomersByQuery(array $parameters): array 113 | { 114 | return $this->usersToCustomers( 115 | $this->userService->findUsers($parameters) 116 | ); 117 | } 118 | 119 | /** 120 | * @param array $parameters 121 | * 122 | * @throws JsonMapper_Exception|JiraException|RuntimeException 123 | * 124 | * @return Customer[] 125 | */ 126 | public function getCustomers(array $parameters): array 127 | { 128 | return $this->usersToCustomers( 129 | $this->userService->getUsers($parameters) 130 | ); 131 | } 132 | 133 | /** 134 | * @param User[] $users 135 | * 136 | * @throws RuntimeException 137 | * 138 | * @return Customer[] 139 | */ 140 | private function usersToCustomers(array $users): array 141 | { 142 | $customers = []; 143 | 144 | foreach ($users as $user) { 145 | if (!$user instanceof User) { 146 | throw new RuntimeException('Only able to parse User-objects.'); 147 | } 148 | 149 | $customers[] = $this->userToCustomer($user); 150 | } 151 | 152 | return $customers; 153 | } 154 | 155 | private function userToCustomer(User $user): Customer 156 | { 157 | $customer = new Customer(); 158 | $customer->name = $user->name; 159 | $customer->key = $user->key; 160 | $customer->accountId = $user->accountId; 161 | $customer->emailAddress = $user->emailAddress; 162 | $customer->displayName = $user->displayName; 163 | $customer->active = $user->active; 164 | $customer->timeZone = $user->timeZone; 165 | $customer->setLinks( 166 | $this->avatarUrlsToLinks($user->self, $user->avatarUrls) 167 | ); 168 | $customer->self = $user->self; 169 | 170 | return $customer; 171 | } 172 | 173 | private function avatarUrlsToLinks(?string $url, ?object $avatarUrls): ?CustomerLinks 174 | { 175 | if ($avatarUrls === null) { 176 | return null; 177 | } 178 | 179 | $customerLinks = new CustomerLinks(); 180 | $customerLinks->jiraRest = $url; 181 | $customerLinks->avatarUrls = $avatarUrls; 182 | 183 | return $customerLinks; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/Board/BoardService.php: -------------------------------------------------------------------------------- 1 | setAPIUri('/rest/agile/'.$this->agileVersion); 21 | } 22 | 23 | /** 24 | * get all boards list. 25 | * 26 | * @param array $paramArray 27 | * 28 | * @throws \JiraCloud\JiraException 29 | * 30 | * @return \ArrayObject|Board[]|null array of Board class 31 | */ 32 | public function getBoardList($paramArray = []): ?\ArrayObject 33 | { 34 | $json = $this->exec($this->uri.$this->toHttpQueryParameter($paramArray), null); 35 | 36 | try { 37 | return $this->json_mapper->mapArray( 38 | json_decode($json, false, 512, $this->getJsonOptions())->values, 39 | new \ArrayObject(), 40 | Board::class 41 | ); 42 | } catch (\JsonException $exception) { 43 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 44 | 45 | return null; 46 | } 47 | } 48 | 49 | public function getBoard($id, $paramArray = []): ?Board 50 | { 51 | $json = $this->exec($this->uri.'/'.$id.$this->toHttpQueryParameter($paramArray), null); 52 | 53 | try { 54 | return $this->json_mapper->map( 55 | json_decode($json, false, 512, $this->getJsonOptions()), 56 | new Board() 57 | ); 58 | } catch (\JsonException $exception) { 59 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 60 | 61 | return null; 62 | } 63 | } 64 | 65 | /** 66 | * @return \ArrayObject|AgileIssue[]|null 67 | */ 68 | public function getBoardIssues($id, $paramArray = []): ?\ArrayObject 69 | { 70 | $json = $this->exec($this->uri.'/'.$id.'/issue'.$this->toHttpQueryParameter($paramArray), null); 71 | 72 | try { 73 | return $this->json_mapper->mapArray( 74 | json_decode($json, false, 512, $this->getJsonOptions())->issues, 75 | new \ArrayObject(), 76 | AgileIssue::class 77 | ); 78 | } catch (\JsonException $exception) { 79 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 80 | 81 | return null; 82 | } 83 | } 84 | 85 | /** 86 | * @return \ArrayObject|AgileIssue[]|null 87 | */ 88 | public function getBoardBacklogIssues($id, array $paramArray = []): ?\ArrayObject 89 | { 90 | $json = $this->exec($this->uri.'/'.$id.'/backlog'.$this->toHttpQueryParameter($paramArray), null); 91 | 92 | try { 93 | return $this->json_mapper->mapArray( 94 | json_decode($json, false, 512, $this->getJsonOptions())->issues, 95 | new \ArrayObject(), 96 | AgileIssue::class 97 | ); 98 | } catch (\JsonException $exception) { 99 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 100 | 101 | return null; 102 | } 103 | } 104 | 105 | /** 106 | * @return \ArrayObject|Sprint[]|null 107 | */ 108 | public function getBoardSprints($boardId, $paramArray = []): ?\ArrayObject 109 | { 110 | $json = $this->exec($this->uri.'/'.$boardId.'/sprint'.$this->toHttpQueryParameter($paramArray), null); 111 | 112 | try { 113 | return $this->json_mapper->mapArray( 114 | json_decode($json, false, 512, $this->getJsonOptions())->values, 115 | new \ArrayObject(), 116 | Sprint::class 117 | ); 118 | } catch (\JsonException $exception) { 119 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 120 | 121 | return null; 122 | } 123 | } 124 | 125 | /** 126 | * @return \ArrayObject|Epic[]|null 127 | */ 128 | public function getBoardEpics($boardId, $paramArray = []): ?\ArrayObject 129 | { 130 | $json = $this->exec($this->uri.'/'.$boardId.'/epic'.$this->toHttpQueryParameter($paramArray), null); 131 | 132 | try { 133 | return $this->json_mapper->mapArray( 134 | json_decode($json, false, 512, $this->getJsonOptions())->values, 135 | new \ArrayObject(), 136 | Epic::class 137 | ); 138 | } catch (\JsonException $exception) { 139 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 140 | 141 | return null; 142 | } 143 | } 144 | 145 | public function getBoardColumnConfiguration($boardId, $paramArray = []): ?BoardColumnConfig 146 | { 147 | $json = $this->exec($this->uri.'/'.$boardId.'/configuration'.$this->toHttpQueryParameter($paramArray), null); 148 | 149 | try { 150 | return $this->json_mapper->map( 151 | json_decode($json, false, 512, $this->getJsonOptions())->columnConfig, 152 | BoardColumnConfig::class 153 | ); 154 | } catch (\JsonException $exception) { 155 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 156 | 157 | return null; 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/Field/FieldService.php: -------------------------------------------------------------------------------- 1 | exec($this->uri, null); 21 | 22 | $fields = $this->json_mapper->mapArray( 23 | json_decode($ret, false), 24 | new \ArrayObject(), 25 | '\JiraCloud\Field\Field' 26 | ); 27 | 28 | // temp array 29 | $ar = []; 30 | if ($fieldType === Field::CUSTOM) { 31 | foreach ($fields as $f) { 32 | if ($f->custom === true) { 33 | array_push($ar, $f); 34 | } 35 | } 36 | $fields = &$ar; 37 | } elseif ($fieldType === Field::SYSTEM) { 38 | foreach ($fields as $f) { 39 | if ($f->custom === false) { 40 | array_push($ar, $f); 41 | } 42 | } 43 | $fields = &$ar; 44 | } 45 | 46 | return $fields; 47 | } 48 | 49 | /** 50 | * Returned if the Custom Field Option exists and is visible by the calling user. 51 | * 52 | * Currently, JIRA doesn't provide a method to retrieve custom field's option. instead use getEditMeta(). 53 | * 54 | * @see IssueService::getEditMeta() . 55 | * 56 | * @param string $id custom field option id 57 | * 58 | * @throws \JiraCloud\JiraException 59 | * 60 | * @return \stdClass 61 | */ 62 | public function getCustomFieldOption($id): \stdClass 63 | { 64 | $ret = $this->exec('/customFieldOption/'.$id); 65 | 66 | $this->log->debug("get custom Field Option=\n".$ret); 67 | 68 | return json_decode($ret); 69 | } 70 | 71 | /** 72 | * create new field. 73 | * 74 | * @param Field $field object of Field class 75 | * 76 | * @throws \JiraCloud\JiraException 77 | * @throws \JsonMapper_Exception 78 | * 79 | * @return Field created field class 80 | */ 81 | public function create(Field $field) 82 | { 83 | $data = json_encode($field); 84 | 85 | $this->log->info("Create Field=\n".$data); 86 | 87 | $ret = $this->exec($this->uri, $data, 'POST'); 88 | 89 | $cf = $this->json_mapper->map( 90 | json_decode($ret), 91 | new Field() 92 | ); 93 | 94 | return $cf; 95 | } 96 | 97 | /** 98 | * @param int $fieldId The custom field Id 99 | * 100 | * @throws \JiraCloud\JiraException 101 | * 102 | * @return string|bool 103 | */ 104 | public function getCustomFieldContexts(int $fieldId) 105 | { 106 | $url = sprintf('%s/customfield_%s/contexts', $this->uri, $fieldId); 107 | $ret = $this->exec($url); 108 | 109 | $this->log->debug("get custom Field Contexts=\n".$ret); 110 | 111 | return $ret; 112 | } 113 | 114 | /** 115 | * Get a custom fields options. 116 | * 117 | * @param int $fieldId The custom field Id 118 | * @param int $contextId Context ID related to the custom field 119 | * @param array $paramArray Query parameters like 'startAt' and 'maxResults' 120 | * 121 | * @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-custom-field-options/#api-rest-api-3-field-fieldid-context-contextid-option-get 122 | * 123 | * @throws \JiraCloud\JiraException 124 | * 125 | * @return string 126 | */ 127 | public function getCustomFieldOptions(int $fieldId, int $contextId, array $paramArray = []) 128 | { 129 | $url = sprintf('%s/customfield_%s/context/%s/option%s', $this->uri, $fieldId, $contextId, $this->toHttpQueryParameter($paramArray)); 130 | $ret = $this->exec($url); 131 | 132 | $this->log->debug("get custom Field Options=\n".$ret); 133 | 134 | return $ret; 135 | } 136 | 137 | /** 138 | * Create custom field options. 139 | * 140 | * @param int $fieldId The custom field Id to add options to 141 | * @param int $contextId Context ID related to the custom field 142 | * @param array $options The options array 143 | * 144 | * @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-custom-field-options/#api-rest-api-3-field-fieldid-context-contextid-option-post 145 | * 146 | * @throws \JiraCloud\JiraException 147 | * 148 | * @return string 149 | */ 150 | public function createCustomFieldOptions(int $fieldId, int $contextId, array $options = []) 151 | { 152 | $url = sprintf('%s/customfield_%s/context/%s/option', $this->uri, $fieldId, $contextId); 153 | $ret = $this->exec($url, json_encode($options), 'POST'); 154 | 155 | $this->log->debug("create custom Field Options=\n".$ret); 156 | 157 | return $ret; 158 | } 159 | 160 | /** 161 | * Update a custom field options. 162 | * 163 | * @param int $fieldId The custom field Id 164 | * @param int $contextId Context ID related to the custom field 165 | * @param array $options The new options array 166 | * 167 | * @see https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-custom-field-options/#api-rest-api-3-field-fieldid-context-contextid-option-put 168 | * 169 | * @throws \JiraCloud\JiraException 170 | * 171 | * @return string 172 | */ 173 | public function updateCustomFieldOptions(int $fieldId, int $contextId, array $options = []) 174 | { 175 | $url = sprintf('%s/customfield_%s/context/%s/option', $this->uri, $fieldId, $contextId); 176 | $ret = $this->exec($url, json_encode($options), 'PUT'); 177 | 178 | $this->log->debug("update custom Field Options=\n".$ret); 179 | 180 | return $ret; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/ServiceDesk/Organisation/OrganisationService.php: -------------------------------------------------------------------------------- 1 | client = $client; 26 | $this->logger = $client->getLogger(); 27 | $this->jsonMapper = $client->getMapper(); 28 | } 29 | 30 | /** 31 | * @throws JsonMapper_Exception|JiraException|JsonException 32 | * 33 | * @see https://docs.atlassian.com/jira-servicedesk/REST/3.6.2/#servicedeskapi/organization-createOrganization 34 | */ 35 | public function create(array|string $data): Organisation 36 | { 37 | if (is_array($data)) { 38 | $data = json_encode($data, JSON_THROW_ON_ERROR); 39 | } 40 | 41 | $this->logger->info("Create ServiceDesk Organisation=\n".$data); 42 | 43 | $result = $this->client->exec($this->uri, $data, 'POST'); 44 | 45 | return $this->createOrganisation($result); 46 | } 47 | 48 | /** 49 | * @throws JsonMapper_Exception|JiraException|JsonException 50 | * 51 | * @see https://docs.atlassian.com/jira-servicedesk/REST/3.6.2/#servicedeskapi/organization-createOrganization 52 | */ 53 | public function createFromOrganisation(Organisation $organisation): Organisation 54 | { 55 | $data = json_encode($organisation, JSON_THROW_ON_ERROR); 56 | 57 | return $this->create($data); 58 | } 59 | 60 | /** 61 | * @throws JsonMapper_Exception|JiraException|JsonException 62 | */ 63 | public function get(string $organisationId): Organisation 64 | { 65 | $result = $this->client->exec( 66 | $this->client->createUrl('%s/%s', [$this->uri, $organisationId]) 67 | ); 68 | 69 | return $this->createOrganisation($result); 70 | } 71 | 72 | /** 73 | * Returns the organisations paginated. 74 | * 75 | * @throws JiraException|JsonMapper_Exception|JsonException 76 | * 77 | * @return Organisation[] 78 | * 79 | * @see https://docs.atlassian.com/jira-servicedesk/REST/3.6.2/#servicedeskapi/organization 80 | */ 81 | public function getOrganisations(int $startIndex, int $amountOfItems): array 82 | { 83 | $result = $this->client->exec( 84 | $this->createGetOrganisationsUrl($startIndex, $amountOfItems) 85 | ); 86 | 87 | $this->logger->info("Result=\n".$result); 88 | 89 | $organisationData = json_decode($result, false, 512, JSON_THROW_ON_ERROR); 90 | $organisations = []; 91 | 92 | foreach ($organisationData->values as $organisation) { 93 | $organisations[] = $this->jsonMapper->map($organisation, new Organisation()); 94 | } 95 | 96 | return $organisations; 97 | } 98 | 99 | /** 100 | * Returns the organisation customers paginated. 101 | * 102 | * @throws JsonMapper_Exception|JiraException|JsonException 103 | * 104 | * @return Customer[] 105 | */ 106 | public function getCustomersForOrganisation(int $startIndex, int $amountOfItems, Organisation $organisation): array 107 | { 108 | $result = $this->client->exec( 109 | $this->createGetCustomersUrl($organisation->id, $startIndex, $amountOfItems) 110 | ); 111 | 112 | $this->logger->info("Result=\n".$result); 113 | 114 | $customerData = json_decode($result, false, 512, JSON_THROW_ON_ERROR); 115 | $customers = []; 116 | 117 | foreach ($customerData as $customer) { 118 | $customers[] = $this->jsonMapper->map( 119 | $customer, 120 | new Customer() 121 | ); 122 | } 123 | 124 | return $customers; 125 | } 126 | 127 | /** 128 | * @param Customer[] $customers 129 | * 130 | * @throws JiraException 131 | */ 132 | public function addCustomersToOrganisation(array $customers, Organisation $organisation): void 133 | { 134 | $customerNames = array_map(static function (Customer $customer) { 135 | return $customer->name; 136 | }, $customers); 137 | 138 | $this->client->exec( 139 | $this->client->createUrl('%s/%s', [$this->uri, $organisation->id]), 140 | ['usernames' => $customerNames], 141 | 'POST' 142 | ); 143 | } 144 | 145 | /** 146 | * @throws JiraException 147 | */ 148 | public function deleteOrganisation(Organisation $organisation): void 149 | { 150 | $this->client->exec( 151 | $this->client->createUrl('%s/%s', [$this->uri, $organisation->id]), 152 | null, 153 | 'DELETE' 154 | ); 155 | } 156 | 157 | /** 158 | * @throws InvalidArgumentException 159 | */ 160 | private function createGetOrganisationsUrl(int $startIndex, int $amountOfItems): string 161 | { 162 | if ($startIndex < 0) { 163 | throw new InvalidArgumentException('Start index can not be lower then 0.'); 164 | } 165 | if ($amountOfItems < 1) { 166 | throw new InvalidArgumentException('Amount of items can not be lower then 1.'); 167 | } 168 | 169 | return $this->client->createUrl( 170 | '%s?%s', 171 | [$this->uri], 172 | ['start' => $startIndex, 'limit' => $amountOfItems] 173 | ); 174 | } 175 | 176 | /** 177 | * @throws InvalidArgumentException 178 | */ 179 | private function createGetCustomersUrl(int $organisationId, int $startIndex, int $amountOfItems): string 180 | { 181 | if ($startIndex < 0) { 182 | throw new InvalidArgumentException('Start index can not be lower then 0.'); 183 | } 184 | if ($amountOfItems < 1) { 185 | throw new InvalidArgumentException('Amount of items can not be lower then 1.'); 186 | } 187 | 188 | return $this->client->createUrl( 189 | '%s/%s/user?%s', 190 | [$this->uri, $organisationId], 191 | ['start' => $startIndex, 'limit' => $amountOfItems] 192 | ); 193 | } 194 | 195 | /** 196 | * @throws JsonMapper_Exception|JsonException 197 | */ 198 | private function createOrganisation(string $data): Organisation 199 | { 200 | return $this->jsonMapper->map( 201 | json_decode($data, true, 512, JSON_THROW_ON_ERROR), 202 | new Organisation() 203 | ); 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/User/UserService.php: -------------------------------------------------------------------------------- 1 | log->info("Create User=\n".$data); 31 | 32 | $ret = $this->exec($this->uri, $data, 'POST'); 33 | 34 | return $this->json_mapper->map( 35 | json_decode($ret), 36 | new User() 37 | ); 38 | } 39 | 40 | /** 41 | * Function to get user. 42 | * 43 | * @param array $paramArray Possible values for $paramArray 'accountId', 'key'. 44 | * "Either the 'username' or the 'key' query parameters need to be provided". 45 | * 46 | * @throws \JsonMapper_Exception 47 | * @throws \JiraCloud\JiraException 48 | * 49 | * @return User User class 50 | */ 51 | public function get(array $paramArray): User 52 | { 53 | $queryParam = '?'.http_build_query($paramArray); 54 | 55 | $ret = $this->exec($this->uri.$queryParam, null); 56 | 57 | $this->log->info("Result=\n".$ret); 58 | 59 | return $this->json_mapper->map( 60 | json_decode($ret), 61 | new User() 62 | ); 63 | } 64 | 65 | /** 66 | * Returns a list of users that match the search string and/or property. 67 | * 68 | * @param array $paramArray 69 | * 70 | * @throws \JsonMapper_Exception 71 | * @throws \JiraCloud\JiraException 72 | * 73 | * @return User[] 74 | */ 75 | public function findUsers(array $paramArray): array 76 | { 77 | $queryParam = '?'.http_build_query($paramArray); 78 | 79 | $ret = $this->exec($this->uri.'/search'.$queryParam, null); 80 | 81 | $this->log->info("Result=\n".$ret); 82 | 83 | $userData = json_decode($ret); 84 | $users = []; 85 | 86 | foreach ($userData as $user) { 87 | $users[] = $this->json_mapper->map( 88 | $user, 89 | new User() 90 | ); 91 | } 92 | 93 | return $users; 94 | } 95 | 96 | /** 97 | * Returns a list of users that match the search string. 98 | * Please note that this resource should be called with an issue key when a list of assignable users is retrieved for editing. 99 | * 100 | * @param array $paramArray 101 | * 102 | * @throws \JsonMapper_Exception 103 | * @throws \JiraCloud\JiraException 104 | * 105 | * @return User[] 106 | * 107 | * @see https://docs.atlassian.com/jira/REST/cloud/#api/2/user-findAssignableUsers 108 | */ 109 | public function findAssignableUsers(array $paramArray): array 110 | { 111 | $queryParam = '?'.http_build_query($paramArray); 112 | 113 | $ret = $this->exec($this->uri.'/assignable/search'.$queryParam, null); 114 | 115 | $this->log->info("Result=\n".$ret); 116 | 117 | $userData = json_decode($ret); 118 | $users = []; 119 | 120 | foreach ($userData as $user) { 121 | $users[] = $this->json_mapper->map( 122 | $user, 123 | new User() 124 | ); 125 | } 126 | 127 | return $users; 128 | } 129 | 130 | /** 131 | * Returns a list of users that match with a specific query. 132 | * 133 | * @param array $paramArray 134 | * 135 | * @throws \JsonMapper_Exception 136 | * @throws \JiraCloud\JiraException 137 | * 138 | * @return User[] 139 | * 140 | * @see https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-query-get 141 | */ 142 | public function findUsersByQuery(array $paramArray): array 143 | { 144 | $queryParam = '?'.http_build_query($paramArray); 145 | 146 | $ret = $this->exec($this->uri.'/search/query'.$queryParam, null); 147 | 148 | $this->log->info("Result=\n".$ret); 149 | 150 | $userData = json_decode($ret); 151 | $users = []; 152 | 153 | foreach ($userData->values as $user) { 154 | $users[] = $this->json_mapper->map( 155 | $user, 156 | new User() 157 | ); 158 | } 159 | 160 | return $users; 161 | } 162 | 163 | /** 164 | * Delete a User. 165 | * 166 | * @param array $paramArray username or keys 167 | * 168 | * @throws \JiraCloud\JiraException 169 | * 170 | * @return string 171 | */ 172 | public function deleteUser(array $paramArray): string 173 | { 174 | $queryParam = '?'.http_build_query($paramArray); 175 | 176 | $ret = $this->exec($this->uri.$queryParam, null, 'DELETE'); 177 | 178 | return $ret; 179 | } 180 | 181 | /** 182 | * get a user info details. 183 | * 184 | * @throws \JiraCloud\JiraException 185 | * 186 | * @return Reporter user Object 187 | */ 188 | public function getMyself() 189 | { 190 | $ret = $this->exec('myself', null); 191 | 192 | $user = $this->json_mapper->map( 193 | json_decode($ret), 194 | new Reporter() 195 | ); 196 | 197 | return $user; 198 | } 199 | 200 | /** 201 | * @param array $paramArray 202 | * 203 | * @throws \JsonMapper_Exception 204 | * @throws \JiraCloud\JiraException 205 | * 206 | * @return User[] 207 | */ 208 | public function getUsers(array $paramArray): array 209 | { 210 | $queryParam = '?'.http_build_query($paramArray); 211 | 212 | $ret = $this->exec('/users'.$queryParam, null); 213 | 214 | $this->log->info("Result=\n".$ret); 215 | 216 | $userData = json_decode($ret); 217 | $users = []; 218 | 219 | foreach ($userData as $user) { 220 | $users[] = $this->json_mapper->map($user, new User()); 221 | } 222 | 223 | return $users; 224 | } 225 | 226 | /** 227 | * Function to update an existing user. 228 | * 229 | * @param array $paramArray 230 | * @param array|User $user 231 | * 232 | * @throws \JsonMapper_Exception 233 | * @throws \JiraCloud\JiraException 234 | * 235 | * @return User User class 236 | */ 237 | public function update(array $paramArray, User|array $user): User 238 | { 239 | $queryParam = '?'.http_build_query($paramArray); 240 | 241 | $data = json_encode($user); 242 | 243 | $this->log->info('Update User ('.$queryParam.") =\n".$data); 244 | 245 | $ret = $this->exec($this->uri.$queryParam, $data, 'PUT'); 246 | 247 | return $this->json_mapper->map( 248 | json_decode($ret), 249 | new User() 250 | ); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/Project/Project.php: -------------------------------------------------------------------------------- 1 | leadName)) { 125 | $params['lead'] = $this->leadName; 126 | unset($params['leadName']); 127 | } 128 | if ($this->versions === null or count($this->versions) === 0) { 129 | unset($params['versions']); 130 | } 131 | 132 | return $params; 133 | } 134 | 135 | public function setId(string $id): static 136 | { 137 | $this->id = $id; 138 | 139 | return $this; 140 | } 141 | 142 | public function setKey(string $key): static 143 | { 144 | $this->key = $key; 145 | 146 | return $this; 147 | } 148 | 149 | public function setName(string $name): static 150 | { 151 | $this->name = $name; 152 | 153 | return $this; 154 | } 155 | 156 | public function setAvatarUrls(\stdClass $avatarUrls): static 157 | { 158 | $this->avatarUrls = $avatarUrls; 159 | 160 | return $this; 161 | } 162 | 163 | public function setProjectCategory(\stdClass $projectCategory): static 164 | { 165 | $this->projectCategory = $projectCategory; 166 | 167 | return $this; 168 | } 169 | 170 | public function setDescription(string $description): static 171 | { 172 | $this->description = $description; 173 | 174 | return $this; 175 | } 176 | 177 | public function setLeadName(string $leadName): static 178 | { 179 | $this->leadName = $leadName; 180 | 181 | return $this; 182 | } 183 | 184 | public function setLeadAccountId(string $leadAccountId): static 185 | { 186 | $this->leadAccountId = $leadAccountId; 187 | 188 | return $this; 189 | } 190 | 191 | public function setUrl(string $url): static 192 | { 193 | $this->url = $url; 194 | 195 | return $this; 196 | } 197 | 198 | public function setProjectTypeKey(string $projectTypeKey): static 199 | { 200 | $this->projectTypeKey = $projectTypeKey; 201 | 202 | return $this; 203 | } 204 | 205 | public function setProjectTemplateKey(string $projectTemplateKey): static 206 | { 207 | $this->projectTemplateKey = $projectTemplateKey; 208 | 209 | return $this; 210 | } 211 | 212 | public function setAvatarId(int $avatarId): static 213 | { 214 | $this->avatarId = $avatarId; 215 | 216 | return $this; 217 | } 218 | 219 | public function setIssueSecurityScheme(int $issueSecurityScheme): static 220 | { 221 | $this->issueSecurityScheme = $issueSecurityScheme; 222 | 223 | return $this; 224 | } 225 | 226 | public function setPermissionScheme(int $permissionScheme): static 227 | { 228 | $this->permissionScheme = $permissionScheme; 229 | 230 | return $this; 231 | } 232 | 233 | public function setNotificationScheme(int $notificationScheme): static 234 | { 235 | $this->notificationScheme = $notificationScheme; 236 | 237 | return $this; 238 | } 239 | 240 | public function setCategoryId(int $categoryId): static 241 | { 242 | $this->categoryId = $categoryId; 243 | 244 | return $this; 245 | } 246 | 247 | /** 248 | * $assigneeType value available for "PROJECT_LEAD" and "UNASSIGNED". 249 | */ 250 | public function setAssigneeType(?string $assigneeType): static 251 | { 252 | if (!in_array($assigneeType, ['PROJECT_LEAD', 'UNASSIGNED'])) { 253 | throw new JiraException('invalid assigneeType:'.$assigneeType); 254 | } 255 | 256 | $this->assigneeType = $assigneeType; 257 | 258 | return $this; 259 | } 260 | 261 | public function setAssigneeTypeAsEnum(AssigneeTypeEnum $assigneeType): static 262 | { 263 | $this->assigneeType = $assigneeType->type(); 264 | 265 | return $this; 266 | } 267 | 268 | public function setWorkflowScheme(int $workflowScheme): static 269 | { 270 | $this->workflowScheme = $workflowScheme; 271 | 272 | return $this; 273 | } 274 | 275 | public function setIssueTypeScreenScheme(int $issueTypeScreenScheme): static 276 | { 277 | $this->issueTypeScreenScheme = $issueTypeScreenScheme; 278 | 279 | return $this; 280 | } 281 | 282 | public function setIssueTypeScheme(int $issueTypeScheme): static 283 | { 284 | $this->issueTypeScheme = $issueTypeScheme; 285 | 286 | return $this; 287 | } 288 | 289 | public function setFieldConfigurationScheme(int $fieldConfigurationScheme): static 290 | { 291 | $this->fieldConfigurationScheme = $fieldConfigurationScheme; 292 | 293 | return $this; 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /test-data/issueField.json: -------------------------------------------------------------------------------- 1 | { 2 | "issuetype": { 3 | "self": "https://jira.example.com/rest/api/2/issuetype/10000", 4 | "id": "10000", 5 | "description": "해야할 일", 6 | "iconUrl": "https://jira.example.com/secure/viewavatar?size=xsmall&avatarId=10318&avatarType=issuetype", 7 | "name": "작업", 8 | "subtask": false, 9 | "avatarId": 10318 10 | }, 11 | "timespent": null, 12 | "project": { 13 | "self": "https://jira.example.com/rest/api/2/project/10000", 14 | "id": "10000", 15 | "key": "TEST", 16 | "name": "test", 17 | "avatarUrls": { 18 | "48x48": "https://jira.example.com/secure/projectavatar?avatarId=10324", 19 | "24x24": "https://jira.example.com/secure/projectavatar?size=small&avatarId=10324", 20 | "16x16": "https://jira.example.com/secure/projectavatar?size=xsmall&avatarId=10324", 21 | "32x32": "https://jira.example.com/secure/projectavatar?size=medium&avatarId=10324" 22 | } 23 | }, 24 | "fixVersions": [], 25 | "aggregatetimespent": null, 26 | "resolution": null, 27 | "customfield_10310": null, 28 | "customfield_10304": null, 29 | "customfield_10305": null, 30 | "customfield_10306": null, 31 | "customfield_10307": null, 32 | "customfield_10308": null, 33 | "resolutiondate": null, 34 | "customfield_10309": null, 35 | "workratio": -1, 36 | "lastViewed": null, 37 | "watches": { 38 | "self": "https://jira.example.com/rest/api/2/issue/TEST-1/watchers", 39 | "watchCount": 0, 40 | "isWatching": false 41 | }, 42 | "created": "2018-02-04T09:07:06.000+0000", 43 | "priority": { 44 | "self": "https://jira.example.com/rest/api/2/priority/3", 45 | "iconUrl": "https://jira.example.com/images/icons/priorities/medium.svg", 46 | "name": "Medium", 47 | "id": "3" 48 | }, 49 | "customfield_10221": null, 50 | "customfield_10222": null, 51 | "labels": [], 52 | "customfield_10301": null, 53 | "customfield_10216": null, 54 | "timeestimate": null, 55 | "aggregatetimeoriginalestimate": null, 56 | "versions": [], 57 | "issuelinks": [], 58 | "assignee": { 59 | "self": "https://jira.example.com/rest/api/2/user?username=lesstif", 60 | "name": "lesstif", 61 | "key": "정광섭", 62 | "emailAddress": "lesstif@gmail.com", 63 | "avatarUrls": { 64 | "48x48": "https://secure.gravatar.com/avatar/e52d3045c4aaab2dd76d6a30133826d2?d=mm&s=48", 65 | "24x24": "https://secure.gravatar.com/avatar/e52d3045c4aaab2dd76d6a30133826d2?d=mm&s=24", 66 | "16x16": "https://secure.gravatar.com/avatar/e52d3045c4aaab2dd76d6a30133826d2?d=mm&s=16", 67 | "32x32": "https://secure.gravatar.com/avatar/e52d3045c4aaab2dd76d6a30133826d2?d=mm&s=32" 68 | }, 69 | "displayName": "lesstif", 70 | "active": true, 71 | "timeZone": "Asia/Seoul" 72 | }, 73 | "updated": "2018-02-06T05:47:31.000+0000", 74 | "status": { 75 | "self": "https://jira.example.com/rest/api/2/status/10000", 76 | "description": "", 77 | "iconUrl": "https://jira.example.com/images/icons/status_generic.gif", 78 | "name": "할 일", 79 | "id": "10000", 80 | "statusCategory": { 81 | "self": "https://jira.example.com/rest/api/2/statuscategory/2", 82 | "id": 2, 83 | "key": "new", 84 | "colorName": "blue-gray", 85 | "name": "할 일" 86 | } 87 | }, 88 | "components": [], 89 | "timeoriginalestimate": null, 90 | "description": "h2. This is your first task. \n{color:#707070} Issues are the things you do in a project. In business projects, issues are called tasks. {color}\nh4. Types of tasks\n{color:#707070}A task can represent a document, a creative asset, a purchase and even a person.{color}\n!IssueTypes.png!\nh4. Details\n{color:#707070}The 'Details' section above, provides the information you need, such as priority and status, to help you track the progress of your tasks.{color}\nNext: [Workflows and statuses|TEST-2]\n\n----\n[Learn more |http://blogs.atlassian.com/2015/11/make-jira-core-issues-work-for-your-business-team/]", 91 | "customfield_10211": null, 92 | "timetracking": {}, 93 | "customfield_10005": "0|i0005r:", 94 | "attachment": [ 95 | { 96 | "self": "https://jira.example.com/rest/api/2/attachment/10000", 97 | "id": "10000", 98 | "filename": "IssueTypes.png", 99 | "author": { 100 | "self": "https://jira.example.com/rest/api/2/user?username=lesstif", 101 | "name": "lesstif", 102 | "key": "lesstif", 103 | "emailAddress": "lesstif@gmail.com", 104 | "avatarUrls": { 105 | "48x48": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=48", 106 | "24x24": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=24", 107 | "16x16": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=16", 108 | "32x32": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=32" 109 | }, 110 | "displayName": "정광섭", 111 | "active": true, 112 | "timeZone": "Asia/Seoul" 113 | }, 114 | "created": "2018-02-04T09:07:06.000+0000", 115 | "size": 12880, 116 | "mimeType": "image/png", 117 | "content": "https://jira.example.com/secure/attachment/10000/IssueTypes.png", 118 | "thumbnail": "https://jira.example.com/secure/thumbnail/10000/_thumb_10000.png" 119 | } 120 | ], 121 | "aggregatetimeestimate": null, 122 | "customfield_10209": null, 123 | "summary": "This is your first task", 124 | "creator": { 125 | "self": "https://jira.example.com/rest/api/2/user?username=lesstif", 126 | "name": "lesstif", 127 | "key": "lesstif", 128 | "emailAddress": "lesstif@gmail.com", 129 | "avatarUrls": { 130 | "48x48": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=48", 131 | "24x24": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=24", 132 | "16x16": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=16", 133 | "32x32": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=32" 134 | }, 135 | "displayName": "정광섭", 136 | "active": true, 137 | "timeZone": "Asia/Seoul" 138 | }, 139 | "subtasks": [], 140 | "reporter": { 141 | "self": "https://jira.example.com/rest/api/2/user?username=lesstif", 142 | "name": "lesstif", 143 | "key": "lesstif", 144 | "emailAddress": "lesstif@gmail.com", 145 | "avatarUrls": { 146 | "48x48": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=48", 147 | "24x24": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=24", 148 | "16x16": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=16", 149 | "32x32": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=32" 150 | }, 151 | "displayName": "정광섭", 152 | "active": true, 153 | "timeZone": "Asia/Seoul" 154 | }, 155 | "customfield_10000": null, 156 | "aggregateprogress": { 157 | "progress": 0, 158 | "total": 0 159 | }, 160 | "customfield_10004": null, 161 | "environment": null, 162 | "duedate": null, 163 | "progress": { 164 | "progress": 0, 165 | "total": 0 166 | }, 167 | "comment": { 168 | "comments": [], 169 | "maxResults": 0, 170 | "total": 0, 171 | "startAt": 0 172 | }, 173 | "votes": { 174 | "self": "https://jira.example.com/rest/api/2/issue/TEST-1/votes", 175 | "votes": 0, 176 | "hasVoted": false 177 | }, 178 | "worklog": { 179 | "startAt": 0, 180 | "maxResults": 20, 181 | "total": 0, 182 | "worklogs": [] 183 | }, 184 | "security": { 185 | "id": 12345 186 | } 187 | } --------------------------------------------------------------------------------