├── src ├── Project │ ├── Component.php │ ├── ProjectType.php │ └── Project.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 │ ├── Property.php │ ├── AgileIssue.php │ ├── AgileIssueFields.php │ ├── Statuscategory.php │ ├── Component.php │ ├── CustomFieldSearchResult.php │ ├── TransitionTo.php │ ├── Comments.php │ ├── History.php │ ├── ChangeLog.php │ ├── ContentField.php │ ├── Priority.php │ ├── SecurityScheme.php │ ├── IssueType.php │ ├── IssueStatus.php │ ├── VersionUnresolvedCount.php │ ├── Visibility.php │ ├── RemoteIssueLinkObject.php │ ├── Attachment.php │ ├── CustomField.php │ ├── Issue.php │ ├── JQLCountResult.php │ ├── VisibilityTrait.php │ ├── Comment.php │ ├── CustomFieldUsage.php │ ├── AgileIssueService.php │ ├── RemoteIssueLink.php │ ├── IssueBulkResult.php │ ├── VersionIssueCounts.php │ ├── IssueSearchResult.php │ ├── PaginatedWorklog.php │ ├── Transition.php │ ├── Reporter.php │ ├── Notify.php │ ├── Version.php │ ├── Worklog.php │ └── TimeTracking.php ├── ServiceDeskTrait.php ├── NoOperationMonologHandlerV3.php ├── Group │ ├── GroupUser.php │ ├── GroupSearchResult.php │ ├── Group.php │ └── GroupService.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 ├── User │ ├── User.php │ └── UserService.php ├── IssueLink │ ├── IssueLinkType.php │ ├── IssueLinkService.php │ └── IssueLink.php ├── Field │ ├── Schema.php │ ├── FieldService.php │ └── Field.php ├── IssueType │ └── IssueTypeService.php ├── JiraRestApiServiceProvider.php ├── Dumper.php ├── StatusCategory │ └── StatusCategoryService.php ├── JiraException.php ├── JsonMapperHelper.php ├── RapidCharts │ └── ScopeChangeBurnDownChartService.php ├── Configuration │ ├── ArrayConfiguration.php │ ├── ConfigurationInterface.php │ ├── DotEnvConfiguration.php │ └── AbstractConfiguration.php ├── ClassSerialize.php ├── Priority │ └── PriorityService.php ├── Board │ ├── Board.php │ ├── Location.php │ ├── PaginatedResult.php │ └── BoardService.php ├── Sprint │ ├── Sprint.php │ ├── SprintSearchResult.php │ └── SprintService.php ├── Attachment │ └── AttachmentService.php ├── Component │ ├── Component.php │ └── ComponentService.php └── Version │ └── VersionService.php ├── phpstan.neon.dist ├── LICENSE ├── test-data ├── reporter-no-email-address.json ├── comment.json ├── issueFieldV3.json └── issueField.json ├── composer.json └── apiary.apib /src/Project/Component.php: -------------------------------------------------------------------------------- 1 | setAPIUri('/rest/greenhopper/'.$version); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - phpstan-baseline.neon 3 | 4 | parameters: 5 | level: 5 6 | paths: 7 | - src 8 | excludes_analyse: 9 | - src/JiraRestApiServiceProvider.php 10 | reportUnmatchedIgnoredErrors: false 11 | -------------------------------------------------------------------------------- /src/Epic/EpicColor.php: -------------------------------------------------------------------------------- 1 | setAPIUri($uri); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/NoOperationMonologHandlerV3.php: -------------------------------------------------------------------------------- 1 | name = $name; 13 | } 14 | 15 | #[\ReturnTypeWillChange] 16 | public function jsonSerialize(): array 17 | { 18 | return array_filter(get_object_vars($this)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/ServiceDesk/Customer/CustomerLinks.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/History.php: -------------------------------------------------------------------------------- 1 | content = []; 22 | } 23 | 24 | #[\ReturnTypeWillChange] 25 | public function jsonSerialize(): array 26 | { 27 | return array_filter(get_object_vars($this)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /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/Auth/CurrentUser.php: -------------------------------------------------------------------------------- 1 | statusDate = new DateTime($statusDate['iso8601']); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Issue/Priority.php: -------------------------------------------------------------------------------- 1 | $value) { 28 | $this->{$key} = $value; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Issue/IssueType.php: -------------------------------------------------------------------------------- 1 | self = $self; 22 | 23 | return $this; 24 | } 25 | 26 | public function setIssuesUnresolvedCount($issuesUnresolvedCount) 27 | { 28 | $this->issuesUnresolvedCount = $issuesUnresolvedCount; 29 | 30 | return $this; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test-data/reporter-no-email-address.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporter": { 3 | "self": "https://jira.example.com/rest/api/2/user?username=lesstif", 4 | "name": "lesstif", 5 | "key": "lesstif", 6 | "avatarUrls": { 7 | "48x48": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=48", 8 | "24x24": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=24", 9 | "16x16": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=16", 10 | "32x32": "https://secure.gravatar.com/avatar/9f1705ef1d8c977eba04f00556e02922?d=mm&s=32" 11 | }, 12 | "displayName": "정광섭", 13 | "active": true, 14 | "timeZone": "Asia/Seoul" 15 | } 16 | } -------------------------------------------------------------------------------- /src/Issue/Visibility.php: -------------------------------------------------------------------------------- 1 | type = $type; 13 | } 14 | 15 | public function setValue(string $value) 16 | { 17 | $this->value = $value; 18 | } 19 | 20 | public function getType(): string 21 | { 22 | return $this->type; 23 | } 24 | 25 | public function getValue(): string 26 | { 27 | return $this->value; 28 | } 29 | 30 | #[\ReturnTypeWillChange] 31 | public function jsonSerialize(): array 32 | { 33 | return array_filter(get_object_vars($this)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Issue/RemoteIssueLinkObject.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 | \JiraRestApi\Status\Status::class 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Field/Schema.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/Auth/LoginInfo.php: -------------------------------------------------------------------------------- 1 | app->bind(ConfigurationInterface::class, function () { 31 | return new DotEnvConfiguration(base_path()); 32 | }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Issue/Issue.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/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 | \JiraRestApi\Issue\Statuscategory::class 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/JiraException.php: -------------------------------------------------------------------------------- 1 | response = $response; 32 | } 33 | 34 | /** 35 | * Get error response. 36 | */ 37 | public function getResponse(): ?string 38 | { 39 | return $this->response; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Issue/JQLCountResult.php: -------------------------------------------------------------------------------- 1 | count; 23 | } 24 | 25 | /** 26 | * Set the count of issues. 27 | * 28 | * @param int $count 29 | */ 30 | public function setCount(int $count): void 31 | { 32 | $this->count = $count; 33 | } 34 | 35 | #[\ReturnTypeWillChange] 36 | public function jsonSerialize(): array 37 | { 38 | return [ 39 | 'count' => $this->count, 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Group/GroupSearchResult.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/ServiceDesk/Customer/Customer.php: -------------------------------------------------------------------------------- 1 | _links = $links; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Issue/Comment.php: -------------------------------------------------------------------------------- 1 | body = $body; 39 | 40 | return $this; 41 | } 42 | 43 | #[\ReturnTypeWillChange] 44 | public function jsonSerialize(): array 45 | { 46 | return array_filter(get_object_vars($this)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /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/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/Request/RequestComment.php: -------------------------------------------------------------------------------- 1 | body = $body; 30 | 31 | return $this; 32 | } 33 | 34 | /** 35 | * @param bool $public True for is public, false otherwise 36 | * 37 | * @return $this 38 | */ 39 | public function setIsPublic(bool $public) 40 | { 41 | $this->public = $public; 42 | 43 | return $this; 44 | } 45 | 46 | #[\ReturnTypeWillChange] 47 | public function jsonSerialize(): array 48 | { 49 | return array_filter(get_object_vars($this), function ($var) { 50 | return $var !== null; 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Issue/AgileIssueService.php: -------------------------------------------------------------------------------- 1 | setAPIUri('/rest/agile/'.$this->agileVersion); 18 | } 19 | 20 | public function get($issueIdOrKey, $paramArray = []): ?AgileIssue 21 | { 22 | $response = $this->exec($this->uri.'/'.$issueIdOrKey.$this->toHttpQueryParameter($paramArray), null); 23 | 24 | try { 25 | return $this->json_mapper->map( 26 | json_decode($response, false, 512, $this->getJsonOptions()), 27 | new AgileIssue() 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 | -------------------------------------------------------------------------------- /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/php-jira-rest-client", 3 | "description": "JIRA REST API Client for PHP Users.", 4 | "type": "library", 5 | "keywords": ["jira", "rest", "jira-php", "jira-rest"], 6 | "require": { 7 | "php": "^8.0", 8 | "ext-curl": "*", 9 | "ext-json": "*", 10 | "netresearch/jsonmapper": "^4.2|^5.0", 11 | "monolog/monolog": "^2.0|^3.0", 12 | "vlucas/phpdotenv": "^5.0|^6.0" 13 | }, 14 | "require-dev": { 15 | "phpunit/phpunit": "^9.0|^10.0", 16 | "mockery/mockery": "^1.0|^2.0", 17 | "symfony/var-dumper": "^5.0|^6.0|^7.0", 18 | "phpstan/phpstan": "^1.0|^2.0" 19 | }, 20 | "license": "Apache-2.0", 21 | "authors": [ 22 | { 23 | "name": "KwangSeob Jeong", 24 | "email": "lesstif@gmail.com", 25 | "homepage": "https://lesstif.com/" 26 | } 27 | ], 28 | "autoload": { 29 | "psr-4" : { 30 | "JiraRestApi\\" : "src" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4" : { 35 | "JiraRestApi\\Test\\" : "tests" 36 | } 37 | }, 38 | "extra": { 39 | "laravel": { 40 | "providers": [ 41 | "JiraRestApi\\JiraRestApiServiceProvider" 42 | ] 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Configuration/ArrayConfiguration.php: -------------------------------------------------------------------------------- 1 | jiraLogEnabled = true; 20 | $this->jiraLogFile = 'jira-rest-client.log'; 21 | $this->jiraLogLevel = 'WARNING'; 22 | $this->curlOptSslVerifyHost = false; 23 | $this->curlOptSslVerifyPeer = false; 24 | $this->curlOptSslCert = ''; 25 | $this->curlOptSslCertPassword = ''; 26 | $this->curlOptSslKey = ''; 27 | $this->curlOptSslKeyPassword = ''; 28 | $this->curlOptVerbose = false; 29 | $this->cookieAuthEnabled = false; 30 | $this->cookieFile = 'jira-cookie.txt'; 31 | $this->curlOptUserAgent = $this->getDefaultUserAgentString(); 32 | $this->serviceDeskId = null; 33 | 34 | $this->useTokenBasedAuth = false; 35 | $this->personalAccessToken = ''; 36 | 37 | foreach ($configuration as $key => $value) { 38 | if (property_exists($this, $key)) { 39 | $this->$key = $value; 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/IssueLink/IssueLinkService.php: -------------------------------------------------------------------------------- 1 | log->info("addIssueLink=\n"); 19 | 20 | $data = json_encode($issueLink); 21 | 22 | $this->log->debug("Create IssueLink=\n".$data); 23 | 24 | $url = $this->uri.'/issueLink'; 25 | $type = 'POST'; 26 | 27 | return $this->exec($url, $data, $type); 28 | } 29 | 30 | /** 31 | * @throws \JiraRestApi\JiraException 32 | * 33 | * @return IssueLinkType[] 34 | */ 35 | public function getIssueLinkTypes() 36 | { 37 | $this->log->info("getIssueLinkTYpes=\n"); 38 | 39 | $url = $this->uri.'/issueLinkType'; 40 | 41 | $ret = $this->exec($url); 42 | 43 | $data = json_encode(json_decode($ret)->issueLinkTypes); 44 | 45 | $linkTypes = $this->json_mapper->mapArray( 46 | json_decode($data, false), 47 | new \ArrayObject(), 48 | '\JiraRestApi\IssueLink\IssueLinkType' 49 | ); 50 | 51 | return $linkTypes; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Issue/RemoteIssueLink.php: -------------------------------------------------------------------------------- 1 | object)) { 39 | $this->object = new self(); 40 | } 41 | 42 | $this->object->url = $url; 43 | 44 | return $this; 45 | } 46 | 47 | public function setTitle($title) 48 | { 49 | $this->object->title = $title; 50 | 51 | return $this; 52 | } 53 | 54 | public function setSummary($summary) 55 | { 56 | $this->object->summary = $summary; 57 | 58 | return $this; 59 | } 60 | 61 | public function setRelationship($relationship) 62 | { 63 | $this->relationship = $relationship; 64 | 65 | return $this; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /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/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 \JiraRestApi\JiraException 44 | * @throws \JsonMapper_Exception 45 | * 46 | * @return \JiraRestApi\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(): array 72 | { 73 | return array_filter(get_object_vars($this), function ($var) { 74 | return !is_null($var); 75 | }); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /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; 35 | 36 | return $this; 37 | } 38 | 39 | public function setIssuesFixedCount($issuesFixedCount) 40 | { 41 | $this->issuesFixedCount = $issuesFixedCount; 42 | 43 | return $this; 44 | } 45 | 46 | public function setIssuesAffectedCount($issuesAffectedCount) 47 | { 48 | $this->issuesAffectedCount = $issuesAffectedCount; 49 | 50 | return $this; 51 | } 52 | 53 | public function setIssueCountWithCustomFieldsShowingVersion($issueCountWithCustomFieldsShowingVersion) 54 | { 55 | $this->issueCountWithCustomFieldsShowingVersion = $issueCountWithCustomFieldsShowingVersion; 56 | 57 | return $this; 58 | } 59 | 60 | public function setCustomFieldUsage($customFieldUsage) 61 | { 62 | $this->customFieldUsage = $customFieldUsage; 63 | 64 | return $this; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Issue/IssueSearchResult.php: -------------------------------------------------------------------------------- 1 | nextPageToken; 47 | } 48 | 49 | /** 50 | * @param string $nextPageToken 51 | */ 52 | public function setNextPageToken($nextPageToken) 53 | { 54 | $this->nextPageToken = $nextPageToken; 55 | } 56 | 57 | /** 58 | * @return Issue[] 59 | */ 60 | public function getIssues() 61 | { 62 | return $this->issues; 63 | } 64 | 65 | /** 66 | * @param Issue[] $issues 67 | */ 68 | public function setIssues($issues) 69 | { 70 | $this->issues = $issues; 71 | } 72 | 73 | /** 74 | * @param int $ndx 75 | * 76 | * @return Issue 77 | */ 78 | public function getIssue($ndx) 79 | { 80 | return $this->issues[$ndx]; 81 | } 82 | 83 | /** 84 | * @return ?string 85 | */ 86 | public function getExpand(): ?string 87 | { 88 | return $this->expand; 89 | } 90 | 91 | public function setExpand(?string $expand) 92 | { 93 | $this->expand = $expand; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /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(): array 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 \JiraRestApi\Issue\Worklog[] Worklogs 84 | */ 85 | public function getWorklogs() 86 | { 87 | return $this->worklogs; 88 | } 89 | 90 | /** 91 | * @param \JiraRestApi\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/IssueLink/IssueLink.php: -------------------------------------------------------------------------------- 1 | type['name'] = $typeName; 38 | 39 | return $this; 40 | } 41 | 42 | /** 43 | * @param string|int $issueKey inward issue key or id 44 | * 45 | * @return $this 46 | */ 47 | public function setInwardIssue($issueKey) 48 | { 49 | $this->inwardIssue['key'] = $issueKey; 50 | 51 | return $this; 52 | } 53 | 54 | /** 55 | * @param string|int $issueKey out ward issue key or id 56 | * 57 | * @return $this 58 | */ 59 | public function setOutwardIssue($issueKey) 60 | { 61 | $this->outwardIssue['key'] = $issueKey; 62 | 63 | return $this; 64 | } 65 | 66 | /** 67 | * @param string|Comment $comment string or \JiraRestApi\Issue\Comment instance 68 | * 69 | * @return $this 70 | */ 71 | public function setComment($comment) 72 | { 73 | if (is_string($comment)) { 74 | $this->comment = new Comment(); 75 | $this->comment->setBody($comment); 76 | } elseif ($comment instanceof Comment) { 77 | $this->comment = $comment; 78 | } 79 | 80 | return $this; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /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/Sprint/Sprint.php: -------------------------------------------------------------------------------- 1 | name = $sprintName; 40 | 41 | return $this; 42 | } 43 | 44 | public function setGoalAsString(string $sprintGoal): self 45 | { 46 | $this->goal = $sprintGoal; 47 | 48 | return $this; 49 | } 50 | 51 | public function setOriginBoardIdAsStringOrInt(string|int $originBoardId): self 52 | { 53 | $this->originBoardId = strval($originBoardId); 54 | 55 | return $this; 56 | } 57 | 58 | public function setStartDateAsDateTime(\DateTimeInterface $startDate, string $format = 'Y-m-d'): static 59 | { 60 | $this->startDate = $startDate->format($format); 61 | 62 | return $this; 63 | } 64 | 65 | public function setStartDateAsString(string $startDate): static 66 | { 67 | $this->startDate = $startDate; 68 | 69 | return $this; 70 | } 71 | 72 | public function setEndDateAsDateTime(\DateTimeInterface $endDate, string $format = 'Y-m-d'): static 73 | { 74 | $this->endDate = $endDate->format($format); 75 | 76 | return $this; 77 | } 78 | 79 | public function setEndDateAsString(string $endDate): static 80 | { 81 | $this->endDate = $endDate; 82 | 83 | return $this; 84 | } 85 | 86 | public function setMoveIssues(array $issues): static 87 | { 88 | $this->issues = $issues; 89 | 90 | return $this; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Issue/Transition.php: -------------------------------------------------------------------------------- 1 | transition)) { 46 | $this->transition = []; 47 | } 48 | 49 | $this->transition['name'] = $name; 50 | } 51 | 52 | /** 53 | * set none translated transition name. 54 | * 55 | * @param string $untranslatedName 56 | */ 57 | public function setUntranslatedName(string $untranslatedName) 58 | { 59 | if (is_null($this->transition)) { 60 | $this->transition = []; 61 | } 62 | 63 | $this->transition['untranslatedName'] = $untranslatedName; 64 | } 65 | 66 | public function setTransitionId($id) 67 | { 68 | if (is_null($this->transition)) { 69 | $this->transition = []; 70 | } 71 | 72 | $this->transition['id'] = $id; 73 | } 74 | 75 | public function setCommentBody($commentBody) 76 | { 77 | if (is_null($this->update)) { 78 | $this->update = []; 79 | $this->update['comment'] = []; 80 | } 81 | 82 | $ar = []; 83 | $ar['add']['body'] = $commentBody; 84 | array_push($this->update['comment'], $ar); 85 | } 86 | 87 | #[\ReturnTypeWillChange] 88 | public function jsonSerialize(): array 89 | { 90 | return array_filter(get_object_vars($this)); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/Attachment/AttachmentService.php: -------------------------------------------------------------------------------- 1 | exec($this->uri.$id, null); 35 | 36 | $this->log->info("Result=\n".$ret); 37 | 38 | $attachment = $this->json_mapper->map( 39 | json_decode($ret), 40 | new Attachment() 41 | ); 42 | 43 | if ($outDir == null) { 44 | return $attachment; 45 | } 46 | 47 | // download contents 48 | if (!file_exists($outDir)) { 49 | mkdir($outDir, $mode, $recursive); 50 | } 51 | 52 | // extract filename 53 | $file = substr(strrchr($attachment->content, '/'), 1); 54 | 55 | if (file_exists($outDir.DIRECTORY_SEPARATOR.$file) && $overwrite == false) { 56 | return $attachment; 57 | } 58 | 59 | $this->download($attachment->content, $outDir, $file); 60 | 61 | return $attachment; 62 | } 63 | 64 | /** 65 | * Remove an attachment from an issue. 66 | * 67 | * @param string|int $id attachment id 68 | * 69 | * @throws \JiraRestApi\JiraException 70 | * 71 | * @return string 72 | */ 73 | public function remove($id) 74 | { 75 | $ret = $this->exec($this->uri.$id, null, 'DELETE'); 76 | 77 | $this->log->info("Result=\n".$ret); 78 | 79 | return $ret; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /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/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(): array 96 | { 97 | return array_filter(get_object_vars($this), function ($var) { 98 | return !is_null($var); 99 | }); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Issue/Reporter.php: -------------------------------------------------------------------------------- 1 | $value) { 50 | if ($key === 'name' && ($this->isWantUnassigned() === true)) { 51 | continue; 52 | } elseif ($key === 'wantUnassigned') { 53 | unset($vars[$key]); 54 | } elseif (is_null($value) || $value === '') { 55 | unset($vars[$key]); 56 | } 57 | } 58 | 59 | if (empty($vars)) { 60 | return null; 61 | } 62 | 63 | return $vars; 64 | } 65 | 66 | /** 67 | * determine class has value for effective json serialize. 68 | * 69 | * @see https://github.com/lesstif/php-jira-rest-client/issues/126 70 | * 71 | * @return bool 72 | */ 73 | public function isEmpty() 74 | { 75 | if (empty($this->name) && empty($this->self)) { 76 | return true; 77 | } 78 | 79 | return false; 80 | } 81 | 82 | /** 83 | * @return bool 84 | */ 85 | public function isWantUnassigned() 86 | { 87 | if ($this->wantUnassigned) { 88 | return true; 89 | } 90 | 91 | return false; 92 | } 93 | 94 | /** 95 | * @param bool $param boolean 96 | */ 97 | public function setWantUnassigned(bool $param) 98 | { 99 | $this->wantUnassigned = $param; 100 | $this->name = null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /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/Board/PaginatedResult.php: -------------------------------------------------------------------------------- 1 | startAt; 46 | } 47 | 48 | /** 49 | * @param int $startAt 50 | */ 51 | public function setStartAt($startAt) 52 | { 53 | $this->startAt = $startAt; 54 | } 55 | 56 | /** 57 | * @return int 58 | */ 59 | public function getMaxResults() 60 | { 61 | return $this->maxResults; 62 | } 63 | 64 | /** 65 | * @param int $maxResults 66 | */ 67 | public function setMaxResults($maxResults) 68 | { 69 | $this->maxResults = $maxResults; 70 | } 71 | 72 | /** 73 | * @return int 74 | */ 75 | public function getTotal() 76 | { 77 | return $this->total; 78 | } 79 | 80 | /** 81 | * @param int $total 82 | */ 83 | public function setTotal($total) 84 | { 85 | $this->total = $total; 86 | } 87 | 88 | /** 89 | * @return array 90 | */ 91 | public function getValues() 92 | { 93 | return $this->values; 94 | } 95 | 96 | /** 97 | * @param array $values 98 | */ 99 | public function setValues($values) 100 | { 101 | $this->values = $values; 102 | } 103 | 104 | /** 105 | * @param int $index 106 | * 107 | * @return mixed 108 | */ 109 | public function getValue($index) 110 | { 111 | return $this->values[$index]; 112 | } 113 | 114 | /** 115 | * @return string 116 | */ 117 | public function getExpand() 118 | { 119 | return $this->expand; 120 | } 121 | 122 | /** 123 | * @param string $expand 124 | */ 125 | public function setExpand($expand) 126 | { 127 | $this->expand = $expand; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /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/FieldService.php: -------------------------------------------------------------------------------- 1 | exec($this->uri, null); 21 | 22 | $fields = $this->json_mapper->mapArray( 23 | json_decode($ret, false), 24 | new \ArrayObject(), 25 | '\JiraRestApi\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 \JiraRestApi\JiraException 59 | * 60 | * @return string 61 | */ 62 | public function getCustomFieldOption($id) 63 | { 64 | $ret = $this->exec('/customFieldOption/'.$id); 65 | 66 | $this->log->debug("get custom Field Option=\n".$ret); 67 | 68 | return $ret; 69 | } 70 | 71 | /** 72 | * create new field. 73 | * 74 | * @param Field $field object of Field class 75 | * 76 | * @throws \JiraRestApi\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 | -------------------------------------------------------------------------------- /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(): array 116 | { 117 | return array_filter(get_object_vars($this)); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /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 | public function getSprintFromJSON(object $json): Sprint 29 | { 30 | $sprint = $this->json_mapper->map( 31 | $json, 32 | new Sprint() 33 | ); 34 | 35 | return $sprint; 36 | } 37 | 38 | public function getSprint(string|int $sprintId): Sprint 39 | { 40 | $ret = $this->exec($this->uri.'/'.$sprintId, null); 41 | 42 | $this->log->info("Result=\n".$ret); 43 | 44 | return $this->json_mapper->map( 45 | json_decode($ret), 46 | new Sprint() 47 | ); 48 | } 49 | 50 | /** 51 | * @throws JiraException 52 | * @throws \JsonMapper_Exception 53 | * 54 | * @return Issue[] array of Issue 55 | */ 56 | public function getSprintIssues(string|int $sprintId, array $paramArray = []) 57 | { 58 | $json = $this->exec($this->uri.'/'.$sprintId.'/issue'.$this->toHttpQueryParameter($paramArray), null); 59 | 60 | $issues = $this->json_mapper->mapArray( 61 | json_decode($json)->issues, 62 | new \ArrayObject(), 63 | Issue::class 64 | ); 65 | 66 | return $issues; 67 | } 68 | 69 | public function createSprint(Sprint $sprint): Sprint 70 | { 71 | $data = json_encode($sprint); 72 | 73 | $ret = $this->exec($this->uri, $data); 74 | 75 | $this->log->debug('createSprint result='.var_export($ret, true)); 76 | 77 | return $this->json_mapper->map( 78 | json_decode($ret), 79 | new Sprint() 80 | ); 81 | } 82 | 83 | /** 84 | * @see https://docs.atlassian.com/jira-software/REST/9.11.0/#agile/1.0/sprint-moveIssuesToSprint 85 | */ 86 | public function moveIssues2Sprint(int $sprintId, Sprint $sprint): bool 87 | { 88 | $data = json_encode($sprint); 89 | 90 | $ret = $this->exec($this->uri.'/'.$sprintId.'/issue', $data); 91 | 92 | $this->log->debug('moveIssues2Sprint result='.var_export($ret, true)); 93 | 94 | return $ret; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /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/ServiceDesk/Attachment/AttachmentService.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, string $serviceDeskId): array 33 | { 34 | $fileNames = $this->getFilenamesFromAttachments($attachments); 35 | 36 | return $this->client->upload( 37 | $this->client->createUrl('/servicedesk/%s/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(): array 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(): array 41 | { 42 | return array_filter(get_object_vars($this)); 43 | } 44 | 45 | public function setProjectId($id): static 46 | { 47 | $this->projectId = $id; 48 | 49 | return $this; 50 | } 51 | 52 | public function setName($name): static 53 | { 54 | $this->name = $name; 55 | 56 | return $this; 57 | } 58 | 59 | public function setDescription($description) 60 | { 61 | $this->description = $description; 62 | 63 | return $this; 64 | } 65 | 66 | public function setArchived($archived): static 67 | { 68 | $this->archived = $archived; 69 | 70 | return $this; 71 | } 72 | 73 | public function setReleased($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 setUserReleaseDateAsDateTime($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/Configuration/ConfigurationInterface.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 \JiraRestApi\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 \JiraRestApi\Group\Group $group 66 | * 67 | * @throws \JiraRestApi\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 \JiraRestApi\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 \JiraRestApi\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/Issue/Worklog.php: -------------------------------------------------------------------------------- 1 | comment = $comment; 94 | 95 | return $this; 96 | } 97 | 98 | // Note that in the docblock below, you cannot replace `mixed` by `\DateTimeInterface|string` because JsonMapper doesn't support that, 99 | // see . 100 | 101 | /** 102 | * Function to set start time of worklog. 103 | * 104 | * @param mixed $started started time value(\DateTimeInterface|string) e.g. - new \DateTime("2016-03-17 11:15:34") or "2016-03-17 11:15:34" 105 | * 106 | * @throws JiraException 107 | * 108 | * @return Worklog 109 | */ 110 | public function setStarted($started) 111 | { 112 | if (is_string($started)) { 113 | $dt = new \DateTime($started); 114 | } elseif ($started instanceof \DateTimeInterface) { 115 | $dt = $started; 116 | } else { 117 | throw new JiraException('field only accept date string or DateTimeInterface object.'.get_class($started)); 118 | } 119 | 120 | // workround micro second 121 | $this->started = $dt->format("Y-m-d\TH:i:s").'.000'.$dt->format('O'); 122 | 123 | return $this; 124 | } 125 | 126 | /** 127 | * Function to set start time of worklog. 128 | * 129 | * @param \DateTimeInterface $started e.g. - new \DateTime("2014-04-05 16:00:00") 130 | * 131 | * @return Worklog 132 | */ 133 | public function setStartedDateTime($started) 134 | { 135 | // workround micro second 136 | $this->started = $started->format("Y-m-d\TH:i:s").'.000'.$started->format('O'); 137 | 138 | return $this; 139 | } 140 | 141 | /** 142 | * Function to set worklog time in string. 143 | * 144 | * @param string $timeSpent 145 | * 146 | * @return Worklog 147 | */ 148 | public function setTimeSpent($timeSpent) 149 | { 150 | $this->timeSpent = $timeSpent; 151 | 152 | return $this; 153 | } 154 | 155 | /** 156 | * Function to set worklog time in seconds. 157 | * 158 | * @param int $timeSpentSeconds 159 | * 160 | * @return Worklog 161 | */ 162 | public function setTimeSpentSeconds($timeSpentSeconds) 163 | { 164 | $this->timeSpentSeconds = $timeSpentSeconds; 165 | 166 | return $this; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/ServiceDesk/Request/Request.php: -------------------------------------------------------------------------------- 1 | requestTypeId = $requestTypeId; 37 | 38 | return $this; 39 | } 40 | 41 | public function setServiceDeskId(string $serviceDeskId): self 42 | { 43 | $this->serviceDeskId = $serviceDeskId; 44 | 45 | return $this; 46 | } 47 | 48 | public function setCreatedDate(object $createdDate): void 49 | { 50 | if (!$createdDate instanceof DateTimeInterface) { 51 | $createdDate = new DateTime($createdDate->iso8601); 52 | } 53 | 54 | $this->createdDate = $createdDate; 55 | } 56 | 57 | public function setReporter(object $reporter): self 58 | { 59 | if (!$reporter instanceof Customer) { 60 | $reporter = $this->map($reporter, new Customer()); 61 | } 62 | 63 | $this->reporter = $reporter; 64 | 65 | return $this; 66 | } 67 | 68 | public function setSummary(string $summary): self 69 | { 70 | $this->requestFieldValues['summary'] = $summary; 71 | 72 | return $this; 73 | } 74 | 75 | public function setDescription(string $description): self 76 | { 77 | $this->requestFieldValues['description'] = $description; 78 | 79 | return $this; 80 | } 81 | 82 | public function addCustomField(string $key, $value): self 83 | { 84 | $this->requestFieldValues[$key] = $value; 85 | 86 | return $this; 87 | } 88 | 89 | public function setCurrentStatus(object $currentStatus): void 90 | { 91 | $this->currentStatus = $this->map($currentStatus, new RequestStatus()); 92 | } 93 | 94 | public function setLinks(object $links): void 95 | { 96 | $this->_links = $links; 97 | } 98 | 99 | /** 100 | * @param Customer[] $requestParticipants 101 | */ 102 | public function setRequestParticipants(array $requestParticipants): self 103 | { 104 | $this->requestParticipants = $requestParticipants; 105 | 106 | return $this; 107 | } 108 | 109 | public function jsonSerialize(): array 110 | { 111 | $data = get_object_vars($this); 112 | if ($this->reporter) { 113 | $data['raiseOnBehalfOf'] = $this->reporter->accountId ?? $this->reporter->emailAddress; 114 | } 115 | unset($data['reporter']); 116 | 117 | $data['requestParticipants'] = array_map(static function (Customer $customer): string { 118 | return $customer->accountId ?? $customer->emailAddress; 119 | }, $this->requestParticipants); 120 | 121 | return array_filter($data); 122 | } 123 | 124 | private function map(object $data, object $target) 125 | { 126 | $mapper = new JsonMapper(); 127 | 128 | // Adjust settings for JsonMapper v5.0 BC 129 | if (property_exists($mapper, 'bStrictNullTypesInArrays')) { 130 | $mapper->bStrictNullTypesInArrays = false; // if you want to allow nulls in arrays 131 | } 132 | $mapper->bStrictNullTypes = false; // if you want to allow nulls 133 | $mapper->bStrictObjectTypeChecking = false; // if you want to disable strict type checking 134 | 135 | return $mapper->map( 136 | $data, 137 | $target 138 | ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Issue/TimeTracking.php: -------------------------------------------------------------------------------- 1 | originalEstimate. 45 | * 46 | * @var int 47 | */ 48 | public $originalEstimateSeconds; 49 | 50 | /** 51 | * Remaining estimate in seconds, generated in jira 52 | * for create/update issue set $this->remainingEstimate. 53 | * 54 | * @var int 55 | */ 56 | public $remainingEstimateSeconds; 57 | 58 | /** 59 | * Time spent in seconds, generated in jira 60 | * for create/update issue set $this->timeSpent. 61 | * 62 | * @var int 63 | */ 64 | public $timeSpentSeconds; 65 | 66 | /** 67 | * @return string 68 | */ 69 | public function getOriginalEstimate() 70 | { 71 | return $this->originalEstimate; 72 | } 73 | 74 | /** 75 | * @param string $originalEstimate 76 | */ 77 | public function setOriginalEstimate($originalEstimate) 78 | { 79 | $this->originalEstimate = $originalEstimate; 80 | } 81 | 82 | /** 83 | * @return string 84 | */ 85 | public function getRemainingEstimate() 86 | { 87 | return $this->remainingEstimate; 88 | } 89 | 90 | /** 91 | * @param string $remainingEstimate 92 | */ 93 | public function setRemainingEstimate($remainingEstimate) 94 | { 95 | $this->remainingEstimate = $remainingEstimate; 96 | } 97 | 98 | /** 99 | * @return string 100 | */ 101 | public function getTimeSpent() 102 | { 103 | return $this->timeSpent; 104 | } 105 | 106 | /** 107 | * @param string $timeSpent 108 | */ 109 | public function setTimeSpent($timeSpent) 110 | { 111 | $this->timeSpent = $timeSpent; 112 | } 113 | 114 | /** 115 | * @return int 116 | */ 117 | public function getOriginalEstimateSeconds() 118 | { 119 | return $this->originalEstimateSeconds; 120 | } 121 | 122 | /** 123 | * @param int $originalEstimateSeconds 124 | */ 125 | public function setOriginalEstimateSeconds($originalEstimateSeconds) 126 | { 127 | $this->originalEstimateSeconds = $originalEstimateSeconds; 128 | } 129 | 130 | /** 131 | * @return int 132 | */ 133 | public function getRemainingEstimateSeconds() 134 | { 135 | return $this->remainingEstimateSeconds; 136 | } 137 | 138 | /** 139 | * @param int $remainingEstimateSeconds 140 | */ 141 | public function setRemainingEstimateSeconds($remainingEstimateSeconds) 142 | { 143 | $this->remainingEstimateSeconds = $remainingEstimateSeconds; 144 | } 145 | 146 | /** 147 | * @return int 148 | */ 149 | public function getTimeSpentSeconds() 150 | { 151 | return $this->timeSpentSeconds; 152 | } 153 | 154 | /** 155 | * @param int $timeSpentSeconds 156 | */ 157 | public function setTimeSpentSeconds($timeSpentSeconds) 158 | { 159 | $this->timeSpentSeconds = $timeSpentSeconds; 160 | } 161 | 162 | /** 163 | * Specify data which should be serialized to JSON. 164 | * 165 | * @link http://php.net/manual/en/jsonserializable.jsonserialize.php 166 | * 167 | * @return array data which can be serialized by json_encode, 168 | * which is a value of any type other than a resource. 169 | */ 170 | #[\ReturnTypeWillChange] 171 | public function jsonSerialize(): array 172 | { 173 | return array_filter(get_object_vars($this)); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /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('JIRA_HOST'); 22 | $this->jiraUser = $this->env('JIRA_USER'); 23 | $this->jiraPassword = $this->env('JIRA_PASS'); 24 | $this->oauthAccessToken = $this->env('OAUTH_ACCESS_TOKEN'); 25 | $this->cookieAuthEnabled = $this->env('COOKIE_AUTH_ENABLED', false); 26 | $this->cookieFile = $this->env('COOKIE_FILE', 'jira-cookie.txt'); 27 | $this->jiraLogEnabled = $this->env('JIRA_LOG_ENABLED', true); 28 | $this->jiraLogFile = $this->env('JIRA_LOG_FILE', 'jira-rest-client.log'); 29 | $this->jiraLogLevel = $this->env('JIRA_LOG_LEVEL', 'WARNING'); 30 | $this->curlOptSslVerifyHost = $this->env('CURLOPT_SSL_VERIFYHOST', false); 31 | $this->curlOptSslVerifyPeer = $this->env('CURLOPT_SSL_VERIFYPEER', false); 32 | $this->curlOptSslCert = $this->env('CURLOPT_SSL_CERT'); 33 | $this->curlOptSslCertPassword = $this->env('CURLOPT_SSL_CERT_PASSWORD'); 34 | $this->curlOptSslKey = $this->env('CURLOPT_SSL_KEY'); 35 | $this->curlOptSslKeyPassword = $this->env('CURLOPT_SSL_KEY_PASSWORD'); 36 | $this->curlOptUserAgent = $this->env('CURLOPT_USERAGENT', $this->getDefaultUserAgentString()); 37 | $this->curlOptVerbose = $this->env('CURLOPT_VERBOSE', false); 38 | $this->proxyServer = $this->env('PROXY_SERVER'); 39 | $this->proxyPort = $this->env('PROXY_PORT'); 40 | $this->proxyType = $this->env('PROXY_TYPE'); 41 | $this->proxyUser = $this->env('PROXY_USER'); 42 | $this->proxyPassword = $this->env('PROXY_PASSWORD'); 43 | 44 | $this->timeout = $this->env('JIRA_TIMEOUT', 30); 45 | 46 | $this->useTokenBasedAuth = $this->env('TOKEN_BASED_AUTH', false); 47 | $this->personalAccessToken = $this->env('PERSONAL_ACCESS_TOKEN', false); 48 | $this->serviceDeskId = $this->env('JIRA_SERVICE_DESK_ID', null); 49 | } 50 | 51 | /** 52 | * Gets the value of an environment variable. Supports boolean, empty and null. 53 | */ 54 | private function env(string $key, mixed $default = null): mixed 55 | { 56 | $value = $_ENV[$key] ?? null; 57 | 58 | if ($value === null) { 59 | return $default; 60 | } 61 | 62 | switch (strtolower($value)) { 63 | case 'true': 64 | case '(true)': 65 | return true; 66 | 67 | case 'false': 68 | case '(false)': 69 | return false; 70 | 71 | case 'empty': 72 | case '(empty)': 73 | return ''; 74 | 75 | case 'null': 76 | case '(null)': 77 | return null; 78 | } 79 | 80 | if ($this->startsWith($value, '"') && $this->endsWith($value, '"')) { 81 | return substr($value, 1, -1); 82 | } 83 | 84 | return $value; 85 | } 86 | 87 | /** 88 | * Determine if a given string starts with a given substring. 89 | */ 90 | public function startsWith(string $haystack, array|string $needles): bool 91 | { 92 | foreach ((array) $needles as $needle) { 93 | if ($needle != '' && strpos($haystack, $needle) === 0) { 94 | return true; 95 | } 96 | } 97 | 98 | return false; 99 | } 100 | 101 | /** 102 | * Determine if a given string ends with a given substring. 103 | */ 104 | public function endsWith(string $haystack, array|string $needles): bool 105 | { 106 | foreach ((array) $needles as $needle) { 107 | if ((string) $needle === substr($haystack, -strlen($needle))) { 108 | return true; 109 | } 110 | } 111 | 112 | return false; 113 | } 114 | 115 | /** 116 | * load dotenv. 117 | */ 118 | private function loadDotEnv(string $path) 119 | { 120 | $requireParam = [ 121 | 'JIRA_HOST', 'TOKEN_BASED_AUTH', 122 | ]; 123 | 124 | // support for dotenv 1.x and 2.x. see also https://github.com/lesstif/php-jira-rest-client/issues/102 125 | //if (method_exists('\Dotenv\Dotenv', 'createImmutable')) { // v4 or above 126 | $dotenv = \Dotenv\Dotenv::createImmutable($path); 127 | 128 | $dotenv->safeLoad(); 129 | $dotenv->required($requireParam); 130 | //} 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /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 \JiraRestApi\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 \JiraRestApi\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 \JiraRestApi\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 \JiraRestApi\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 \JiraRestApi\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 \JiraRestApi\JiraException 174 | * 175 | * @return bool 176 | */ 177 | public function release() 178 | { 179 | $this->exec('websudo', '', 'DELETE'); 180 | 181 | return true; 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/Configuration/AbstractConfiguration.php: -------------------------------------------------------------------------------- 1 | jiraHost; 101 | } 102 | 103 | public function getJiraUser(): string 104 | { 105 | return $this->jiraUser; 106 | } 107 | 108 | public function getJiraPassword(): string 109 | { 110 | return $this->jiraPassword; 111 | } 112 | 113 | public function getJiraLogEnabled(): bool 114 | { 115 | return $this->jiraLogEnabled; 116 | } 117 | 118 | public function getJiraLogFile(): string 119 | { 120 | return $this->jiraLogFile; 121 | } 122 | 123 | public function getJiraLogLevel(): string 124 | { 125 | return $this->jiraLogLevel; 126 | } 127 | 128 | public function isCurlOptSslVerifyHost(): bool 129 | { 130 | return $this->curlOptSslVerifyHost; 131 | } 132 | 133 | public function isCurlOptSslVerifyPeer(): bool 134 | { 135 | return $this->curlOptSslVerifyPeer; 136 | } 137 | 138 | public function isCurlOptSslCert(): ?string 139 | { 140 | return $this->curlOptSslCert; 141 | } 142 | 143 | public function isCurlOptSslCertPassword(): ?string 144 | { 145 | return $this->curlOptSslCertPassword; 146 | } 147 | 148 | public function isCurlOptSslKey(): ?string 149 | { 150 | return $this->curlOptSslKey; 151 | } 152 | 153 | public function isCurlOptSslKeyPassword(): ?string 154 | { 155 | return $this->curlOptSslKeyPassword; 156 | } 157 | 158 | public function isCurlOptVerbose(): bool 159 | { 160 | return $this->curlOptVerbose; 161 | } 162 | 163 | /** 164 | * Get curl option CURLOPT_USERAGENT. 165 | */ 166 | public function getCurlOptUserAgent(): ?string 167 | { 168 | return $this->curlOptUserAgent; 169 | } 170 | 171 | public function getOAuthAccessToken(): string 172 | { 173 | return $this->oauthAccessToken; 174 | } 175 | 176 | public function isCookieAuthorizationEnabled(): bool 177 | { 178 | return $this->cookieAuthEnabled; 179 | } 180 | 181 | /** 182 | * get default User-Agent String. 183 | */ 184 | public function getDefaultUserAgentString(): string 185 | { 186 | $curlVersion = curl_version(); 187 | 188 | return sprintf('curl/%s (%s)', $curlVersion['version'], $curlVersion['host']); 189 | } 190 | 191 | public function getCookieFile(): ?string 192 | { 193 | return $this->cookieFile; 194 | } 195 | 196 | public function getProxyServer(): ?string 197 | { 198 | return $this->proxyServer; 199 | } 200 | 201 | public function getProxyPort(): ?string 202 | { 203 | return $this->proxyPort; 204 | } 205 | 206 | public function getProxyType(): ?int 207 | { 208 | return $this->proxyType; 209 | } 210 | 211 | public function getProxyUser(): ?string 212 | { 213 | return $this->proxyUser; 214 | } 215 | 216 | public function getProxyPassword(): ?string 217 | { 218 | return $this->proxyPassword; 219 | } 220 | 221 | public function getTimeout(): int 222 | { 223 | return $this->timeout; 224 | } 225 | 226 | public function isTokenBasedAuth(): bool 227 | { 228 | return $this->useTokenBasedAuth; 229 | } 230 | 231 | public function getPersonalAccessToken(): string 232 | { 233 | return $this->personalAccessToken; 234 | } 235 | 236 | public function getServiceDeskId(): ?int 237 | { 238 | return $this->serviceDeskId; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /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/Version/VersionService.php: -------------------------------------------------------------------------------- 1 | releaseDate instanceof \DateTimeInterface) { 28 | $version->releaseDate = $version->releaseDate->format('Y-m-d'); 29 | } 30 | $data = json_encode($version); 31 | 32 | $this->log->info("Create Version=\n".$data); 33 | 34 | $ret = $this->exec($this->uri, $data, 'POST'); 35 | 36 | return $this->json_mapper->map( 37 | json_decode($ret), 38 | Version::class 39 | ); 40 | } 41 | 42 | /** 43 | * Modify a version's sequence within a project. 44 | * 45 | * @param Version $version 46 | * 47 | * @throws JiraException 48 | */ 49 | public function move(Version $version) 50 | { 51 | throw new JiraException('move version not yet implemented'); 52 | } 53 | 54 | /** 55 | * get project version. 56 | * 57 | * @param string $id version id 58 | * 59 | * @throws JiraException 60 | * @throws \JsonMapper_Exception 61 | * 62 | * @return Version 63 | * 64 | * @see ProjectService::getVersions() 65 | */ 66 | public function get(string $id) 67 | { 68 | $ret = $this->exec($this->uri.'/'.$id); 69 | 70 | $this->log->info('Result='.$ret); 71 | 72 | return $this->json_mapper->map( 73 | json_decode($ret), 74 | Version::class 75 | ); 76 | } 77 | 78 | /** 79 | * @author Martijn Smidt 80 | * 81 | * @param Version $version 82 | * 83 | * @throws JiraException 84 | * 85 | * @return Version 86 | */ 87 | public function update(Version $version): Version 88 | { 89 | if (!$version->id || !is_numeric($version->id)) { 90 | throw new JiraException($version->id.' is not a valid version id.'); 91 | } 92 | 93 | // avoid weird error "Only one of 'releaseDate' and 'userReleaseDate' can be specified when editing a version." 94 | $version->userReleaseDate = null; 95 | $version->userStartDate = null; 96 | 97 | $data = json_encode($version); 98 | 99 | $ret = $this->exec($this->uri.'/'.$version->id, $data, 'PUT'); 100 | 101 | return $this->json_mapper->map( 102 | json_decode($ret), 103 | Version::class 104 | ); 105 | } 106 | 107 | /** 108 | * @author Martijn Smidt 109 | * 110 | * @param Version $version 111 | * @param Version|bool $moveAffectedIssuesTo 112 | * @param Version|bool $moveFixIssuesTo 113 | * 114 | * @throws JiraException 115 | * 116 | * @return string 117 | */ 118 | public function delete(Version $version, $moveAffectedIssuesTo = false, $moveFixIssuesTo = false) 119 | { 120 | if (!$version->id || !is_numeric($version->id)) { 121 | throw new JiraException($version->id.' is not a valid version id.'); 122 | } 123 | 124 | $data = []; 125 | 126 | if ($moveAffectedIssuesTo && $moveAffectedIssuesTo instanceof Version) { 127 | $data['moveAffectedIssuesTo'] = $moveAffectedIssuesTo->name; 128 | } 129 | 130 | if ($moveFixIssuesTo && $moveFixIssuesTo instanceof Version) { 131 | $data['moveFixIssuesTo'] = $moveFixIssuesTo->name; 132 | } 133 | 134 | $ret = $this->exec($this->uri.'/'.$version->id, json_encode($data), 'DELETE'); 135 | 136 | return $ret; 137 | } 138 | 139 | public function merge($ver) 140 | { 141 | throw new JiraException('merge version not yet implemented'); 142 | } 143 | 144 | /** 145 | * Returns a bean containing the number of fixed in and affected issues for the given version. 146 | * 147 | * @param Version $version 148 | * 149 | * @throws JiraException 150 | * 151 | * @see https://docs.atlassian.com/jira/REST/server/#api/2/version-getVersionRelatedIssues 152 | */ 153 | public function getRelatedIssues(Version $version) 154 | { 155 | if (!$version->id || !is_numeric($version->id)) { 156 | throw new JiraException($version->id.' is not a valid version id.'); 157 | } 158 | 159 | $ret = $this->exec($this->uri.'/'.$version->id.'/relatedIssueCounts'); 160 | 161 | return $this->json_mapper->map( 162 | json_decode($ret), 163 | new VersionIssueCounts() 164 | ); 165 | } 166 | 167 | /** 168 | * Returns a bean containing the number of unresolved issues for the given version. 169 | * 170 | * @param Version $version 171 | * 172 | * @throws JiraException 173 | * 174 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/latest/#api/2/version-getVersionUnresolvedIssues 175 | * 176 | * @return VersionUnresolvedCount 177 | */ 178 | public function getUnresolvedIssues(Version $version) 179 | { 180 | if (!$version->id || !is_numeric($version->id)) { 181 | throw new JiraException($version->id.' is not a valid version id.'); 182 | } 183 | 184 | $ret = $this->exec($this->uri.'/'.$version->id.'/unresolvedIssueCount'); 185 | 186 | return $this->json_mapper->map( 187 | json_decode($ret), 188 | new VersionUnresolvedCount() 189 | ); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /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/5.2.0/#servicedeskapi/organization-createOrganization 34 | */ 35 | public function create(string $orgName): Organisation 36 | { 37 | $this->logger->info("Create ServiceDesk Organisation=\n".$orgName); 38 | 39 | $data = json_encode(['name' => $orgName]); 40 | 41 | $result = $this->client->exec($this->uri, $data, 'POST'); 42 | 43 | return $this->createOrganisation($result); 44 | } 45 | 46 | /** 47 | * @throws JsonMapper_Exception|JiraException|JsonException 48 | * 49 | * @see https://docs.atlassian.com/jira-servicedesk/REST/5.2.0/#servicedeskapi/organization-createOrganization 50 | */ 51 | public function createFromOrganisation(Organisation $organisation): Organisation 52 | { 53 | $data = json_encode($organisation, JSON_THROW_ON_ERROR); 54 | 55 | return $this->create($data); 56 | } 57 | 58 | /** 59 | * @throws JsonMapper_Exception|JiraException|JsonException 60 | */ 61 | public function get(string $organisationId): Organisation 62 | { 63 | $result = $this->client->exec( 64 | $this->client->createUrl('%s/%s', [$this->uri, $organisationId]) 65 | ); 66 | 67 | return $this->createOrganisation($result); 68 | } 69 | 70 | /** 71 | * Returns the organisations paginated. 72 | * 73 | * @throws JiraException|JsonMapper_Exception|JsonException 74 | * 75 | * @return Organisation[] 76 | * 77 | * @see https://docs.atlassian.com/jira-servicedesk/REST/5.2.0/#servicedeskapi/organization-getOrganizations 78 | */ 79 | public function getOrganisations(int $start, int $limit): array 80 | { 81 | $paramArray = $this->client->toHttpQueryParameter([ 82 | 'start' => $start, 83 | 'limit' => $limit, 84 | ]); 85 | 86 | $response = $this->client->exec($this->uri.$paramArray, null); 87 | 88 | $this->logger->info("Result=\n".$response); 89 | 90 | $organisationData = json_decode($response, true, 512, JSON_THROW_ON_ERROR); 91 | $organisations = []; 92 | 93 | foreach ($organisationData['values'] as $organisation) { 94 | $organisations[] = $this->jsonMapper->map($organisation, new Organisation()); 95 | } 96 | 97 | return $organisations; 98 | } 99 | 100 | /** 101 | * Returns the organisation customers paginated. 102 | * 103 | * @throws JsonMapper_Exception|JiraException|JsonException 104 | * 105 | * @return Customer[] 106 | */ 107 | public function getCustomersForOrganisation(int $startIndex, int $amountOfItems, Organisation $organisation): array 108 | { 109 | $result = $this->client->exec( 110 | $this->createGetCustomersUrl($organisation->id, $startIndex, $amountOfItems) 111 | ); 112 | 113 | $this->logger->info("Result=\n".$result); 114 | 115 | $customerData = json_decode($result, true, 512, JSON_THROW_ON_ERROR); 116 | $customers = []; 117 | 118 | foreach ($customerData as $customer) { 119 | $customers[] = $this->jsonMapper->map( 120 | $customer, 121 | new Customer() 122 | ); 123 | } 124 | 125 | return $customers; 126 | } 127 | 128 | /** 129 | * @param Customer[] $customers 130 | * 131 | * @throws JiraException 132 | */ 133 | public function addCustomersToOrganisation(array $customers, Organisation $organisation): void 134 | { 135 | $customerNames = array_map(static function (Customer $customer) { 136 | return $customer->name; 137 | }, $customers); 138 | 139 | $this->client->exec( 140 | $this->client->createUrl('%s/%s', [$this->uri, $organisation->id]), 141 | ['usernames' => $customerNames], 142 | 'POST' 143 | ); 144 | } 145 | 146 | /** 147 | * @throws JiraException 148 | */ 149 | public function deleteOrganisation(Organisation $organisation): void 150 | { 151 | $this->client->exec( 152 | $this->client->createUrl('%s/%s', [$this->uri, $organisation->id]), 153 | null, 154 | 'DELETE' 155 | ); 156 | } 157 | 158 | /** 159 | * @throws InvalidArgumentException 160 | */ 161 | private function createGetCustomersUrl(int $organisationId, int $startIndex, int $amountOfItems): string 162 | { 163 | if ($startIndex < 0) { 164 | throw new InvalidArgumentException('Start index can not be lower then 0.'); 165 | } 166 | if ($amountOfItems < 1) { 167 | throw new InvalidArgumentException('Amount of items can not be lower then 1.'); 168 | } 169 | 170 | return $this->client->createUrl( 171 | '%s/%s/user?%s', 172 | [$this->uri, $organisationId], 173 | ['start' => $startIndex, 'limit' => $amountOfItems] 174 | ); 175 | } 176 | 177 | /** 178 | * @throws JsonMapper_Exception|JsonException 179 | */ 180 | private function createOrganisation(string $data): Organisation 181 | { 182 | return $this->jsonMapper->map( 183 | json_decode($data, true, 512, JSON_THROW_ON_ERROR), 184 | new Organisation() 185 | ); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/Project/Project.php: -------------------------------------------------------------------------------- 1 | leadName)) { 115 | $params['lead'] = $this->leadName; 116 | unset($params['leadName']); 117 | } 118 | if ($this->versions === null or count($this->versions) === 0) { 119 | unset($params['version']); 120 | } 121 | 122 | return $params; 123 | } 124 | 125 | public function setId(string $id): static 126 | { 127 | $this->id = $id; 128 | 129 | return $this; 130 | } 131 | 132 | public function setKey(string $key): static 133 | { 134 | $this->key = $key; 135 | 136 | return $this; 137 | } 138 | 139 | public function setName(string $name): static 140 | { 141 | $this->name = $name; 142 | 143 | return $this; 144 | } 145 | 146 | public function setAvatarUrls(\stdClass $avatarUrls): static 147 | { 148 | $this->avatarUrls = $avatarUrls; 149 | 150 | return $this; 151 | } 152 | 153 | public function setProjectCategory(\stdClass $projectCategory): static 154 | { 155 | $this->projectCategory = $projectCategory; 156 | 157 | return $this; 158 | } 159 | 160 | public function setDescription(string $description): static 161 | { 162 | $this->description = $description; 163 | 164 | return $this; 165 | } 166 | 167 | public function setLeadName(string $leadName): static 168 | { 169 | $this->leadName = $leadName; 170 | 171 | return $this; 172 | } 173 | 174 | public function setLeadAccountId(string $leadAccountId): static 175 | { 176 | $this->leadAccountId = $leadAccountId; 177 | 178 | return $this; 179 | } 180 | 181 | public function setUrl(string $url): static 182 | { 183 | $this->url = $url; 184 | 185 | return $this; 186 | } 187 | 188 | public function setProjectTypeKey(string $projectTypeKey): static 189 | { 190 | $this->projectTypeKey = $projectTypeKey; 191 | 192 | return $this; 193 | } 194 | 195 | public function setProjectTemplateKey(string $projectTemplateKey): static 196 | { 197 | $this->projectTemplateKey = $projectTemplateKey; 198 | 199 | return $this; 200 | } 201 | 202 | public function setAvatarId(int $avatarId): static 203 | { 204 | $this->avatarId = $avatarId; 205 | 206 | return $this; 207 | } 208 | 209 | public function setIssueSecurityScheme(int $issueSecurityScheme): static 210 | { 211 | $this->issueSecurityScheme = $issueSecurityScheme; 212 | 213 | return $this; 214 | } 215 | 216 | public function setPermissionScheme(int $permissionScheme): static 217 | { 218 | $this->permissionScheme = $permissionScheme; 219 | 220 | return $this; 221 | } 222 | 223 | public function setNotificationScheme(int $notificationScheme): static 224 | { 225 | $this->notificationScheme = $notificationScheme; 226 | 227 | return $this; 228 | } 229 | 230 | public function setCategoryId(int $categoryId): static 231 | { 232 | $this->categoryId = $categoryId; 233 | 234 | return $this; 235 | } 236 | 237 | /** 238 | * $assigneeType value available for "PROJECT_LEAD" and "UNASSIGNED". 239 | */ 240 | public function setAssigneeType(?string $assigneeType): static 241 | { 242 | if (!in_array($assigneeType, ['PROJECT_LEAD', 'UNASSIGNED'])) { 243 | throw new JiraException('invalid assigneeType:'.$assigneeType); 244 | } 245 | 246 | $this->assigneeType = $assigneeType; 247 | 248 | return $this; 249 | } 250 | 251 | public function setAssigneeTypeAsEnum(AssigneeTypeEnum $assigneeType): static 252 | { 253 | $this->assigneeType = $assigneeType->type(); 254 | 255 | return $this; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /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 \JiraRestApi\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 | /** 50 | * Get list of boards with paginated results. 51 | * 52 | * @param array $paramArray 53 | * 54 | * @throws \JiraRestApi\JiraException 55 | * 56 | * @return PaginatedResult|null array of Board class 57 | */ 58 | public function getBoards($paramArray = []): ?PaginatedResult 59 | { 60 | $json = $this->exec($this->uri.$this->toHttpQueryParameter($paramArray), null); 61 | 62 | try { 63 | return $this->json_mapper->map( 64 | json_decode($json, false, 512, $this->getJsonOptions()), 65 | new PaginatedResult() 66 | ); 67 | } catch (\JsonException $exception) { 68 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 69 | 70 | return null; 71 | } 72 | } 73 | 74 | public function getBoard($id, $paramArray = []): ?Board 75 | { 76 | $json = $this->exec($this->uri.'/'.$id.$this->toHttpQueryParameter($paramArray), null); 77 | 78 | try { 79 | return $this->json_mapper->map( 80 | json_decode($json, false, 512, $this->getJsonOptions()), 81 | new Board() 82 | ); 83 | } catch (\JsonException $exception) { 84 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 85 | 86 | return null; 87 | } 88 | } 89 | 90 | /** 91 | * @return \ArrayObject|AgileIssue[]|null 92 | */ 93 | public function getBoardIssues($id, $paramArray = []): ?\ArrayObject 94 | { 95 | $json = $this->exec($this->uri.'/'.$id.'/issue'.$this->toHttpQueryParameter($paramArray), null); 96 | 97 | try { 98 | return $this->json_mapper->mapArray( 99 | json_decode($json, false, 512, $this->getJsonOptions())->issues, 100 | new \ArrayObject(), 101 | AgileIssue::class 102 | ); 103 | } catch (\JsonException $exception) { 104 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 105 | 106 | return null; 107 | } 108 | } 109 | 110 | /** 111 | * @return \ArrayObject|AgileIssue[]|null 112 | */ 113 | public function getBoardBacklogIssues($id, array $paramArray = []): ?\ArrayObject 114 | { 115 | $json = $this->exec($this->uri.'/'.$id.'/backlog'.$this->toHttpQueryParameter($paramArray), null); 116 | 117 | try { 118 | return $this->json_mapper->mapArray( 119 | json_decode($json, false, 512, $this->getJsonOptions())->issues, 120 | new \ArrayObject(), 121 | AgileIssue::class 122 | ); 123 | } catch (\JsonException $exception) { 124 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 125 | 126 | return null; 127 | } 128 | } 129 | 130 | /** 131 | * @return \ArrayObject|Sprint[]|null 132 | */ 133 | public function getBoardSprints($boardId, $paramArray = []): ?\ArrayObject 134 | { 135 | $json = $this->exec($this->uri.'/'.$boardId.'/sprint'.$this->toHttpQueryParameter($paramArray), null); 136 | 137 | try { 138 | return $this->json_mapper->mapArray( 139 | json_decode($json, false, 512, $this->getJsonOptions())->values, 140 | new \ArrayObject(), 141 | Sprint::class 142 | ); 143 | } catch (\JsonException $exception) { 144 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 145 | 146 | return null; 147 | } 148 | } 149 | 150 | /** 151 | * Get list of boards with paginated results. 152 | * 153 | * @param array $paramArray 154 | * 155 | * @throws \JiraRestApi\JiraException 156 | * 157 | * @return PaginatedResult|null array of Board class 158 | */ 159 | public function getSprintsForBoard($boardId, $paramArray = []): ?PaginatedResult 160 | { 161 | $json = $this->exec($this->uri.'/'.$boardId.'/sprint'.$this->toHttpQueryParameter($paramArray), null); 162 | 163 | try { 164 | return $this->json_mapper->map( 165 | json_decode($json, false, 512, $this->getJsonOptions()), 166 | new PaginatedResult() 167 | ); 168 | } catch (\JsonException $exception) { 169 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 170 | 171 | return null; 172 | } 173 | } 174 | 175 | /** 176 | * @return \ArrayObject|Epic[]|null 177 | */ 178 | public function getBoardEpics($boardId, $paramArray = []): ?\ArrayObject 179 | { 180 | $json = $this->exec($this->uri.'/'.$boardId.'/epic'.$this->toHttpQueryParameter($paramArray), null); 181 | 182 | try { 183 | return $this->json_mapper->mapArray( 184 | json_decode($json, false, 512, $this->getJsonOptions())->values, 185 | new \ArrayObject(), 186 | Epic::class 187 | ); 188 | } catch (\JsonException $exception) { 189 | $this->log->error("Response cannot be decoded from json\nException: {$exception->getMessage()}"); 190 | 191 | return null; 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/User/UserService.php: -------------------------------------------------------------------------------- 1 | log->info("Create User=\n".$data); 29 | 30 | $ret = $this->exec($this->uri, $data, 'POST'); 31 | 32 | return $this->json_mapper->map( 33 | json_decode($ret), 34 | new User() 35 | ); 36 | } 37 | 38 | /** 39 | * Function to get user. 40 | * 41 | * @param array $paramArray Possible values for $paramArray 'username', 'key'. 42 | * "Either the 'username' or the 'key' query parameters need to be provided". 43 | * 44 | * @throws \JsonMapper_Exception 45 | * @throws \JiraRestApi\JiraException 46 | * 47 | * @return User 48 | */ 49 | public function get(array $paramArray): User 50 | { 51 | $queryParam = '?'.http_build_query($paramArray); 52 | 53 | $ret = $this->exec($this->uri.$queryParam, null); 54 | 55 | $this->log->info("Result=\n".$ret); 56 | 57 | return $this->json_mapper->map( 58 | json_decode($ret), 59 | new User() 60 | ); 61 | } 62 | 63 | /** 64 | * Returns a list of users that match the search string and/or property. 65 | * 66 | * @param array $paramArray 67 | * 68 | * @throws \JsonMapper_Exception 69 | * @throws \JiraRestApi\JiraException 70 | * 71 | * @return User[] 72 | */ 73 | public function findUsers(array $paramArray): array 74 | { 75 | $queryParam = '?'.http_build_query($paramArray); 76 | 77 | $ret = $this->exec($this->uri.'/search'.$queryParam, null); 78 | 79 | $this->log->info("Result=\n".$ret); 80 | 81 | $userData = json_decode($ret); 82 | $users = []; 83 | 84 | foreach ($userData as $user) { 85 | $users[] = $this->json_mapper->map( 86 | $user, 87 | new User() 88 | ); 89 | } 90 | 91 | return $users; 92 | } 93 | 94 | /** 95 | * Returns a list of users that match the search string. 96 | * Please note that this resource should be called with an issue key when a list of assignable users is retrieved for editing. 97 | * 98 | * @param array $paramArray 99 | * 100 | * @throws \JsonMapper_Exception 101 | * @throws \JiraRestApi\JiraException 102 | * 103 | * @return User[] 104 | * 105 | * @see https://docs.atlassian.com/jira/REST/cloud/#api/2/user-findAssignableUsers 106 | */ 107 | public function findAssignableUsers(array $paramArray): array 108 | { 109 | $queryParam = '?'.http_build_query($paramArray); 110 | 111 | $ret = $this->exec($this->uri.'/assignable/search'.$queryParam, null); 112 | 113 | $this->log->info("Result=\n".$ret); 114 | 115 | $userData = json_decode($ret); 116 | $users = []; 117 | 118 | foreach ($userData as $user) { 119 | $users[] = $this->json_mapper->map( 120 | $user, 121 | new User() 122 | ); 123 | } 124 | 125 | return $users; 126 | } 127 | 128 | /** 129 | * Returns a list of users that match with a specific query. 130 | * 131 | * @param array $paramArray 132 | * 133 | * @throws \JsonMapper_Exception 134 | * @throws \JiraRestApi\JiraException 135 | * 136 | * @return User[] 137 | * 138 | * @see https://developer.atlassian.com/cloud/jira/platform/rest/v2/#api-rest-api-2-user-search-query-get 139 | */ 140 | public function findUsersByQuery(array $paramArray): array 141 | { 142 | $queryParam = '?'.http_build_query($paramArray); 143 | 144 | $ret = $this->exec($this->uri.'/search/query'.$queryParam, null); 145 | 146 | $this->log->info("Result=\n".$ret); 147 | 148 | $userData = json_decode($ret); 149 | $users = []; 150 | 151 | foreach ($userData->values as $user) { 152 | $users[] = $this->json_mapper->map( 153 | $user, 154 | new User() 155 | ); 156 | } 157 | 158 | return $users; 159 | } 160 | 161 | /** 162 | * Delete a User. 163 | * 164 | * @param array $paramArray username or keys 165 | * 166 | * @throws \JiraRestApi\JiraException 167 | * 168 | * @return string 169 | */ 170 | public function deleteUser(array $paramArray): string 171 | { 172 | $queryParam = '?'.http_build_query($paramArray); 173 | 174 | $ret = $this->exec($this->uri.$queryParam, null, 'DELETE'); 175 | 176 | return $ret; 177 | } 178 | 179 | /** 180 | * get a user info details. 181 | * 182 | * @throws \JiraRestApi\JiraException 183 | * 184 | * @return User 185 | */ 186 | public function getMyself(): User 187 | { 188 | $ret = $this->exec('myself', null); 189 | 190 | $user = $this->json_mapper->map( 191 | json_decode($ret), 192 | new User() 193 | ); 194 | 195 | return $user; 196 | } 197 | 198 | /** 199 | * @param array $paramArray 200 | * 201 | * @throws \JsonMapper_Exception 202 | * @throws \JiraRestApi\JiraException 203 | * 204 | * @return User[] 205 | */ 206 | public function getUsers(array $paramArray): array 207 | { 208 | $queryParam = '?'.http_build_query($paramArray); 209 | 210 | $ret = $this->exec('/users'.$queryParam, null); 211 | 212 | $this->log->info("Result=\n".$ret); 213 | 214 | $userData = json_decode($ret); 215 | $users = []; 216 | 217 | foreach ($userData as $user) { 218 | $users[] = $this->json_mapper->map($user, new User()); 219 | } 220 | 221 | return $users; 222 | } 223 | 224 | /** 225 | * Function to update an existing user. 226 | * 227 | * @param array $paramArray 228 | * @param array|User $user 229 | * 230 | * @throws \JsonMapper_Exception 231 | * @throws \JiraRestApi\JiraException 232 | * 233 | * @return User 234 | */ 235 | public function update(array $paramArray, array|User $user): User 236 | { 237 | $queryParam = '?'.http_build_query($paramArray); 238 | 239 | $data = json_encode($user); 240 | 241 | $this->log->info('Update User ('.$queryParam.") =\n".$data); 242 | 243 | $ret = $this->exec($this->uri.$queryParam, $data, 'PUT'); 244 | 245 | return $this->json_mapper->map( 246 | json_decode($ret), 247 | new User() 248 | ); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /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 | } --------------------------------------------------------------------------------