├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bin ├── README.md ├── find-field └── generate ├── composer.json ├── composer.lock ├── examples ├── create-new-issue.php ├── jira-generator-config.yaml └── measure-load-time.php ├── src ├── CFGenerator │ ├── Generator.php │ ├── ITemplate.php │ ├── README.md │ ├── SimpleLogger.php │ ├── SimpleTemplate.php │ └── templates │ │ ├── CheckboxField.tpl │ │ ├── NumberField.tpl │ │ ├── RadioField.tpl │ │ ├── SelectField.tpl │ │ ├── SingleSelectField.tpl │ │ ├── SingleUserField.tpl │ │ ├── TextField.tpl │ │ ├── UserField.tpl │ │ └── _template-config.json ├── CLI │ ├── ClimateLogger.php │ └── Configuration.php ├── Component.php ├── CustomFields │ ├── CheckboxField.php │ ├── CustomField.php │ ├── LabelField.php │ ├── NumberField.php │ ├── RadioField.php │ ├── SelectField.php │ ├── SingleCheckboxField.php │ ├── SingleSelectField.php │ ├── SingleUserField.php │ ├── TextField.php │ └── UserField.php ├── Exception.php ├── Exception │ ├── Component.php │ ├── CustomField.php │ ├── File.php │ ├── Issue.php │ ├── Link.php │ ├── User.php │ └── Version.php ├── Group.php ├── Helpers │ ├── Files.php │ ├── Json.php │ └── Strings.php ├── Issue.php ├── Issue │ ├── Attachments.php │ ├── Changelog.php │ ├── Comment.php │ ├── CreateRequest.php │ ├── File.php │ ├── History.php │ ├── HistoryRecord.php │ ├── ILogRecord.php │ ├── Link.php │ ├── LinkType.php │ ├── LinksList.php │ ├── LogRecordItem.php │ ├── Priority.php │ ├── Resolution.php │ ├── Status.php │ ├── StatusCategory.php │ ├── Type.php │ └── WatchersList.php ├── REST │ ├── Client.php │ ├── ClientRaw.php │ ├── Exception.php │ ├── Exception │ │ └── Authorization.php │ └── Section │ │ ├── Attachment.php │ │ ├── Comment.php │ │ ├── Component.php │ │ ├── Field.php │ │ ├── Group.php │ │ ├── Issue.php │ │ ├── IssueAttachment.php │ │ ├── IssueLink.php │ │ ├── IssueLinkType.php │ │ ├── IssueTransitions.php │ │ ├── IssueType.php │ │ ├── Jql.php │ │ ├── Priority.php │ │ ├── Project.php │ │ ├── Resolution.php │ │ ├── Section.php │ │ ├── SecurityLevel.php │ │ ├── Status.php │ │ ├── StatusCategory.php │ │ ├── User.php │ │ ├── Version.php │ │ └── Watchers.php ├── Security.php ├── User.php ├── UsersList.php ├── Version.php └── autoload.php └── tests ├── Helpers └── StringsTest.php └── IssueTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /.idea/ 3 | /test.php 4 | /.php_cs.cache 5 | /composer.phar 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 4 | ## [1.1.0](https://github.com/badoo/jira-client/compare/v1.0.0...v1.1.0) (2020-02-04) 5 | 6 | ### Dependencies 7 | 8 | * allow symfony/yaml v5.0 as a dependency ([#11](https://github.com/badoo/jira-client/issues/11)) 9 | 10 | ### Features 11 | 12 | * add custom label field ([#4](https://github.com/badoo/jira-client/issues/4)) ([beaa668](https://github.com/badoo/jira-client/commit/beaa6687aabe2e3b14c836d63d3bc4119af44cbe)) 13 | * add getAll method for project section ([#8](https://github.com/badoo/jira-client/issues/8)) ([d689325](https://github.com/badoo/jira-client/commit/d68932571e133b6115fd2c99e7a6f8ade525a885)) 14 | * add getLatestVersion() for project section ([9dde112](https://github.com/badoo/jira-client/commit/9dde112fb3d038b5ef8eb78eb0649d2ab684dc36)) 15 | * add project statuses list ([#6](https://github.com/badoo/jira-client/issues/6)) ([ddfd595](https://github.com/badoo/jira-client/commit/ddfd5952fb14bd1b7aaac4590f7395449956055f)) 16 | 17 | 18 | ### Bug Fixes 19 | 20 | * Client::instance() will return it`s successor ([#5](https://github.com/badoo/jira-client/issues/5)) ([09079da](https://github.com/badoo/jira-client/commit/09079dafc70d115d1bf4607c5646bef52847788f)) 21 | * issue field meta contains flag 'custom' ([#7](https://github.com/badoo/jira-client/issues/7)) ([8edbff7](https://github.com/badoo/jira-client/commit/8edbff7e6ebe9c4dbe51b9593ec76f0e537358bf)) 22 | 23 | ## 1.0.0 (2019-11-01) 24 | -------------------------------------------------------------------------------- /bin/find-field: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 6 | */ 7 | 8 | require realpath(dirname(__DIR__)) . 9 | DIRECTORY_SEPARATOR . 'src' . 10 | DIRECTORY_SEPARATOR . 'autoload.php'; 11 | 12 | $Climate = new \League\CLImate\CLImate(); 13 | 14 | $Climate->arguments->add( 15 | [ 16 | 'help' => [ 17 | 'prefix' => 'h', 18 | 'longPrefix' => 'help', 19 | 'description' => 'show help message and exit.', 20 | 'noValue' => true, 21 | ], 22 | 23 | 'config' => [ 24 | 'prefix' => 'c', 25 | 'longPrefix' => 'config', 26 | 'description' => 'use alternative configuration file path.', 27 | ], 28 | 29 | 'log-level' => [ 30 | 'prefix' => 'l', 31 | 'longPrefix' => 'log-level', 32 | 'description' => 'change max log level. Use PSR-3 log level names here', 33 | ], 34 | 35 | // JIRA client configuration 36 | 'jira-url' => [ 37 | 'longPrefix' => 'jira-url', 38 | 'description' => 'use API of this JIRA instance (e.g. https://jira.localhost/).', 39 | ], 40 | 'timeout' => [ 41 | 'longPrefix' => 'timeout', 42 | 'description' => 'API request timeout in seconds. Defaults to 60', 43 | 'castTo' => 'int', 44 | ], 45 | 'user' => [ 46 | 'prefix' => 'U', 47 | 'longPrefix' => 'user', 48 | 'description' => 'auth in JIRA API with this user', 49 | ], 50 | 'password' => [ 51 | 'prefix' => 'P', 52 | 'longPrefix' => 'password', 53 | 'description' => 'auth in JIRA API with this password or API token.', 54 | ], 55 | 'credentials-file' => [ 56 | 'longPrefix' => 'credentials-file', 57 | 'description' => 'read : pair from this file', 58 | ], 59 | 60 | // Find-field configuration 61 | 'ignore-case' => [ 62 | 'prefix' => 'i', 63 | 'longPrefix' => 'ignore-case', 64 | 'description' => 'run case insensitive search.', 65 | 'noValue' => true, 66 | ], 67 | 'pretty' => [ 68 | 'prefix' => 'p', 69 | 'longPrefix' => 'pretty', 70 | 'description' => 'output pretty JSON (human-readable)', 71 | 'noValue' => true, 72 | ], 73 | 'field-name' => [ 74 | 'description' => 'name of field to search for.', 75 | ] 76 | ] 77 | ); 78 | 79 | $Climate->description('Find JIRA custom field imformation using API'); 80 | 81 | // Initialize lgger as soon as possible 82 | $Logger = new \Badoo\Jira\CLI\ClimateLogger($Climate); 83 | 84 | try { 85 | $Climate->arguments->parse(); 86 | } catch (\League\CLImate\Exceptions\InvalidArgumentException $e) { 87 | $Logger->error($e->getMessage()); 88 | die(1); 89 | } 90 | 91 | // Change logging level first 92 | $log_level = $Climate->arguments->get('log-level'); 93 | if (!empty($log_level)) { 94 | $Logger->setLevelThreshold($log_level); 95 | } 96 | 97 | if ($Climate->arguments->defined('help')) { 98 | $Climate->usage(); 99 | die(0); 100 | } 101 | 102 | $field_name = $Climate->arguments->get('field-name'); 103 | if (empty($field_name)) { 104 | $Logger->error("Field name to look for is required"); 105 | $Climate->to('error')->br(); 106 | $Climate->usage(); 107 | die(1); 108 | } 109 | 110 | $config_path = $Climate->arguments->get('config'); 111 | if (empty($config_path)) { 112 | $config_path = \Badoo\Jira\CLI\Configuration::DEFAULT_CONFIG_FILE; 113 | } 114 | 115 | $Config = new \Badoo\Jira\CLI\Configuration(); 116 | if (\Badoo\Jira\Helpers\Files::exists($config_path)) { 117 | $Config->load($config_path); 118 | } else { 119 | $msg = "Config file {$config_path} not found."; 120 | if ($Climate->arguments->defined('config')) { 121 | $Logger->error($msg); 122 | die(1); 123 | } 124 | 125 | $Logger->warning($msg); 126 | } 127 | 128 | // Override configuration options with ones from CLI arguments 129 | $Logger->debug('Overriding configuration with command line options...'); 130 | 131 | if (!empty($log_level)) { 132 | $Config->setLogLevel($log_level); 133 | } 134 | 135 | $jira_url = $Climate->arguments->get('jira-url'); 136 | if (!empty($jira_url)) { 137 | $Config->setJiraUrl($jira_url); 138 | } 139 | 140 | $timeout = $Climate->arguments->get('timeout'); 141 | if (!empty($timeout)) { 142 | $Config->setJiraTimeout($timeout); 143 | } 144 | 145 | $jira_cred_file = $Climate->arguments->get('credentials-file'); 146 | if (!empty($jira_cred_file)) { 147 | $Config->setJiraCredentialsFile($jira_cred_file); 148 | } 149 | 150 | $jira_user = $Climate->arguments->get('user'); 151 | if (!empty($jira_user)) { 152 | $Config->setJiraUser($jira_user); 153 | } 154 | 155 | $jira_password = $Climate->arguments->get('password'); 156 | if (!empty($jira_password)) { 157 | $Config->setJiraPassword($jira_password); 158 | } 159 | 160 | // Actual script 161 | 162 | $Jira = $Config->getJiraClient(); 163 | 164 | if (strpos($field_name, 'customfield_') === 0) { 165 | $FieldInfo = $Jira->field()->get($field_name); 166 | $fields = [$FieldInfo]; 167 | } else { 168 | $fields = $Jira->field()->search($field_name, !$Climate->arguments->defined('ignore-case')); 169 | } 170 | 171 | $load_options_types = [ 172 | 'option', 173 | 'array', 174 | ]; 175 | 176 | $load_option_items = [ 177 | '', 178 | 'option' 179 | ]; 180 | 181 | foreach ($fields as $FieldInfo) { 182 | $type = $FieldInfo->schema->type; 183 | $item_type = $FieldInfo->schema->items ?? ''; 184 | $is_custom = isset($FieldInfo->schema->custom); 185 | 186 | if ($is_custom && in_array($type, $load_options_types) && in_array($item_type, $load_option_items)) { 187 | $FieldInfo->options = $Jira->jql()->getFieldSuggestions($FieldInfo->name); 188 | } 189 | } 190 | 191 | $opts = 0; 192 | if ($Climate->arguments->get('pretty')) { 193 | $opts = $opts | JSON_PRETTY_PRINT; 194 | } 195 | 196 | echo json_encode($fields, $opts) . PHP_EOL; 197 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "badoo/jira-client", 3 | "type": "library", 4 | "description": "Jira REST API client with comfortable wrappers for most commonly used API instances like issues, custom fields, components and so on", 5 | "version": "1.1.0", 6 | "minimum-stability": "stable", 7 | "license": "GPL-3.0-only", 8 | "homepage": "https://badoo.com/", 9 | "authors": [ 10 | { 11 | "name": "Badoo Development" 12 | }, 13 | { 14 | "name": "Korenevskiy Denis", 15 | "email": "denkoren@corp.badoo.com", 16 | "role": "Developer" 17 | } 18 | ], 19 | "bin": [ 20 | "bin/find-field", 21 | "bin/generate" 22 | ], 23 | "require": { 24 | "php": ">=7.2", 25 | "ext-json": "*", 26 | "ext-curl": "*", 27 | "psr/log": "^1.0", 28 | "symfony/yaml": "^4.2|^5.0", 29 | "league/climate": "^3.5" 30 | }, 31 | "require-dev": { 32 | "phpunit/phpunit": "^8.2", 33 | "friendsofphp/php-cs-fixer": "^2.15" 34 | }, 35 | "autoload": { 36 | "psr-4": { 37 | "Badoo\\Jira\\": "src/", 38 | "Badoo\\Jira\\UTests\\": "tests/" 39 | } 40 | }, 41 | "config": { 42 | "preferred-install": "source" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /examples/create-new-issue.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | $Jira = \Badoo\Jira\REST\Client::instance(); 8 | $Jira->setJiraUrl('https://jira.localhost/'); 9 | $Jira->setAuth('user', 'password'); 10 | 11 | $Request = new \Badoo\Jira\Issue\CreateRequest('SMPL', 'Task'); 12 | $Request 13 | ->setSummary('Summary') 14 | ->setDescription('Description') 15 | ->setAssignee('username') 16 | ->addComponents('Component1', 'Component2', 12345) 17 | ->setDateField('customfield_10500', time()) 18 | ->setDateField('My custom field name', 'yesterday') 19 | ->setSecurityLevel(123) 20 | ->setLabels('label1', 'label2', 'label3') 21 | ->setFieldValue('My custom field name 2', 'some value here') 22 | ->setFieldValue('customfield_10200', ['select1', 'select2']) 23 | ->setFieldValue('another custom name', 'radio1'); 24 | 25 | // ... 26 | 27 | $Issue = $Request->send(); 28 | 29 | print_r( 30 | [ 31 | 'id' => $Issue->getId(), 32 | 'key' => $Issue->getKey(), 33 | 'summary' => $Issue->getSummary(), 34 | 'labels' => $Issue->getLabels(), 35 | // ... 36 | ] 37 | ); 38 | -------------------------------------------------------------------------------- /examples/jira-generator-config.yaml: -------------------------------------------------------------------------------- 1 | # Logging level for generator and CLI script loggers 2 | # Use PSR-3 compatible logging levels: 3 | # 'emergency' 4 | # 'alert' 5 | # 'critical' 6 | # 'error' 7 | # 'warning' 8 | # 'notice' 9 | # 'info' 10 | # 'debug' 11 | # 12 | # Defaults to 'warning' 13 | # 14 | #log-level: 'warning' 15 | 16 | # 17 | # JIRA configuration section. Server URL, API auth user and password and request timeouts. 18 | # Things utilities must know to just work. 19 | # 20 | #JIRA: 21 | # URL to your JIRA web interface root 22 | # 23 | # Defailts to https://jira.localhost/ 24 | # 25 | #URL: 'https://jira.localhost/' 26 | 27 | # Credentials file with text consisting of two fields separated with colon (':' symbol) 28 | # : 29 | # This file is read by CLI scripts to get JIRA user and password/token for interaction with API 30 | # 31 | # By default credentials file path is empty, script will not try to search for any files with creds. 32 | # 33 | #credentials-file: './jira-credentials' 34 | 35 | # User name to use in JIRA API requests. 36 | # This parameter has higher priority than data read from credentials file 37 | # 38 | # Defaults to 'automation' user 39 | # 40 | #user: 'automation' 41 | 42 | # Password or API token to use in JIRA API requests. 43 | # This parameter has higher priority than data read from credentials file 44 | # 45 | # Defaults to empty string 46 | # 47 | #password: 'very strong passsword' 48 | 49 | # API requsts timeout in seconds. If JIRA does not responds to CLI script's request longer than this amount of 50 | # seconds, scripts thinks JIRA is not available by the given URL 51 | # 52 | # Defaults to 60 seconds 53 | # 54 | #timeout: 60 55 | 56 | # 57 | # Generator config section. Controls the behaviuor of 'generate' command: what fields to skip, what additional templates 58 | # to use and so on. 59 | # 60 | #Generator: 61 | # Target directory. All generated classes will be saved to files within this directory 62 | # 63 | # Defaults to './' (current working dir) 64 | # 65 | #target-directory: './' 66 | 67 | # Generated map file. After each generation generator saves mappings of fields it treated and classes it generated. 68 | # This allows handle field renames and keep changes within the same class for the same field even when field was 69 | # renamed. 70 | # It also allows to prevent generator from corrupting hacky files, that are about custom fields but were written by 71 | # hand and should not be touched by generator. 72 | # 73 | # Defaults to '.generated-fields.map.json' within target directory 74 | # 75 | #map-file: '' 76 | 77 | # Target namespace for generated classes. All classes for JIRA custom fields will be within this PHP namespace 78 | # 79 | # Defaults to '' (global namespace) 80 | # 81 | #target-namespace: '' 82 | 83 | # Skip generation for this fields. 84 | # Fields with IDs from this list will be skipped by generator, if field has 'true' associated value. 85 | # 86 | # This option has highest priority: 87 | # it overrides 'skip-types' option 88 | # it overrides 'skip-type-patterns' option 89 | # 90 | # Example: 91 | # you have 2 fields: , and , . 92 | # if your 'skip-type' contains record, both fields will be skipped. 93 | # if you set 'skip-fields' for to 'false', it field will not be skipped, while - will. 94 | # 95 | # We recommend you to write textual field names in the comment to each field ID, to ease field identification 96 | # in the future 97 | # 98 | # By default this map is empty. 99 | # 100 | #skip-fields: 101 | #: # 102 | #: 103 | #<... other field IDs>: 104 | 105 | # Skip generation for this fields. 106 | # Fields of types from this list will be skipped by generator, if field has 'true' associated value. 107 | # 108 | # This option has 'middle' priority: 109 | # it can be overriden by 'skip-fields' option. 110 | # it overrides 'skip-type-patterns' option. 111 | # 112 | # By default this map is empty. 113 | # 114 | #skip-types: 115 | #: 116 | #: 117 | #<... other custom field type>: 118 | 119 | # Skip generation for this fields. 120 | # Fields of types that match any of regexs in list will be skipped by generator, if field has 'true' associated value. 121 | # This option has least possible priority 122 | # This can be overriden by 'skip-fields' or 'skip-types' option. 123 | # 124 | # By default this map is empty. 125 | # 126 | #skip-type-patterns: 127 | #: 128 | #: 129 | 130 | # Custom templates to load and bind to field IDs and types 131 | # All custom templates are treated by \Badoo\Jira\CFGenerator\SimpleTemplate class. If you want more - you have to use 132 | # \Badoo\Jira\CFGenerator\Generator class to configure generator manually and run it from your own PHP script. 133 | # 134 | # Generator API allows you to use any kind of template engine, you just need to implement simple ITemplate interface 135 | # 136 | #custom-templates: 137 | # Template name. Any string, that will help you identify the template if the error appear during generation. 138 | # 139 | #: 140 | # Path to template file. Relative paths are treated as relative to config file location 141 | # 142 | #path: 143 | 144 | # Ask template engine to load options for custom field before rendering. 145 | # When your field type has limited range of allowed values, template engine can request JIRA API for possible 146 | # suggestions for field values and pass them to the template. This is how Select Field works, for example. 147 | # If your field type has unlimited range of values, set this parameter to 'false' because it saves a lot of time 148 | # during generation. 149 | # 150 | #load-options: 151 | 152 | # List of fields that should be treated by this template 153 | # Fields are identified by their IDs. 154 | # Example: 155 | # fields: 156 | # - 'customfield_12345' 157 | # - 'customfield_10800' 158 | # 159 | #fields: 160 | 161 | # List of field types that should be treated by this template 162 | # You can get field type from custom field info from '/schema/custom' leaf. 163 | # To get custom field info with CLI use bin/find-field sctipt 164 | # Example: 165 | # types: 166 | # - 'com.atlassian.jira.plugin.system.customfieldtypes:importid' 167 | # - 'com.onresolve.jira.groovy.groovyrunner:hideable-textarea' 168 | # 169 | #types: 170 | 171 | #: 172 | -------------------------------------------------------------------------------- /examples/measure-load-time.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | $Jira = \Badoo\Jira\REST\Client::instance(); 8 | 9 | // Configure client here: 10 | $Jira 11 | ->setJiraUrl('https://jira.example.com/') 12 | ->setAuth('user', 'password'); 13 | 14 | $issue_keys = [ 15 | 'SMPL-1', 16 | 'SMPL-2', 17 | ]; 18 | 19 | function measure(\Badoo\Jira\REST\Client $Jira, string $issue_key, array $fields = []) 20 | { 21 | $iterations = 10; 22 | 23 | $time = 0; 24 | for ($i = 0; $i < $iterations; $i++) { 25 | $start = microtime(true); 26 | $Jira->issue()->get($issue_key, $fields); 27 | $time += microtime(true) - $start; 28 | } 29 | 30 | return $time/$iterations; 31 | } 32 | 33 | $times = []; 34 | 35 | foreach ($issue_keys as $issue_key) { 36 | // trigger JIRA's DB and engine to cache all data on issue for us 37 | // to make measurement 'fair' 38 | $Jira->issue()->get($issue_key); 39 | 40 | $times[$issue_key] = [ 41 | 'single' => measure($Jira, $issue_key, ['description']), 42 | 'all' => measure($Jira, $issue_key), 43 | ]; 44 | } 45 | 46 | echo json_encode($times, JSON_PRETTY_PRINT) . "\n"; 47 | -------------------------------------------------------------------------------- /src/CFGenerator/ITemplate.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CFGenerator; 8 | 9 | interface ITemplate 10 | { 11 | /** 12 | * Render class definition for field 13 | * 14 | * @param \stdClass $FieldInfo - JIRA custom field info as it is returned from JIRA API 15 | * @param string $full_class_name - name of field class with namespace. 16 | * 17 | * @return string - generated class text 18 | */ 19 | public function render(\stdClass $FieldInfo, string $full_class_name) : string; 20 | } 21 | -------------------------------------------------------------------------------- /src/CFGenerator/README.md: -------------------------------------------------------------------------------- 1 | # Badoo JIRA Custom fields classes generator 2 | 3 | Here is the generator, which can load list of custom fields from JIRA and create classes for them 4 | The main idea is to free yourself from handwriting bunch of similar code 5 | 6 | > NOTE: This section of documentation is for kind of advanced Generator usage. 7 | > There is a special CLI script for Generator, which makes its usage much more simple out of the box. 8 | > 9 | > If you just explore the possibilities of instruments in this repository, we recommend to start from CLI script first. 10 | > Check `bin/README.md` for more information. 11 | 12 | ## Howto's 13 | 14 | ### Generate single field class by regular rules 15 | 16 | Here is an example of code for single field class generation. 17 | 18 | ```php 19 | // Initizlize API client 20 | 21 | $Jira = new \Badoo\Jira\REST\Client; 22 | $Jira 23 | ->setJiraUrl('https://jira.example.com/') 24 | ->setAuth('user', 'token/password'); 25 | 26 | // Create instance of generator 27 | 28 | $Generator = new \Badoo\Jira\CFGenerator\Generator($Jira); 29 | 30 | // Set it up 31 | $Generator 32 | ->setTargetDirectory('your path here') 33 | ->setTargetNamespace('your namespace here'); 34 | 35 | // Prevent all fields from being generated: 36 | $Generator->skipTypePattern('/.*/'); 37 | 38 | // Allow only one we want 39 | $Generator->skipField('customfield_12345', false); 40 | 41 | $Generator->generateField('customfield_12345'); 42 | ``` 43 | 44 | > NOTE: generateField ignores all skip rules. If you tell generator "do exactly this field" - it does. 45 | 46 | ### Give the generator a custom template 47 | 48 | If you plan to use native PHP templates with the renderer provided with Generator out of the box: 49 | 50 | ```php 51 | // Template name is used only in error messages and allows to narrow the field of error when you face it 52 | $Template = new \Badoo\Jira\CFGenerator\SimpleTemplate('MyCustomTemplate', $Jira); 53 | 54 | // When field has unlimited values and JQL search line does not suggest anything for field in 55 | // JIRA WEB UI - this is just a waste of time. Think if you need it. 56 | $Template->setLoadOptions(true); // true if we want template to load suggestions for field. 57 | 58 | // Template file location 59 | $Template->setTemplatePath('path to template file'); 60 | 61 | // Map particular field to your template 62 | $Generator->mapFieldToTemplate('customfield_12345', $Template); 63 | 64 | // Map all fields of specific type to your template 65 | $Generator->mapTypeToTemplate('com.atlassian.jira.plugins...', $Template); 66 | 67 | // Now generator will use new template for configured 68 | //$Generator->generateField('customfield_12345'); 69 | //$Generator->generateAll(); 70 | 71 | ``` 72 | 73 | ### Use custom template engine 74 | 75 | Generator expects not a \Badoo\Jira\CFGenerator\SimpleTemplate class as a template. 76 | It expects \Badoo\Jira\CFGenerator\ITemplate 77 | 78 | Once you implemented it - you can map the field or field type to your very special template, 79 | written on awesome template engine you use. 80 | 81 | ```php 82 | namespace \Example; 83 | 84 | class SpecialTemplate implements ITemplate 85 | { 86 | public function render(\stdClass $FieldInfo, string $full_class_name) : string 87 | { 88 | // You must use provided $full_class_name for class name and namespace 89 | // for consistency of code you generate 90 | 91 | // Split full class path into namespace and class 92 | $class_parts = explode('\\', $full_class_name); 93 | $class_name = array_pop($class_parts); 94 | $namespace = implode('\\', $class_parts); 95 | 96 | // Load options for field if you want. You need a JIRA client inside your class for that: 97 | $this->Jira->jql()->getFieldSuggestions($FieldInfo->name) 98 | 99 | // Generate class for field 100 | // you now what to do. 101 | return ''; 102 | } 103 | } 104 | ``` 105 | 106 | ### Resolve class name collisions 107 | 108 | Generator uses `generated-fields.lock` file. That's no the feature of CLI utility, it is built in generator code. 109 | 110 | Use the recipe from `bin/README.md` to solve the problem. This solution will make generator to provide custom 111 | class name to `$full_field_class_name` argument of `ITemplate->render()` method. 112 | 113 | ### Use custom logger 114 | 115 | Generator uses PSR-3 logger inside. This means you can provide it any logger that is embedded into your 116 | infrastructure to track what is happening. 117 | 118 | ```php 119 | $Logger = ; 120 | 121 | $Generator->setLogger($Logger); 122 | ``` 123 | 124 | ### Generate single field class from custom template 125 | 126 | ```php 127 | $Generator = 128 | 129 | $Template = ; 130 | $FieldInfo = $Jira->fields()->get('customfield_10500'); 131 | $class_name = 'IWantToUseSpecialClassForIt'; 132 | 133 | $Generator->generateField($FieldInfo, $Template, $class_name); 134 | ``` 135 | -------------------------------------------------------------------------------- /src/CFGenerator/SimpleLogger.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CFGenerator; 8 | 9 | class SimpleLogger extends \Psr\Log\AbstractLogger 10 | { 11 | const LEVELS_MAP = [ 12 | \Psr\Log\LogLevel::EMERGENCY => 0, 13 | \Psr\Log\LogLevel::ALERT => 1, 14 | \Psr\Log\LogLevel::CRITICAL => 2, 15 | \Psr\Log\LogLevel::ERROR => 3, 16 | \Psr\Log\LogLevel::WARNING => 4, 17 | \Psr\Log\LogLevel::NOTICE => 5, 18 | \Psr\Log\LogLevel::INFO => 6, 19 | \Psr\Log\LogLevel::DEBUG => 7, 20 | ]; 21 | 22 | protected $level_threshold = 6; 23 | 24 | public function log($level, $message, array $context = []) 25 | { 26 | $level_index = self::LEVELS_MAP[$level] ?? null; 27 | 28 | if (!isset($level_index)) { 29 | throw new \Psr\Log\InvalidArgumentException( 30 | "Unknown message level {$level}. Use PSR-3 compatible logging level names" 31 | ); 32 | } 33 | 34 | if ($level_index > $this->level_threshold) { 35 | return; 36 | } 37 | 38 | $search = []; 39 | $replace = []; 40 | foreach ($context as $key => $value) { 41 | $search[] = "{{$key}}"; 42 | $replace[] = $value; 43 | } 44 | 45 | $message = str_replace($search, $replace, $message); 46 | 47 | echo "[{$level}]: {$message}\n"; 48 | } 49 | 50 | public function setLevelThreshold(string $level_threshold) : SimpleLogger 51 | { 52 | if (!isset(self::LEVELS_MAP[$level_threshold])) { 53 | throw new \Psr\Log\InvalidArgumentException( 54 | "Unknown log message level {$level_threshold}. Use PSR-3 compatible logging level names" 55 | ); 56 | } 57 | 58 | $this->level_threshold = self::LEVELS_MAP[$level_threshold]; 59 | return $this; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/CFGenerator/SimpleTemplate.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CFGenerator; 8 | 9 | class SimpleTemplate implements ITemplate 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var string */ 15 | protected $name; 16 | protected $load_options = false; 17 | 18 | /** @var string */ 19 | protected $template_path; 20 | 21 | public function __construct(string $name, \Badoo\Jira\REST\Client $Jira = null) 22 | { 23 | if (!isset($Jira)) { 24 | $Jira = \Badoo\Jira\REST\Client::instance(); 25 | } 26 | 27 | $this->name = $name; 28 | $this->Jira = $Jira; 29 | } 30 | 31 | protected function quoteOptionValue(string $value) : string 32 | { 33 | // drop quotes: '"Value\\With\\'slashes"' => 'Value\\With\\'slashes' 34 | $value = trim($value, '"'); 35 | 36 | // drop backslashes (just to look nicer) 'Value\\With\\'slashes' => 'Value\With\'slashes' 37 | $value = str_replace("\\\\", "\\", $value); 38 | 39 | // escape single quotes: 'Value\With\'slashes' => 'Value\With\\'slashes' 40 | $value = str_replace("'", "\'", $value); 41 | 42 | // handle edge case of initial value: backslash followed by single qoute 43 | $value = str_replace("\\\\'", "\\\\\'", $value); 44 | 45 | return $value; 46 | } 47 | 48 | public function setTemplatePath(string $path) : SimpleTemplate 49 | { 50 | $this->template_path = $path; 51 | 52 | if (!\Badoo\Jira\Helpers\Files::exists($path)) { 53 | trigger_error( 54 | "Template file '{$path}' not found for template '{$this->name}'", 55 | E_USER_WARNING 56 | ); 57 | } 58 | 59 | return $this; 60 | } 61 | 62 | public function setLoadOptions(bool $load_options = true) : SimpleTemplate 63 | { 64 | $this->load_options = $load_options; 65 | return $this; 66 | } 67 | 68 | public function render(\stdClass $FieldInfo, string $full_class_name) : string 69 | { 70 | $options = []; 71 | if ($this->load_options) { 72 | foreach ($this->Jira->jql()->getFieldSuggestions($FieldInfo->name) as $OptionInfo) { 73 | $capital_name = \Badoo\Jira\Helpers\Strings::toCapitalPHPLabel($OptionInfo->value); 74 | 75 | $options[$OptionInfo->value] = [ 76 | 'capital_name' => $capital_name, 77 | 'camelcase_name' => \Badoo\Jira\Helpers\Strings::toCamelCasePHPLabel($OptionInfo->value), 78 | 'const_name' => 'VALUE_' . ltrim($capital_name, '_'), 79 | 'value' => $this->quoteOptionValue($OptionInfo->value), 80 | ]; 81 | } 82 | } 83 | 84 | $class_parts = explode('\\', $full_class_name); 85 | $class_name = array_pop($class_parts); 86 | $namespace = implode('\\', $class_parts); 87 | 88 | try { 89 | return $this->renderTemplate( 90 | $this->template_path, 91 | [ 92 | 'namespace' => $namespace, 93 | 'class_name' => $class_name, 94 | 'field_id' => $FieldInfo->id, 95 | 'field_name' => $FieldInfo->name, 96 | 'options' => $options, 97 | ] 98 | ); 99 | } catch (\Throwable $e) { 100 | throw new \RuntimeException( 101 | "Failed to render template {$this->name} for field {$FieldInfo->id}: {$e->getMessage()}", 102 | 0, 103 | $e 104 | ); 105 | } 106 | } 107 | 108 | protected function renderTemplate(string $template_path, array $params) 109 | { 110 | ob_start(); 111 | 112 | extract($params); 113 | /** @noinspection PhpIncludeInspection */ 114 | include $template_path; 115 | 116 | return ob_get_clean(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/CFGenerator/templates/CheckboxField.tpl: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This is a generated wrapper class for JIRA custom field '' 4 | */ 5 | 10 | 11 | class extends \Badoo\Jira\CustomFields\CheckboxField 12 | { 13 | const ID = ''; 14 | const NAME = ''; 15 | 16 | /* Available field values. */ 17 | 18 | const = ''; 19 | 20 | 21 | const VALUES = [ 22 | 23 | self::, 24 | 25 | ]; 26 | 27 | public function getCheckboxesList() : array 28 | { 29 | return static::VALUES; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/CFGenerator/templates/NumberField.tpl: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This is a generated wrapper class for JIRA custom field '' 4 | */ 5 | 10 | 11 | class extends \Badoo\Jira\CustomFields\NumberField 12 | { 13 | const ID = ''; 14 | const NAME = ''; 15 | } 16 | -------------------------------------------------------------------------------- /src/CFGenerator/templates/RadioField.tpl: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This is a generated wrapper class for JIRA custom field '' 4 | */ 5 | 10 | 11 | class extends \Badoo\Jira\CustomFields\RadioField 12 | { 13 | const ID = ''; 14 | const NAME = ''; 15 | 16 | /* Available field values. */ 17 | 18 | const = ''; 19 | 20 | 21 | const VALUES = [ 22 | 23 | self::, 24 | 25 | ]; 26 | 27 | public function getItemsList() : array 28 | { 29 | return static::VALUES; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/CFGenerator/templates/SelectField.tpl: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This is a generated wrapper class for JIRA custom field '' 4 | */ 5 | 10 | 11 | class extends \Badoo\Jira\CustomFields\SelectField 12 | { 13 | const ID = ''; 14 | const NAME = ''; 15 | 16 | /* Available field values. */ 17 | 18 | const = ''; 19 | 20 | 21 | const VALUES = [ 22 | 23 | self::, 24 | 25 | ]; 26 | 27 | public function getItemsList() : array 28 | { 29 | return static::VALUES; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/CFGenerator/templates/SingleSelectField.tpl: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This is a generated wrapper class for JIRA custom field '' 4 | */ 5 | 10 | 11 | class extends \Badoo\Jira\CustomFields\SingleSelectField 12 | { 13 | const ID = ''; 14 | const NAME = ''; 15 | 16 | /* Available field values. */ 17 | 18 | const = ''; 19 | 20 | 21 | const VALUES = [ 22 | 23 | self::, 24 | 25 | ]; 26 | 27 | public function getItemsList() : array 28 | { 29 | return static::VALUES; 30 | } 31 | 36 | 37 | public function isYes() : bool 38 | { 39 | return $this->getValue() === self::; 40 | } 41 | 42 | 46 | 47 | public function isNo() : bool 48 | { 49 | return $this->getValue() === self::; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/CFGenerator/templates/SingleUserField.tpl: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This is a generated wrapper class for JIRA custom field '' 4 | */ 5 | 10 | 11 | class extends \Badoo\Jira\CustomFields\SingleUserField 12 | { 13 | const ID = ''; 14 | const NAME = ''; 15 | } 16 | -------------------------------------------------------------------------------- /src/CFGenerator/templates/TextField.tpl: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This is a generated wrapper class for JIRA custom field '' 4 | */ 5 | 10 | 11 | class extends \Badoo\Jira\CustomFields\TextField 12 | { 13 | const ID = ''; 14 | const NAME = ''; 15 | } 16 | -------------------------------------------------------------------------------- /src/CFGenerator/templates/UserField.tpl: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * This is a generated wrapper class for JIRA custom field '' 4 | */ 5 | 10 | 11 | class extends \Badoo\Jira\CustomFields\UserField 12 | { 13 | const ID = ''; 14 | const NAME = ''; 15 | } 16 | -------------------------------------------------------------------------------- /src/CFGenerator/templates/_template-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "com.atlassian.jira.plugin.system.customfieldtypes:textfield": { 3 | "name": "TextField" 4 | }, 5 | "com.atlassian.jira.plugin.system.customfieldtypes:textarea": { 6 | "name": "TextField" 7 | }, 8 | "com.atlassian.jira.plugin.system.customfieldtypes:float": { 9 | "name": "NumberField" 10 | }, 11 | "com.atlassian.jira.plugin.system.customfieldtypes:userpicker": { 12 | "name": "SingleUserField" 13 | }, 14 | "com.atlassian.jira.plugin.system.customfieldtypes:multiuserpicker": { 15 | "name": "UserField", 16 | "load-options": true 17 | }, 18 | "com.atlassian.jira.plugin.system.customfieldtypes:select": { 19 | "name": "SingleSelectField", 20 | "load-options": true 21 | }, 22 | "com.atlassian.jira.plugin.system.customfieldtypes:radiobuttons": { 23 | "name": "RadioField", 24 | "load-options": true 25 | }, 26 | "com.atlassian.jira.plugin.system.customfieldtypes:multiselect": { 27 | "name": "SelectField", 28 | "load-options": true 29 | }, 30 | "com.atlassian.jira.plugin.system.customfieldtypes:multicheckboxes": { 31 | "name": "CheckboxField", 32 | "load-options": true 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/CLI/ClimateLogger.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CLI; 8 | 9 | class ClimateLogger extends \Psr\Log\AbstractLogger 10 | { 11 | const LEVELS_MAP = [ 12 | \Psr\Log\LogLevel::EMERGENCY => 0, 13 | \Psr\Log\LogLevel::ALERT => 1, 14 | \Psr\Log\LogLevel::CRITICAL => 2, 15 | \Psr\Log\LogLevel::ERROR => 3, 16 | \Psr\Log\LogLevel::WARNING => 4, 17 | \Psr\Log\LogLevel::NOTICE => 5, 18 | \Psr\Log\LogLevel::INFO => 6, 19 | \Psr\Log\LogLevel::DEBUG => 7, 20 | ]; 21 | 22 | const LEVEL_COLOR_MAP = [ 23 | \Psr\Log\LogLevel::EMERGENCY => 'background_red', 24 | \Psr\Log\LogLevel::ALERT => 'red', 25 | \Psr\Log\LogLevel::CRITICAL => 'light_red', 26 | \Psr\Log\LogLevel::ERROR => 'light_red', 27 | \Psr\Log\LogLevel::WARNING => 'light_yellow', 28 | \Psr\Log\LogLevel::NOTICE => 'yellow', 29 | \Psr\Log\LogLevel::INFO => 'green', 30 | \Psr\Log\LogLevel::DEBUG => 'light_blue', 31 | ]; 32 | 33 | /** @var \League\CLImate\CLImate */ 34 | protected $Climate; 35 | 36 | protected $level_threshold = 4; 37 | 38 | public function __construct(\League\CLImate\CLImate $Climate) 39 | { 40 | $this->Climate = $Climate; 41 | } 42 | 43 | protected function getLevelIndex(string $level) : int 44 | { 45 | $level_index = self::LEVELS_MAP[$level] ?? null; 46 | 47 | if (!isset($level_index)) { 48 | throw new \Psr\Log\InvalidArgumentException( 49 | "Unknown message level {$level}. Use PSR-3 compatible logging level names" 50 | ); 51 | } 52 | 53 | return $level_index; 54 | } 55 | 56 | public function setLevelThreshold(string $level_threshold) : ClimateLogger 57 | { 58 | $this->level_threshold = $this->getLevelIndex($level_threshold); 59 | return $this; 60 | } 61 | 62 | public function log($level, $message, array $context = []) 63 | { 64 | $level_index = $this->getLevelIndex($level); 65 | 66 | if ($level_index > $this->level_threshold) { 67 | return; 68 | } 69 | 70 | $search = []; 71 | $replace = []; 72 | foreach ($context as $key => $value) { 73 | $search[] = "{{$key}}"; 74 | $replace[] = $value; 75 | } 76 | 77 | $message = str_replace($search, $replace, $message); 78 | 79 | $color = self::LEVEL_COLOR_MAP[$level] ?? 'white'; 80 | $level = strtoupper($level); 81 | 82 | $this->Climate->to('error')->out("<$color>[{$level}]: {$message}"); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/CLI/Configuration.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CLI; 8 | 9 | class Configuration 10 | { 11 | const DEFAULT_CONFIG_FILE = './jira-generator-config.yaml'; 12 | 13 | /** @var array */ 14 | protected $Config = []; 15 | protected $config_file_path = self::DEFAULT_CONFIG_FILE; 16 | 17 | protected function readCredentials(string $file_path) : array 18 | { 19 | $creds = \Badoo\Jira\Helpers\Files::fileGetContents($file_path); 20 | $creds = rtrim($creds, "\n"); // drop newlines after password 21 | 22 | list($user, $password) = explode(':', $creds, 2); 23 | return [$user, $password]; 24 | } 25 | 26 | public function resolvePath(string ...$path_parts) : string 27 | { 28 | $path_parts = array_filter($path_parts); // drop empty parts 29 | $path = implode(DIRECTORY_SEPARATOR, $path_parts); // join into single path 30 | 31 | if (empty($path)) { 32 | return $this->getConfigFileDir(); 33 | } 34 | 35 | if ($path[0] === DIRECTORY_SEPARATOR) { 36 | // Absolute path 37 | return $path; 38 | } 39 | 40 | // relative non-empty path 41 | return $this->getConfigFileDir() . DIRECTORY_SEPARATOR . $path; 42 | } 43 | 44 | public function load(string $config_path = self::DEFAULT_CONFIG_FILE) 45 | { 46 | $this->config_file_path = realpath($config_path); 47 | $this->Config = \Symfony\Component\Yaml\Yaml::parseFile($config_path); 48 | } 49 | 50 | public function getConfigFilePath() : string 51 | { 52 | return $this->config_file_path; 53 | } 54 | 55 | public function getConfigFileDir() : string 56 | { 57 | return dirname($this->config_file_path); 58 | } 59 | 60 | public function setLogLevel(string $log_level) : Configuration 61 | { 62 | $this->Config['log-level'] = $log_level; 63 | return $this; 64 | } 65 | 66 | public function getLogLevel() 67 | { 68 | return $this->Config['log-level'] ?? \Psr\Log\LogLevel::WARNING; 69 | } 70 | 71 | public function setJiraUrl(string $jira_url) : Configuration 72 | { 73 | $this->Config['JIRA']['URL'] = $jira_url; 74 | return $this; 75 | } 76 | 77 | public function getJiraURL() : string 78 | { 79 | return $this->Config['JIRA']['URL'] ?? 'https://jira.localhost/'; 80 | } 81 | 82 | public function setJiraCredentialsFile(string $jira_credentials_file) : Configuration 83 | { 84 | $this->Config['JIRA']['credentials-file'] = $jira_credentials_file; 85 | return $this; 86 | } 87 | 88 | public function setJiraUser(string $jira_user) : Configuration 89 | { 90 | $this->Config['JIRA']['user'] = $jira_user; 91 | return $this; 92 | } 93 | 94 | public function getJiraUser() : string 95 | { 96 | if (isset($this->Config['JIRA']['user'])) { 97 | return $this->Config['JIRA']['user']; 98 | } 99 | 100 | if (isset($this->Config['JIRA']['credentials-file'])) { 101 | return $this->readCredentials($this->Config['JIRA']['credentials-file'])[0]; 102 | } 103 | 104 | return 'automation'; 105 | } 106 | 107 | public function setJiraPassword(string $jira_password) : Configuration 108 | { 109 | $this->Config['JIRA']['password'] = $jira_password; 110 | return $this; 111 | } 112 | 113 | public function getJiraPassword() : string 114 | { 115 | if (isset($this->Config['JIRA']['password'])) { 116 | return $this->Config['JIRA']['password']; 117 | } 118 | 119 | if (isset($this->Config['JIRA']['credentials-file'])) { 120 | return $this->readCredentials($this->Config['JIRA']['credentials-file'])[1]; 121 | } 122 | 123 | return ''; 124 | } 125 | 126 | public function setJiraTimeout(int $jira_timeout) : Configuration 127 | { 128 | $this->Config['JIRA']['timeout'] = $jira_timeout; 129 | return $this; 130 | } 131 | 132 | public function getJiraTimeout() : string 133 | { 134 | return $this->Config['JIRA']['timeout'] ?? 60; 135 | } 136 | 137 | public function getJiraClient() : \Badoo\Jira\REST\Client 138 | { 139 | $Jira = new \Badoo\Jira\REST\Client($this->getJiraURL()); 140 | $Jira->getRawClient() 141 | ->setAuth( 142 | $this->getJiraUser(), 143 | $this->getJiraPassword() 144 | ) 145 | ->setRequestTimeout( 146 | $this->getJiraTimeout() 147 | ); 148 | 149 | return $Jira; 150 | } 151 | 152 | public function setGeneratorTargetDirectory(string $path) : Configuration 153 | { 154 | $this->Config['Generator']['target-directory'] = $path; 155 | return $this; 156 | } 157 | 158 | public function getGeneratorTargetDirectory() : string 159 | { 160 | return $this->resolvePath($this->Config['Generator']['target-directory'] ?? ''); 161 | } 162 | 163 | public function setGeneratorMapFile(string $path) : Configuration 164 | { 165 | $this->Config['Generator']['map-file'] = $path; 166 | return $this; 167 | } 168 | 169 | public function getGeneratorMapFile() : string 170 | { 171 | $path = $this->Config['Generator']['map-file'] ?? ''; 172 | if (empty($path)) { 173 | return ''; // let Generator decide whan name should be default 174 | } 175 | 176 | return $this->resolvePath($path); 177 | } 178 | 179 | public function setGeneratorTargetNamespace(string $path) : Configuration 180 | { 181 | $this->Config['Generator']['target-namespace'] = $path; 182 | return $this; 183 | } 184 | 185 | public function getGeneratorTargetNamespace() : string 186 | { 187 | return $this->Config['Generator']['target-namespace'] ?? '.'; 188 | } 189 | 190 | public function getGeneratorCustomTemplates() : array 191 | { 192 | return $this->Config['Generator']['custom-templates'] ?? []; 193 | } 194 | 195 | /** 196 | * @return bool[] 197 | */ 198 | public function getGeneratorSkipFields() : array 199 | { 200 | return $this->Config['Generator']['skip-fields'] ?? []; 201 | } 202 | 203 | /** 204 | * @return bool[] 205 | */ 206 | public function getGeneratorSkipTypes() : array 207 | { 208 | return $this->Config['Generator']['skip-types'] ?? []; 209 | } 210 | 211 | /** 212 | * @return bool[] 213 | */ 214 | public function getGeneratorSkipTypePatterns() : array 215 | { 216 | return $this->Config['Generator']['skip-type-patterns'] ?? []; 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/CustomFields/CheckboxField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CustomFields; 8 | 9 | /** 10 | * Class CheckboxField 11 | * @package Badoo\Jira\CustomFields\Abstracts 12 | * 13 | * Wrapper class for 'checkbox' type custom field with several checkboxes 14 | */ 15 | abstract class CheckboxField extends CustomField 16 | { 17 | /** @var bool[] - checkbox states, indexed by checkbox names */ 18 | protected $value; 19 | 20 | /** @var bool[] - new checkbox states */ 21 | protected $update; 22 | 23 | /** @return string[] - list of checkboxes available for this field. */ 24 | abstract public function getCheckboxesList() : array; 25 | 26 | public function dropCache() 27 | { 28 | $this->value = null; 29 | $this->update = null; 30 | 31 | return parent::dropCache(); 32 | } 33 | 34 | /** 35 | * @return bool[] - list of all checkboxes and their states (checked or not) 36 | * 37 | * @throws \Badoo\Jira\REST\Exception 38 | */ 39 | protected function getCheckboxesState() 40 | { 41 | if (!isset($this->value)) { 42 | $this->value = []; 43 | 44 | $Field = $this->getOriginalObject(); 45 | 46 | foreach ($this->getCheckboxesList() as $checkbox_name) { 47 | $this->value[$checkbox_name] = false; 48 | } 49 | 50 | foreach ((array)$Field as $CheckboxInfo) { 51 | /** @var \stdClass $CheckboxInfo */ 52 | $this->value[$CheckboxInfo->value] = true; 53 | } 54 | } 55 | 56 | return $this->value; 57 | } 58 | 59 | /** 60 | * @return string[] - list of names of checked items 61 | * 62 | * @throws \Badoo\Jira\REST\Exception 63 | */ 64 | public function getValue() 65 | { 66 | return array_keys(array_filter($this->getCheckboxesState())); 67 | } 68 | 69 | /** 70 | * @param string[] $value - list of names for checkboxes we want to mark 'checked' 71 | * @return array 72 | */ 73 | public static function generateSetter($value) : array 74 | { 75 | $items_to_check = []; 76 | foreach ($value as $checkbox_name) { 77 | $items_to_check[] = ['value' => $checkbox_name]; 78 | } 79 | 80 | return [ ['set' => $items_to_check] ]; 81 | } 82 | 83 | /** 84 | * @param string[] $value - list of checked boxes. 85 | * @return $this 86 | */ 87 | public function setValue($value) 88 | { 89 | $this->update = []; 90 | foreach ((array)$value as $checkbox) { 91 | $this->update[$checkbox] = true; 92 | } 93 | 94 | $update = static::generateSetter($value); 95 | $this->Issue->edit($this->getID(), $update); 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * @param string $checkbox_name 102 | * @param bool $checked_state - true: 'check' item, 103 | * false: 'uncheck' it. 104 | * 105 | * @return $this 106 | * 107 | * @throws \Badoo\Jira\Exception\CustomField 108 | * @throws \Badoo\Jira\REST\Exception 109 | */ 110 | public function checkItem(string $checkbox_name, bool $checked_state = true) 111 | { 112 | if (!in_array($checkbox_name, $this->getCheckboxesList())) { 113 | throw new \Badoo\Jira\Exception\CustomField( 114 | "Can't change state of unknown checkbox '{$checkbox_name}'. " 115 | . "Available checkboxes for field '{$this->getName()}' are: '" 116 | . implode("', '", $this->getCheckboxesList()) . "'\n" 117 | ); 118 | } 119 | 120 | if (!isset($this->update)) { 121 | $this->update = $this->getCheckboxesState(); 122 | } 123 | $this->update[$checkbox_name] = $checked_state; 124 | 125 | $update = static::generateSetter(array_keys(array_filter($this->update))); 126 | $this->Issue->edit($this->getID(), $update); 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * @param string $checkbox 133 | * @return bool 134 | * 135 | * @throws \Badoo\Jira\REST\Exception 136 | */ 137 | public function isChecked(string $checkbox) : bool 138 | { 139 | return $this->getCheckboxesState()[$checkbox] ?? false; 140 | } 141 | 142 | /** 143 | * @param bool $checked_state - true: check all boxes, 144 | * false: uncheck all boxes. 145 | */ 146 | public function checkAll($checked_state = true) 147 | { 148 | if ($checked_state) { 149 | $this->setValue($this->getCheckboxesList()); 150 | } else { 151 | $this->setValue([]); 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/CustomFields/LabelField.php: -------------------------------------------------------------------------------- 1 | getOriginalObject(); 18 | } 19 | 20 | /** 21 | * @inheritDoc 22 | * @param $value array|null 23 | */ 24 | public static function generateSetter($value): array 25 | { 26 | if (!\is_array($value)) { 27 | $value = [$value]; 28 | } 29 | return [['set' => $value]]; 30 | } 31 | 32 | /** 33 | * @param string $label 34 | * @return $this 35 | */ 36 | public function addLabel(string $label): self 37 | { 38 | $this->getIssue()->edit($this->getID(), [['add' => $label]]); 39 | return $this; 40 | } 41 | 42 | /** 43 | * @param array $labels 44 | * @return $this 45 | */ 46 | public function addLabels(array $labels): self 47 | { 48 | $updates = []; 49 | foreach ($labels as $label) { 50 | $updates[] = ['add' => $label]; 51 | } 52 | $this->getIssue()->edit($this->getID(), $updates); 53 | return $this; 54 | } 55 | 56 | /** 57 | * @param array $labels 58 | * @return $this 59 | */ 60 | public function setLabels(array $labels): self 61 | { 62 | $this->getIssue()->edit($this->getID(), self::generateSetter(\array_filter(\array_unique($labels)))); 63 | return $this; 64 | } 65 | } -------------------------------------------------------------------------------- /src/CustomFields/NumberField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CustomFields; 8 | 9 | /** 10 | * Class NumberField 11 | * @package Badoo\Jira\CustomFields\Abstracts 12 | * 13 | * Wrapper class for 'number' type custom field 14 | */ 15 | abstract class NumberField extends CustomField 16 | { 17 | /** 18 | * @return float 19 | * 20 | * @throws \Badoo\Jira\REST\Exception 21 | */ 22 | public function getValue() : float 23 | { 24 | return (float)$this->getOriginalObject(); 25 | } 26 | 27 | /** 28 | * @param float|null $value 29 | * @return array 30 | */ 31 | public static function generateSetter($value) : array 32 | { 33 | if ($value !== null) { 34 | $value = (float)$value; 35 | } 36 | 37 | return [ ['set' => $value] ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/CustomFields/RadioField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CustomFields; 8 | 9 | /** 10 | * Class RadioButtons 11 | * @package Badoo\Jira\CustomFields\Abstracts 12 | * 13 | * Wrapper class for 'radio' type custom firld 14 | * 15 | * Actually I can't find any difference between RadioButtons and SingleSelectField in terms of interaction with API 16 | */ 17 | abstract class RadioField extends SingleSelectField 18 | { 19 | } 20 | -------------------------------------------------------------------------------- /src/CustomFields/SelectField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CustomFields; 8 | 9 | /** 10 | * Class SelectField 11 | * @package Badoo\Jira\CustomFields\Abstracts 12 | * 13 | * Wrapper class for 'select list' type custom field with several items 14 | */ 15 | abstract class SelectField extends CustomField 16 | { 17 | /** @var bool[] - current items state. Item names are used as keys. 18 | * True: item is selected. 19 | False: item is not selected. */ 20 | protected $value; 21 | 22 | /** @var bool[] - new state to be set on ->save() call */ 23 | protected $update; 24 | 25 | /** @return string[] - list of items available for this field. */ 26 | abstract public function getItemsList() : array; 27 | 28 | public function dropCache() 29 | { 30 | $this->value = null; 31 | $this->update = null; 32 | 33 | return parent::dropCache(); 34 | } 35 | 36 | /** 37 | * @return bool[] - list of all items in select list with their state (selected or not) 38 | * 39 | * @throws \Badoo\Jira\REST\Exception 40 | */ 41 | protected function getItemsState() 42 | { 43 | if (!isset($this->value)) { 44 | $this->value = []; 45 | 46 | $Field = $this->getOriginalObject(); 47 | 48 | foreach ($this->getItemsList() as $item_name) { 49 | $this->value[$item_name] = false; 50 | } 51 | 52 | foreach ((array)$Field as $SelectItem) { 53 | /** @var \stdClass $SelectItem */ 54 | $this->value[$SelectItem->value] = true; 55 | } 56 | } 57 | 58 | return $this->value; 59 | } 60 | 61 | /** 62 | * @return string[] - list of names of selected items 63 | * 64 | * @throws \Badoo\Jira\REST\Exception 65 | */ 66 | public function getValue() : array 67 | { 68 | return array_keys(array_filter($this->getItemsState())); 69 | } 70 | 71 | /** 72 | * @param string[] $value - list of names for items we want to mark 'selected' 73 | * @return array 74 | */ 75 | public static function generateSetter($value) : array 76 | { 77 | $items_to_select = []; 78 | foreach ($value as $item_name) { 79 | $items_to_select[] = ['value' => $item_name]; 80 | } 81 | 82 | return [ ['set' => $items_to_select] ]; 83 | } 84 | 85 | 86 | /** 87 | * @param string[] $value - list of names of selected items. 88 | * @return $this 89 | */ 90 | public function setValue($value) 91 | { 92 | $this->update = []; 93 | foreach ((array)$value as $item) { 94 | $this->update[$item] = true; 95 | } 96 | 97 | $update = static::generateSetter($value); 98 | $this->Issue->edit($this->getID(), $update); 99 | 100 | return $this; 101 | } 102 | 103 | /** 104 | * @param string $item_name 105 | * @param bool $selected_state - true: select item, 106 | * false: deselect it. 107 | * 108 | * @return $this 109 | * 110 | * @throws \Badoo\Jira\Exception\CustomField 111 | * @throws \Badoo\Jira\REST\Exception 112 | */ 113 | public function selectItem(string $item_name, bool $selected_state = true) 114 | { 115 | if (!in_array($item_name, $this->getItemsList())) { 116 | throw new \Badoo\Jira\Exception\CustomField( 117 | "Can't change state of unknown item '{$item_name}'. " 118 | . "Available items for field '{$this->getName()}' are: '" 119 | . implode("', '", $this->getItemsList()) . "'\n" 120 | ); 121 | } 122 | 123 | if (!isset($this->update)) { 124 | $this->update = $this->getItemsState(); 125 | } 126 | $this->update[$item_name] = $selected_state; 127 | 128 | $update = static::generateSetter(array_keys(array_filter($this->update))); 129 | $this->Issue->edit($this->getID(), $update); 130 | 131 | return $this; 132 | } 133 | 134 | /** 135 | * @param string $item 136 | * @return bool 137 | * 138 | * @throws \Badoo\Jira\REST\Exception 139 | */ 140 | public function isSelected(string $item) : bool 141 | { 142 | return $this->getItemsState()[$item] ?? false; 143 | } 144 | 145 | /** 146 | * @param bool $selected_state - true: select all items, 147 | * false: deselect all items. 148 | */ 149 | public function selectAll($selected_state = true) 150 | { 151 | if ($selected_state) { 152 | $this->setValue($this->getItemsList()); 153 | } else { 154 | $this->setValue([]); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/CustomFields/SingleCheckboxField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | namespace Badoo\Jira\CustomFields; 7 | 8 | /** 9 | * Class SingleCheckboxField 10 | * @package Badoo\Jira\CustomFields\Abstracts 11 | * 12 | * Wrapper class for 'checkbox' type custom field with single choice 13 | */ 14 | abstract class SingleCheckboxField extends CheckboxField 15 | { 16 | /** 17 | * @param bool $checked_state - true: 'check' item, 18 | * false: 'uncheck' it 19 | * @return $this 20 | */ 21 | public function setChecked($checked_state = true) 22 | { 23 | $this->checkAll($checked_state); 24 | return $this; 25 | } 26 | 27 | public function isChecked(string $checkbox = '') : bool 28 | { 29 | return !$this->isEmpty(); 30 | } 31 | 32 | /** 33 | * @return string - name of checked item, if any 34 | * 35 | * @throws \Badoo\Jira\REST\Exception 36 | */ 37 | public function getValue() 38 | { 39 | $checked = parent::getValue(); 40 | return reset($checked) ?: ''; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/CustomFields/SingleSelectField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CustomFields; 8 | 9 | /** 10 | * Class SingleSelectField 11 | * @package Badoo\Jira\CustomFields\Abstracts 12 | * 13 | * Wrapper class for 'single select' type custom field 14 | */ 15 | abstract class SingleSelectField extends CustomField 16 | { 17 | /** @var string */ 18 | protected $value; 19 | 20 | /** @return string[] - list of items available for this field. */ 21 | abstract public function getItemsList() : array; 22 | 23 | /** 24 | * @return string 25 | * 26 | * @throws \Badoo\Jira\REST\Exception 27 | */ 28 | public function getValue() : string 29 | { 30 | if ($this->getOriginalObject() === null) { 31 | return ''; 32 | } 33 | 34 | return $this->getOriginalObject()->value; 35 | } 36 | 37 | /** 38 | * @param string $value 39 | * @return array 40 | */ 41 | public static function generateSetter($value) : array 42 | { 43 | if ($value !== null) { 44 | $value = ['value' => $value]; 45 | } 46 | 47 | return [ [ 'set' => $value ] ]; 48 | } 49 | 50 | /** 51 | * @param string $value 52 | * @return $this 53 | * 54 | * @throws \Badoo\Jira\Exception\CustomField 55 | */ 56 | public function setValue($value) 57 | { 58 | if (isset($value) && !in_array($value, $this->getItemsList())) { 59 | throw new \Badoo\Jira\Exception\CustomField( 60 | "Can't select '{$value}' item. " 61 | . "Available items for field '{$this->getName()}' are: '" 62 | . implode("', '", $this->getItemsList()) . "'\n" 63 | ); 64 | } 65 | 66 | parent::setValue($value); 67 | 68 | return $this; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/CustomFields/SingleUserField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CustomFields; 8 | 9 | /** 10 | * Class SingleUserField 11 | * @package Badoo\Jira\CustomFields\Abstracts 12 | * 13 | * Wrapper class for 'user picker' type custom field 14 | */ 15 | abstract class SingleUserField extends CustomField 16 | { 17 | /** @var \Badoo\Jira\User */ 18 | protected $User; 19 | 20 | public function dropCache() 21 | { 22 | $this->User = null; 23 | return parent::dropCache(); 24 | } 25 | 26 | /** 27 | * @return \Badoo\Jira\User|null 28 | * 29 | * @throws \Badoo\Jira\REST\Exception 30 | */ 31 | public function getValue() : ?\Badoo\Jira\User 32 | { 33 | if ($this->isEmpty()) { 34 | return null; 35 | } 36 | 37 | if (!isset($this->User)) { 38 | $UserInfo = $this->getOriginalObject(); 39 | $this->User = \Badoo\Jira\User::fromStdClass($UserInfo, $this->Issue, $this->Issue->getJira()); 40 | } 41 | 42 | return $this->User; 43 | } 44 | 45 | /** 46 | * @param string|null $user - name of user 47 | * @return array 48 | */ 49 | public static function generateSetter($user) : array 50 | { 51 | if (!isset($user)) { 52 | return [ [ 'set' => null ] ]; 53 | } 54 | 55 | return [ [ 'set' => ['name' => $user] ] ]; 56 | } 57 | 58 | /** 59 | * @param $user 60 | * @return \Badoo\Jira\User 61 | * 62 | * @throws \Badoo\Jira\REST\Exception 63 | * @throws \Badoo\Jira\Exception\CustomField 64 | */ 65 | protected function loadUser($user) : \Badoo\Jira\User 66 | { 67 | if ($user instanceof \Badoo\Jira\User) { 68 | return $user; 69 | } 70 | 71 | if (is_string($user)) { 72 | $users = \Badoo\Jira\User::search($user, $this->Issue->getJira()); 73 | 74 | if (!empty($users)) { 75 | return reset($users); 76 | } 77 | } 78 | 79 | throw new \Badoo\Jira\Exception\CustomField( 80 | "User '{$user}' not found in Jira. Can't change '{$this->getName()}' field value." 81 | ); 82 | } 83 | 84 | /** 85 | * @param \Badoo\Jira\User|string $user 86 | * 87 | * @return $this 88 | * 89 | * @throws \Badoo\Jira\REST\Exception 90 | * @throws \Badoo\Jira\Exception\CustomField 91 | */ 92 | public function setValue($user) 93 | { 94 | if (!isset($user)) { 95 | parent::setValue(null); 96 | return $this; 97 | } 98 | 99 | $User = $this->loadUser($user); 100 | parent::setValue($User->getName()); 101 | 102 | return $this; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/CustomFields/TextField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CustomFields; 8 | 9 | /** 10 | * Class TextField 11 | * @package Badoo\Jira\CustomFields\Abstracts 12 | * 13 | * Wrapper class for 'text' type custom field 14 | */ 15 | abstract class TextField extends CustomField 16 | { 17 | /** 18 | * @return string 19 | * 20 | * @throws \Badoo\Jira\REST\Exception 21 | */ 22 | public function getValue() 23 | { 24 | return (string)$this->getOriginalObject(); 25 | } 26 | 27 | /** 28 | * @param string $value 29 | * @return array 30 | */ 31 | public static function generateSetter($value) : array 32 | { 33 | if ($value !== null) { 34 | $value = (string)$value; 35 | } 36 | 37 | return [ ['set' => $value] ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/CustomFields/UserField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\CustomFields; 8 | 9 | /** 10 | * Class UserField 11 | * @package Badoo\Jira\CustomFields 12 | * 13 | * Wrapper class for 'multi user picker' type custom field 14 | */ 15 | abstract class UserField extends CustomField 16 | { 17 | /** @var \Badoo\Jira\User[] - users listed in field */ 18 | protected $value; 19 | /** @var \Badoo\Jira\User[] - new field state to be set on ->save() call: [ => true, => true ] */ 20 | protected $update; 21 | 22 | public function dropCache() 23 | { 24 | $this->value = null; 25 | $this->update = null; 26 | 27 | return parent::dropCache(); 28 | } 29 | 30 | /** 31 | * @return \Badoo\Jira\User[] - list of users listed in field 32 | * 33 | * @throws \Badoo\Jira\REST\Exception 34 | */ 35 | protected function getSelectedUsers() 36 | { 37 | if (!isset($this->value)) { 38 | $this->value = []; 39 | 40 | $Field = $this->getOriginalObject(); 41 | 42 | foreach ((array)$Field as $UserData) { 43 | $User = \Badoo\Jira\User::fromStdClass($UserData, $this->Issue, $this->Issue->getJira()); 44 | $this->value[$User->getName()] = $User; 45 | } 46 | } 47 | 48 | return $this->value; 49 | } 50 | 51 | /** 52 | * Search user in JIRA. 53 | * 54 | * @param \Badoo\Jira\User|string $user 55 | * 56 | * @return \Badoo\Jira\User|null 57 | * 58 | * @throws \Badoo\Jira\REST\Exception on JIRA API interaction error 59 | * @throws \Badoo\Jira\Exception\CustomField when user does not exist in JIRA 60 | */ 61 | protected function loadUser($user) : \Badoo\Jira\User 62 | { 63 | if ($user instanceof \Badoo\Jira\User) { 64 | return $user; 65 | } 66 | 67 | if (is_string($user)) { 68 | $users = \Badoo\Jira\User::search($user, $this->Issue->getJira()); 69 | 70 | if (!empty($users)) { 71 | return reset($users); 72 | } 73 | } 74 | 75 | throw new \Badoo\Jira\Exception\CustomField( 76 | "User '{$user}' not found in Jira. Can't add it to '{$this->getName()}' field." 77 | ); 78 | } 79 | 80 | /** 81 | * Get current list of users selected in a field value 82 | * 83 | * @return \Badoo\Jira\User[] 84 | * 85 | * @throws \Badoo\Jira\REST\Exception 86 | */ 87 | public function getValue() : array 88 | { 89 | return $this->getSelectedUsers(); 90 | } 91 | 92 | /** 93 | * @param string[] $value - list of user names to lists in field 94 | * @return array 95 | */ 96 | public static function generateSetter($value) : array 97 | { 98 | $users_to_select = []; 99 | foreach ($value as $item_name) { 100 | $users_to_select[] = ['name' => $item_name]; 101 | } 102 | 103 | return [ ['set' => $users_to_select] ]; 104 | } 105 | 106 | /** 107 | * Set field value to exact list of users 108 | * 109 | * @param \Badoo\Jira\User[]|string[] $value - list of names of selected users, or User objects, or emails JIRA 110 | * can use to find users. You can mix the values. 111 | * 112 | * @return $this 113 | * 114 | * @throws \Badoo\Jira\REST\Exception on JIRA API interaction error 115 | * @throws \Badoo\Jira\Exception\CustomField when user does not exist in JIRA 116 | */ 117 | public function setValue($value) 118 | { 119 | $this->update = []; 120 | foreach ($value as $user) { 121 | $User = $this->loadUser($user); 122 | $this->update[$User->getName()] = true; 123 | } 124 | 125 | $update = static::generateSetter(array_keys($this->update)); 126 | $this->Issue->edit($this->getID(), $update); 127 | 128 | return $this; 129 | } 130 | 131 | /** 132 | * Clear field value. Equivalent to ->setValue([]) 133 | * 134 | * @return $this 135 | * 136 | * @throws \Badoo\Jira\REST\Exception on JIRA API interaction error 137 | * @throws \Badoo\Jira\Exception\CustomField when user does not exist in JIRA 138 | */ 139 | public function clear() 140 | { 141 | return $this->setValue([]); 142 | } 143 | 144 | /** 145 | * Add user to field value 146 | * 147 | * @param \Badoo\Jira\User|string $user - username, name or e-mail address as string or User object 148 | * 149 | * @return $this 150 | * 151 | * @throws \Badoo\Jira\REST\Exception on JIRA API interaction error 152 | * @throws \Badoo\Jira\Exception\CustomField when user does not exist in JIRA 153 | */ 154 | public function addUser($user) 155 | { 156 | if (!isset($this->update)) { 157 | $this->update = $this->getSelectedUsers(); 158 | } 159 | 160 | $User = $this->loadUser($user); 161 | $this->update[$User->getName()] = $User; 162 | 163 | $update = static::generateSetter(array_keys($this->update)); 164 | $this->Issue->edit($this->getID(), $update); 165 | 166 | return $this; 167 | } 168 | 169 | /** 170 | * Remove user from field value 171 | * 172 | * @param \Badoo\Jira\User|string $user 173 | * 174 | * @return $this 175 | * 176 | * @throws \Badoo\Jira\REST\Exception on JIRA API interaction error 177 | * @throws \Badoo\Jira\Exception\CustomField when user does not exist in JIRA 178 | */ 179 | public function removeUser($user) 180 | { 181 | if (!isset($this->update)) { 182 | $this->update = $this->getSelectedUsers(); 183 | } 184 | 185 | if (is_string($user) && isset($this->update[$user])) { 186 | unset($this->update[$user]); 187 | } else { 188 | $User = $this->loadUser($user); 189 | unset($this->update[$User->getName()]); 190 | } 191 | 192 | $update = static::generateSetter(array_keys($this->update)); 193 | $this->Issue->edit($this->getID(), $update); 194 | 195 | return $this; 196 | } 197 | 198 | /** 199 | * Check if user is already selected in field 200 | * 201 | * @param \Badoo\Jira\User|string $user 202 | * 203 | * @return bool - true when user is listed in current field value 204 | * 205 | * @throws \Badoo\Jira\REST\Exception on JIRA API interaction error 206 | * @throws \Badoo\Jira\Exception\CustomField when user does not exist in JIRA 207 | */ 208 | public function hasUser($user) 209 | { 210 | $selected_users = $this->getSelectedUsers(); 211 | if (is_string($user) && isset($selected_users[$user])) { 212 | return true; 213 | } 214 | 215 | $User = $this->loadUser($user); 216 | 217 | return isset($selected_users[$User->getName()]); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/Exception.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira; 8 | 9 | class Exception extends \Exception 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Exception/Component.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Exception; 8 | 9 | class Component extends \Badoo\Jira\Exception 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Exception/CustomField.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Exception; 8 | 9 | class CustomField extends \Badoo\Jira\Exception 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Exception/File.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Exception; 8 | 9 | class File extends \Badoo\Jira\Exception 10 | { 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/Exception/Issue.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Exception; 8 | 9 | class Issue extends \Badoo\Jira\Exception 10 | { 11 | const ERROR_CODE_UNKNOWN_COMPONENT = 0x01; 12 | const ERROR_CODE_UNKNOWN_FIELD = 0x02; 13 | const ERROR_CODE_UNKNOWN_TYPE = 0x03; 14 | const ERROR_CODE_UNKNOWN_PRIORITY = 0x04; 15 | const ERROR_CODE_UNKNOWN_FIELD_VALUE = 0x05; 16 | 17 | const ERROR_CODE_UNKNOWN_ERROR = 0xff; 18 | } 19 | -------------------------------------------------------------------------------- /src/Exception/Link.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Exception; 8 | 9 | class Link extends \Badoo\Jira\Exception 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Exception/User.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Exception; 8 | 9 | class User extends \Badoo\Jira\Exception 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Exception/Version.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Exception; 8 | 9 | class Version extends \Badoo\Jira\Exception 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/Group.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira; 8 | 9 | class Group 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var string */ 18 | protected $name; // Developers 19 | 20 | /** @var string */ 21 | protected $self; // https:///rest/api/2/group?groupname=Developers" 22 | 23 | /** @var \Badoo\Jira\User[] */ 24 | protected $users; 25 | 26 | public static function fromStdClass(\stdClass $GroupInfo, \Badoo\Jira\REST\Client $Jira = null) : Group 27 | { 28 | $Instance = new static($GroupInfo->name, $Jira); 29 | $Instance->init($GroupInfo); 30 | 31 | return $Instance; 32 | } 33 | 34 | public function __construct(string $name, \Badoo\Jira\REST\Client $Jira = null) 35 | { 36 | if (!isset($Jira)) { 37 | $Jira = \Badoo\Jira\REST\Client::instance(); 38 | } 39 | 40 | $this->name = $name; 41 | $this->Jira = $Jira; 42 | } 43 | 44 | public function __toString() 45 | { 46 | return $this->getName(); 47 | } 48 | 49 | protected function init(\stdClass $GroupInfo) 50 | { 51 | $this->OriginalObject = $GroupInfo; 52 | 53 | $this->name = $GroupInfo->name; 54 | $this->self = $GroupInfo->self; 55 | } 56 | 57 | public function getName() : string 58 | { 59 | return $this->name; 60 | } 61 | 62 | /** 63 | * Get all users in group 64 | * WARNING: for large groups can take long time 65 | * 66 | * @see \Badoo\Jira\REST\Section\Group::listAllUsers for more information 67 | */ 68 | public function getAllUsers() : array 69 | { 70 | if (!isset($this->users)) { 71 | $users = $this->Jira->group()->listAllUsers($this->getName()); 72 | 73 | $this->users = []; 74 | foreach ($users as $UserInfo) { 75 | $User = \Badoo\Jira\User::fromStdClass($UserInfo); 76 | $this->users[$User->getName()] = $User; 77 | } 78 | } 79 | 80 | return $this->users; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Helpers/Files.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Helpers; 8 | 9 | class Files 10 | { 11 | public static function filePutContents($file, $contents, $flags = 0) 12 | { 13 | if (file_put_contents($file, $contents, $flags) !== mb_strlen($contents, '8bit')) { 14 | throw new \RuntimeException("Failed to write to $file"); 15 | } 16 | } 17 | 18 | public static function fileGetContents($file) : string 19 | { 20 | $contents = file_get_contents($file); 21 | 22 | if ($contents === false) { 23 | throw new \RuntimeException("Failed to get file contents for $file"); 24 | } 25 | 26 | return $contents; 27 | } 28 | 29 | public static function exists(string $file_path) : bool 30 | { 31 | return \file_exists($file_path); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Helpers/Json.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Helpers; 8 | 9 | class Json 10 | { 11 | /** 12 | * Same as `json_encode` but throws exceptions on errors. 13 | * 14 | * @see https://www.php.net/manual/en/function.json-encode.php 15 | * 16 | * @throws \UnexpectedValueException 17 | */ 18 | public static function encode($value, $options = 0, $depth = 512) : string 19 | { 20 | $json = \json_encode($value, $options, $depth); 21 | if (JSON_ERROR_NONE !== json_last_error()) { 22 | throw new \UnexpectedValueException( 23 | 'failed to json_encode value: ' . json_last_error_msg(), 24 | json_last_error() 25 | ); 26 | } 27 | 28 | return $json; 29 | } 30 | 31 | /** 32 | * Same as `json_decode` but throws exceptions on errors. 33 | * 34 | * @see https://www.php.net/manual/en/function.json-decode.php 35 | * 36 | * @throws \UnexpectedValueException 37 | */ 38 | public static function decode($json, $assoc = false, $depth = 512, $options = 0) 39 | { 40 | $data = \json_decode($json, $assoc, $depth, $options); 41 | if (JSON_ERROR_NONE !== json_last_error()) { 42 | throw new \UnexpectedValueException( 43 | 'failed to json_decode string: ' . json_last_error_msg(), 44 | json_last_error() 45 | ); 46 | } 47 | 48 | return $data; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Helpers/Strings.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Helpers; 8 | 9 | class Strings 10 | { 11 | /** @var string - a valid PHP letter as it is described in PHP language documentation. 12 | * Is used in REGEX'es for valid PHP labels (constant names, class names, variables and so on) */ 13 | const PHP_LETTER = 'a-zA-Z'; 14 | 15 | /** 16 | * Replaces all incorrect characters (that could not be used in PHP labels, like constants or class names) 17 | * with the given 18 | * @see https://www.php.net/manual/en/language.variables.basics.php 19 | * 20 | * @param string $text - text to translate into a valid PHP label 21 | * @param string $replacement - a character to be used as replacement for incorrect chars in 22 | * 23 | * @return string - valid PHP label that can be used as const, class or variable name 24 | */ 25 | public static function toPHPLabel(string $text, string $replacement = '_') : string 26 | { 27 | $letter = self::PHP_LETTER; 28 | $label = preg_replace("/[^_0-9{$letter}]+/", $replacement, $text); // e.g. ' 0. #With @specials!' -> '_0_With_specials_' 29 | $label = trim($label, $replacement); // '_0_With_specials_' -> '0_With_specials' 30 | 31 | if (preg_match("/^[0-9]/", $label)) { 32 | // label can't start with a number. Put a '_' to the beginning to make a valid label 33 | $label = "_{$label}"; // '0_With_specials' -> '_0_With_specials' 34 | } 35 | 36 | if (empty($label)) { 37 | $label = '_'; 38 | } 39 | 40 | return $label; 41 | } 42 | 43 | /** 44 | * Checks if given string contains valid PHP label 45 | * @see https://www.php.net/manual/en/language.variables.basics.php 46 | * 47 | * @param string $text - text to check 48 | * @return bool - true when is a valid PHP label (can be used as variable, constant or class name) 49 | */ 50 | public static function isValidPHPLabel(string $text) : bool 51 | { 52 | $letter = self::PHP_LETTER; 53 | return (bool)preg_match("/^[_{$letter}][_0-9{$letter}]*$/", $text); 54 | } 55 | 56 | /** 57 | * Generates a valid PHP label with CAPITAL letters and '_' as replacement for unallowed characters 58 | * 59 | * Examples: 60 | * 'my 1 sample textual value!' -> MY_1_SAMPLE_TEXTUAL_VALUE 61 | * 'translate `em all' -> TRANSLATE_EM_ALL 62 | * '1 - option one' -> '_1_OPTION_ONE' 63 | */ 64 | public static function toCapitalPHPLabel(string $text) : string 65 | { 66 | $label = static::toPHPLabel($text); // ' 1 - some random text' -> '_1_some_random_text' 67 | return strtoupper($label); // '_1_some_random_text' -> '_1_SOME_RANDOM_TEXT' 68 | } 69 | 70 | /** 71 | * Generate a valid camel cased PHP label 72 | * 73 | * Examples: 74 | * 'my 1 sample textual value!' -> My1SampleTextualValue 75 | * 'translate `em all' -> TranslateEmAll 76 | * '1 - option one' -> '_1OptionOne' 77 | */ 78 | public static function toCamelCasePHPLabel(string $text) : string 79 | { 80 | $capital_label = static::toCapitalPHPLabel($text); 81 | 82 | $parts = []; 83 | 84 | if ($capital_label[0] === '_') { 85 | // make CamelCasedLabel to start with _ if CAPITAL has it first. 86 | // This means the second char might be a number 87 | $parts[] = '_'; 88 | } 89 | 90 | $part = strtok($capital_label, '_'); 91 | while ($part !== false) { 92 | $parts[] = ucfirst(strtolower($part)); // 'AWESOME' -> 'Awesome' 93 | $part = strtok('_'); 94 | } 95 | 96 | return implode('', $parts); // ['Awesome', 'Class', 'Name'] -> 'AwesomeClassName' 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Issue/Attachments.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class Attachments 10 | { 11 | /** @var \Badoo\Jira\Issue */ 12 | protected $Issue; 13 | 14 | /** @var File[] */ 15 | protected $files; 16 | 17 | public static function fromStdClass( 18 | array $files, 19 | \Badoo\Jira\Issue $Issue 20 | ) : Attachments { 21 | $Instance = new static($Issue); 22 | 23 | foreach ($files as $AttachmentInfo) { 24 | $Instance->files[] = File::fromStdClass($AttachmentInfo, $Issue, $Issue->getJira()); 25 | } 26 | 27 | return $Instance; 28 | } 29 | 30 | public static function forIssue(string $issue_key, \Badoo\Jira\REST\Client $Jira = null) : Attachments 31 | { 32 | $Issue = \Badoo\Jira\Issue::byKey($issue_key, ['attachment'], [], $Jira); 33 | return $Issue->getAttachments(); 34 | } 35 | 36 | public function __construct(\Badoo\Jira\Issue $Issue) 37 | { 38 | $this->Issue = $Issue; 39 | } 40 | 41 | protected function getJira() : \Badoo\Jira\REST\Client 42 | { 43 | return $this->Issue->getJira(); 44 | } 45 | 46 | /** 47 | * @return File[] 48 | * @throws \Badoo\Jira\REST\Exception 49 | */ 50 | public function getFiles() : array 51 | { 52 | if (!isset($this->files)) { 53 | $this->files = []; 54 | 55 | $attachments = $this->getJira()->issue()->attachment()->list($this->Issue->getKey()); 56 | foreach ($attachments as $AttachmentInfo) { 57 | $this->files[] = File::fromStdClass($AttachmentInfo, $this->Issue, $this->getJira()); 58 | } 59 | } 60 | 61 | return $this->files; 62 | } 63 | 64 | public function attach(string $file_path, ?string $file_name = null, ?string $file_type = null) : File 65 | { 66 | if (!\Badoo\Jira\Helpers\Files::exists($file_path)) { 67 | throw new \Badoo\Jira\Exception\File( 68 | "File {$file_path} not found on disk. Can't upload it to JIRA" 69 | ); 70 | } 71 | 72 | $AttachmentInfo = $this->getJira()->issue()->attachment()->create($this->Issue->getKey(), $file_path, $file_name, $file_type); 73 | $File = File::fromStdClass($AttachmentInfo, $this->Issue, $this->getJira()); 74 | 75 | if (isset($this->files)) { 76 | $this->files[] = $File; 77 | } 78 | 79 | return $File; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Issue/Changelog.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | /** 10 | * Class Changelog 11 | * @package Badoo\Jira\Issue 12 | * 13 | * List of changes for issue's fields obtained from WebHook Jira request. 14 | * They differ a bit from regular issue's History, so we created special class for it 15 | */ 16 | class Changelog implements ILogRecord 17 | { 18 | /** @var int - unique changelog record ID. */ 19 | protected $id; 20 | /** @var \Badoo\Jira\Issue */ 21 | protected $Issue; 22 | /** @var int */ 23 | protected $timestamp; 24 | /** @var LogRecordItem[] */ 25 | protected $items = []; 26 | 27 | /** 28 | * @param \Badoo\Jira\Issue $Issue 29 | * @param \stdClass $Changelog 30 | * @param int $timestamp - changelog record's timestamp 31 | * @return Changelog 32 | */ 33 | public static function fromStdClass(\stdClass $Changelog, \Badoo\Jira\Issue $Issue, int $timestamp) 34 | { 35 | $Instance = new self(); 36 | $Instance->id = $Changelog->id; 37 | $Instance->Issue = $Issue; 38 | $Instance->timestamp = $timestamp; 39 | 40 | foreach ($Changelog->items as $Item) { 41 | $Instance->items[] = LogRecordItem::fromStdClass($Item, $Instance); 42 | } 43 | return $Instance; 44 | } 45 | 46 | public function getIssue() : \Badoo\Jira\Issue 47 | { 48 | return $this->Issue; 49 | } 50 | 51 | public function getCreated() : int 52 | { 53 | return $this->timestamp; 54 | } 55 | 56 | /** 57 | * @return LogRecordItem[] 58 | */ 59 | public function getItems() : array 60 | { 61 | return $this->items; 62 | } 63 | 64 | /** 65 | * @param callable $callable 66 | * @return array 67 | */ 68 | public function filter($callable) 69 | { 70 | if (!is_callable($callable)) { 71 | throw new \RuntimeException(); 72 | } 73 | 74 | return array_filter($this->items, $callable); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Issue/Comment.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class Comment 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var \Badoo\Jira\Issue */ 18 | protected $Issue; 19 | 20 | /** @var int $id */ 21 | protected $id; 22 | 23 | /** @var array */ 24 | protected $cache = []; 25 | 26 | /** 27 | * Initialize Comment object on data obtained from API 28 | * 29 | * @param \stdClass $CommentInfo - issue comment information received from JIRA API. 30 | * @param \Badoo\Jira\Issue $Issue - when current Comment object represents current comment of some issue. 31 | * 32 | * @return static 33 | */ 34 | public static function fromStdClass(\stdClass $CommentInfo, \Badoo\Jira\Issue $Issue) : Comment 35 | { 36 | $Instance = new static($CommentInfo->id, $Issue); 37 | $Instance->OriginalObject = $CommentInfo; 38 | 39 | return $Instance; 40 | } 41 | 42 | /** 43 | * Get Comment info from API by ID. 44 | * 45 | * This method makes an API request immediately, while 46 | * $Comment = new Comment(, ); 47 | * requests JIRA only when you really need the data (e.g. the first time you call $Comment->getText()). 48 | * 49 | * @param int $id - ID of comment you want to get 50 | * @param \Badoo\Jira\Issue - issue that contains the comment 51 | * 52 | * @return static 53 | * 54 | * @throws \Badoo\Jira\REST\Exception 55 | */ 56 | public static function get(int $id, \Badoo\Jira\Issue $Issue) 57 | { 58 | $Instance = new static($id, $Issue); 59 | $Instance->getOriginalObject(); 60 | 61 | return $Instance; 62 | } 63 | 64 | public function __construct(int $id, \Badoo\Jira\Issue $Issue) 65 | { 66 | $this->id = $id; 67 | $this->Issue = $Issue; 68 | } 69 | 70 | protected function getOriginalObject($expand_rendered = false) : \stdClass 71 | { 72 | if (!isset($this->OriginalObject)) { 73 | $this->OriginalObject = $this->Issue->getJira()->issue()->comment()->get( 74 | $this->Issue->getKey(), 75 | $this->id, 76 | $expand_rendered 77 | ); 78 | } 79 | 80 | return $this->OriginalObject; 81 | } 82 | 83 | protected function dropCache() : void 84 | { 85 | $this->OriginalObject = null; 86 | $this->cache = []; 87 | } 88 | 89 | public function getIssue() : \Badoo\Jira\Issue 90 | { 91 | return $this->Issue; 92 | } 93 | 94 | public function getID() : int 95 | { 96 | return $this->id; 97 | } 98 | 99 | public function getText() : string 100 | { 101 | return $this->getOriginalObject()->body; 102 | } 103 | 104 | public function getRendered() : string 105 | { 106 | if (isset($this->OriginalObject) && !isset($this->OriginalObject->renderedBody)) { 107 | $this->dropCache(); 108 | } 109 | 110 | return $this->getOriginalObject(true)->renderedBody; 111 | } 112 | 113 | public function getCreated() : int 114 | { 115 | $timestamp = $this->cache['created'] ?? null; 116 | 117 | if (!isset($timestamp)) { 118 | $timestamp = strtotime($this->getOriginalObject()->created) ?: 0; 119 | $this->cache['created'] = $timestamp; 120 | } 121 | 122 | return $timestamp; 123 | } 124 | 125 | public function getUpdated() : int 126 | { 127 | $timestamp = $this->cache['updated'] ?? null; 128 | 129 | if (!isset($timestamp)) { 130 | $timestamp = strtotime($this->getOriginalObject()->updated) ?: 0; 131 | $this->cache['updated'] = $timestamp; 132 | } 133 | 134 | return $timestamp; 135 | } 136 | 137 | public function getAuthor() : \Badoo\Jira\User 138 | { 139 | $User = $this->cache['Author'] ?? null; 140 | 141 | if (!isset($User)) { 142 | $UserInfo = $this->getOriginalObject()->author; 143 | $User = \Badoo\Jira\User::fromStdClass($UserInfo); 144 | 145 | $this->cache['Author'] = $User; 146 | } 147 | 148 | return $User; 149 | } 150 | 151 | public function getUpdateAuthor() : \Badoo\Jira\User 152 | { 153 | $User = $this->cache['UpdateAuthor'] ?? null; 154 | 155 | if (!isset($User)) { 156 | $UserInfo = $this->getOriginalObject()->updateAuthor; 157 | $User = \Badoo\Jira\User::fromStdClass($UserInfo); 158 | 159 | $this->cache['UpdateAuthor'] = $User; 160 | } 161 | 162 | return $User; 163 | } 164 | 165 | /** 166 | * Check if comment body contains text. The search is performed on original body value, not the rendered one 167 | */ 168 | public function contains(string $text) : bool 169 | { 170 | return strpos($this->getText(), $text) !== false; 171 | } 172 | 173 | public function update(string $new_text, array $visibility = []) : Comment 174 | { 175 | $CommentInfo = $this->Issue->getJira()->issue()->comment()->update( 176 | $this->Issue->getKey(), 177 | $this->id, 178 | $new_text, 179 | $visibility 180 | ); 181 | 182 | $this->dropCache(); 183 | $this->OriginalObject = $CommentInfo; 184 | 185 | return $this; 186 | } 187 | 188 | public function delete() : void 189 | { 190 | $this->Issue->getJira()->issue()->comment()->delete($this->Issue->getKey(), $this->id); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/Issue/File.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class File 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var \Badoo\Jira\Issue */ 18 | protected $Issue; 19 | 20 | /** @var int $id */ 21 | protected $id; 22 | 23 | /** @var array */ 24 | protected $cache = []; 25 | 26 | public static function fromStdClass( 27 | \stdClass $AttachmentInfo, 28 | \Badoo\Jira\Issue $Issue, 29 | \Badoo\Jira\REST\Client $Jira = null 30 | ) : File { 31 | $Instance = new static((int)$AttachmentInfo->id, $Issue, $Jira); 32 | $Instance->OriginalObject = $AttachmentInfo; 33 | 34 | return $Instance; 35 | } 36 | 37 | public function __construct(int $id, \Badoo\Jira\Issue $Issue = null, \Badoo\Jira\REST\Client $Jira = null) 38 | { 39 | if (!isset($Jira)) { 40 | $Jira = \Badoo\Jira\REST\Client::instance(); 41 | } 42 | 43 | $this->id = $id; 44 | $this->Issue = $Issue; 45 | $this->Jira = $Jira; 46 | } 47 | 48 | protected function getOriginalObject() : \stdClass 49 | { 50 | if (!isset($this->OriginalObject)) { 51 | $this->OriginalObject = $this->Jira->attachment()->get($this->id); 52 | } 53 | 54 | return $this->OriginalObject; 55 | } 56 | 57 | protected function dropCache() : void 58 | { 59 | $this->OriginalObject = null; 60 | $this->cache = []; 61 | } 62 | 63 | public function getId() : int 64 | { 65 | return $this->id; 66 | } 67 | 68 | public function getName() : string 69 | { 70 | return $this->getOriginalObject()->filename; 71 | } 72 | 73 | public function getSize() : int 74 | { 75 | return (int)$this->getOriginalObject()->size; 76 | } 77 | 78 | public function getMimeType() : string 79 | { 80 | return $this->getOriginalObject()->mimeType; 81 | } 82 | 83 | public function getContentLink() : string 84 | { 85 | return $this->getOriginalObject()->content; 86 | } 87 | 88 | public function getThumbnailLink() : string 89 | { 90 | return $this->getOriginalObject()->thumbnail; 91 | } 92 | 93 | public function getCreated() : int 94 | { 95 | $key = 'created'; 96 | 97 | if (!isset($this->cache[$key])) { 98 | $time = $this->getOriginalObject()->created; 99 | $this->cache[$key] = (int)strtotime($time); 100 | } 101 | 102 | return $this->cache[$key]; 103 | } 104 | 105 | public function getAuthor() : \Badoo\Jira\User 106 | { 107 | $key = 'Author'; 108 | 109 | if (!isset($this->cache[$key])) { 110 | $UserInfo = $this->getOriginalObject()->author; 111 | $this->cache[$key] = \Badoo\Jira\User::fromStdClass($UserInfo, $this->Issue, $this->Jira); 112 | } 113 | 114 | return $this->cache[$key]; 115 | } 116 | 117 | public function delete() 118 | { 119 | $this->Jira->attachment()->delete($this->getId()); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Issue/HistoryRecord.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | /** 10 | * Class HistoryRecord 11 | * 12 | * Wrapper for Issue's changelog history records. Each record contains one or more field change items. 13 | * One record represents single issue change during issue update or transition. 14 | */ 15 | class HistoryRecord implements ILogRecord 16 | { 17 | /** @var \stdClass */ 18 | protected $OriginalObject; 19 | 20 | /** @var History */ 21 | protected $History; 22 | 23 | /** @var array */ 24 | protected $cache = []; 25 | 26 | public static function fromStdClass(\stdClass $Record, History $History) : HistoryRecord 27 | { 28 | $Instance = new static(); 29 | $Instance->OriginalObject = $Record; 30 | 31 | $Instance->History = $History; 32 | 33 | return $Instance; 34 | } 35 | 36 | protected function getOriginalObject() : \stdClass 37 | { 38 | return $this->OriginalObject; 39 | } 40 | 41 | public function getIssue() : \Badoo\Jira\Issue 42 | { 43 | return $this->History->getIssue(); 44 | } 45 | 46 | public function getHistory() : History 47 | { 48 | return $this->History; 49 | } 50 | 51 | public function getId() : int 52 | { 53 | return (int)$this->getOriginalObject()->id; 54 | } 55 | 56 | public function getAuthor() : \Badoo\Jira\User 57 | { 58 | $key = 'Author'; 59 | 60 | if (!array_key_exists($key, $this->cache)) { 61 | $AuthorInfo = $this->getOriginalObject()->author; 62 | $Issue = $this->getIssue(); 63 | 64 | $this->cache[$key] = \Badoo\Jira\User::fromStdClass($AuthorInfo, $Issue, $Issue->getJira()); 65 | } 66 | 67 | return $this->cache[$key]; 68 | } 69 | 70 | public function getCreated() : int 71 | { 72 | $key = 'created'; 73 | 74 | if (!array_key_exists($key, $this->cache)) { 75 | $this->cache[$key] = (int)strtotime($this->getOriginalObject()->created); 76 | } 77 | 78 | return $this->cache[$key]; 79 | } 80 | 81 | /** 82 | * Get all field changes within this hostory record. Single history record refers to single issue update: 83 | * field change in UI, transition and so on. 84 | * 85 | * @return LogRecordItem[] 86 | */ 87 | public function getItems() : array 88 | { 89 | $key = 'changes'; 90 | 91 | if (!array_key_exists($key, $this->cache)) { 92 | $changes = []; 93 | 94 | $changes_info = $this->getOriginalObject()->items; 95 | foreach ($changes_info as $ChangeInfo) { 96 | $changes[] = LogRecordItem::fromStdClass($ChangeInfo, $this); 97 | } 98 | 99 | $this->cache[$key] = $changes; 100 | } 101 | 102 | return $this->cache[$key]; 103 | } 104 | 105 | /** 106 | * Get change for specific field, if it was changed in this record. 107 | * 108 | * @param string $field_name - field display name (e.g. 'Developer' or 'Epic Link') 109 | * @return LogRecordItem|null 110 | */ 111 | public function getFieldChange(string $field_name) : ?LogRecordItem 112 | { 113 | foreach ($this->getItems() as $Change) { 114 | if ($Change->getFieldName() === $field_name) { 115 | return $Change; 116 | } 117 | } 118 | 119 | return null; 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/Issue/ILogRecord.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | /** 10 | * Interface ILogRecord 11 | * 12 | * Each issue log record must have information about it's creation time and it's issue. 13 | */ 14 | interface ILogRecord 15 | { 16 | /** 17 | * Log record parent's issue. 18 | * @return \Badoo\Jira\Issue 19 | */ 20 | public function getIssue() : \Badoo\Jira\Issue; 21 | 22 | /** 23 | * Log record creation time. 24 | * @return int 25 | */ 26 | public function getCreated() : int; 27 | 28 | /** 29 | * List of items (changes made to issue) within this log record. 30 | * @return LogRecordItem[] 31 | */ 32 | public function getItems() : array; 33 | } 34 | -------------------------------------------------------------------------------- /src/Issue/LinkType.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class LinkType 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var int */ 18 | protected $id; 19 | 20 | /** @var string[] */ 21 | protected $update = []; 22 | 23 | /** 24 | * Initialize LinkType object on data obtained from API 25 | * 26 | * @param \stdClass $LinkTypeInfo - issue link type information received from JIRA API. 27 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 28 | * Enables you to access several JIRA instances from one piece of code, 29 | * or use different users for different actions. 30 | * 31 | * @return static 32 | */ 33 | public static function fromStdClass(\stdClass $LinkTypeInfo, \Badoo\Jira\REST\Client $Jira = null) : LinkType 34 | { 35 | $Instance = new static($LinkTypeInfo->id, $Jira); 36 | $Instance->OriginalObject = $LinkTypeInfo; 37 | 38 | return $Instance; 39 | } 40 | 41 | /** 42 | * Get LinkType info from API by ID. 43 | * 44 | * This method makes an API request immediately, while 45 | * $LinkType = new LinkType(, ); 46 | * requests JIRA only when you really need the data (e.g. the first time you call $LinkType->getName()). 47 | * 48 | * @param int $id - ID of link type you want to get 49 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 50 | * Enables you to access several JIRA instances from one piece of code, 51 | * or use different users for different actions. 52 | * 53 | * @return static 54 | * 55 | * @throws \Badoo\Jira\REST\Exception 56 | */ 57 | public static function get(int $id, \Badoo\Jira\REST\Client $Jira = null) : LinkType 58 | { 59 | $Instance = new static($id, $Jira); 60 | $Instance->getOriginalObject(); 61 | 62 | return $Instance; 63 | } 64 | 65 | public function __construct(int $id = 0, \Badoo\Jira\REST\Client $Jira = null) 66 | { 67 | if (!isset($Jira)) { 68 | $Jira = \Badoo\Jira\REST\Client::instance(); 69 | } 70 | 71 | $this->id = $id; 72 | $this->Jira = $Jira; 73 | } 74 | 75 | /** 76 | * @return \stdClass 77 | * @throws \Badoo\Jira\REST\Exception 78 | */ 79 | protected function getOriginalObject() : \stdClass 80 | { 81 | if (!isset($this->OriginalObject)) { 82 | $this->OriginalObject = $this->Jira->issueLinkType()->get($this->getId()); 83 | } 84 | 85 | return $this->OriginalObject; 86 | } 87 | 88 | /** 89 | * Drop internal object cache. 90 | * @return $this 91 | */ 92 | public function dropCache() 93 | { 94 | $this->OriginalObject = null; 95 | return $this; 96 | } 97 | 98 | public function getId() : int 99 | { 100 | return $this->id; 101 | } 102 | 103 | public function getName() : string 104 | { 105 | return $this->getOriginalObject()->name; 106 | } 107 | 108 | public function setName(string $new_name) : LinkType 109 | { 110 | $this->update['name'] = $new_name; 111 | return $this; 112 | } 113 | 114 | public function getInward() 115 | { 116 | return $this->getOriginalObject()->inward; 117 | } 118 | 119 | public function setInward(string $new_inward_description) : LinkType 120 | { 121 | $this->update['inward'] = $new_inward_description; 122 | return $this; 123 | } 124 | 125 | public function getOutward() 126 | { 127 | return $this->getOriginalObject()->outward; 128 | } 129 | 130 | public function setOutward(string $new_outward_description) : LinkType 131 | { 132 | $this->update['outward'] = $new_outward_description; 133 | return $this; 134 | } 135 | 136 | /** 137 | * @throws \Badoo\Jira\REST\Exception 138 | */ 139 | public function save() : LinkType 140 | { 141 | if (empty($this->update) && $this->getId() !== 0) { 142 | return $this; 143 | } 144 | 145 | if ($this->getId() === 0) { 146 | $LinkTypeInfo = $this->Jira->issueLinkType()->create( 147 | $this->update['name'] ?? '', 148 | $this->update['inward'] ?? '', 149 | $this->update['outward'] ?? '' 150 | ); 151 | 152 | $this->id = $LinkTypeInfo->id; 153 | } else { 154 | $LinkTypeInfo = $this->Jira->issueLinkType()->update( 155 | $this->getId(), 156 | $this->update['name'] ?? '', 157 | $this->update['inward'] ?? '', 158 | $this->update['outward'] ?? '' 159 | ); 160 | } 161 | 162 | 163 | $this->dropCache(); 164 | $this->OriginalObject = $LinkTypeInfo; 165 | $this->update = []; 166 | 167 | return $this; 168 | } 169 | 170 | public function delete() : LinkType 171 | { 172 | if ($this->getId() === 0) { 173 | return $this; 174 | } 175 | 176 | $this->Jira->issueLinkType()->delete($this->getId()); 177 | 178 | return $this; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/Issue/LogRecordItem.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | /** 10 | * Class LogRecordItem 11 | * 12 | * Wrapper for issue change objects of jira history and changelog. 13 | * Stores information about single issue field change. 14 | */ 15 | class LogRecordItem 16 | { 17 | const FIELD_TYPE_JIRA = 'jira'; 18 | const FIELD_TYPE_CUSTOM = 'custom'; 19 | 20 | /** @var \stdClass */ 21 | protected $OriginalObject; 22 | 23 | /** @var ILogRecord */ 24 | protected $LogRecord; 25 | 26 | /** 27 | * Get record item, initialized from \stdClass object (from Jira REST response or WebHook event). 28 | * @param ILogRecord $ParentLogRecord 29 | * @param \stdClass $Item 30 | * 31 | * @return LogRecordItem 32 | */ 33 | public static function fromStdClass(\stdClass $Item, ILogRecord $ParentLogRecord) : LogRecordItem 34 | { 35 | $Instance = new static(); 36 | $Instance->OriginalObject = $Item; 37 | 38 | $Instance->LogRecord = $ParentLogRecord; 39 | 40 | return $Instance; 41 | } 42 | 43 | protected function getOriginalObject() : \stdClass 44 | { 45 | return $this->OriginalObject; 46 | } 47 | 48 | public function getIssue() : \Badoo\Jira\Issue 49 | { 50 | return $this->LogRecord->getIssue(); 51 | } 52 | 53 | public function getChangeTime() : int 54 | { 55 | return $this->LogRecord->getCreated(); 56 | } 57 | 58 | public function getFieldName() : string 59 | { 60 | return (string)$this->getOriginalObject()->field; 61 | } 62 | 63 | public function getFieldType() : string 64 | { 65 | return (string)$this->getOriginalObject()->fieldtype; 66 | } 67 | 68 | public function isFieldSystem() : bool 69 | { 70 | return $this->getFieldType() === static::FIELD_TYPE_JIRA; 71 | } 72 | 73 | public function isFieldCustom() : bool 74 | { 75 | return $this->getFieldType() === static::FIELD_TYPE_CUSTOM; 76 | } 77 | 78 | public function getFrom() 79 | { 80 | return $this->getOriginalObject()->from; 81 | } 82 | 83 | public function getTo() 84 | { 85 | return $this->getOriginalObject()->to; 86 | } 87 | 88 | public function getFromString() : string 89 | { 90 | return (string)$this->getOriginalObject()->fromString; 91 | } 92 | 93 | public function getToString() : string 94 | { 95 | return (string)$this->getOriginalObject()->toString; 96 | } 97 | 98 | public function isStringChanged() : bool 99 | { 100 | return $this->getFromString() !== $this->getToString(); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Issue/Priority.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class Priority 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var int */ 18 | protected $id; 19 | 20 | /** @var \Badoo\Jira\Issue */ 21 | protected $Issue; 22 | 23 | /** 24 | * Initialize Priority object on data obtained from API 25 | * 26 | * @param \stdClass $PriorityInfo - issue priority information received from JIRA API. 27 | * @param \Badoo\Jira\Issue $Issue - when current Priority object represents current priority of some issue. 28 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 29 | * Enables you to access several JIRA instances from one piece of code, 30 | * or use different users for different actions. 31 | * 32 | * @return static 33 | */ 34 | public static function fromStdClass( 35 | \stdClass $PriorityInfo, 36 | \Badoo\Jira\Issue $Issue = null, 37 | \Badoo\Jira\REST\Client $Jira = null 38 | ) : Priority { 39 | $Instance = new static((int)$PriorityInfo->id, $Jira); 40 | 41 | $Instance->OriginalObject = $PriorityInfo; 42 | $Instance->Issue = $Issue; 43 | 44 | return $Instance; 45 | } 46 | 47 | /** 48 | * Get Priority info from API by ID. 49 | * 50 | * This method makes an API request immediately, while 51 | * $Priority = new Priority(, ); 52 | * requests JIRA only when you really need the data (e.g. the first time you call $Priority->getName()). 53 | * 54 | * @param int $id - ID of priority you want to get 55 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 56 | * Enables you to access several JIRA instances from one piece of code, 57 | * or use different users for different actions. 58 | * 59 | * @return static 60 | * 61 | * @throws \Badoo\Jira\REST\Exception 62 | */ 63 | public static function get(int $id, \Badoo\Jira\REST\Client $Jira = null) 64 | { 65 | $Instance = new static($id, $Jira); 66 | $Instance->getOriginalObject(); 67 | 68 | return $Instance; 69 | } 70 | 71 | public function __construct(int $id, \Badoo\Jira\REST\Client $Jira = null) 72 | { 73 | if (!isset($Jira)) { 74 | $Jira = \Badoo\Jira\REST\Client::instance(); 75 | } 76 | 77 | $this->id = $id; 78 | $this->Jira = $Jira; 79 | } 80 | 81 | /** 82 | * @return \stdClass 83 | * @throws \Badoo\Jira\REST\Exception 84 | */ 85 | protected function getOriginalObject() 86 | { 87 | if (!isset($this->OriginalObject)) { 88 | $this->OriginalObject = $this->Jira->priority()->get($this->getId()); 89 | } 90 | 91 | return $this->OriginalObject; 92 | } 93 | 94 | public function getIssue() : ?\Badoo\Jira\Issue 95 | { 96 | return $this->Issue; 97 | } 98 | 99 | public function getId() : int 100 | { 101 | return $this->id; 102 | } 103 | 104 | public function getName() : string 105 | { 106 | return $this->getOriginalObject()->name; 107 | } 108 | 109 | public function getIconUrl() : string 110 | { 111 | return $this->getOriginalObject()->iconUrl ?? ''; 112 | } 113 | 114 | public function getSelf() : string 115 | { 116 | return $this->getOriginalObject()->self; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Issue/Resolution.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class Resolution 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var int */ 18 | protected $id; 19 | 20 | /** @var \Badoo\Jira\Issue */ 21 | protected $Issue; 22 | 23 | /** 24 | * Initialize Resolution object on data obtained from API 25 | * 26 | * @param \stdClass $ResolutionInfo - issue resolution information received from JIRA API. 27 | * @param \Badoo\Jira\Issue $Issue - when current Resolution object represents current resolution of some issue. 28 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 29 | * Enables you to access several JIRA instances from one piece of code, 30 | * or use different users for different actions. 31 | * 32 | * @return static 33 | */ 34 | public static function fromStdClass( 35 | \stdClass $ResolutionInfo, 36 | \Badoo\Jira\Issue $Issue = null, 37 | \Badoo\Jira\REST\Client $Jira = null 38 | ) : Resolution { 39 | $Instance = new static((int)$ResolutionInfo->id, $Jira); 40 | 41 | $Instance->OriginalObject = $ResolutionInfo; 42 | $Instance->Issue = $Issue; 43 | 44 | return $Instance; 45 | } 46 | 47 | /** 48 | * Get Resolution info from API by ID. 49 | * 50 | * This method makes an API request immediately, while 51 | * $Resolution = new Resolution(, ); 52 | * requests JIRA only when you really need the data (e.g. the first time you call $Resolution->getName()). 53 | * 54 | * @param int $id - ID of resolution you want to get 55 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 56 | * Enables you to access several JIRA instances from one piece of code, 57 | * or use different users for different actions. 58 | * 59 | * @return static 60 | * 61 | * @throws \Badoo\Jira\REST\Exception 62 | */ 63 | public static function get(int $id, \Badoo\Jira\REST\Client $Jira = null) 64 | { 65 | $Instance = new static($id, $Jira); 66 | $Instance->getOriginalObject(); 67 | 68 | return $Instance; 69 | } 70 | 71 | public function __construct(int $id, \Badoo\Jira\REST\Client $Jira = null) 72 | { 73 | if (!isset($Jira)) { 74 | $Jira = \Badoo\Jira\REST\Client::instance(); 75 | } 76 | 77 | $this->id = $id; 78 | $this->Jira = $Jira; 79 | } 80 | 81 | protected function getOriginalObject() 82 | { 83 | if (!isset($this->OriginalObject)) { 84 | $this->OriginalObject = $this->Jira->resolution()->get($this->getId()); 85 | } 86 | 87 | return $this->OriginalObject; 88 | } 89 | 90 | public function getIssue() : ?\Badoo\Jira\Issue 91 | { 92 | return $this->Issue; 93 | } 94 | 95 | public function getId() : int 96 | { 97 | return $this->id; 98 | } 99 | 100 | public function getName() : string 101 | { 102 | return $this->getOriginalObject()->name; 103 | } 104 | 105 | public function getDescription() : string 106 | { 107 | return $this->getOriginalObject()->description ?? ''; 108 | } 109 | 110 | public function getSelf() : string 111 | { 112 | return $this->getOriginalObject()->self; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/Issue/Status.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class Status 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var int */ 18 | protected $id; 19 | 20 | /** @var StatusCategory */ 21 | protected $StatusCategory; 22 | 23 | /** @var \Badoo\Jira\Issue */ 24 | protected $Issue; 25 | 26 | /** 27 | * Initialize Status object on data loaded from API 28 | * 29 | * @param \stdClass $StatusInfo - status information received from JIRA API. 30 | * @param \Badoo\Jira\Issue $Issue - when current Status object represents current status of some issue. 31 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 32 | * Enables you to access several JIRA instances from one piece of code, 33 | * or use different users for different actions. 34 | * @return static 35 | */ 36 | public static function fromStdClass( 37 | \stdClass $StatusInfo, 38 | \Badoo\Jira\Issue $Issue = null, 39 | \Badoo\Jira\REST\Client $Jira = null 40 | ) : Status { 41 | $Instance = new static((int)$StatusInfo->id, $Jira); 42 | 43 | $Instance->OriginalObject = $StatusInfo; 44 | $Instance->Issue = $Issue; 45 | 46 | return $Instance; 47 | } 48 | 49 | /** 50 | * Get Status info by ID. 51 | * 52 | * This method makes an API request immediately, while 53 | * $Status = new Status(, getName()). 55 | * 56 | * @param int $id - ID of status to get from API 57 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 58 | * Enables you to access several JIRA instances from one piece of code, 59 | * or use different users for different actions. 60 | * 61 | * @return static 62 | * 63 | * @throws \Badoo\Jira\REST\Exception 64 | */ 65 | public static function get(int $id, \Badoo\Jira\REST\Client $Jira = null) 66 | { 67 | if (!isset($Jira)) { 68 | $Jira = \Badoo\Jira\REST\Client::instance(); 69 | } 70 | 71 | $StatusInfo = $Jira->status()->get($id); 72 | 73 | return static::fromStdClass($StatusInfo, null, $Jira); 74 | } 75 | 76 | public function __construct(int $id, \Badoo\Jira\REST\Client $Jira = null) 77 | { 78 | if (!isset($Jira)) { 79 | $Jira = \Badoo\Jira\REST\Client::instance(); 80 | } 81 | 82 | $this->id = $id; 83 | $this->Jira = $Jira; 84 | } 85 | 86 | protected function getOriginalObject() 87 | { 88 | if (!isset($this->OriginalObject)) { 89 | $this->OriginalObject = $this->Jira->status()->get($this->getId()); 90 | } 91 | 92 | return $this->OriginalObject; 93 | } 94 | 95 | public function getIssue() : ?\Badoo\Jira\Issue 96 | { 97 | return $this->Issue; 98 | } 99 | 100 | public function getId() : int 101 | { 102 | return $this->id; 103 | } 104 | 105 | public function getName() : string 106 | { 107 | return $this->getOriginalObject()->name; 108 | } 109 | 110 | public function getDescription() : string 111 | { 112 | return $this->getOriginalObject()->description ?? ''; 113 | } 114 | 115 | public function getStatusCategory() : StatusCategory 116 | { 117 | if (!isset($this->StatusCategory)) { 118 | $this->StatusCategory = StatusCategory::fromStdClass($this->getOriginalObject()->statusCategory, $this->Jira); 119 | } 120 | 121 | return $this->StatusCategory; 122 | } 123 | 124 | public function getIconUrl() : string 125 | { 126 | return $this->getOriginalObject()->iconUrl; 127 | } 128 | 129 | public function getSelf() : string 130 | { 131 | return $this->getOriginalObject()->self; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/Issue/StatusCategory.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class StatusCategory 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var int */ 18 | protected $id; 19 | 20 | /** 21 | * Initialize StatusCategory object on data obtained from API 22 | * 23 | * @param \stdClass $CategoryInfo - issue type information received from JIRA API. 24 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 25 | * Enables you to access several JIRA instances from one piece of code, 26 | * or use different users for different actions. 27 | * 28 | * @return static 29 | */ 30 | public static function fromStdClass( 31 | \stdClass $CategoryInfo, 32 | \Badoo\Jira\REST\Client $Jira = null 33 | ) : StatusCategory { 34 | $Instance = new static((int)$CategoryInfo->id, $Jira); 35 | 36 | $Instance->OriginalObject = $CategoryInfo; 37 | 38 | return $Instance; 39 | } 40 | 41 | /** 42 | * Get StatusCategory info from API by ID. 43 | * 44 | * This method makes an API request immediately, while 45 | * $StatusCategory = new StatusCategory(, ); 46 | * requests JIRA only when you really need the data (e.g. the first time you call $StatusCategory->getName()). 47 | * 48 | * @param int $id - ID of status category you want to get 49 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 50 | * Enables you to access several JIRA instances from one piece of code, 51 | * or use different users for different actions. 52 | * 53 | * @return static 54 | * 55 | * @throws \Badoo\Jira\REST\Exception 56 | */ 57 | public static function get(int $id, \Badoo\Jira\REST\Client $Jira = null) 58 | { 59 | $Instance = new static($id, $Jira); 60 | $Instance->getOriginalObject(); 61 | 62 | return $Instance; 63 | } 64 | 65 | public function __construct(int $id, \Badoo\Jira\REST\Client $Jira = null) 66 | { 67 | if (!isset($Jira)) { 68 | $Jira = \Badoo\Jira\REST\Client::instance(); 69 | } 70 | 71 | $this->id = $id; 72 | $this->Jira = $Jira; 73 | } 74 | 75 | /** 76 | * @return \stdClass 77 | * @throws \Badoo\Jira\REST\Exception 78 | */ 79 | protected function getOriginalObject() 80 | { 81 | if (!isset($this->OriginalObject)) { 82 | $this->OriginalObject = $this->Jira->statusCategory()->get($this->getId()); 83 | } 84 | 85 | return $this->OriginalObject; 86 | } 87 | 88 | public function getId() : int 89 | { 90 | return $this->id; 91 | } 92 | 93 | public function getKey() : string 94 | { 95 | return $this->getOriginalObject()->key; 96 | } 97 | 98 | public function getName() : string 99 | { 100 | return $this->getOriginalObject()->name; 101 | } 102 | 103 | public function getColorName() : string 104 | { 105 | return $this->getOriginalObject()->colorName; 106 | } 107 | 108 | public function getSelf() : string 109 | { 110 | return $this->getOriginalObject()->self; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Issue/Type.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class Type 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var int */ 18 | protected $id; 19 | 20 | /** @var \Badoo\Jira\Issue */ 21 | private $Issue; 22 | 23 | /** 24 | * Initialize Type object on data obtained from API 25 | * 26 | * @param \stdClass $TypeInfo - issue type information received from JIRA API. 27 | * @param \Badoo\Jira\Issue $Issue - when current Type object represents current type of some issue. 28 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 29 | * Enables you to access several JIRA instances from one piece of code, 30 | * or use different users for different actions. 31 | * 32 | * @return static 33 | */ 34 | public static function fromStdClass( 35 | \stdClass $TypeInfo, 36 | \Badoo\Jira\Issue $Issue = null, 37 | \Badoo\Jira\REST\Client $Jira = null 38 | ) : Type { 39 | $Instance = new static((int)$TypeInfo->id, $Jira); 40 | 41 | $Instance->OriginalObject = $TypeInfo; 42 | $Instance->Issue = $Issue; 43 | 44 | return $Instance; 45 | } 46 | 47 | /** 48 | * Get Type info from API by ID. 49 | * 50 | * This method makes an API request immediately, while 51 | * $Type = new Type(, ); 52 | * requests JIRA only when you really need the data (e.g. the first time you call $Type->getName()). 53 | * 54 | * @param int $id - ID of type you want to get 55 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 56 | * Enables you to access several JIRA instances from one piece of code, 57 | * or use different users for different actions. 58 | * 59 | * @return static 60 | * 61 | * @throws \Badoo\Jira\REST\Exception 62 | */ 63 | public static function get(int $id, \Badoo\Jira\REST\Client $Jira = null) 64 | { 65 | $Instance = new static($id, $Jira); 66 | $Instance->getOriginalObject(); 67 | 68 | return $Instance; 69 | } 70 | 71 | public function __construct(int $id, \Badoo\Jira\REST\Client $Jira = null) 72 | { 73 | if (!isset($Jira)) { 74 | $Jira = \Badoo\Jira\REST\Client::instance(); 75 | } 76 | 77 | $this->id = $id; 78 | $this->Jira = $Jira; 79 | } 80 | 81 | /** 82 | * @return \stdClass 83 | * 84 | * @throws \Badoo\Jira\REST\Exception 85 | */ 86 | protected function getOriginalObject() 87 | { 88 | if (!isset($this->OriginalObject)) { 89 | $this->OriginalObject = $this->Jira->issueType()->get($this->getId()); 90 | } 91 | 92 | return $this->OriginalObject; 93 | } 94 | 95 | public function getIssue() : ?\Badoo\Jira\Issue 96 | { 97 | return $this->Issue; 98 | } 99 | 100 | public function getId() : int 101 | { 102 | return $this->id; 103 | } 104 | 105 | public function getName() : string 106 | { 107 | return $this->getOriginalObject()->name; 108 | } 109 | 110 | public function getDescription() : string 111 | { 112 | return $this->getOriginalObject()->description ?? ''; 113 | } 114 | 115 | public function isSubtask() : bool 116 | { 117 | return $this->getOriginalObject()->subtask ?? false; 118 | } 119 | 120 | public function getSelf() : string 121 | { 122 | return $this->getOriginalObject()->self; 123 | } 124 | 125 | public function getIconUrl() : string 126 | { 127 | return $this->getOriginalObject()->iconUrl ?? ''; 128 | } 129 | 130 | public function getAvatarId() : string 131 | { 132 | return $this->getOriginalObject()->avatarId ?? ''; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/Issue/WatchersList.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\Issue; 8 | 9 | class WatchersList extends \Badoo\Jira\UsersList 10 | { 11 | protected $initialized = false; 12 | protected $loaded = false; 13 | 14 | /** 15 | * @param array $users_info 16 | * @param \Badoo\Jira\Issue $Issue 17 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 18 | * Enables you to access several JIRA instances from one piece of code, 19 | * or use different users for different actions. 20 | * 21 | * @return static 22 | * 23 | * @throws \Badoo\Jira\Exception 24 | */ 25 | public static function fromStdClass(array $users_info, \Badoo\Jira\Issue $Issue = null, \Badoo\Jira\REST\Client $Jira = null) : \Badoo\Jira\UsersList 26 | { 27 | if (!isset($Issue)) { 28 | throw new \Badoo\Jira\Exception("Watchers list requires parent Issue object to work properly"); 29 | } 30 | 31 | $users = []; 32 | foreach ($users_info as $UserInfo) { 33 | $users[] = \Badoo\Jira\User::fromStdClass($UserInfo, $Issue, $Jira); 34 | } 35 | 36 | $Instance = new static($users, $Jira); 37 | $Instance->Issue = $Issue; 38 | $Instance->initialized = true; 39 | 40 | return $Instance; 41 | } 42 | 43 | /** 44 | * @param string $issue_key 45 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 46 | * Enables you to access several JIRA instances from one piece of code, 47 | * or use different users for different actions. 48 | * 49 | * @return WatchersList 50 | * 51 | * @throws \Badoo\Jira\Exception 52 | * @throws \Badoo\Jira\Exception\Issue 53 | * @throws \Badoo\Jira\REST\Exception 54 | */ 55 | public static function forIssue(string $issue_key, \Badoo\Jira\REST\Client $Jira = null) : WatchersList 56 | { 57 | $Issue = new \Badoo\Jira\Issue($issue_key, $Jira); 58 | return $Issue->getWatchers(); 59 | } 60 | 61 | /** 62 | * @return $this 63 | * 64 | * @throws \Badoo\Jira\REST\Exception 65 | */ 66 | public function clearList() : \Badoo\Jira\UsersList 67 | { 68 | if ($this->initialized) { 69 | foreach ($this->getUsers() as $User) { 70 | $User->watchIssue($this->Issue->getKey(), false); 71 | }; 72 | } 73 | 74 | $this->loaded = false; 75 | return \Badoo\Jira\UsersList::clearList(); 76 | } 77 | 78 | /** 79 | * @return \Badoo\Jira\User[] 80 | * @throws \Badoo\Jira\REST\Exception 81 | */ 82 | public function getUsers() : array 83 | { 84 | if (!$this->loaded) { 85 | $watchers = $this->Jira->issue()->watchers()->list($this->Issue->getKey()); 86 | 87 | foreach ($watchers as $UserInfo) { 88 | $Watcher = \Badoo\Jira\User::fromStdClass($UserInfo, $this->Issue, $this->Jira); 89 | parent::addUsers($Watcher); 90 | } 91 | 92 | $this->loaded = true; 93 | } 94 | 95 | return parent::getUsers(); 96 | } 97 | 98 | /** 99 | * Add user to list of issue's watchers, using user's name (login) 100 | * 101 | * @param string ...$names 102 | * 103 | * @return $this 104 | * 105 | * @throws \Badoo\Jira\REST\Exception 106 | */ 107 | public function addUsersByName(string ...$names) : WatchersList 108 | { 109 | $users = []; 110 | foreach ($names as $name) { 111 | $users[] = new \Badoo\Jira\User($name, $this->Jira); 112 | } 113 | return $this->addUsers(...$users); 114 | } 115 | 116 | /** 117 | * Add user to list of issue's watchers 118 | * 119 | * @param \Badoo\Jira\User ...$users 120 | * 121 | * @return $this 122 | * 123 | * @throws \Badoo\Jira\REST\Exception 124 | */ 125 | public function addUsers(\Badoo\Jira\User ...$users) : \Badoo\Jira\UsersList 126 | { 127 | if ($this->initialized) { 128 | foreach ($users as $User) { 129 | $User->watchIssue($this->Issue->getKey()); 130 | } 131 | } 132 | 133 | return \Badoo\Jira\UsersList::addUsers(...$users); 134 | } 135 | 136 | /** 137 | * Remove user from list of issue's watchers, using user's name (login) 138 | * 139 | * @param string ...$names 140 | * 141 | * @return $this 142 | * 143 | * @throws \Badoo\Jira\REST\Exception 144 | */ 145 | public function removeUsersByName(string ...$names) : WatchersList 146 | { 147 | $users = []; 148 | foreach ($names as $name) { 149 | $users[] = new \Badoo\Jira\User($name, $this->Jira); 150 | } 151 | ; 152 | return $this->removeUsers($users); 153 | } 154 | 155 | /** 156 | * Remove user from list of issue's watchers 157 | * 158 | * @param \Badoo\Jira\User ...$users 159 | * 160 | * @return $this 161 | * 162 | * @throws \Badoo\Jira\REST\Exception 163 | */ 164 | public function removeUsers(\Badoo\Jira\User ...$users) : \Badoo\Jira\UsersList 165 | { 166 | if ($this->initialized) { 167 | foreach ($users as $User) { 168 | $User->watchIssue($this->Issue->getKey(), false); 169 | } 170 | } 171 | 172 | return \Badoo\Jira\UsersList::removeUsers(...$users); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/REST/Exception.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST; 8 | 9 | class Exception extends \Badoo\Jira\Exception 10 | { 11 | protected $api_response = null; 12 | 13 | /** 14 | * @return mixed 15 | */ 16 | public function getApiResponse() 17 | { 18 | return $this->api_response; 19 | } 20 | 21 | /** 22 | * @param mixed $api_response 23 | * @return $this 24 | */ 25 | public function setApiResponse($api_response) 26 | { 27 | $this->api_response = $api_response; 28 | return $this; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/REST/Exception/Authorization.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Exception; 8 | 9 | class Authorization extends \Badoo\Jira\REST\Exception 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/REST/Section/Attachment.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Attachment extends \Badoo\Jira\REST\Section\Section 10 | { 11 | /** 12 | * Get attachment file metadata by file ID 13 | * 14 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/attachment-getAttachment 15 | * 16 | * @param int $id - ID of attachment you want to load 17 | * 18 | * @return \stdClass - attachment metadata 19 | * 20 | * @throws \Badoo\Jira\REST\Exception 21 | */ 22 | public function get(int $id) : \stdClass 23 | { 24 | return $this->Jira->get("attachment/{$id}"); 25 | } 26 | 27 | /** 28 | * Delete attachment file from JIRA. 29 | * 30 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/attachment-removeAttachment 31 | * 32 | * @param int $id - ID of file to delete 33 | * 34 | * @throws \Badoo\Jira\REST\Exception 35 | */ 36 | public function delete(int $id) : void 37 | { 38 | $this->Jira->delete("attachment/{$id}"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/REST/Section/Comment.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Comment extends Section 10 | { 11 | /** 12 | * List at most comments starting from for issue 13 | * 14 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-getComments 15 | * 16 | * @param string $issue_key - parent issue key 17 | * @param int $start_at - list start position 18 | * @param int $max_results - maximum list size 19 | * @param string $order_by - field name to use for ordering 20 | * @param bool $expand_rendered - include 'renderedBody' into response data 21 | * 22 | * @return \stdClass[] - list of issue comments. 23 | * @see Comment::get() DocBlock method documentation for brief info about response data format 24 | * 25 | * @throws \Badoo\Jira\REST\Exception 26 | */ 27 | public function list(string $issue_key, int $start_at = 0, int $max_results = -1, string $order_by = '', bool $expand_rendered = false) : array 28 | { 29 | $args = [ 30 | 'startAt' => $start_at, 31 | ]; 32 | 33 | if ($max_results >= 0) { 34 | $args['maxResults'] = $max_results; 35 | } 36 | 37 | if (!empty($order_by)) { 38 | $args['orderBy'] = $order_by; 39 | } 40 | 41 | if ($expand_rendered) { 42 | $args['expand'] = 'renderedBody'; 43 | } 44 | 45 | $result = $this->Jira->get("issue/{$issue_key}/comment", $args); 46 | return $result->comments; 47 | } 48 | 49 | /** 50 | * Get single comment ID data 51 | * 52 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-getComment 53 | * 54 | * @param string $issue_key - parent issue key 55 | * @param int $id - unique comment ID 56 | * @param bool $expand_rendered - include 'renderedBody' into response data 57 | * 58 | * @return \stdClass - comment info (some of data is not shown) 59 | * [ 60 | * 'id' => , 61 | * 'author' => , 62 | * 'updateAuthor' => , 63 | * 'body' => , 64 | * 'renderedBody' => , 65 | * 'created' => , 66 | * 'updated' => , 67 | * 'visibility' => 68 | * ] 69 | * 70 | * @throws \Badoo\Jira\REST\Exception 71 | */ 72 | public function get(string $issue_key, int $id, $expand_rendered = false) : \stdClass 73 | { 74 | $args = []; 75 | if ($expand_rendered) { 76 | $args['expand'] = 'renderedBody'; 77 | } 78 | 79 | return $this->Jira->get("issue/{$issue_key}/comment/{$id}", $args); 80 | } 81 | 82 | /** 83 | * Add a comment with text to issue with key 84 | * 85 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-addComment 86 | * 87 | * @param string $issue_key - parent issue key 88 | * @param string $text - comment body as raw text 89 | * @param array|null $visibility - visibility restrictions, 90 | * ["type" => "role", "value" => "Administrators"] - project Administrators only 91 | * [] - default restrictions for new comments 92 | * null - no restrictions (public access) 93 | * @param bool $expand_rendered - include 'renderedBody' into response data 94 | * 95 | * @return \stdClass 96 | * @see Comment::get() DocBlock for more info about format 97 | * 98 | * @throws \Badoo\Jira\REST\Exception 99 | */ 100 | public function create(string $issue_key, string $text, ?array $visibility = [], bool $expand_rendered = false) : \stdClass 101 | { 102 | $args = [ 103 | 'body' => $text, 104 | ]; 105 | 106 | if ($visibility !== []) { 107 | $args['visibility'] = $visibility; 108 | } 109 | if ($expand_rendered) { 110 | $args['expand'] = 'renderedBody'; 111 | } 112 | 113 | return $this->Jira->post("issue/{$issue_key}/comment", $args); 114 | } 115 | 116 | /** 117 | * Update an existing comment 118 | * 119 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-updateComment 120 | * @see Comment::create() method DocBlock for more info about parameters and returned data. 121 | * 122 | * @param string $issue_key - key of issue that contains the comment 123 | * @param int $id - unique comment ID 124 | * @param string $text - new comment body (raw text) 125 | * @param array|null $visibility - the visibility restrictions. See Comment::add DocBlock for more info 126 | * [] - don't update restrictions 127 | * null - drop restrictions (public access) 128 | * @param bool $expand_rendered - include 'renderedBody' of new comment text into response data 129 | * 130 | * @return \stdClass - updated comment data 131 | * @see Comment::get() DocBlock for more info about format 132 | * 133 | * @throws \Badoo\Jira\REST\Exception 134 | */ 135 | public function update( 136 | string $issue_key, 137 | int $id, 138 | string $text, 139 | ?array $visibility = [], 140 | bool $expand_rendered = false 141 | ) : \stdClass { 142 | $args = [ 143 | 'body' => $text, 144 | ]; 145 | 146 | if ($visibility !== []) { 147 | $args['visibility'] = $visibility; 148 | } 149 | 150 | if ($expand_rendered) { 151 | $args['expand'] = 'renderedBody'; 152 | } 153 | 154 | return $this->Jira->put("issue/{$issue_key}/comment/{$id}", $args); 155 | } 156 | 157 | /** 158 | * Delete an existing comment 159 | * 160 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-deleteComment 161 | * 162 | * @param string $issue_key - key of issue that contains the comment 163 | * @param int $id - unique comment ID 164 | * 165 | * @throws \Badoo\Jira\REST\Exception 166 | */ 167 | public function delete(string $issue_key, int $id) : void 168 | { 169 | $this->Jira->delete("issue/{$issue_key}/comment/{$id}"); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/REST/Section/Component.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Component extends Section 10 | { 11 | /** 12 | * Get single JIRA Component data by ID 13 | * 14 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/component-getComponent 15 | * 16 | * @param int $id - unique Component ID 17 | * 18 | * @return \stdClass - component info (some of data is not shown) 19 | * [ 20 | * 'project' => , 21 | * 'projectId' => , 22 | * 'id' => , 23 | * 'name' => , 24 | * 'description' => , 25 | * 'lead' => , 26 | * 'assignee' => , 27 | * 'assigneeType' => 28 | * ... 29 | * ] 30 | * 31 | * @throws \Badoo\Jira\REST\Exception 32 | */ 33 | public function get(int $id) : \stdClass 34 | { 35 | return $this->Jira->get("component/{$id}"); 36 | } 37 | 38 | /** 39 | * Create new component with name in project 40 | * 41 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/component-createComponent 42 | * 43 | * @param string|int $project - parent project ID or key (e.g. 10000 or 'EX') 44 | * @param string $name - component name 45 | * @param array $optional_fields - additional fields to set for component 46 | * 47 | * @return \stdClass 48 | * @see Component::get() DocBlock for more info about format 49 | * 50 | * @throws \Badoo\Jira\REST\Exception 51 | */ 52 | public function create(string $project, string $name, array $optional_fields = []) : \stdClass 53 | { 54 | $args = $optional_fields; 55 | 56 | if (is_numeric($project)) { 57 | $args['projectId'] = (int)$project; 58 | } else { 59 | $args['project'] = $project; 60 | } 61 | 62 | $args['name'] = $name; 63 | 64 | return $this->Jira->post("component", $args); 65 | } 66 | 67 | /** 68 | * Update an existing component 69 | * 70 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/component-updateComponent 71 | * @see Component::create() method DocBlock for more info about parameters and returned data. 72 | * 73 | * @param int $id - unique Component ID 74 | * @param array $update - fields to update 75 | * e.g.: 76 | * { 77 | * 'name': "Component 1", 78 | * 'description': "This is a JIRA component", 79 | * 'leadUserName': "fred", 80 | * 'assigneeType': "PROJECT_LEAD", 81 | * 'isAssigneeTypeValid': false, 82 | * 'project': "PROJECTKEY", 83 | * 'projectId': 10000 84 | * } 85 | * 86 | * @return \stdClass - updated component info 87 | * @see Component::get() DocBlock for more info about format 88 | * 89 | * @throws \Badoo\Jira\REST\Exception 90 | */ 91 | public function update( 92 | int $id, 93 | array $update = [] 94 | ) : \stdClass { 95 | return $this->Jira->put("component/{$id}", $update); 96 | } 97 | 98 | 99 | /** 100 | * Delete an existing Component 101 | * 102 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/component-delete 103 | * 104 | * @param int $id - unique Component ID 105 | * @param int $move_issues_to - apply this component to all issues, who had the deleted one 106 | * 107 | * @throws \Badoo\Jira\REST\Exception 108 | */ 109 | public function delete(int $id, int $move_issues_to = 0) : void 110 | { 111 | $args = []; 112 | 113 | if ($move_issues_to === 0) { 114 | $args['moveIssuesTo'] = $move_issues_to; 115 | } 116 | 117 | $this->Jira->delete("component/{$id}", $args); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/REST/Section/Field.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Field extends Section 10 | { 11 | /** @var array */ 12 | protected $fields_list; 13 | /** @var array */ 14 | protected $system_fields; 15 | /** @var array */ 16 | protected $custom_fields; 17 | 18 | /** 19 | * Create new custom field. 20 | * 21 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/field-createCustomField 22 | * 23 | * @param string $name - new custom field name 24 | * @param string $description - field description 25 | * @param string $type - field type 26 | * @param array $add_properties - additional properties for field. 27 | * They may depend on field type you are trying to create. 28 | * 29 | * @throws \Badoo\Jira\REST\Exception 30 | */ 31 | public function create(string $name, string $description, string $type, array $add_properties = []) : void 32 | { 33 | $args = [ 34 | 'name' => $name, 35 | 'description' => $description, 36 | 'type' => $type, 37 | ]; 38 | 39 | $args = array_merge($args, $add_properties); 40 | 41 | $this->Jira->post('/field', $args); 42 | 43 | // Drop fields list cache, we just have added a new field into the list 44 | $this->fields_list = null; 45 | $this->system_fields = null; 46 | $this->custom_fields = null; 47 | } 48 | 49 | /** 50 | * Get list of all fields, system and custom ones 51 | * 52 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/field-getFields 53 | * 54 | * @param bool $reload_cache - force data reload. The method caches fields list, you can bypass cache and make it 55 | * to load fresh data from API once again. 56 | * 57 | * @return \stdClass[] - list of fields, both system and custom, indexed by IDs 58 | * (e.g. 'description' or 'customfield_12345') 59 | * 60 | * @throws \Badoo\Jira\REST\Exception 61 | */ 62 | public function list(bool $reload_cache = false) : array 63 | { 64 | if (!isset($this->fields_list) || $reload_cache) { 65 | $this->fields_list = []; 66 | 67 | $fields_list = $this->Jira->get('/field'); 68 | foreach ($fields_list as $field_info) { 69 | $is_custom = $field_info->custom; 70 | $field_id = $field_info->id; 71 | 72 | $this->fields_list[$field_id] = $field_info; 73 | 74 | if ($is_custom) { 75 | $this->custom_fields[$field_id] = $field_info; 76 | } else { 77 | $this->system_fields[$field_id] = $field_info; 78 | } 79 | } 80 | } 81 | 82 | return $this->fields_list; 83 | } 84 | 85 | /** 86 | * @see Field::list() method DocBlock for more information 87 | * 88 | * Get list of system fields 89 | * 90 | * @param bool $reload_cache - force data reload 91 | * 92 | * @return \stdClass[] - list of system fields indexed by field ID (e.g. 'description') 93 | * 94 | * @throws \Badoo\Jira\REST\Exception 95 | */ 96 | public function listSystem(bool $reload_cache = false) : array 97 | { 98 | if (!isset($this->system_fields) || $reload_cache) { 99 | $this->list($reload_cache); 100 | } 101 | 102 | return $this->system_fields; 103 | } 104 | 105 | /** 106 | * @see Field::list() method DocBlock for more information 107 | * 108 | * Get list of custom fields 109 | * 110 | * @param bool $reload_cache - force data reload 111 | * 112 | * @return \stdClass[] - list of custom fields indexed by field ID (e.g. 'customfield_12345') 113 | * @throws \Badoo\Jira\REST\Exception 114 | */ 115 | public function listCustom(bool $reload_cache = false) : array 116 | { 117 | if (!isset($this->custom_fields) || $reload_cache) { 118 | $this->list($reload_cache); 119 | } 120 | 121 | return $this->custom_fields; 122 | } 123 | 124 | /** 125 | * @param string $id 126 | * @param bool $reload_cache 127 | * 128 | * @return \stdClass 129 | * 130 | * @throws \Badoo\Jira\REST\Exception 131 | */ 132 | public function get(string $id, bool $reload_cache = false) : \stdClass 133 | { 134 | $fields = $this->list($reload_cache); 135 | 136 | if (!isset($fields[$id])) { 137 | throw new \Badoo\Jira\REST\Exception("Field with ID '{$id}' not found in JIRA"); 138 | } 139 | 140 | return $fields[$id]; 141 | } 142 | 143 | /** 144 | * Search field by name. Return the first one found 145 | * 146 | * NOTE: this is synthetic method, JIRA API has no special method 147 | * 148 | * @param string $name - search for field with this name 149 | * @param bool $case_sensitive - perform case-sensitive or case-insensitive search 150 | * @param bool $reload_cache - force internal cache reload and request API for the fresh data before search 151 | * 152 | * @return \stdClass[] - list of fields with given name 153 | * 154 | * @throws \Badoo\Jira\REST\Exception 155 | */ 156 | public function search(string $name, bool $case_sensitive = true, bool $reload_cache = false) : array 157 | { 158 | $found = []; 159 | 160 | if ($case_sensitive) { 161 | foreach ($this->list($reload_cache) as $FieldInfo) { 162 | if ($FieldInfo->name === $name) { 163 | $found[$FieldInfo->id] = $FieldInfo; 164 | } 165 | } 166 | } else { 167 | $name = strtolower($name); 168 | 169 | foreach ($this->list($reload_cache) as $FieldInfo) { 170 | if (strtolower($FieldInfo->name) === $name) { 171 | $found[$FieldInfo->id] = $FieldInfo; 172 | } 173 | } 174 | } 175 | 176 | return $found; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/REST/Section/IssueAttachment.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class IssueAttachment extends Section 10 | { 11 | protected $attachments = []; 12 | 13 | protected function cacheAttachments(\stdClass $IssueInfo) : array 14 | { 15 | $attachments = $IssueInfo->fields->attachment ?? []; 16 | 17 | $this->attachments[$IssueInfo->key] = $attachments; 18 | $this->attachments[$IssueInfo->id] = $attachments; 19 | 20 | return $attachments; 21 | } 22 | 23 | protected function getCached(string $issue_key) : ?array 24 | { 25 | return $this->attachments[$issue_key] ?? null; 26 | } 27 | 28 | /** 29 | * List all issue attachments. 30 | * 31 | * NOTE: this is synthetic method, JIRA API has no special method for listing issue attachments. 32 | * They are listed as part of issue in 'attachment' field 33 | * 34 | * @param string $issue_key 35 | * @param bool $reload_cache - force internal cache reload. 36 | * When you try to load attachments for the same issue twice, it will cause only one 37 | * real API request if is false 38 | * 39 | * @return \stdClass[] - list of files attached to issue 40 | * 41 | * @throws \Badoo\Jira\REST\Exception 42 | */ 43 | public function list(string $issue_key, bool $reload_cache = false) : array 44 | { 45 | $attachments = $this->getCached($issue_key); 46 | 47 | if (!isset($attachments) || $reload_cache) { 48 | $IssueInfo = $this->Jira->get( 49 | "issue/{$issue_key}", 50 | [ 'fields' => "id,key,attachment"] 51 | ); 52 | $attachments = $this->cacheAttachments($IssueInfo); 53 | } 54 | 55 | return $attachments; 56 | } 57 | 58 | /** 59 | * Attach new file to issue 60 | * 61 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue/{issueIdOrKey}/attachments-addAttachment 62 | * 63 | * @param string $issue_key - key of issue to attach files to. 64 | * @param string $file_path - path to file to upload to Jira as attachment to an issue. 65 | * @param string $file_type - file's mime type 66 | * @param string $file_name - name of file to be sent to Jira. 67 | * 68 | * @return \stdClass - attachment info 69 | * [ 70 | * 'id' => , 71 | * 'filename' => , 72 | * 'author' => , 73 | * 'created' => , 74 | * 'size' => , 75 | * 'mimeType' => , 76 | * 'content' => , 77 | * 'thumbnail' => 78 | * ] 79 | * 80 | * @throws \Badoo\Jira\REST\Exception 81 | */ 82 | public function create(string $issue_key, string $file_path, ?string $file_name = null, ?string $file_type = null) : \stdClass 83 | { 84 | $File = new \CURLFile($file_path, $file_type, $file_name); 85 | $response = $this->Jira->multipart("issue/{$issue_key}/attachments", ['file' => $File]); 86 | 87 | return reset($response); 88 | } 89 | 90 | /** 91 | * Get specific issue attachment info 92 | * 93 | * NOTE: this is synthetic method, JIRA API has no special method 94 | * 95 | * @param string $issue_key 96 | * @param int $id 97 | * @param bool $reload_cache 98 | * 99 | * @return \stdClass 100 | * 101 | * @throws \Badoo\Jira\REST\Exception 102 | */ 103 | public function get(string $issue_key, int $id, bool $reload_cache = false) : \stdClass 104 | { 105 | $attachments = $this->list($issue_key, $reload_cache); 106 | 107 | foreach ($attachments as $AttachmentInfo) { 108 | if ((int)$AttachmentInfo->id === $id) { 109 | return $AttachmentInfo; 110 | } 111 | } 112 | 113 | throw new \Badoo\Jira\REST\Exception( 114 | "Attachment with ID {$id} not found in issue {$issue_key}" 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/REST/Section/IssueLink.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class IssueLink extends Section 10 | { 11 | /** 12 | * Create a link between two issues. 13 | * 14 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issueLink-linkIssues 15 | * 16 | * @param string $type - link type name. Don't mess with link texts for 'in' and 'out' ends. 17 | * @param string $inward_issue - attach inward end of the link to this issue. 18 | * this issue will be shown on page with Inward Description text 19 | * @param string $outward_issue - attach outward end of the link to this issue 20 | * this issue will be shown on page with Outward Description text 21 | * @param string $comment - add a comment to both linked issues 22 | * @param array $visibility - set comment visibility 23 | * 24 | * @throws \Badoo\Jira\REST\Exception 25 | */ 26 | public function create( 27 | string $type, 28 | string $outward_issue, 29 | string $inward_issue, 30 | string $comment = '', 31 | array $visibility = [] 32 | ) : void { 33 | $args = [ 34 | 'type' => ['name' => $type], 35 | 'outwardIssue' => ['key' => $outward_issue], 36 | 'inwardIssue' => ['key' => $inward_issue], 37 | ]; 38 | 39 | if (!empty($comment)) { 40 | $comment_arg = [ 41 | 'body' => $comment 42 | ]; 43 | 44 | if (!empty($visibility)) { 45 | $comment_arg['visibility'] = $visibility; 46 | } 47 | 48 | $args['comment'] = $comment_arg; 49 | } 50 | 51 | $this->Jira->post('issueLink', $args); 52 | } 53 | 54 | /** 55 | * Get info for specific link between two issues 56 | * 57 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issueLink-getIssueLink 58 | * 59 | * @param int $link_id - ID of link to get 60 | * 61 | * @return \stdClass 62 | * 63 | * @throws \Badoo\Jira\REST\Exception 64 | */ 65 | public function get(int $link_id) : \stdClass 66 | { 67 | return $this->Jira->get("issueLink/{$link_id}"); 68 | } 69 | 70 | /** 71 | * Delete a link between issues 72 | * 73 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issueLink-deleteIssueLink 74 | * 75 | * @param int $link_id - ID of link to delete 76 | * 77 | * @throws \Badoo\Jira\REST\Exception 78 | */ 79 | public function delete(int $link_id) : void 80 | { 81 | $this->Jira->delete("issueLink/{$link_id}"); 82 | } 83 | 84 | /** 85 | * List links attached to specific issue. 86 | * 87 | * NOTE: this is a synthetic method. JIRA API has no special method for doing this. 88 | * 89 | * @param string $issue_key - get links attached to this issue 90 | * @param string $type - list only links of specific type. Search is performed in link type name AND 91 | * inward/outward description text 92 | * Example: 93 | * for 'Dependency' value this will return both 'blocks' and 'is blocked by' 94 | * for 'blocks' this will return only 'blocks' links 95 | * @param bool $case_sensitive - perform case sensitive filtration by parameter 96 | * 97 | * @return \stdClass[] 98 | * 99 | * @throws \Badoo\Jira\REST\Exception 100 | */ 101 | public function listForIssue(string $issue_key, string $type = '', bool $case_sensitive = false) : array 102 | { 103 | $IssueInfo = $this->Jira->get("/issue/{$issue_key}", ['fields' => 'issuelinks']); 104 | 105 | $links = $IssueInfo->fields->issuelinks; 106 | 107 | if (empty($type)) { 108 | return $links; 109 | } 110 | 111 | $filtered = []; 112 | if ($case_sensitive) { 113 | foreach ($links as $LinkInfo) { 114 | if ($LinkInfo->type->name === $type || 115 | $LinkInfo->type->inward === $type || 116 | $LinkInfo->type->outward === $type) { 117 | $filtered[] = $LinkInfo; 118 | } 119 | } 120 | } else { 121 | $type = strtolower($type); 122 | foreach ($links as $LinkInfo) { 123 | if (strtolower($LinkInfo->type->name) === $type || 124 | strtolower($LinkInfo->type->inward) === $type || 125 | strtolower($LinkInfo->type->outward) === $type) { 126 | $filtered[] = $LinkInfo; 127 | } 128 | } 129 | } 130 | 131 | return $filtered; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/REST/Section/IssueLinkType.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class IssueLinkType extends Section 10 | { 11 | /** @var \stdClass[] */ 12 | protected $link_types_list = []; 13 | /** @var bool */ 14 | protected $all_cached = false; 15 | 16 | protected function cacheLinkType(\stdClass $LinkTypeInfo) 17 | { 18 | $this->link_types_list[$LinkTypeInfo->id] = $LinkTypeInfo; 19 | } 20 | 21 | /** 22 | * Get list of all known issue types 23 | * 24 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issueLinkType-getIssueLinkTypes 25 | * 26 | * @param bool $reload_cache - ignore cache and load fresh data from API 27 | * 28 | * @return \stdClass[] - list of all known issue types indexed by IDs 29 | * 30 | * @throws \Badoo\Jira\REST\Exception 31 | */ 32 | public function list(bool $reload_cache = false) : array 33 | { 34 | if (!$this->all_cached || $reload_cache) { 35 | $response = $this->Jira->get('issueLinkType'); 36 | 37 | foreach ($response->issueLinkTypes as $LinkTypeInfo) { 38 | $this->cacheLinkType($LinkTypeInfo); 39 | } 40 | 41 | $this->all_cached = true; 42 | } 43 | 44 | return $this->link_types_list; 45 | } 46 | 47 | /** 48 | * Create a link between two issues. 49 | * 50 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issueLinkType-createIssueLinkType 51 | * 52 | * @param string $name - link type name 53 | * @param string $outward - text to display for inward issue 54 | * @param string $inward - text to display for outward issue 55 | * 56 | * @return \stdClass 57 | * 58 | * @throws \Badoo\Jira\REST\Exception 59 | */ 60 | public function create( 61 | string $name, 62 | string $inward, 63 | string $outward 64 | ) : \stdClass { 65 | $args = [ 66 | 'name' => $name, 67 | 'inward' => $inward, 68 | 'outward' => $outward, 69 | ]; 70 | 71 | $LinkTypeInfo = $this->Jira->post('issueLinkType', $args); 72 | $this->cacheLinkType($LinkTypeInfo); 73 | 74 | return $LinkTypeInfo; 75 | } 76 | 77 | /** 78 | * Get info for specific link type 79 | * 80 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issueLinkType-getIssueLinkType 81 | * 82 | * @param int $link_type_id - ID of link type to get 83 | * @param bool $reload_cache - ignore cache and load fresh data from API 84 | * 85 | * @return \stdClass - link type info, see ::create method DocBlock for format description. 86 | * 87 | * @throws \Badoo\Jira\REST\Exception 88 | */ 89 | public function get(int $link_type_id, bool $reload_cache = false) : \stdClass 90 | { 91 | $LinkTypeInfo = $this->link_types_list[$link_type_id] ?? null; 92 | if (!isset($LinkTypeInfo) || $reload_cache) { 93 | $LinkTypeInfo = $this->Jira->get("issueLinkType/{$link_type_id}"); 94 | $this->cacheLinkType($LinkTypeInfo); 95 | } 96 | 97 | return $LinkTypeInfo; 98 | } 99 | 100 | /** 101 | * Update link type information 102 | * 103 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issueLinkType-updateIssueLinkType 104 | * 105 | * @param int $link_type_id - ID of link type to get 106 | * @param string $name - new link type name. Empty string means 'do not update'. 107 | * @param string $outward - new text to display for inward issue. Empty string means 'do not update'. 108 | * @param string $inward - new text to display for outward issue. Empty string means 'do not update'. 109 | * 110 | * @return \stdClass - link type info, see ::create method DocBlock for format description. 111 | * 112 | * @throws \Badoo\Jira\REST\Exception 113 | */ 114 | public function update( 115 | int $link_type_id, 116 | string $name = '', 117 | string $inward = '', 118 | string $outward = '' 119 | ) : \stdClass { 120 | $args = [ 121 | 'name' => $name, 122 | 'inward' => $inward, 123 | 'outward' => $outward, 124 | ]; 125 | 126 | if (isset($name)) { 127 | $args['name'] = $name; 128 | } 129 | if (isset($inward)) { 130 | $args['inward'] = $inward; 131 | } 132 | if (isset($outward)) { 133 | $args['outward'] = $outward; 134 | } 135 | 136 | $LinkTypeInfo = $this->Jira->put("issueLinkType/{$link_type_id}", $args); 137 | $this->cacheLinkType($LinkTypeInfo); 138 | 139 | return $this->Jira->put("issueLinkType/{$link_type_id}", $args); 140 | } 141 | 142 | /** 143 | * Delete a link between issues 144 | * 145 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issueLink-deleteIssueLink 146 | * 147 | * @param int $link_id - ID of link to delete 148 | * 149 | * @throws \Badoo\Jira\REST\Exception 150 | */ 151 | public function delete(int $link_id) : void 152 | { 153 | $this->Jira->delete("issueLinkType/{$link_id}"); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/REST/Section/IssueType.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class IssueType extends Section 10 | { 11 | /** @var \stdClass[] */ 12 | protected $types_list; 13 | /** @var bool - sign that contains full list of types available for user */ 14 | protected $all_cached = false; 15 | 16 | protected function cacheTypeInfo(\stdClass $TypeInfo) 17 | { 18 | $this->types_list[$TypeInfo->id] = $TypeInfo; 19 | } 20 | 21 | /** 22 | * List all issue types visible to the current user 23 | * 24 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issuetype-getIssueAllTypes 25 | * 26 | * @param bool $reload_cache - ignore internal client cache and request API for fresh data 27 | * 28 | * @return \stdClass[] 29 | * 30 | * @throws \Badoo\Jira\REST\Exception 31 | */ 32 | public function list(bool $reload_cache = false) : array 33 | { 34 | if (!$this->all_cached || $reload_cache) { 35 | $this->types_list = []; 36 | foreach ($this->Jira->get("/issuetype") as $TypeInfo) { 37 | $this->cacheTypeInfo($TypeInfo); 38 | } 39 | $this->all_cached = true; 40 | } 41 | 42 | return $this->types_list; 43 | } 44 | 45 | /** 46 | * Get issue type info 47 | * 48 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issuetype-getIssueType 49 | * 50 | * @param int $id - unique ID of issue type 51 | * @param bool $reload_cache - ignore internal client cache and request API for fresh data 52 | * 53 | * @return \stdClass 54 | * 55 | * @throws \Badoo\Jira\REST\Exception 56 | */ 57 | public function get(int $id, bool $reload_cache = false) : \stdClass 58 | { 59 | $TypeInfo = $this->types_list[$id] ?? null; 60 | if (!isset($TypeInfo) || $reload_cache) { 61 | $TypeInfo = $this->Jira->get("/issuetype/{$id}"); 62 | } 63 | 64 | $this->cacheTypeInfo($TypeInfo); 65 | return $TypeInfo; 66 | } 67 | 68 | /** 69 | * Search issue type by name. 70 | * 71 | * NOTE: this is synthetic method, JIRA API has no special method searching types by name 72 | * The full list of issue types is loaded before search. 73 | * 74 | * @see IssueType::list() 75 | * 76 | * @param string $type_name - desired issue type name 77 | * @param bool $case_sensitive - perform case sensitive search. True by default 78 | * @param bool $reload_cache - ignore internal client cache and request JIRA API for fresh data 79 | * 80 | * @return \stdClass 81 | * 82 | * @throws \Badoo\Jira\REST\Exception 83 | */ 84 | public function searchByName(string $type_name, bool $case_sensitive = true, bool $reload_cache = false) : ?\stdClass 85 | { 86 | $types = $this->list($reload_cache); 87 | 88 | if ($case_sensitive) { 89 | foreach ($types as $TypeInfo) { 90 | if ($TypeInfo->name === $type_name) { 91 | return $TypeInfo; 92 | } 93 | } 94 | 95 | return null; 96 | } 97 | 98 | $type_name = strtolower($type_name); 99 | 100 | foreach ($types as $TypeInfo) { 101 | if (strtolower($TypeInfo->name) === $type_name) { 102 | return $TypeInfo; 103 | } 104 | } 105 | 106 | return null; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/REST/Section/Jql.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Jql extends Section 10 | { 11 | /** 12 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/jql/autocompletedata-getFieldAutoCompleteForQueryString 13 | * 14 | * @param string $field_name - list possible values for field 15 | * @param string $field_value - list only values starting with this text 16 | * @param string $predicate_name - see API Web documentation 17 | * @param string $predicate_value - see API Web documentation 18 | * 19 | * @return array 20 | * 21 | * @throws \Badoo\Jira\REST\Exception 22 | */ 23 | public function getFieldSuggestions( 24 | string $field_name, 25 | string $field_value = '', 26 | string $predicate_name = '', 27 | string $predicate_value = '' 28 | ) { 29 | $parameters = [ 30 | 'fieldName' => $field_name, 31 | ]; 32 | 33 | if (!empty($field_value)) { 34 | $parameters['fieldValue'] = $field_value; 35 | } 36 | 37 | if (!empty($predicate_name)) { 38 | $parameters['predicateName'] = $predicate_name; 39 | } 40 | 41 | if (!empty($predicate_value)) { 42 | $parameters['predicateValue'] = $predicate_value; 43 | } 44 | 45 | $Response = $this->Jira->get('jql/autocompletedata/suggestions', $parameters); 46 | return $Response->results; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/REST/Section/Priority.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Priority extends Section 10 | { 11 | /** @var \stdClass[] */ 12 | protected $priorities_list = []; 13 | /** @var bool */ 14 | protected $all_cached = false; 15 | 16 | protected function cachePriority(\stdClass $PriorityInfo) 17 | { 18 | $this->priorities_list[(int)$PriorityInfo->id] = $PriorityInfo; 19 | } 20 | 21 | /** 22 | * List all known issue priorities 23 | * 24 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/priority-getPriorities 25 | * 26 | * @param bool $reload_cache - force API request to load fresh data 27 | * 28 | * @return \stdClass[] 29 | * 30 | * @throws \Badoo\Jira\REST\Exception 31 | */ 32 | public function list($reload_cache = false) : array 33 | { 34 | if (!$this->all_cached || $reload_cache) { 35 | $this->priorities_list = []; 36 | foreach ($this->Jira->get('priority') as $PriorityInfo) { 37 | $this->cachePriority($PriorityInfo); 38 | } 39 | $this->all_cached = true; 40 | } 41 | 42 | return $this->priorities_list; 43 | } 44 | 45 | /** 46 | * Get particular priority info 47 | * 48 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/priority-getPriority 49 | * 50 | * @param int $id - ID of priority 51 | * @param bool $reload_cache - force API request to load fresh data 52 | * 53 | * @return \stdClass|\stdClass[]|string|null 54 | * 55 | * @throws \Badoo\Jira\REST\Exception 56 | */ 57 | public function get(int $id, bool $reload_cache = false) : \stdClass 58 | { 59 | $PriorityInfo = $this->priorities_list[$id] ?? null; 60 | 61 | if (!isset($PriorityInfo) || $reload_cache) { 62 | $PriorityInfo = $this->Jira->get("priority/{$id}"); 63 | $this->cachePriority($PriorityInfo); 64 | } 65 | 66 | return $PriorityInfo; 67 | } 68 | 69 | /** 70 | * Search priority by name. 71 | * 72 | * NOTE: this is synthetic method, JIRA API has no special method searching priorities by name 73 | * The full list of priorities is loaded before search. 74 | * 75 | * @see Priority::list() 76 | * 77 | * @param string $priority_name - desired priority name 78 | * @param bool $case_sensitive - perform case sensitive search. True by default 79 | * @param bool $reload_cache - ignore internal client cache and request JIRA API for fresh data 80 | * 81 | * @return \stdClass 82 | * 83 | * @throws \Badoo\Jira\REST\Exception 84 | * 85 | */ 86 | public function searchByName(string $priority_name, bool $case_sensitive = true, bool $reload_cache = false) : ?\stdClass 87 | { 88 | $priorities = $this->list($reload_cache); 89 | 90 | if ($case_sensitive) { 91 | foreach ($priorities as $PriorityInfo) { 92 | if ($PriorityInfo->name === $priority_name) { 93 | return $PriorityInfo; 94 | } 95 | } 96 | 97 | return null; 98 | } 99 | 100 | $priority_name = strtolower($priority_name); 101 | 102 | foreach ($priorities as $PriorityInfo) { 103 | if (strtolower($PriorityInfo->name) === $priority_name) { 104 | return $PriorityInfo; 105 | } 106 | } 107 | 108 | return null; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/REST/Section/Project.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Project extends Section 10 | { 11 | /** 12 | * Get specific project info 13 | * 14 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/project-getProject 15 | * 16 | * @param string|int $project - project ID (e.g. 100500) or key (e.g. 'EX') 17 | * @param string[] $expand - ask JIRA to provide additional info in response 18 | * 19 | * @return \stdClass - project info 20 | * 21 | * @throws \Badoo\Jira\REST\Exception 22 | */ 23 | public function get($project, array $expand = []) 24 | { 25 | $parameters = []; 26 | 27 | if (!empty($expand)) { 28 | $parameters['expand'] = implode(',', $expand); 29 | } 30 | 31 | return $this->Jira->get("project/{$project}", $parameters); 32 | } 33 | 34 | /** 35 | * Returns all projects which are visible for the currently logged in user. 36 | * If no user is logged in, it returns the list of projects that are visible when using anonymous access. 37 | * 38 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/project 39 | * 40 | * @return \stdClass[] 41 | * 42 | * @throws \Badoo\Jira\REST\Exception 43 | */ 44 | public function list() : array 45 | { 46 | return $this->Jira->get('project'); 47 | } 48 | 49 | /** 50 | * List all project components 51 | * 52 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/project-getProjectComponents 53 | * 54 | * @param string|int $project - project ID (e.g. 100500) or key (e.g. 'EX') 55 | * 56 | * @return \stdClass[] - list of Component info objects 57 | * @see Component::get() for mor info about data structure 58 | * 59 | * @throws \Badoo\Jira\REST\Exception 60 | */ 61 | public function listComponents($project) : array 62 | { 63 | return $this->Jira->get("project/{$project}/components"); 64 | } 65 | 66 | /** 67 | * List all project versions 68 | * 69 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/project-getProjectVersions 70 | * 71 | * @param string|int $project - project ID (e.g. 100500) or key (e.g. 'EX') 72 | * 73 | * @return \stdClass[] - list of Version info objects 74 | * 75 | * @throws \Badoo\Jira\REST\Exception 76 | */ 77 | public function listVersions($project) : array 78 | { 79 | return $this->Jira->get("project/{$project}/versions"); 80 | } 81 | 82 | /** 83 | * Returns latest version in project
84 | * **ATTENTION**: semver only 85 | * 86 | * @see https://www.php.net/manual/en/function.version-compare.php More about version comparison 87 | * 88 | * @param $project string|int 89 | * 90 | * @return \stdClass|null 91 | * 92 | * @throws \Badoo\Jira\REST\Exception 93 | */ 94 | public function getLatestVersion($project) : ?\stdClass 95 | { 96 | $versions = $this->listVersions($project); 97 | if (empty($versions)) { 98 | return null; 99 | } 100 | $latest_version = array_pop($versions); 101 | foreach ($versions as $version) { 102 | if (version_compare($version->name ?? '', $latest_version->name ?? '', 'gt')) { 103 | $latest_version = $version; 104 | } 105 | } 106 | return $latest_version; 107 | } 108 | 109 | 110 | /** 111 | * List all project statuses 112 | * 113 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/project-getAllStatuses 114 | * 115 | * @param $project 116 | * 117 | * @return \stdClass[] 118 | * 119 | * @throws \Badoo\Jira\REST\Exception 120 | */ 121 | public function listStatuses($project) : array 122 | { 123 | return $this->Jira->get("project/{$project}/statuses"); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/REST/Section/Resolution.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Resolution extends Section 10 | { 11 | /** @var array */ 12 | protected $resolutions_list = []; 13 | /** @var bool */ 14 | protected $all_cached = false; 15 | 16 | protected function cacheResolutionInfo(\stdClass $ResolutionInfo) 17 | { 18 | $this->resolutions_list[(int)$ResolutionInfo->id]; 19 | } 20 | 21 | /** 22 | * Get list of all resolutions configured in current JIRA installation 23 | * 24 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/resolution-getResolutions 25 | * 26 | * @param bool $reload_cache - force API request to get fresh data from JIRA 27 | * 28 | * @return \stdClass[] - list of resolutions, indexed by IDs 29 | * 30 | * @throws \Badoo\Jira\REST\Exception 31 | */ 32 | public function list(bool $reload_cache = false) : array 33 | { 34 | if (!$this->all_cached || $reload_cache) { 35 | foreach ($this->Jira->get('/resolution') as $ResolutionInfo) { 36 | $this->cacheResolutionInfo($ResolutionInfo); 37 | } 38 | $this->all_cached = true; 39 | } 40 | 41 | return $this->resolutions_list; 42 | } 43 | 44 | /** 45 | * Get particular resolution info identified by it's unique ID 46 | * 47 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/resolution-getResolution 48 | * 49 | * @param int $id - ID of resolution you want to load 50 | * @param bool $reload_cache - force API request to get fresh data from JIRA 51 | * 52 | * @return \stdClass 53 | * 54 | * @throws \Badoo\Jira\REST\Exception 55 | */ 56 | public function get(int $id, bool $reload_cache = false) : \stdClass 57 | { 58 | $ResolutionInfo = $this->resolutions_list[$id] || null; 59 | 60 | if (!isset($ResolutionInfo) || $reload_cache) { 61 | $ResolutionInfo = $this->Jira->get("/resolution/{$id}"); 62 | $this->cacheResolutionInfo($ResolutionInfo); 63 | } 64 | 65 | return $ResolutionInfo; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/REST/Section/Section.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Section 10 | { 11 | /** @var \Badoo\Jira\REST\ClientRaw */ 12 | protected $Jira; 13 | 14 | /** @var \Badoo\Jira\REST\Section\Section[] $sections */ 15 | protected $sections = []; 16 | 17 | /** @var bool $isCloudJira - for some sections it's important to know if we query cloud or server Jira */ 18 | protected $is_cloud_jira = false; 19 | 20 | /** 21 | * ASection constructor. 22 | * @param \Badoo\Jira\REST\ClientRaw $Jira 23 | */ 24 | public function __construct(\Badoo\Jira\REST\ClientRaw $Jira, ?bool $is_cloud_jira = null) 25 | { 26 | $this->Jira = $Jira; 27 | $this->is_cloud_jira = $is_cloud_jira; 28 | } 29 | 30 | protected function isCloudJira(): bool 31 | { 32 | if ($this->is_cloud_jira === null) { 33 | $server_info = $this->Jira->get("/serverInfo"); 34 | $this->is_cloud_jira = ($server_info->deploymentType ?? '') === 'Cloud'; 35 | } 36 | return $this->is_cloud_jira; 37 | } 38 | 39 | /** 40 | * @param string $section_key - the unique section key for cache. This prevents twin objects creation for 41 | * the same section on each method call. 42 | * 43 | * @param string $section_class - use special custom class for given section. 44 | * E.g. ->getSubSection('/issue', '\Badoo\Jira\REST\Section\Issue') will initialize 45 | * and return \Badoo\Jira\REST\Section\Issue class for section /issue. 46 | * 47 | * @return Section 48 | */ 49 | protected function getSection(string $section_key, string $section_class) 50 | { 51 | if (!isset($this->sections[$section_key])) { 52 | $Section = new $section_class($this->Jira, $this->isCloudJira()); 53 | $this->sections[$section_key] = $Section; 54 | } 55 | 56 | return $this->sections[$section_key]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/REST/Section/SecurityLevel.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class SecurityLevel extends Section 10 | { 11 | /** @var array */ 12 | protected $security_levels_list = []; 13 | 14 | protected function cacheSecurityLevelInfo(\stdClass $SecurityLevelInfo) 15 | { 16 | $this->security_levels_list[(int)$SecurityLevelInfo->id] = $SecurityLevelInfo; 17 | } 18 | 19 | /** 20 | * Get particular security level info identified by it's unique ID 21 | * 22 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/securitylevel-getIssuesecuritylevel 23 | * 24 | * @param int $id - ID of security level you want to load 25 | * @param bool $reload_cache - force API request to get fresh data from JIRA 26 | * 27 | * @return \stdClass 28 | * 29 | * @throws \Badoo\Jira\REST\Exception 30 | */ 31 | public function get(int $id, bool $reload_cache = false) : \stdClass 32 | { 33 | $SecurityLevelInfo = $this->security_levels_list[$id] ?? null; 34 | 35 | if (!isset($SecurityLevelInfo) || $reload_cache) { 36 | $SecurityLevelInfo = $this->Jira->get("/securitylevel/{$id}"); 37 | $this->cacheSecurityLevelInfo($SecurityLevelInfo); 38 | } 39 | 40 | return $SecurityLevelInfo; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/REST/Section/Status.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Status extends Section 10 | { 11 | /** @var array */ 12 | protected $statuses_list = []; 13 | /** @var bool */ 14 | protected $all_cached = false; 15 | 16 | protected function cachestatusInfo(\stdClass $StatusInfo) 17 | { 18 | $this->statuses_list[(int)$StatusInfo->id] = $StatusInfo; 19 | } 20 | 21 | /** 22 | * Get list of all statuses configured in current JIRA installation 23 | * 24 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/status-getStatuses 25 | * 26 | * @param bool $reload_cache - force API request to get fresh data from JIRA 27 | * 28 | * @return \stdClass[] - list of statuses, indexed by IDs 29 | * 30 | * @throws \Badoo\Jira\REST\Exception 31 | */ 32 | public function list(bool $reload_cache = false) : array 33 | { 34 | if (!$this->all_cached || $reload_cache) { 35 | foreach ($this->Jira->get('/status') as $StatusInfo) { 36 | $this->cachestatusInfo($StatusInfo); 37 | } 38 | $this->all_cached = true; 39 | } 40 | 41 | return $this->statuses_list; 42 | } 43 | 44 | /** 45 | * Get particular status info identified by it's unique ID 46 | * 47 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/status-getStatus 48 | * 49 | * @param int $id - ID of status you want to load 50 | * @param bool $reload_cache - force API request to get fresh data from JIRA 51 | * 52 | * @return \stdClass 53 | * 54 | * @throws \Badoo\Jira\REST\Exception 55 | */ 56 | public function get(int $id, bool $reload_cache = false) : \stdClass 57 | { 58 | $statusInfo = $this->statuses_list[$id] ?? null; 59 | 60 | if (!isset($statusInfo) || $reload_cache) { 61 | $statusInfo = $this->Jira->get("/status/{$id}"); 62 | $this->cachestatusInfo($statusInfo); 63 | } 64 | 65 | return $statusInfo; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/REST/Section/StatusCategory.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class StatusCategory extends Section 10 | { 11 | /** @var array */ 12 | protected $categories_list = []; 13 | /** @var bool */ 14 | protected $all_cached = false; 15 | 16 | protected function cacheStatusCategoryInfo(\stdClass $StatusCategoryInfo) 17 | { 18 | $this->categories_list[(int)$StatusCategoryInfo->id] = $StatusCategoryInfo; 19 | } 20 | 21 | /** 22 | * Get list of all status categories configured in current JIRA installation 23 | * 24 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/statuscategory-getStatusCategoryes 25 | * 26 | * @param bool $reload_cache - force API request to get fresh data from JIRA 27 | * 28 | * @return \stdClass[] - list of statuscategories, indexed by IDs 29 | * 30 | * @throws \Badoo\Jira\REST\Exception 31 | */ 32 | public function list(bool $reload_cache = false) : array 33 | { 34 | if (!$this->all_cached || $reload_cache) { 35 | foreach ($this->Jira->get('/statuscategory') as $StatusCategoryInfo) { 36 | $this->cacheStatusCategoryInfo($StatusCategoryInfo); 37 | } 38 | $this->all_cached = true; 39 | } 40 | 41 | return $this->categories_list; 42 | } 43 | 44 | /** 45 | * Get particular status category info identified by it's unique ID 46 | * 47 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/statuscategory-getStatusCategory 48 | * 49 | * @param int $id - ID of statuscategory you want to load 50 | * @param bool $reload_cache - force API request to get fresh data from JIRA 51 | * 52 | * @return \stdClass 53 | * 54 | * @throws \Badoo\Jira\REST\Exception 55 | */ 56 | public function get(int $id, bool $reload_cache = false) : \stdClass 57 | { 58 | $StatusCategoryInfo = $this->categories_list[$id] ?? null; 59 | 60 | if (!isset($StatusCategoryInfo) || $reload_cache) { 61 | $StatusCategoryInfo = $this->Jira->get("/statuscategory/{$id}"); 62 | $this->cacheStatusCategoryInfo($StatusCategoryInfo); 63 | } 64 | 65 | return $StatusCategoryInfo; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/REST/Section/Version.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Version extends Section 10 | { 11 | /** 12 | * Create new version in given project 13 | * 14 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/version-createVersion 15 | * 16 | * @param $project - project ID (e.g. 100500) or key (e.g. EX) 17 | * @param string $name - version name 18 | * @param array $optional_fields - all other version info fields that are not required to be set at the creation 19 | * See API methof description on web page for more information. 20 | * 21 | * @return \stdClass 22 | * 23 | * @throws \Badoo\Jira\REST\Exception 24 | */ 25 | public function create( 26 | $project, 27 | string $name, 28 | array $optional_fields = [] 29 | ) : \stdClass { 30 | $parameters = [ 31 | "name" => $name, 32 | ]; 33 | 34 | if (is_numeric($project)) { 35 | $parameters["projectId"] = (int)$project; 36 | } else { 37 | $parameters["project"] = $project; 38 | } 39 | 40 | $parameters = array_merge($optional_fields, $parameters); 41 | 42 | return $this->Jira->post('/version', $parameters); 43 | } 44 | 45 | /** 46 | * Update version information. 47 | * 48 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/version-updateVersion 49 | * 50 | * @param int $id 51 | * @param array $update - info to update. See API method description on web page for more information. 52 | * 53 | * @return \stdClass 54 | * 55 | * @throws \Badoo\Jira\REST\Exception 56 | */ 57 | public function update( 58 | int $id, 59 | array $update 60 | ) : \stdClass { 61 | return $this->Jira->put("/version/{$id}", $update); 62 | } 63 | 64 | /** 65 | * Delete version 66 | * 67 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/version-delete 68 | * 69 | * @param int $id - ID of verison to delete 70 | * @param string|null $move_fixed_to - replace deleted version with another one in fixVersions field, 71 | * null value just deletes version from field 72 | * @param string|null $move_affected_to - replace deleted version with another one in affectedVersion field 73 | * null value just deletes version from field 74 | * 75 | * @throws \Badoo\Jira\REST\Exception 76 | */ 77 | public function delete(int $id, string $move_fixed_to = null, string $move_affected_to = null) : void 78 | { 79 | $parameters = []; 80 | 81 | if (!empty($move_fixed_to)) { 82 | $parameters['moveFixIssuesTo'] = $move_fixed_to; 83 | } 84 | 85 | if (!empty($move_affected_to)) { 86 | $parameters['moveAffectedIssuesTo'] = $move_affected_to; 87 | } 88 | 89 | $this->Jira->delete("/version/{$id}", $parameters); 90 | } 91 | 92 | /** 93 | * Reorder versions sequence on page. Move given issue to the position, or put it after some other version in a list 94 | * 95 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/version-moveVersion 96 | * 97 | * @param int $id 98 | * @param string $position - new absolute position of version in list 99 | * @param null $after - relative position, put version after the one specified by self link 100 | * 101 | * @return \stdClass 102 | * 103 | * @throws \Badoo\Jira\REST\Exception 104 | */ 105 | public function move(int $id, $position = null, $after = null) : \stdClass 106 | { 107 | $parameters = []; 108 | 109 | if (isset($position)) { 110 | $parameters['position'] = $position; 111 | } else { 112 | $parameters['after'] = $after; 113 | } 114 | 115 | return $this->Jira->post("/version/{$id}/move", $parameters); 116 | } 117 | 118 | /** 119 | * Get full info about version with 120 | * 121 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/version-getVersion 122 | * 123 | * @param int $id - ID of version to load from JIRA 124 | * 125 | * @return \stdClass 126 | * 127 | * @throws \Badoo\Jira\REST\Exception 128 | */ 129 | public function get(int $id) : \stdClass 130 | { 131 | return $this->Jira->get("/version/{$id}"); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/REST/Section/Watchers.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\REST\Section; 8 | 9 | class Watchers extends Section 10 | { 11 | /** 12 | * List issue watchers 13 | * 14 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-getIssueWatchers 15 | * 16 | * @param string $issue_key 17 | * 18 | * @return \stdClass[] - list of objects 19 | * 20 | * @throws \Badoo\Jira\REST\Exception 21 | */ 22 | public function list(string $issue_key) 23 | { 24 | $response = $this->Jira->get("issue/{$issue_key}/watchers"); 25 | 26 | if (!isset($response->watchers)) { 27 | return []; 28 | } 29 | 30 | return $response->watchers; 31 | } 32 | 33 | /** 34 | * Add watcher to issue 35 | * 36 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-addWatcher 37 | * 38 | * @param string $issue_key 39 | * @param string $user_login 40 | * 41 | * @throws \Badoo\Jira\REST\Exception 42 | */ 43 | public function add(string $issue_key, string $user_login) : void 44 | { 45 | $this->Jira->post("issue/{$issue_key}/watchers", $user_login); 46 | } 47 | 48 | /** 49 | * Stop watching issue 50 | * 51 | * @see https://docs.atlassian.com/software/jira/docs/api/REST/7.6.1/#api/2/issue-removeWatcher 52 | * 53 | * @param string $issue_key 54 | * @param string $user_login 55 | * 56 | * @throws \Badoo\Jira\REST\Exception 57 | */ 58 | public function remove(string $issue_key, string $user_login) : void 59 | { 60 | $this->Jira->delete("issue/{$issue_key}/watchers", ['username' => $user_login]); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Security.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira; 8 | 9 | class Security 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \stdClass */ 15 | protected $OriginalObject; 16 | 17 | /** @var int */ 18 | protected $id; // 10000 19 | 20 | /** @var \Badoo\Jira\Issue */ 21 | protected $Issue; 22 | 23 | public static function fromStdClass( 24 | \stdClass $SecurityLevel, 25 | \Badoo\Jira\Issue $Issue = null, 26 | \Badoo\Jira\REST\Client $Jira = null 27 | ) : Security { 28 | $Instance = new static((int)$SecurityLevel->id, $Jira); 29 | 30 | $Instance->OriginalObject = $SecurityLevel; 31 | $Instance->Issue = $Issue; 32 | 33 | return $Instance; 34 | } 35 | 36 | public static function get(int $id, \Badoo\Jira\REST\Client $Jira = null) 37 | { 38 | $Instance = new static($id, $Jira); 39 | $Instance->getOriginalObject(); 40 | 41 | return $Instance; 42 | } 43 | 44 | public function __construct(int $id, \Badoo\Jira\REST\Client $Jira = null) 45 | { 46 | if (!isset($Jira)) { 47 | $Jira = \Badoo\Jira\REST\Client::instance(); 48 | } 49 | 50 | $this->id = $id; 51 | $this->Jira = $Jira; 52 | } 53 | 54 | protected function getOriginalObject() 55 | { 56 | if (!isset($this->OriginalObject)) { 57 | $this->OriginalObject = $this->Jira->securityLevel()->get($this->getId()); 58 | } 59 | 60 | return $this->OriginalObject; 61 | } 62 | 63 | public function getIssue() : ?\Badoo\Jira\Issue 64 | { 65 | return $this->Issue; 66 | } 67 | 68 | public function getId() : int 69 | { 70 | return $this->id; 71 | } 72 | 73 | public function getName() : string 74 | { 75 | return $this->getOriginalObject()->name; 76 | } 77 | 78 | public function getDescription() : string 79 | { 80 | return $this->getOriginalObject()->description ?? ''; 81 | } 82 | 83 | public function getSelf() : string 84 | { 85 | return $this->getOriginalObject()->self; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/UsersList.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira; 8 | 9 | class UsersList 10 | { 11 | /** @var \Badoo\Jira\REST\Client */ 12 | protected $Jira; 13 | 14 | /** @var \Badoo\Jira\Issue */ 15 | protected $Issue; 16 | 17 | /** @var User[] */ 18 | protected $by_name = []; 19 | /** @var User[] */ 20 | protected $by_email = []; 21 | 22 | public static function fromStdClass(array $users_info, \Badoo\Jira\Issue $Issue = null, \Badoo\Jira\REST\Client $Jira = null) : UsersList 23 | { 24 | $users = []; 25 | foreach ($users_info as $UserInfo) { 26 | $users[] = User::fromStdClass($UserInfo, $Issue, $Jira); 27 | } 28 | 29 | $Instance = new static($users, $Jira); 30 | $Instance->Issue = $Issue; 31 | 32 | return $Instance; 33 | } 34 | 35 | /** 36 | * @param User[] $users - users in list 37 | * @param \Badoo\Jira\REST\Client $Jira - JIRA API client to use instead of global one. 38 | * Enables you to access several JIRA instances from one piece of code, 39 | * or use different users for different actions. 40 | */ 41 | public function __construct(array $users, \Badoo\Jira\REST\Client $Jira = null) 42 | { 43 | if (!isset($Jira)) { 44 | $Jira = \Badoo\Jira\REST\Client::instance(); 45 | } 46 | 47 | $this->addUsers(...$users); 48 | $this->Jira = $Jira; 49 | } 50 | 51 | /** 52 | * Clear list from all users 53 | * 54 | * @return $this 55 | */ 56 | public function clearList() : UsersList 57 | { 58 | $this->by_name = []; 59 | $this->by_email = []; 60 | 61 | return $this; 62 | } 63 | 64 | /** 65 | * Get full list of users 66 | * 67 | * @return User[] 68 | */ 69 | public function getUsers() : array 70 | { 71 | return $this->by_name ?? []; 72 | } 73 | 74 | /** 75 | * Add user to list 76 | * 77 | * @param User ...$users 78 | * 79 | * @return $this 80 | */ 81 | public function addUsers(User ...$users) : UsersList 82 | { 83 | foreach ($users as $User) { 84 | $this->by_name[$User->getName()] = $User; 85 | $this->by_email[$User->getEmail()] = $User; 86 | } 87 | return $this; 88 | } 89 | 90 | /** 91 | * @param User ...$users 92 | * @return UsersList 93 | */ 94 | public function removeUsers(User ...$users) : UsersList 95 | { 96 | foreach ($users as $User) { 97 | // Check if we have user in caches. If not - it is better not to call ->getEmail or other methods to not 98 | // trigger useless background API requests under $User object. 99 | if ($this->hasName($User->getName())) { 100 | unset($this->by_name[$User->getName()]); 101 | unset($this->by_email[$User->getEmail()]); 102 | } 103 | } 104 | 105 | return $this; 106 | } 107 | 108 | public function hasName(string $name) : bool 109 | { 110 | return isset($this->by_name[$name]); 111 | } 112 | 113 | public function hasEmail(string $email) : bool 114 | { 115 | return isset($this->by_email[$email]); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/autoload.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\UTests\Helpers; 8 | 9 | class StringsTest extends \PHPUnit\Framework\TestCase 10 | { 11 | public function providerToLabels() 12 | { 13 | return [ 14 | 'empty string' => ['', '_', '_'], 15 | 'valid label' => ['valid_phpLabel', '_', 'valid_phpLabel'], 16 | 'number first' => ['123 number and spaces', '', '_123numberandspaces'], 17 | 'number middle' => ['a1number/in/the!middle', 'Q', 'a1numberQinQtheQmiddle'], 18 | ]; 19 | } 20 | 21 | /** 22 | * @dataProvider providerToLabels 23 | * 24 | * @param string $source - text to transform 25 | * @param string $replace - replacement character 26 | * @param string $expected - expected transformation result 27 | */ 28 | public function testToPhpLabel(string $source, string $replace, string $expected) 29 | { 30 | $result = \Badoo\Jira\Helpers\Strings::toPHPLabel($source, $replace); 31 | 32 | self::assertEquals($expected, $result, 'Incorrect text to PHP label conversion result'); 33 | self::assertTrue(\Badoo\Jira\Helpers\Strings::isValidPHPLabel($result), "the conversion result '{$result}' is not a valid PHP label"); 34 | } 35 | 36 | public function providerIsValidLabel() 37 | { 38 | return [ 39 | 'empty string' => ['', false], 40 | 'valid' => ['valid_label', true], 41 | 'number first' => ['1invalid' , false], 42 | 'frbidden characters' => ['text with spaces', false], 43 | 'valid all capitals' => ['ALLCAPITALS', true], 44 | 'underscored' => ['_1_number', true], 45 | ]; 46 | } 47 | 48 | /** 49 | * @dataProvider providerIsValidLabel 50 | * 51 | * @param string $to_test - text to test for validity as class/constant/variable name 52 | * @param bool $expected_result - is really valid? 53 | */ 54 | public function testIsValidLabel(string $to_test, bool $expected_result) 55 | { 56 | $is_valid = \Badoo\Jira\Helpers\Strings::isValidPHPLabel($to_test); 57 | 58 | $in = $expected_result ? '' : 'in'; 59 | self::assertEquals($expected_result, $is_valid, "Wring result for {$in}valid PHP label '{$to_test}''"); 60 | } 61 | 62 | public function testToCapitalPhpLabel() 63 | { 64 | $from = ' 1 some text'; 65 | 66 | $capitals = \Badoo\Jira\Helpers\Strings::toCapitalPHPLabel($from); 67 | 68 | self::assertEquals('_1_SOME_TEXT', $capitals); 69 | } 70 | 71 | public function testToCamelCasePHPLabel() 72 | { 73 | $from = ' 1 some text'; 74 | 75 | $capitals = \Badoo\Jira\Helpers\Strings::toCamelCasePHPLabel($from); 76 | 77 | self::assertEquals('_1SomeText', $capitals); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /tests/IssueTest.php: -------------------------------------------------------------------------------- 1 | 5 | */ 6 | 7 | namespace Badoo\Jira\UTests; 8 | 9 | class IssueTest extends \PHPUnit\Framework\TestCase 10 | { 11 | public static function getBaseIssue(string $key) : \stdClass 12 | { 13 | $BaseIssue = new \stdClass(); 14 | $BaseIssue->key = $key; 15 | 16 | $BaseIssue->fields = new \stdClass(); 17 | return $BaseIssue; 18 | } 19 | 20 | protected function getIssueMock(\stdClass $BaseIssue, string $key = 'ISSUE-1') : \Badoo\Jira\Issue 21 | { 22 | $IssueMockBuilder = $this->getMockBuilder(\Badoo\Jira\Issue::class); 23 | $IssueMockBuilder 24 | ->setConstructorArgs([$key]) 25 | ->disableArgumentCloning() 26 | ->disallowMockingUnknownTypes() 27 | ->setMethods(['getBaseIssue']); 28 | 29 | 30 | $IssueMock = $IssueMockBuilder->getMock(); 31 | $IssueMock->method('getBaseIssue')->willReturn($BaseIssue); 32 | 33 | /** @var \Badoo\Jira\Issue $IssueMock */ 34 | return $IssueMock; 35 | } 36 | 37 | public function testGetFieldValue_originalValue() 38 | { 39 | $expected_value = 'value from jira'; 40 | 41 | $Base = static::getBaseIssue('EX-1'); 42 | $Base->fields->summary = $expected_value; 43 | 44 | $Issue = $this->getIssueMock($Base); 45 | 46 | self::assertEquals($expected_value, $Issue->getSummary()); 47 | } 48 | 49 | public function summariesForCache() 50 | { 51 | return [ 52 | 'non empty string' => ['non empty string in summary cache'], 53 | 'empty string' => [''], 54 | 'null' => [null], 55 | ]; 56 | } 57 | 58 | /** 59 | * @dataProvider summariesForCache 60 | */ 61 | public function testGetFieldValue_cachedValue(?string $expected_value) 62 | { 63 | $jira_value = 'value from jira'; 64 | 65 | $Base = static::getBaseIssue('EX-1'); 66 | $Base->fields->summary = $jira_value; 67 | 68 | $Issue = $this->getIssueMock($Base); 69 | 70 | $cacheData = new \ReflectionMethod($Issue, 'cacheData'); 71 | $cacheData->setAccessible(true); 72 | 73 | $cacheData->invokeArgs($Issue, ['summary', $expected_value]); 74 | 75 | self::assertNotEquals($expected_value, $jira_value); 76 | self::assertEquals($expected_value, $Issue->getSummary()); 77 | } 78 | 79 | public function datesToParse() 80 | { 81 | return [ 82 | 'empty date' => ['', 0], 83 | 'meaningful date' => ['2018-06-19T12:53:31.000+0000', 1529412811], 84 | 'null date' => [null, 0], 85 | ]; 86 | } 87 | 88 | /** 89 | * @dataProvider datesToParse 90 | */ 91 | public function testGetDateField_isParsed(?string $date_to_parse, int $expected_value) 92 | { 93 | $Base = static::getBaseIssue('EX-1'); 94 | $Base->fields->created = $date_to_parse; 95 | 96 | $Issue = $this->getIssueMock($Base); 97 | 98 | self::assertEquals($expected_value, $Issue->getCreatedDate(), 'Incorrect created date after parsing'); 99 | 100 | $getCachedData = new \ReflectionMethod($Issue, 'getCachedData'); 101 | $getCachedData->setAccessible(true); 102 | 103 | $cached = $getCachedData->invokeArgs($Issue, ['created', $expected_value]); 104 | 105 | self::assertEquals($expected_value, $cached, 'The created date timestamp seems not cached after parsing'); 106 | self::assertEquals($expected_value, $Issue->getCreatedDate(), 'Strange created date timestamp after second call'); 107 | } 108 | 109 | public function testGetPriority_empty() 110 | { 111 | $Base = static::getBaseIssue('EX-1'); 112 | 113 | $Issue = $this->getIssueMock($Base); 114 | 115 | self::assertEquals(null, $Issue->getPriority()); 116 | } 117 | 118 | public function testPartialFieldsInit() 119 | { 120 | $jira_id = 12345; 121 | $jira_key = 'EX-1'; 122 | $jira_cf_value = 'value 12345'; 123 | 124 | $cached_id = 54321; 125 | $cached_key = 'EX-2'; 126 | $cached_self_link = 'https://self.issue.link/'; 127 | $cached_cf_value = 'value 54321'; 128 | 129 | $JiraIssue = static::getBaseIssue($jira_key); 130 | $JiraIssue->id = $jira_id; 131 | $JiraIssue->fields->customfield_12345 = $jira_cf_value; 132 | 133 | $IssueToCache = static::getBaseIssue($cached_key); 134 | $IssueToCache->id = $cached_id; 135 | $IssueToCache->self = $cached_self_link; 136 | $IssueToCache->fields->customfield_54321 = $cached_cf_value; 137 | 138 | $IssueMock = $this->getIssueMock($JiraIssue); 139 | 140 | $Issue = $IssueMock::fromStdClass($IssueToCache, ['id', 'key', 'self', 'customfield_54321']); 141 | $Issue->method('getBaseIssue')->willReturn($JiraIssue); 142 | 143 | self::assertEquals($cached_id, $Issue->getId()); 144 | self::assertEquals($cached_key, $Issue->getKey()); 145 | self::assertEquals($cached_cf_value, $Issue->getFieldValue('customfield_54321')); 146 | self::assertEquals($cached_self_link, $Issue->getSelfUrl()); 147 | 148 | $dropCache = new \ReflectionMethod($Issue, 'dropCache'); 149 | $dropCache->setAccessible(true); 150 | $dropCache->invokeArgs($Issue, []); // drop cache. We hacked getBaseIssue() and this breaks thing we try to check 151 | 152 | self::assertEquals($jira_cf_value, $Issue->getFieldValue('customfield_12345')); 153 | self::assertEquals($jira_id, $Issue->getId()); 154 | } 155 | 156 | public function testUpdateKey() 157 | { 158 | $key_before_update = 'IS-1'; 159 | $key_after_update = 'EX-1'; 160 | 161 | $BaseIssue = $this->getBaseIssue($key_after_update); 162 | $Issue = $this->getIssueMock($BaseIssue, $key_before_update); 163 | 164 | self::assertEquals($key_before_update, $Issue->getKey()); 165 | 166 | $Issue->updateKey(); 167 | self::assertEquals($key_after_update, $Issue->getKey()); 168 | } 169 | } 170 | --------------------------------------------------------------------------------