@@ -7,77 +7,77 @@ |
||
| 7 | 7 | |
| 8 | 8 | if ( !class_exists(ThemeUpdateChecker::class, false) ): |
| 9 | 9 | |
| 10 | - class ThemeUpdateChecker extends Theme\UpdateChecker implements BaseChecker { |
|
| 11 | - use VcsCheckerMethods; |
|
| 10 | + class ThemeUpdateChecker extends Theme\UpdateChecker implements BaseChecker { |
|
| 11 | + use VcsCheckerMethods; |
|
| 12 | 12 | |
| 13 | - /** |
|
| 14 | - * ThemeUpdateChecker constructor. |
|
| 15 | - * |
|
| 16 | - * @param Api $api |
|
| 17 | - * @param null $stylesheet |
|
| 18 | - * @param null $customSlug |
|
| 19 | - * @param int $checkPeriod |
|
| 20 | - * @param string $optionName |
|
| 21 | - */ |
|
| 22 | - public function __construct($api, $stylesheet = null, $customSlug = null, $checkPeriod = 12, $optionName = '') { |
|
| 23 | - $this->api = $api; |
|
| 13 | + /** |
|
| 14 | + * ThemeUpdateChecker constructor. |
|
| 15 | + * |
|
| 16 | + * @param Api $api |
|
| 17 | + * @param null $stylesheet |
|
| 18 | + * @param null $customSlug |
|
| 19 | + * @param int $checkPeriod |
|
| 20 | + * @param string $optionName |
|
| 21 | + */ |
|
| 22 | + public function __construct($api, $stylesheet = null, $customSlug = null, $checkPeriod = 12, $optionName = '') { |
|
| 23 | + $this->api = $api; |
|
| 24 | 24 | |
| 25 | - parent::__construct($api->getRepositoryUrl(), $stylesheet, $customSlug, $checkPeriod, $optionName); |
|
| 25 | + parent::__construct($api->getRepositoryUrl(), $stylesheet, $customSlug, $checkPeriod, $optionName); |
|
| 26 | 26 | |
| 27 | - $this->api->setHttpFilterName($this->getUniqueName('request_update_options')); |
|
| 28 | - $this->api->setStrategyFilterName($this->getUniqueName('vcs_update_detection_strategies')); |
|
| 29 | - $this->api->setSlug($this->slug); |
|
| 30 | - } |
|
| 27 | + $this->api->setHttpFilterName($this->getUniqueName('request_update_options')); |
|
| 28 | + $this->api->setStrategyFilterName($this->getUniqueName('vcs_update_detection_strategies')); |
|
| 29 | + $this->api->setSlug($this->slug); |
|
| 30 | + } |
|
| 31 | 31 | |
| 32 | - public function requestUpdate() { |
|
| 33 | - $api = $this->api; |
|
| 34 | - $api->setLocalDirectory($this->package->getAbsoluteDirectoryPath()); |
|
| 32 | + public function requestUpdate() { |
|
| 33 | + $api = $this->api; |
|
| 34 | + $api->setLocalDirectory($this->package->getAbsoluteDirectoryPath()); |
|
| 35 | 35 | |
| 36 | - $update = new Theme\Update(); |
|
| 37 | - $update->slug = $this->slug; |
|
| 36 | + $update = new Theme\Update(); |
|
| 37 | + $update->slug = $this->slug; |
|
| 38 | 38 | |
| 39 | - //Figure out which reference (tag or branch) we'll use to get the latest version of the theme. |
|
| 40 | - $updateSource = $api->chooseReference($this->branch); |
|
| 41 | - if ( $updateSource ) { |
|
| 42 | - $ref = $updateSource->name; |
|
| 43 | - $update->download_url = $updateSource->downloadUrl; |
|
| 44 | - } else { |
|
| 45 | - do_action( |
|
| 46 | - 'puc_api_error', |
|
| 47 | - new \WP_Error( |
|
| 48 | - 'puc-no-update-source', |
|
| 49 | - 'Could not retrieve version information from the repository. ' |
|
| 50 | - . 'This usually means that the update checker either can\'t connect ' |
|
| 51 | - . 'to the repository or it\'s configured incorrectly.' |
|
| 52 | - ), |
|
| 53 | - null, null, $this->slug |
|
| 54 | - ); |
|
| 55 | - $ref = $this->branch; |
|
| 56 | - } |
|
| 39 | + //Figure out which reference (tag or branch) we'll use to get the latest version of the theme. |
|
| 40 | + $updateSource = $api->chooseReference($this->branch); |
|
| 41 | + if ( $updateSource ) { |
|
| 42 | + $ref = $updateSource->name; |
|
| 43 | + $update->download_url = $updateSource->downloadUrl; |
|
| 44 | + } else { |
|
| 45 | + do_action( |
|
| 46 | + 'puc_api_error', |
|
| 47 | + new \WP_Error( |
|
| 48 | + 'puc-no-update-source', |
|
| 49 | + 'Could not retrieve version information from the repository. ' |
|
| 50 | + . 'This usually means that the update checker either can\'t connect ' |
|
| 51 | + . 'to the repository or it\'s configured incorrectly.' |
|
| 52 | + ), |
|
| 53 | + null, null, $this->slug |
|
| 54 | + ); |
|
| 55 | + $ref = $this->branch; |
|
| 56 | + } |
|
| 57 | 57 | |
| 58 | - //Get headers from the main stylesheet in this branch/tag. Its "Version" header and other metadata |
|
| 59 | - //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags. |
|
| 60 | - $remoteHeader = $this->package->getFileHeader($api->getRemoteFile('style.css', $ref)); |
|
| 61 | - $update->version = Utils::findNotEmpty(array( |
|
| 62 | - $remoteHeader['Version'], |
|
| 63 | - Utils::get($updateSource, 'version'), |
|
| 64 | - )); |
|
| 58 | + //Get headers from the main stylesheet in this branch/tag. Its "Version" header and other metadata |
|
| 59 | + //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags. |
|
| 60 | + $remoteHeader = $this->package->getFileHeader($api->getRemoteFile('style.css', $ref)); |
|
| 61 | + $update->version = Utils::findNotEmpty(array( |
|
| 62 | + $remoteHeader['Version'], |
|
| 63 | + Utils::get($updateSource, 'version'), |
|
| 64 | + )); |
|
| 65 | 65 | |
| 66 | - //The details URL defaults to the Theme URI header or the repository URL. |
|
| 67 | - $update->details_url = Utils::findNotEmpty(array( |
|
| 68 | - $remoteHeader['ThemeURI'], |
|
| 69 | - $this->package->getHeaderValue('ThemeURI'), |
|
| 70 | - $this->metadataUrl, |
|
| 71 | - )); |
|
| 66 | + //The details URL defaults to the Theme URI header or the repository URL. |
|
| 67 | + $update->details_url = Utils::findNotEmpty(array( |
|
| 68 | + $remoteHeader['ThemeURI'], |
|
| 69 | + $this->package->getHeaderValue('ThemeURI'), |
|
| 70 | + $this->metadataUrl, |
|
| 71 | + )); |
|
| 72 | 72 | |
| 73 | - if ( empty($update->version) ) { |
|
| 74 | - //It looks like we didn't find a valid update after all. |
|
| 75 | - $update = null; |
|
| 76 | - } |
|
| 73 | + if ( empty($update->version) ) { |
|
| 74 | + //It looks like we didn't find a valid update after all. |
|
| 75 | + $update = null; |
|
| 76 | + } |
|
| 77 | 77 | |
| 78 | - $update = $this->filterUpdateResult($update); |
|
| 79 | - return $update; |
|
| 80 | - } |
|
| 81 | - } |
|
| 78 | + $update = $this->filterUpdateResult($update); |
|
| 79 | + return $update; |
|
| 80 | + } |
|
| 81 | + } |
|
| 82 | 82 | |
| 83 | 83 | endif; |
@@ -5,7 +5,7 @@ discard block |
||
| 5 | 5 | use YahnisElsts\PluginUpdateChecker\v5p0\Theme; |
| 6 | 6 | use YahnisElsts\PluginUpdateChecker\v5p0\Utils; |
| 7 | 7 | |
| 8 | -if ( !class_exists(ThemeUpdateChecker::class, false) ): |
|
| 8 | +if (!class_exists(ThemeUpdateChecker::class, false)): |
|
| 9 | 9 | |
| 10 | 10 | class ThemeUpdateChecker extends Theme\UpdateChecker implements BaseChecker { |
| 11 | 11 | use VcsCheckerMethods; |
@@ -38,7 +38,7 @@ discard block |
||
| 38 | 38 | |
| 39 | 39 | //Figure out which reference (tag or branch) we'll use to get the latest version of the theme. |
| 40 | 40 | $updateSource = $api->chooseReference($this->branch); |
| 41 | - if ( $updateSource ) { |
|
| 41 | + if ($updateSource) { |
|
| 42 | 42 | $ref = $updateSource->name; |
| 43 | 43 | $update->download_url = $updateSource->downloadUrl; |
| 44 | 44 | } else { |
@@ -70,7 +70,7 @@ discard block |
||
| 70 | 70 | $this->metadataUrl, |
| 71 | 71 | )); |
| 72 | 72 | |
| 73 | - if ( empty($update->version) ) { |
|
| 73 | + if (empty($update->version)) { |
|
| 74 | 74 | //It looks like we didn't find a valid update after all. |
| 75 | 75 | $update = null; |
| 76 | 76 | } |
@@ -6,255 +6,255 @@ |
||
| 6 | 6 | |
| 7 | 7 | if ( !class_exists(PluginUpdateChecker::class, false) ): |
| 8 | 8 | |
| 9 | - class PluginUpdateChecker extends Plugin\UpdateChecker implements BaseChecker { |
|
| 10 | - use VcsCheckerMethods; |
|
| 11 | - |
|
| 12 | - /** |
|
| 13 | - * PluginUpdateChecker constructor. |
|
| 14 | - * |
|
| 15 | - * @param Api $api |
|
| 16 | - * @param string $pluginFile |
|
| 17 | - * @param string $slug |
|
| 18 | - * @param int $checkPeriod |
|
| 19 | - * @param string $optionName |
|
| 20 | - * @param string $muPluginFile |
|
| 21 | - */ |
|
| 22 | - public function __construct($api, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') { |
|
| 23 | - $this->api = $api; |
|
| 24 | - |
|
| 25 | - parent::__construct($api->getRepositoryUrl(), $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile); |
|
| 26 | - |
|
| 27 | - $this->api->setHttpFilterName($this->getUniqueName('request_info_options')); |
|
| 28 | - $this->api->setStrategyFilterName($this->getUniqueName('vcs_update_detection_strategies')); |
|
| 29 | - $this->api->setSlug($this->slug); |
|
| 30 | - } |
|
| 31 | - |
|
| 32 | - public function requestInfo($unusedParameter = null) { |
|
| 33 | - //We have to make several remote API requests to gather all the necessary info |
|
| 34 | - //which can take a while on slow networks. |
|
| 35 | - if ( function_exists('set_time_limit') ) { |
|
| 36 | - @set_time_limit(60); |
|
| 37 | - } |
|
| 38 | - |
|
| 39 | - $api = $this->api; |
|
| 40 | - $api->setLocalDirectory($this->package->getAbsoluteDirectoryPath()); |
|
| 41 | - |
|
| 42 | - $info = new Plugin\PluginInfo(); |
|
| 43 | - $info->filename = $this->pluginFile; |
|
| 44 | - $info->slug = $this->slug; |
|
| 45 | - |
|
| 46 | - $this->setInfoFromHeader($this->package->getPluginHeader(), $info); |
|
| 47 | - $this->setIconsFromLocalAssets($info); |
|
| 48 | - $this->setBannersFromLocalAssets($info); |
|
| 49 | - |
|
| 50 | - //Pick a branch or tag. |
|
| 51 | - $updateSource = $api->chooseReference($this->branch); |
|
| 52 | - if ( $updateSource ) { |
|
| 53 | - $ref = $updateSource->name; |
|
| 54 | - $info->version = $updateSource->version; |
|
| 55 | - $info->last_updated = $updateSource->updated; |
|
| 56 | - $info->download_url = $updateSource->downloadUrl; |
|
| 57 | - |
|
| 58 | - if ( !empty($updateSource->changelog) ) { |
|
| 59 | - $info->sections['changelog'] = $updateSource->changelog; |
|
| 60 | - } |
|
| 61 | - if ( isset($updateSource->downloadCount) ) { |
|
| 62 | - $info->downloaded = $updateSource->downloadCount; |
|
| 63 | - } |
|
| 64 | - } else { |
|
| 65 | - //There's probably a network problem or an authentication error. |
|
| 66 | - do_action( |
|
| 67 | - 'puc_api_error', |
|
| 68 | - new \WP_Error( |
|
| 69 | - 'puc-no-update-source', |
|
| 70 | - 'Could not retrieve version information from the repository. ' |
|
| 71 | - . 'This usually means that the update checker either can\'t connect ' |
|
| 72 | - . 'to the repository or it\'s configured incorrectly.' |
|
| 73 | - ), |
|
| 74 | - null, null, $this->slug |
|
| 75 | - ); |
|
| 76 | - return null; |
|
| 77 | - } |
|
| 78 | - |
|
| 79 | - //Get headers from the main plugin file in this branch/tag. Its "Version" header and other metadata |
|
| 80 | - //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags. |
|
| 81 | - $mainPluginFile = basename($this->pluginFile); |
|
| 82 | - $remotePlugin = $api->getRemoteFile($mainPluginFile, $ref); |
|
| 83 | - if ( !empty($remotePlugin) ) { |
|
| 84 | - $remoteHeader = $this->package->getFileHeader($remotePlugin); |
|
| 85 | - $this->setInfoFromHeader($remoteHeader, $info); |
|
| 86 | - } |
|
| 87 | - |
|
| 88 | - //Try parsing readme.txt. If it's formatted according to WordPress.org standards, it will contain |
|
| 89 | - //a lot of useful information like the required/tested WP version, changelog, and so on. |
|
| 90 | - if ( $this->readmeTxtExistsLocally() ) { |
|
| 91 | - $this->setInfoFromRemoteReadme($ref, $info); |
|
| 92 | - } |
|
| 93 | - |
|
| 94 | - //The changelog might be in a separate file. |
|
| 95 | - if ( empty($info->sections['changelog']) ) { |
|
| 96 | - $info->sections['changelog'] = $api->getRemoteChangelog($ref, $this->package->getAbsoluteDirectoryPath()); |
|
| 97 | - if ( empty($info->sections['changelog']) ) { |
|
| 98 | - $info->sections['changelog'] = __('There is no changelog available.', 'plugin-update-checker'); |
|
| 99 | - } |
|
| 100 | - } |
|
| 101 | - |
|
| 102 | - if ( empty($info->last_updated) ) { |
|
| 103 | - //Fetch the latest commit that changed the tag or branch and use it as the "last_updated" date. |
|
| 104 | - $latestCommitTime = $api->getLatestCommitTime($ref); |
|
| 105 | - if ( $latestCommitTime !== null ) { |
|
| 106 | - $info->last_updated = $latestCommitTime; |
|
| 107 | - } |
|
| 108 | - } |
|
| 109 | - |
|
| 110 | - $info = apply_filters($this->getUniqueName('request_info_result'), $info, null); |
|
| 111 | - return $info; |
|
| 112 | - } |
|
| 113 | - |
|
| 114 | - /** |
|
| 115 | - * Check if the currently installed version has a readme.txt file. |
|
| 116 | - * |
|
| 117 | - * @return bool |
|
| 118 | - */ |
|
| 119 | - protected function readmeTxtExistsLocally() { |
|
| 120 | - return $this->package->fileExists($this->api->getLocalReadmeName()); |
|
| 121 | - } |
|
| 122 | - |
|
| 123 | - /** |
|
| 124 | - * Copy plugin metadata from a file header to a Plugin Info object. |
|
| 125 | - * |
|
| 126 | - * @param array $fileHeader |
|
| 127 | - * @param Plugin\PluginInfo $pluginInfo |
|
| 128 | - */ |
|
| 129 | - protected function setInfoFromHeader($fileHeader, $pluginInfo) { |
|
| 130 | - $headerToPropertyMap = array( |
|
| 131 | - 'Version' => 'version', |
|
| 132 | - 'Name' => 'name', |
|
| 133 | - 'PluginURI' => 'homepage', |
|
| 134 | - 'Author' => 'author', |
|
| 135 | - 'AuthorName' => 'author', |
|
| 136 | - 'AuthorURI' => 'author_homepage', |
|
| 137 | - |
|
| 138 | - 'Requires WP' => 'requires', |
|
| 139 | - 'Tested WP' => 'tested', |
|
| 140 | - 'Requires at least' => 'requires', |
|
| 141 | - 'Tested up to' => 'tested', |
|
| 142 | - |
|
| 143 | - 'Requires PHP' => 'requires_php', |
|
| 144 | - ); |
|
| 145 | - foreach ($headerToPropertyMap as $headerName => $property) { |
|
| 146 | - if ( isset($fileHeader[$headerName]) && !empty($fileHeader[$headerName]) ) { |
|
| 147 | - $pluginInfo->$property = $fileHeader[$headerName]; |
|
| 148 | - } |
|
| 149 | - } |
|
| 150 | - |
|
| 151 | - if ( !empty($fileHeader['Description']) ) { |
|
| 152 | - $pluginInfo->sections['description'] = $fileHeader['Description']; |
|
| 153 | - } |
|
| 154 | - } |
|
| 155 | - |
|
| 156 | - /** |
|
| 157 | - * Copy plugin metadata from the remote readme.txt file. |
|
| 158 | - * |
|
| 159 | - * @param string $ref GitHub tag or branch where to look for the readme. |
|
| 160 | - * @param Plugin\PluginInfo $pluginInfo |
|
| 161 | - */ |
|
| 162 | - protected function setInfoFromRemoteReadme($ref, $pluginInfo) { |
|
| 163 | - $readme = $this->api->getRemoteReadme($ref); |
|
| 164 | - if ( empty($readme) ) { |
|
| 165 | - return; |
|
| 166 | - } |
|
| 167 | - |
|
| 168 | - if ( isset($readme['sections']) ) { |
|
| 169 | - $pluginInfo->sections = array_merge($pluginInfo->sections, $readme['sections']); |
|
| 170 | - } |
|
| 171 | - if ( !empty($readme['tested_up_to']) ) { |
|
| 172 | - $pluginInfo->tested = $readme['tested_up_to']; |
|
| 173 | - } |
|
| 174 | - if ( !empty($readme['requires_at_least']) ) { |
|
| 175 | - $pluginInfo->requires = $readme['requires_at_least']; |
|
| 176 | - } |
|
| 177 | - if ( !empty($readme['requires_php']) ) { |
|
| 178 | - $pluginInfo->requires_php = $readme['requires_php']; |
|
| 179 | - } |
|
| 180 | - |
|
| 181 | - if ( isset($readme['upgrade_notice'], $readme['upgrade_notice'][$pluginInfo->version]) ) { |
|
| 182 | - $pluginInfo->upgrade_notice = $readme['upgrade_notice'][$pluginInfo->version]; |
|
| 183 | - } |
|
| 184 | - } |
|
| 185 | - |
|
| 186 | - /** |
|
| 187 | - * Add icons from the currently installed version to a Plugin Info object. |
|
| 188 | - * |
|
| 189 | - * The icons should be in a subdirectory named "assets". Supported image formats |
|
| 190 | - * and file names are described here: |
|
| 191 | - * @link https://developer.wordpress.org/plugins/wordpress-org/plugin-assets/#plugin-icons |
|
| 192 | - * |
|
| 193 | - * @param Plugin\PluginInfo $pluginInfo |
|
| 194 | - */ |
|
| 195 | - protected function setIconsFromLocalAssets($pluginInfo) { |
|
| 196 | - $icons = $this->getLocalAssetUrls(array( |
|
| 197 | - 'icon.svg' => 'svg', |
|
| 198 | - 'icon-256x256.png' => '2x', |
|
| 199 | - 'icon-256x256.jpg' => '2x', |
|
| 200 | - 'icon-128x128.png' => '1x', |
|
| 201 | - 'icon-128x128.jpg' => '1x', |
|
| 202 | - )); |
|
| 203 | - |
|
| 204 | - if ( !empty($icons) ) { |
|
| 205 | - //The "default" key seems to be used only as last-resort fallback in WP core (5.8/5.9), |
|
| 206 | - //but we'll set it anyway in case some code somewhere needs it. |
|
| 207 | - reset($icons); |
|
| 208 | - $firstKey = key($icons); |
|
| 209 | - $icons['default'] = $icons[$firstKey]; |
|
| 210 | - |
|
| 211 | - $pluginInfo->icons = $icons; |
|
| 212 | - } |
|
| 213 | - } |
|
| 214 | - |
|
| 215 | - /** |
|
| 216 | - * Add banners from the currently installed version to a Plugin Info object. |
|
| 217 | - * |
|
| 218 | - * The banners should be in a subdirectory named "assets". Supported image formats |
|
| 219 | - * and file names are described here: |
|
| 220 | - * @link https://developer.wordpress.org/plugins/wordpress-org/plugin-assets/#plugin-headers |
|
| 221 | - * |
|
| 222 | - * @param Plugin\PluginInfo $pluginInfo |
|
| 223 | - */ |
|
| 224 | - protected function setBannersFromLocalAssets($pluginInfo) { |
|
| 225 | - $banners = $this->getLocalAssetUrls(array( |
|
| 226 | - 'banner-772x250.png' => 'high', |
|
| 227 | - 'banner-772x250.jpg' => 'high', |
|
| 228 | - 'banner-1544x500.png' => 'low', |
|
| 229 | - 'banner-1544x500.jpg' => 'low', |
|
| 230 | - )); |
|
| 231 | - |
|
| 232 | - if ( !empty($banners) ) { |
|
| 233 | - $pluginInfo->banners = $banners; |
|
| 234 | - } |
|
| 235 | - } |
|
| 236 | - |
|
| 237 | - /** |
|
| 238 | - * @param array<string, string> $filesToKeys |
|
| 239 | - * @return array<string, string> |
|
| 240 | - */ |
|
| 241 | - protected function getLocalAssetUrls($filesToKeys) { |
|
| 242 | - $assetDirectory = $this->package->getAbsoluteDirectoryPath() . DIRECTORY_SEPARATOR . 'assets'; |
|
| 243 | - if ( !is_dir($assetDirectory) ) { |
|
| 244 | - return array(); |
|
| 245 | - } |
|
| 246 | - $assetBaseUrl = trailingslashit(plugins_url('', $assetDirectory . '/imaginary.file')); |
|
| 247 | - |
|
| 248 | - $foundAssets = array(); |
|
| 249 | - foreach ($filesToKeys as $fileName => $key) { |
|
| 250 | - $fullBannerPath = $assetDirectory . DIRECTORY_SEPARATOR . $fileName; |
|
| 251 | - if ( !isset($icons[$key]) && is_file($fullBannerPath) ) { |
|
| 252 | - $foundAssets[$key] = $assetBaseUrl . $fileName; |
|
| 253 | - } |
|
| 254 | - } |
|
| 255 | - |
|
| 256 | - return $foundAssets; |
|
| 257 | - } |
|
| 258 | - } |
|
| 9 | + class PluginUpdateChecker extends Plugin\UpdateChecker implements BaseChecker { |
|
| 10 | + use VcsCheckerMethods; |
|
| 11 | + |
|
| 12 | + /** |
|
| 13 | + * PluginUpdateChecker constructor. |
|
| 14 | + * |
|
| 15 | + * @param Api $api |
|
| 16 | + * @param string $pluginFile |
|
| 17 | + * @param string $slug |
|
| 18 | + * @param int $checkPeriod |
|
| 19 | + * @param string $optionName |
|
| 20 | + * @param string $muPluginFile |
|
| 21 | + */ |
|
| 22 | + public function __construct($api, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') { |
|
| 23 | + $this->api = $api; |
|
| 24 | + |
|
| 25 | + parent::__construct($api->getRepositoryUrl(), $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile); |
|
| 26 | + |
|
| 27 | + $this->api->setHttpFilterName($this->getUniqueName('request_info_options')); |
|
| 28 | + $this->api->setStrategyFilterName($this->getUniqueName('vcs_update_detection_strategies')); |
|
| 29 | + $this->api->setSlug($this->slug); |
|
| 30 | + } |
|
| 31 | + |
|
| 32 | + public function requestInfo($unusedParameter = null) { |
|
| 33 | + //We have to make several remote API requests to gather all the necessary info |
|
| 34 | + //which can take a while on slow networks. |
|
| 35 | + if ( function_exists('set_time_limit') ) { |
|
| 36 | + @set_time_limit(60); |
|
| 37 | + } |
|
| 38 | + |
|
| 39 | + $api = $this->api; |
|
| 40 | + $api->setLocalDirectory($this->package->getAbsoluteDirectoryPath()); |
|
| 41 | + |
|
| 42 | + $info = new Plugin\PluginInfo(); |
|
| 43 | + $info->filename = $this->pluginFile; |
|
| 44 | + $info->slug = $this->slug; |
|
| 45 | + |
|
| 46 | + $this->setInfoFromHeader($this->package->getPluginHeader(), $info); |
|
| 47 | + $this->setIconsFromLocalAssets($info); |
|
| 48 | + $this->setBannersFromLocalAssets($info); |
|
| 49 | + |
|
| 50 | + //Pick a branch or tag. |
|
| 51 | + $updateSource = $api->chooseReference($this->branch); |
|
| 52 | + if ( $updateSource ) { |
|
| 53 | + $ref = $updateSource->name; |
|
| 54 | + $info->version = $updateSource->version; |
|
| 55 | + $info->last_updated = $updateSource->updated; |
|
| 56 | + $info->download_url = $updateSource->downloadUrl; |
|
| 57 | + |
|
| 58 | + if ( !empty($updateSource->changelog) ) { |
|
| 59 | + $info->sections['changelog'] = $updateSource->changelog; |
|
| 60 | + } |
|
| 61 | + if ( isset($updateSource->downloadCount) ) { |
|
| 62 | + $info->downloaded = $updateSource->downloadCount; |
|
| 63 | + } |
|
| 64 | + } else { |
|
| 65 | + //There's probably a network problem or an authentication error. |
|
| 66 | + do_action( |
|
| 67 | + 'puc_api_error', |
|
| 68 | + new \WP_Error( |
|
| 69 | + 'puc-no-update-source', |
|
| 70 | + 'Could not retrieve version information from the repository. ' |
|
| 71 | + . 'This usually means that the update checker either can\'t connect ' |
|
| 72 | + . 'to the repository or it\'s configured incorrectly.' |
|
| 73 | + ), |
|
| 74 | + null, null, $this->slug |
|
| 75 | + ); |
|
| 76 | + return null; |
|
| 77 | + } |
|
| 78 | + |
|
| 79 | + //Get headers from the main plugin file in this branch/tag. Its "Version" header and other metadata |
|
| 80 | + //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags. |
|
| 81 | + $mainPluginFile = basename($this->pluginFile); |
|
| 82 | + $remotePlugin = $api->getRemoteFile($mainPluginFile, $ref); |
|
| 83 | + if ( !empty($remotePlugin) ) { |
|
| 84 | + $remoteHeader = $this->package->getFileHeader($remotePlugin); |
|
| 85 | + $this->setInfoFromHeader($remoteHeader, $info); |
|
| 86 | + } |
|
| 87 | + |
|
| 88 | + //Try parsing readme.txt. If it's formatted according to WordPress.org standards, it will contain |
|
| 89 | + //a lot of useful information like the required/tested WP version, changelog, and so on. |
|
| 90 | + if ( $this->readmeTxtExistsLocally() ) { |
|
| 91 | + $this->setInfoFromRemoteReadme($ref, $info); |
|
| 92 | + } |
|
| 93 | + |
|
| 94 | + //The changelog might be in a separate file. |
|
| 95 | + if ( empty($info->sections['changelog']) ) { |
|
| 96 | + $info->sections['changelog'] = $api->getRemoteChangelog($ref, $this->package->getAbsoluteDirectoryPath()); |
|
| 97 | + if ( empty($info->sections['changelog']) ) { |
|
| 98 | + $info->sections['changelog'] = __('There is no changelog available.', 'plugin-update-checker'); |
|
| 99 | + } |
|
| 100 | + } |
|
| 101 | + |
|
| 102 | + if ( empty($info->last_updated) ) { |
|
| 103 | + //Fetch the latest commit that changed the tag or branch and use it as the "last_updated" date. |
|
| 104 | + $latestCommitTime = $api->getLatestCommitTime($ref); |
|
| 105 | + if ( $latestCommitTime !== null ) { |
|
| 106 | + $info->last_updated = $latestCommitTime; |
|
| 107 | + } |
|
| 108 | + } |
|
| 109 | + |
|
| 110 | + $info = apply_filters($this->getUniqueName('request_info_result'), $info, null); |
|
| 111 | + return $info; |
|
| 112 | + } |
|
| 113 | + |
|
| 114 | + /** |
|
| 115 | + * Check if the currently installed version has a readme.txt file. |
|
| 116 | + * |
|
| 117 | + * @return bool |
|
| 118 | + */ |
|
| 119 | + protected function readmeTxtExistsLocally() { |
|
| 120 | + return $this->package->fileExists($this->api->getLocalReadmeName()); |
|
| 121 | + } |
|
| 122 | + |
|
| 123 | + /** |
|
| 124 | + * Copy plugin metadata from a file header to a Plugin Info object. |
|
| 125 | + * |
|
| 126 | + * @param array $fileHeader |
|
| 127 | + * @param Plugin\PluginInfo $pluginInfo |
|
| 128 | + */ |
|
| 129 | + protected function setInfoFromHeader($fileHeader, $pluginInfo) { |
|
| 130 | + $headerToPropertyMap = array( |
|
| 131 | + 'Version' => 'version', |
|
| 132 | + 'Name' => 'name', |
|
| 133 | + 'PluginURI' => 'homepage', |
|
| 134 | + 'Author' => 'author', |
|
| 135 | + 'AuthorName' => 'author', |
|
| 136 | + 'AuthorURI' => 'author_homepage', |
|
| 137 | + |
|
| 138 | + 'Requires WP' => 'requires', |
|
| 139 | + 'Tested WP' => 'tested', |
|
| 140 | + 'Requires at least' => 'requires', |
|
| 141 | + 'Tested up to' => 'tested', |
|
| 142 | + |
|
| 143 | + 'Requires PHP' => 'requires_php', |
|
| 144 | + ); |
|
| 145 | + foreach ($headerToPropertyMap as $headerName => $property) { |
|
| 146 | + if ( isset($fileHeader[$headerName]) && !empty($fileHeader[$headerName]) ) { |
|
| 147 | + $pluginInfo->$property = $fileHeader[$headerName]; |
|
| 148 | + } |
|
| 149 | + } |
|
| 150 | + |
|
| 151 | + if ( !empty($fileHeader['Description']) ) { |
|
| 152 | + $pluginInfo->sections['description'] = $fileHeader['Description']; |
|
| 153 | + } |
|
| 154 | + } |
|
| 155 | + |
|
| 156 | + /** |
|
| 157 | + * Copy plugin metadata from the remote readme.txt file. |
|
| 158 | + * |
|
| 159 | + * @param string $ref GitHub tag or branch where to look for the readme. |
|
| 160 | + * @param Plugin\PluginInfo $pluginInfo |
|
| 161 | + */ |
|
| 162 | + protected function setInfoFromRemoteReadme($ref, $pluginInfo) { |
|
| 163 | + $readme = $this->api->getRemoteReadme($ref); |
|
| 164 | + if ( empty($readme) ) { |
|
| 165 | + return; |
|
| 166 | + } |
|
| 167 | + |
|
| 168 | + if ( isset($readme['sections']) ) { |
|
| 169 | + $pluginInfo->sections = array_merge($pluginInfo->sections, $readme['sections']); |
|
| 170 | + } |
|
| 171 | + if ( !empty($readme['tested_up_to']) ) { |
|
| 172 | + $pluginInfo->tested = $readme['tested_up_to']; |
|
| 173 | + } |
|
| 174 | + if ( !empty($readme['requires_at_least']) ) { |
|
| 175 | + $pluginInfo->requires = $readme['requires_at_least']; |
|
| 176 | + } |
|
| 177 | + if ( !empty($readme['requires_php']) ) { |
|
| 178 | + $pluginInfo->requires_php = $readme['requires_php']; |
|
| 179 | + } |
|
| 180 | + |
|
| 181 | + if ( isset($readme['upgrade_notice'], $readme['upgrade_notice'][$pluginInfo->version]) ) { |
|
| 182 | + $pluginInfo->upgrade_notice = $readme['upgrade_notice'][$pluginInfo->version]; |
|
| 183 | + } |
|
| 184 | + } |
|
| 185 | + |
|
| 186 | + /** |
|
| 187 | + * Add icons from the currently installed version to a Plugin Info object. |
|
| 188 | + * |
|
| 189 | + * The icons should be in a subdirectory named "assets". Supported image formats |
|
| 190 | + * and file names are described here: |
|
| 191 | + * @link https://developer.wordpress.org/plugins/wordpress-org/plugin-assets/#plugin-icons |
|
| 192 | + * |
|
| 193 | + * @param Plugin\PluginInfo $pluginInfo |
|
| 194 | + */ |
|
| 195 | + protected function setIconsFromLocalAssets($pluginInfo) { |
|
| 196 | + $icons = $this->getLocalAssetUrls(array( |
|
| 197 | + 'icon.svg' => 'svg', |
|
| 198 | + 'icon-256x256.png' => '2x', |
|
| 199 | + 'icon-256x256.jpg' => '2x', |
|
| 200 | + 'icon-128x128.png' => '1x', |
|
| 201 | + 'icon-128x128.jpg' => '1x', |
|
| 202 | + )); |
|
| 203 | + |
|
| 204 | + if ( !empty($icons) ) { |
|
| 205 | + //The "default" key seems to be used only as last-resort fallback in WP core (5.8/5.9), |
|
| 206 | + //but we'll set it anyway in case some code somewhere needs it. |
|
| 207 | + reset($icons); |
|
| 208 | + $firstKey = key($icons); |
|
| 209 | + $icons['default'] = $icons[$firstKey]; |
|
| 210 | + |
|
| 211 | + $pluginInfo->icons = $icons; |
|
| 212 | + } |
|
| 213 | + } |
|
| 214 | + |
|
| 215 | + /** |
|
| 216 | + * Add banners from the currently installed version to a Plugin Info object. |
|
| 217 | + * |
|
| 218 | + * The banners should be in a subdirectory named "assets". Supported image formats |
|
| 219 | + * and file names are described here: |
|
| 220 | + * @link https://developer.wordpress.org/plugins/wordpress-org/plugin-assets/#plugin-headers |
|
| 221 | + * |
|
| 222 | + * @param Plugin\PluginInfo $pluginInfo |
|
| 223 | + */ |
|
| 224 | + protected function setBannersFromLocalAssets($pluginInfo) { |
|
| 225 | + $banners = $this->getLocalAssetUrls(array( |
|
| 226 | + 'banner-772x250.png' => 'high', |
|
| 227 | + 'banner-772x250.jpg' => 'high', |
|
| 228 | + 'banner-1544x500.png' => 'low', |
|
| 229 | + 'banner-1544x500.jpg' => 'low', |
|
| 230 | + )); |
|
| 231 | + |
|
| 232 | + if ( !empty($banners) ) { |
|
| 233 | + $pluginInfo->banners = $banners; |
|
| 234 | + } |
|
| 235 | + } |
|
| 236 | + |
|
| 237 | + /** |
|
| 238 | + * @param array<string, string> $filesToKeys |
|
| 239 | + * @return array<string, string> |
|
| 240 | + */ |
|
| 241 | + protected function getLocalAssetUrls($filesToKeys) { |
|
| 242 | + $assetDirectory = $this->package->getAbsoluteDirectoryPath() . DIRECTORY_SEPARATOR . 'assets'; |
|
| 243 | + if ( !is_dir($assetDirectory) ) { |
|
| 244 | + return array(); |
|
| 245 | + } |
|
| 246 | + $assetBaseUrl = trailingslashit(plugins_url('', $assetDirectory . '/imaginary.file')); |
|
| 247 | + |
|
| 248 | + $foundAssets = array(); |
|
| 249 | + foreach ($filesToKeys as $fileName => $key) { |
|
| 250 | + $fullBannerPath = $assetDirectory . DIRECTORY_SEPARATOR . $fileName; |
|
| 251 | + if ( !isset($icons[$key]) && is_file($fullBannerPath) ) { |
|
| 252 | + $foundAssets[$key] = $assetBaseUrl . $fileName; |
|
| 253 | + } |
|
| 254 | + } |
|
| 255 | + |
|
| 256 | + return $foundAssets; |
|
| 257 | + } |
|
| 258 | + } |
|
| 259 | 259 | |
| 260 | 260 | endif; |
@@ -4,7 +4,7 @@ discard block |
||
| 4 | 4 | |
| 5 | 5 | use YahnisElsts\PluginUpdateChecker\v5p0\Plugin; |
| 6 | 6 | |
| 7 | -if ( !class_exists(PluginUpdateChecker::class, false) ): |
|
| 7 | +if (!class_exists(PluginUpdateChecker::class, false)): |
|
| 8 | 8 | |
| 9 | 9 | class PluginUpdateChecker extends Plugin\UpdateChecker implements BaseChecker { |
| 10 | 10 | use VcsCheckerMethods; |
@@ -32,7 +32,7 @@ discard block |
||
| 32 | 32 | public function requestInfo($unusedParameter = null) { |
| 33 | 33 | //We have to make several remote API requests to gather all the necessary info |
| 34 | 34 | //which can take a while on slow networks. |
| 35 | - if ( function_exists('set_time_limit') ) { |
|
| 35 | + if (function_exists('set_time_limit')) { |
|
| 36 | 36 | @set_time_limit(60); |
| 37 | 37 | } |
| 38 | 38 | |
@@ -49,16 +49,16 @@ discard block |
||
| 49 | 49 | |
| 50 | 50 | //Pick a branch or tag. |
| 51 | 51 | $updateSource = $api->chooseReference($this->branch); |
| 52 | - if ( $updateSource ) { |
|
| 52 | + if ($updateSource) { |
|
| 53 | 53 | $ref = $updateSource->name; |
| 54 | 54 | $info->version = $updateSource->version; |
| 55 | 55 | $info->last_updated = $updateSource->updated; |
| 56 | 56 | $info->download_url = $updateSource->downloadUrl; |
| 57 | 57 | |
| 58 | - if ( !empty($updateSource->changelog) ) { |
|
| 58 | + if (!empty($updateSource->changelog)) { |
|
| 59 | 59 | $info->sections['changelog'] = $updateSource->changelog; |
| 60 | 60 | } |
| 61 | - if ( isset($updateSource->downloadCount) ) { |
|
| 61 | + if (isset($updateSource->downloadCount)) { |
|
| 62 | 62 | $info->downloaded = $updateSource->downloadCount; |
| 63 | 63 | } |
| 64 | 64 | } else { |
@@ -80,29 +80,29 @@ discard block |
||
| 80 | 80 | //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags. |
| 81 | 81 | $mainPluginFile = basename($this->pluginFile); |
| 82 | 82 | $remotePlugin = $api->getRemoteFile($mainPluginFile, $ref); |
| 83 | - if ( !empty($remotePlugin) ) { |
|
| 83 | + if (!empty($remotePlugin)) { |
|
| 84 | 84 | $remoteHeader = $this->package->getFileHeader($remotePlugin); |
| 85 | 85 | $this->setInfoFromHeader($remoteHeader, $info); |
| 86 | 86 | } |
| 87 | 87 | |
| 88 | 88 | //Try parsing readme.txt. If it's formatted according to WordPress.org standards, it will contain |
| 89 | 89 | //a lot of useful information like the required/tested WP version, changelog, and so on. |
| 90 | - if ( $this->readmeTxtExistsLocally() ) { |
|
| 90 | + if ($this->readmeTxtExistsLocally()) { |
|
| 91 | 91 | $this->setInfoFromRemoteReadme($ref, $info); |
| 92 | 92 | } |
| 93 | 93 | |
| 94 | 94 | //The changelog might be in a separate file. |
| 95 | - if ( empty($info->sections['changelog']) ) { |
|
| 95 | + if (empty($info->sections['changelog'])) { |
|
| 96 | 96 | $info->sections['changelog'] = $api->getRemoteChangelog($ref, $this->package->getAbsoluteDirectoryPath()); |
| 97 | - if ( empty($info->sections['changelog']) ) { |
|
| 97 | + if (empty($info->sections['changelog'])) { |
|
| 98 | 98 | $info->sections['changelog'] = __('There is no changelog available.', 'plugin-update-checker'); |
| 99 | 99 | } |
| 100 | 100 | } |
| 101 | 101 | |
| 102 | - if ( empty($info->last_updated) ) { |
|
| 102 | + if (empty($info->last_updated)) { |
|
| 103 | 103 | //Fetch the latest commit that changed the tag or branch and use it as the "last_updated" date. |
| 104 | 104 | $latestCommitTime = $api->getLatestCommitTime($ref); |
| 105 | - if ( $latestCommitTime !== null ) { |
|
| 105 | + if ($latestCommitTime !== null) { |
|
| 106 | 106 | $info->last_updated = $latestCommitTime; |
| 107 | 107 | } |
| 108 | 108 | } |
@@ -143,12 +143,12 @@ discard block |
||
| 143 | 143 | 'Requires PHP' => 'requires_php', |
| 144 | 144 | ); |
| 145 | 145 | foreach ($headerToPropertyMap as $headerName => $property) { |
| 146 | - if ( isset($fileHeader[$headerName]) && !empty($fileHeader[$headerName]) ) { |
|
| 146 | + if (isset($fileHeader[$headerName]) && !empty($fileHeader[$headerName])) { |
|
| 147 | 147 | $pluginInfo->$property = $fileHeader[$headerName]; |
| 148 | 148 | } |
| 149 | 149 | } |
| 150 | 150 | |
| 151 | - if ( !empty($fileHeader['Description']) ) { |
|
| 151 | + if (!empty($fileHeader['Description'])) { |
|
| 152 | 152 | $pluginInfo->sections['description'] = $fileHeader['Description']; |
| 153 | 153 | } |
| 154 | 154 | } |
@@ -161,24 +161,24 @@ discard block |
||
| 161 | 161 | */ |
| 162 | 162 | protected function setInfoFromRemoteReadme($ref, $pluginInfo) { |
| 163 | 163 | $readme = $this->api->getRemoteReadme($ref); |
| 164 | - if ( empty($readme) ) { |
|
| 164 | + if (empty($readme)) { |
|
| 165 | 165 | return; |
| 166 | 166 | } |
| 167 | 167 | |
| 168 | - if ( isset($readme['sections']) ) { |
|
| 168 | + if (isset($readme['sections'])) { |
|
| 169 | 169 | $pluginInfo->sections = array_merge($pluginInfo->sections, $readme['sections']); |
| 170 | 170 | } |
| 171 | - if ( !empty($readme['tested_up_to']) ) { |
|
| 171 | + if (!empty($readme['tested_up_to'])) { |
|
| 172 | 172 | $pluginInfo->tested = $readme['tested_up_to']; |
| 173 | 173 | } |
| 174 | - if ( !empty($readme['requires_at_least']) ) { |
|
| 174 | + if (!empty($readme['requires_at_least'])) { |
|
| 175 | 175 | $pluginInfo->requires = $readme['requires_at_least']; |
| 176 | 176 | } |
| 177 | - if ( !empty($readme['requires_php']) ) { |
|
| 177 | + if (!empty($readme['requires_php'])) { |
|
| 178 | 178 | $pluginInfo->requires_php = $readme['requires_php']; |
| 179 | 179 | } |
| 180 | 180 | |
| 181 | - if ( isset($readme['upgrade_notice'], $readme['upgrade_notice'][$pluginInfo->version]) ) { |
|
| 181 | + if (isset($readme['upgrade_notice'], $readme['upgrade_notice'][$pluginInfo->version])) { |
|
| 182 | 182 | $pluginInfo->upgrade_notice = $readme['upgrade_notice'][$pluginInfo->version]; |
| 183 | 183 | } |
| 184 | 184 | } |
@@ -201,7 +201,7 @@ discard block |
||
| 201 | 201 | 'icon-128x128.jpg' => '1x', |
| 202 | 202 | )); |
| 203 | 203 | |
| 204 | - if ( !empty($icons) ) { |
|
| 204 | + if (!empty($icons)) { |
|
| 205 | 205 | //The "default" key seems to be used only as last-resort fallback in WP core (5.8/5.9), |
| 206 | 206 | //but we'll set it anyway in case some code somewhere needs it. |
| 207 | 207 | reset($icons); |
@@ -229,7 +229,7 @@ discard block |
||
| 229 | 229 | 'banner-1544x500.jpg' => 'low', |
| 230 | 230 | )); |
| 231 | 231 | |
| 232 | - if ( !empty($banners) ) { |
|
| 232 | + if (!empty($banners)) { |
|
| 233 | 233 | $pluginInfo->banners = $banners; |
| 234 | 234 | } |
| 235 | 235 | } |
@@ -240,7 +240,7 @@ discard block |
||
| 240 | 240 | */ |
| 241 | 241 | protected function getLocalAssetUrls($filesToKeys) { |
| 242 | 242 | $assetDirectory = $this->package->getAbsoluteDirectoryPath() . DIRECTORY_SEPARATOR . 'assets'; |
| 243 | - if ( !is_dir($assetDirectory) ) { |
|
| 243 | + if (!is_dir($assetDirectory)) { |
|
| 244 | 244 | return array(); |
| 245 | 245 | } |
| 246 | 246 | $assetBaseUrl = trailingslashit(plugins_url('', $assetDirectory . '/imaginary.file')); |
@@ -248,7 +248,7 @@ discard block |
||
| 248 | 248 | $foundAssets = array(); |
| 249 | 249 | foreach ($filesToKeys as $fileName => $key) { |
| 250 | 250 | $fullBannerPath = $assetDirectory . DIRECTORY_SEPARATOR . $fileName; |
| 251 | - if ( !isset($icons[$key]) && is_file($fullBannerPath) ) { |
|
| 251 | + if (!isset($icons[$key]) && is_file($fullBannerPath)) { |
|
| 252 | 252 | $foundAssets[$key] = $assetBaseUrl . $fileName; |
| 253 | 253 | } |
| 254 | 254 | } |
@@ -3,390 +3,390 @@ |
||
| 3 | 3 | |
| 4 | 4 | if ( !class_exists(GitLabApi::class, false) ): |
| 5 | 5 | |
| 6 | - class GitLabApi extends Api { |
|
| 7 | - /** |
|
| 8 | - * @var string GitLab username. |
|
| 9 | - */ |
|
| 10 | - protected $userName; |
|
| 11 | - |
|
| 12 | - /** |
|
| 13 | - * @var string GitLab server host. |
|
| 14 | - */ |
|
| 15 | - protected $repositoryHost; |
|
| 16 | - |
|
| 17 | - /** |
|
| 18 | - * @var string Protocol used by this GitLab server: "http" or "https". |
|
| 19 | - */ |
|
| 20 | - protected $repositoryProtocol = 'https'; |
|
| 21 | - |
|
| 22 | - /** |
|
| 23 | - * @var string GitLab repository name. |
|
| 24 | - */ |
|
| 25 | - protected $repositoryName; |
|
| 26 | - |
|
| 27 | - /** |
|
| 28 | - * @var string GitLab authentication token. Optional. |
|
| 29 | - */ |
|
| 30 | - protected $accessToken; |
|
| 31 | - |
|
| 32 | - /** |
|
| 33 | - * @var bool Whether to download release assets instead of the auto-generated source code archives. |
|
| 34 | - */ |
|
| 35 | - protected $releaseAssetsEnabled = false; |
|
| 36 | - |
|
| 37 | - /** |
|
| 38 | - * @var bool Whether to download release asset package rather than release asset source. |
|
| 39 | - */ |
|
| 40 | - protected $releasePackageEnabled = false; |
|
| 41 | - |
|
| 42 | - public function __construct($repositoryUrl, $accessToken = null, $subgroup = null) { |
|
| 43 | - //Parse the repository host to support custom hosts. |
|
| 44 | - $port = wp_parse_url($repositoryUrl, PHP_URL_PORT); |
|
| 45 | - if ( !empty($port) ) { |
|
| 46 | - $port = ':' . $port; |
|
| 47 | - } |
|
| 48 | - $this->repositoryHost = wp_parse_url($repositoryUrl, PHP_URL_HOST) . $port; |
|
| 49 | - |
|
| 50 | - if ( $this->repositoryHost !== 'gitlab.com' ) { |
|
| 51 | - $this->repositoryProtocol = wp_parse_url($repositoryUrl, PHP_URL_SCHEME); |
|
| 52 | - } |
|
| 53 | - |
|
| 54 | - //Find the repository information |
|
| 55 | - $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); |
|
| 56 | - if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) { |
|
| 57 | - $this->userName = $matches['username']; |
|
| 58 | - $this->repositoryName = $matches['repository']; |
|
| 59 | - } elseif ( ($this->repositoryHost === 'gitlab.com') ) { |
|
| 60 | - //This is probably a repository in a subgroup, e.g. "/organization/category/repo". |
|
| 61 | - $parts = explode('/', trim($path, '/')); |
|
| 62 | - if ( count($parts) < 3 ) { |
|
| 63 | - throw new \InvalidArgumentException('Invalid GitLab.com repository URL: "' . $repositoryUrl . '"'); |
|
| 64 | - } |
|
| 65 | - $lastPart = array_pop($parts); |
|
| 66 | - $this->userName = implode('/', $parts); |
|
| 67 | - $this->repositoryName = $lastPart; |
|
| 68 | - } else { |
|
| 69 | - //There could be subgroups in the URL: gitlab.domain.com/group/subgroup/subgroup2/repository |
|
| 70 | - if ( $subgroup !== null ) { |
|
| 71 | - $path = str_replace(trailingslashit($subgroup), '', $path); |
|
| 72 | - } |
|
| 73 | - |
|
| 74 | - //This is not a traditional url, it could be gitlab is in a deeper subdirectory. |
|
| 75 | - //Get the path segments. |
|
| 76 | - $segments = explode('/', untrailingslashit(ltrim($path, '/'))); |
|
| 77 | - |
|
| 78 | - //We need at least /user-name/repository-name/ |
|
| 79 | - if ( count($segments) < 2 ) { |
|
| 80 | - throw new \InvalidArgumentException('Invalid GitLab repository URL: "' . $repositoryUrl . '"'); |
|
| 81 | - } |
|
| 82 | - |
|
| 83 | - //Get the username and repository name. |
|
| 84 | - $usernameRepo = array_splice($segments, -2, 2); |
|
| 85 | - $this->userName = $usernameRepo[0]; |
|
| 86 | - $this->repositoryName = $usernameRepo[1]; |
|
| 87 | - |
|
| 88 | - //Append the remaining segments to the host if there are segments left. |
|
| 89 | - if ( count($segments) > 0 ) { |
|
| 90 | - $this->repositoryHost = trailingslashit($this->repositoryHost) . implode('/', $segments); |
|
| 91 | - } |
|
| 92 | - |
|
| 93 | - //Add subgroups to username. |
|
| 94 | - if ( $subgroup !== null ) { |
|
| 95 | - $this->userName = $usernameRepo[0] . '/' . untrailingslashit($subgroup); |
|
| 96 | - } |
|
| 97 | - } |
|
| 98 | - |
|
| 99 | - parent::__construct($repositoryUrl, $accessToken); |
|
| 100 | - } |
|
| 101 | - |
|
| 102 | - /** |
|
| 103 | - * Get the latest release from GitLab. |
|
| 104 | - * |
|
| 105 | - * @return Reference|null |
|
| 106 | - */ |
|
| 107 | - public function getLatestRelease() { |
|
| 108 | - $releases = $this->api('/:id/releases'); |
|
| 109 | - if ( is_wp_error($releases) || empty($releases) || !is_array($releases) ) { |
|
| 110 | - return null; |
|
| 111 | - } |
|
| 112 | - |
|
| 113 | - foreach ($releases as $release) { |
|
| 114 | - if ( true !== $release->upcoming_release ) { |
|
| 115 | - break 1; //Break the loop on the first release we find that isn't an upcoming release |
|
| 116 | - } |
|
| 117 | - } |
|
| 118 | - if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) { |
|
| 119 | - return null; |
|
| 120 | - } |
|
| 121 | - |
|
| 122 | - $reference = new Reference(array( |
|
| 123 | - 'name' => $release->tag_name, |
|
| 124 | - 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". |
|
| 125 | - 'downloadUrl' => '', |
|
| 126 | - 'updated' => $release->released_at, |
|
| 127 | - 'apiResponse' => $release, |
|
| 128 | - )); |
|
| 129 | - $download_url = false; |
|
| 130 | - |
|
| 131 | - if ( $this->releasePackageEnabled && isset($release->assets, $release->assets->links) ) { |
|
| 132 | - /** |
|
| 133 | - * Use the first asset LINK that is a zip format file generated by a Gitlab Release Pipeline |
|
| 134 | - * |
|
| 135 | - * @link https://gist.github.com/timwiel/9dfd3526c768efad4973254085e065ce |
|
| 136 | - */ |
|
| 137 | - foreach ($release->assets->links as $link) { |
|
| 138 | - //TODO: Check the "format" property instead of the URL suffix. |
|
| 139 | - if ( 'zip' === substr($link->url, -3) ) { |
|
| 140 | - $download_url = $link->url; |
|
| 141 | - break 1; |
|
| 142 | - } |
|
| 143 | - } |
|
| 144 | - if ( empty( $download_url ) ) { |
|
| 145 | - return null; |
|
| 146 | - } |
|
| 147 | - if ( ! empty( $this->accessToken ) ) { |
|
| 148 | - $download_url = add_query_arg('private_token', $this->accessToken, $download_url); |
|
| 149 | - } |
|
| 150 | - $reference->downloadUrl = $download_url; |
|
| 151 | - return $reference; |
|
| 152 | - |
|
| 153 | - } elseif ( isset($release->assets) ) { |
|
| 154 | - /** |
|
| 155 | - * Use the first asset SOURCE file that is a zip format from a Gitlab Release which should be a zip file |
|
| 156 | - */ |
|
| 157 | - foreach ($release->assets->sources as $source) { |
|
| 158 | - if ( 'zip' === $source->format ) { |
|
| 159 | - $download_url = $source->url; |
|
| 160 | - break 1; |
|
| 161 | - } |
|
| 162 | - } |
|
| 163 | - if ( empty( $download_url ) ) { |
|
| 164 | - return null; |
|
| 165 | - } |
|
| 166 | - if ( ! empty( $this->accessToken ) ) { |
|
| 167 | - $download_url = add_query_arg('private_token', $this->accessToken, $download_url); |
|
| 168 | - } |
|
| 169 | - $reference->downloadUrl = $download_url; |
|
| 170 | - return $reference; |
|
| 171 | - |
|
| 172 | - } |
|
| 173 | - |
|
| 174 | - //If we get this far without a return then obviosuly noi release download urls were found |
|
| 175 | - return null; |
|
| 176 | - } |
|
| 177 | - |
|
| 178 | - /** |
|
| 179 | - * Get the tag that looks like the highest version number. |
|
| 180 | - * |
|
| 181 | - * @return Reference|null |
|
| 182 | - */ |
|
| 183 | - public function getLatestTag() { |
|
| 184 | - $tags = $this->api('/:id/repository/tags'); |
|
| 185 | - if ( is_wp_error($tags) || empty($tags) || !is_array($tags) ) { |
|
| 186 | - return null; |
|
| 187 | - } |
|
| 188 | - |
|
| 189 | - $versionTags = $this->sortTagsByVersion($tags); |
|
| 190 | - if ( empty($versionTags) ) { |
|
| 191 | - return null; |
|
| 192 | - } |
|
| 193 | - |
|
| 194 | - $tag = $versionTags[0]; |
|
| 195 | - return new Reference(array( |
|
| 196 | - 'name' => $tag->name, |
|
| 197 | - 'version' => ltrim($tag->name, 'v'), |
|
| 198 | - 'downloadUrl' => $this->buildArchiveDownloadUrl($tag->name), |
|
| 199 | - 'apiResponse' => $tag, |
|
| 200 | - )); |
|
| 201 | - } |
|
| 202 | - |
|
| 203 | - /** |
|
| 204 | - * Get a branch by name. |
|
| 205 | - * |
|
| 206 | - * @param string $branchName |
|
| 207 | - * @return null|Reference |
|
| 208 | - */ |
|
| 209 | - public function getBranch($branchName) { |
|
| 210 | - $branch = $this->api('/:id/repository/branches/' . $branchName); |
|
| 211 | - if ( is_wp_error($branch) || empty($branch) ) { |
|
| 212 | - return null; |
|
| 213 | - } |
|
| 214 | - |
|
| 215 | - $reference = new Reference(array( |
|
| 216 | - 'name' => $branch->name, |
|
| 217 | - 'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name), |
|
| 218 | - 'apiResponse' => $branch, |
|
| 219 | - )); |
|
| 220 | - |
|
| 221 | - if ( isset($branch->commit, $branch->commit->committed_date) ) { |
|
| 222 | - $reference->updated = $branch->commit->committed_date; |
|
| 223 | - } |
|
| 224 | - |
|
| 225 | - return $reference; |
|
| 226 | - } |
|
| 227 | - |
|
| 228 | - /** |
|
| 229 | - * Get the timestamp of the latest commit that changed the specified branch or tag. |
|
| 230 | - * |
|
| 231 | - * @param string $ref Reference name (e.g. branch or tag). |
|
| 232 | - * @return string|null |
|
| 233 | - */ |
|
| 234 | - public function getLatestCommitTime($ref) { |
|
| 235 | - $commits = $this->api('/:id/repository/commits/', array('ref_name' => $ref)); |
|
| 236 | - if ( is_wp_error($commits) || !is_array($commits) || !isset($commits[0]) ) { |
|
| 237 | - return null; |
|
| 238 | - } |
|
| 239 | - |
|
| 240 | - return $commits[0]->committed_date; |
|
| 241 | - } |
|
| 242 | - |
|
| 243 | - /** |
|
| 244 | - * Perform a GitLab API request. |
|
| 245 | - * |
|
| 246 | - * @param string $url |
|
| 247 | - * @param array $queryParams |
|
| 248 | - * @return mixed|\WP_Error |
|
| 249 | - */ |
|
| 250 | - protected function api($url, $queryParams = array()) { |
|
| 251 | - $baseUrl = $url; |
|
| 252 | - $url = $this->buildApiUrl($url, $queryParams); |
|
| 253 | - |
|
| 254 | - $options = array('timeout' => 10); |
|
| 255 | - if ( !empty($this->httpFilterName) ) { |
|
| 256 | - $options = apply_filters($this->httpFilterName, $options); |
|
| 257 | - } |
|
| 258 | - |
|
| 259 | - $response = wp_remote_get($url, $options); |
|
| 260 | - if ( is_wp_error($response) ) { |
|
| 261 | - do_action('puc_api_error', $response, null, $url, $this->slug); |
|
| 262 | - return $response; |
|
| 263 | - } |
|
| 264 | - |
|
| 265 | - $code = wp_remote_retrieve_response_code($response); |
|
| 266 | - $body = wp_remote_retrieve_body($response); |
|
| 267 | - if ( $code === 200 ) { |
|
| 268 | - return json_decode($body); |
|
| 269 | - } |
|
| 270 | - |
|
| 271 | - $error = new \WP_Error( |
|
| 272 | - 'puc-gitlab-http-error', |
|
| 273 | - sprintf('GitLab API error. URL: "%s", HTTP status code: %d.', $baseUrl, $code) |
|
| 274 | - ); |
|
| 275 | - do_action('puc_api_error', $error, $response, $url, $this->slug); |
|
| 276 | - |
|
| 277 | - return $error; |
|
| 278 | - } |
|
| 279 | - |
|
| 280 | - /** |
|
| 281 | - * Build a fully qualified URL for an API request. |
|
| 282 | - * |
|
| 283 | - * @param string $url |
|
| 284 | - * @param array $queryParams |
|
| 285 | - * @return string |
|
| 286 | - */ |
|
| 287 | - protected function buildApiUrl($url, $queryParams) { |
|
| 288 | - $variables = array( |
|
| 289 | - 'user' => $this->userName, |
|
| 290 | - 'repo' => $this->repositoryName, |
|
| 291 | - 'id' => $this->userName . '/' . $this->repositoryName, |
|
| 292 | - ); |
|
| 293 | - |
|
| 294 | - foreach ($variables as $name => $value) { |
|
| 295 | - $url = str_replace("/:{$name}", '/' . urlencode($value), $url); |
|
| 296 | - } |
|
| 297 | - |
|
| 298 | - $url = substr($url, 1); |
|
| 299 | - $url = sprintf('%1$s://%2$s/api/v4/projects/%3$s', $this->repositoryProtocol, $this->repositoryHost, $url); |
|
| 300 | - |
|
| 301 | - if ( !empty($this->accessToken) ) { |
|
| 302 | - $queryParams['private_token'] = $this->accessToken; |
|
| 303 | - } |
|
| 304 | - |
|
| 305 | - if ( !empty($queryParams) ) { |
|
| 306 | - $url = add_query_arg($queryParams, $url); |
|
| 307 | - } |
|
| 308 | - |
|
| 309 | - return $url; |
|
| 310 | - } |
|
| 311 | - |
|
| 312 | - /** |
|
| 313 | - * Get the contents of a file from a specific branch or tag. |
|
| 314 | - * |
|
| 315 | - * @param string $path File name. |
|
| 316 | - * @param string $ref |
|
| 317 | - * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error. |
|
| 318 | - */ |
|
| 319 | - public function getRemoteFile($path, $ref = 'master') { |
|
| 320 | - $response = $this->api('/:id/repository/files/' . $path, array('ref' => $ref)); |
|
| 321 | - if ( is_wp_error($response) || !isset($response->content) || $response->encoding !== 'base64' ) { |
|
| 322 | - return null; |
|
| 323 | - } |
|
| 324 | - |
|
| 325 | - return base64_decode($response->content); |
|
| 326 | - } |
|
| 327 | - |
|
| 328 | - /** |
|
| 329 | - * Generate a URL to download a ZIP archive of the specified branch/tag/etc. |
|
| 330 | - * |
|
| 331 | - * @param string $ref |
|
| 332 | - * @return string |
|
| 333 | - */ |
|
| 334 | - public function buildArchiveDownloadUrl($ref = 'master') { |
|
| 335 | - $url = sprintf( |
|
| 336 | - '%1$s://%2$s/api/v4/projects/%3$s/repository/archive.zip', |
|
| 337 | - $this->repositoryProtocol, |
|
| 338 | - $this->repositoryHost, |
|
| 339 | - urlencode($this->userName . '/' . $this->repositoryName) |
|
| 340 | - ); |
|
| 341 | - $url = add_query_arg('sha', urlencode($ref), $url); |
|
| 342 | - |
|
| 343 | - if ( !empty($this->accessToken) ) { |
|
| 344 | - $url = add_query_arg('private_token', $this->accessToken, $url); |
|
| 345 | - } |
|
| 346 | - |
|
| 347 | - return $url; |
|
| 348 | - } |
|
| 349 | - |
|
| 350 | - /** |
|
| 351 | - * Get a specific tag. |
|
| 352 | - * |
|
| 353 | - * @param string $tagName |
|
| 354 | - * @return void |
|
| 355 | - */ |
|
| 356 | - public function getTag($tagName) { |
|
| 357 | - throw new \LogicException('The ' . __METHOD__ . ' method is not implemented and should not be used.'); |
|
| 358 | - } |
|
| 359 | - |
|
| 360 | - protected function getUpdateDetectionStrategies($configBranch) { |
|
| 361 | - $strategies = array(); |
|
| 362 | - |
|
| 363 | - if ( ($configBranch === 'main') || ($configBranch === 'master') ) { |
|
| 364 | - $strategies[self::STRATEGY_LATEST_RELEASE] = array($this, 'getLatestRelease'); |
|
| 365 | - $strategies[self::STRATEGY_LATEST_TAG] = array($this, 'getLatestTag'); |
|
| 366 | - } |
|
| 367 | - |
|
| 368 | - $strategies[self::STRATEGY_BRANCH] = function() use ($configBranch) { |
|
| 369 | - return $this->getBranch($configBranch); |
|
| 370 | - }; |
|
| 371 | - |
|
| 372 | - return $strategies; |
|
| 373 | - } |
|
| 374 | - |
|
| 375 | - public function setAuthentication($credentials) { |
|
| 376 | - parent::setAuthentication($credentials); |
|
| 377 | - $this->accessToken = is_string($credentials) ? $credentials : null; |
|
| 378 | - } |
|
| 379 | - |
|
| 380 | - public function enableReleaseAssets() { |
|
| 381 | - $this->releaseAssetsEnabled = true; |
|
| 382 | - $this->releasePackageEnabled = false; |
|
| 383 | - } |
|
| 384 | - |
|
| 385 | - public function enableReleasePackages() { |
|
| 386 | - $this->releaseAssetsEnabled = false; |
|
| 387 | - $this->releasePackageEnabled = true; |
|
| 388 | - } |
|
| 389 | - |
|
| 390 | - } |
|
| 6 | + class GitLabApi extends Api { |
|
| 7 | + /** |
|
| 8 | + * @var string GitLab username. |
|
| 9 | + */ |
|
| 10 | + protected $userName; |
|
| 11 | + |
|
| 12 | + /** |
|
| 13 | + * @var string GitLab server host. |
|
| 14 | + */ |
|
| 15 | + protected $repositoryHost; |
|
| 16 | + |
|
| 17 | + /** |
|
| 18 | + * @var string Protocol used by this GitLab server: "http" or "https". |
|
| 19 | + */ |
|
| 20 | + protected $repositoryProtocol = 'https'; |
|
| 21 | + |
|
| 22 | + /** |
|
| 23 | + * @var string GitLab repository name. |
|
| 24 | + */ |
|
| 25 | + protected $repositoryName; |
|
| 26 | + |
|
| 27 | + /** |
|
| 28 | + * @var string GitLab authentication token. Optional. |
|
| 29 | + */ |
|
| 30 | + protected $accessToken; |
|
| 31 | + |
|
| 32 | + /** |
|
| 33 | + * @var bool Whether to download release assets instead of the auto-generated source code archives. |
|
| 34 | + */ |
|
| 35 | + protected $releaseAssetsEnabled = false; |
|
| 36 | + |
|
| 37 | + /** |
|
| 38 | + * @var bool Whether to download release asset package rather than release asset source. |
|
| 39 | + */ |
|
| 40 | + protected $releasePackageEnabled = false; |
|
| 41 | + |
|
| 42 | + public function __construct($repositoryUrl, $accessToken = null, $subgroup = null) { |
|
| 43 | + //Parse the repository host to support custom hosts. |
|
| 44 | + $port = wp_parse_url($repositoryUrl, PHP_URL_PORT); |
|
| 45 | + if ( !empty($port) ) { |
|
| 46 | + $port = ':' . $port; |
|
| 47 | + } |
|
| 48 | + $this->repositoryHost = wp_parse_url($repositoryUrl, PHP_URL_HOST) . $port; |
|
| 49 | + |
|
| 50 | + if ( $this->repositoryHost !== 'gitlab.com' ) { |
|
| 51 | + $this->repositoryProtocol = wp_parse_url($repositoryUrl, PHP_URL_SCHEME); |
|
| 52 | + } |
|
| 53 | + |
|
| 54 | + //Find the repository information |
|
| 55 | + $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); |
|
| 56 | + if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) { |
|
| 57 | + $this->userName = $matches['username']; |
|
| 58 | + $this->repositoryName = $matches['repository']; |
|
| 59 | + } elseif ( ($this->repositoryHost === 'gitlab.com') ) { |
|
| 60 | + //This is probably a repository in a subgroup, e.g. "/organization/category/repo". |
|
| 61 | + $parts = explode('/', trim($path, '/')); |
|
| 62 | + if ( count($parts) < 3 ) { |
|
| 63 | + throw new \InvalidArgumentException('Invalid GitLab.com repository URL: "' . $repositoryUrl . '"'); |
|
| 64 | + } |
|
| 65 | + $lastPart = array_pop($parts); |
|
| 66 | + $this->userName = implode('/', $parts); |
|
| 67 | + $this->repositoryName = $lastPart; |
|
| 68 | + } else { |
|
| 69 | + //There could be subgroups in the URL: gitlab.domain.com/group/subgroup/subgroup2/repository |
|
| 70 | + if ( $subgroup !== null ) { |
|
| 71 | + $path = str_replace(trailingslashit($subgroup), '', $path); |
|
| 72 | + } |
|
| 73 | + |
|
| 74 | + //This is not a traditional url, it could be gitlab is in a deeper subdirectory. |
|
| 75 | + //Get the path segments. |
|
| 76 | + $segments = explode('/', untrailingslashit(ltrim($path, '/'))); |
|
| 77 | + |
|
| 78 | + //We need at least /user-name/repository-name/ |
|
| 79 | + if ( count($segments) < 2 ) { |
|
| 80 | + throw new \InvalidArgumentException('Invalid GitLab repository URL: "' . $repositoryUrl . '"'); |
|
| 81 | + } |
|
| 82 | + |
|
| 83 | + //Get the username and repository name. |
|
| 84 | + $usernameRepo = array_splice($segments, -2, 2); |
|
| 85 | + $this->userName = $usernameRepo[0]; |
|
| 86 | + $this->repositoryName = $usernameRepo[1]; |
|
| 87 | + |
|
| 88 | + //Append the remaining segments to the host if there are segments left. |
|
| 89 | + if ( count($segments) > 0 ) { |
|
| 90 | + $this->repositoryHost = trailingslashit($this->repositoryHost) . implode('/', $segments); |
|
| 91 | + } |
|
| 92 | + |
|
| 93 | + //Add subgroups to username. |
|
| 94 | + if ( $subgroup !== null ) { |
|
| 95 | + $this->userName = $usernameRepo[0] . '/' . untrailingslashit($subgroup); |
|
| 96 | + } |
|
| 97 | + } |
|
| 98 | + |
|
| 99 | + parent::__construct($repositoryUrl, $accessToken); |
|
| 100 | + } |
|
| 101 | + |
|
| 102 | + /** |
|
| 103 | + * Get the latest release from GitLab. |
|
| 104 | + * |
|
| 105 | + * @return Reference|null |
|
| 106 | + */ |
|
| 107 | + public function getLatestRelease() { |
|
| 108 | + $releases = $this->api('/:id/releases'); |
|
| 109 | + if ( is_wp_error($releases) || empty($releases) || !is_array($releases) ) { |
|
| 110 | + return null; |
|
| 111 | + } |
|
| 112 | + |
|
| 113 | + foreach ($releases as $release) { |
|
| 114 | + if ( true !== $release->upcoming_release ) { |
|
| 115 | + break 1; //Break the loop on the first release we find that isn't an upcoming release |
|
| 116 | + } |
|
| 117 | + } |
|
| 118 | + if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) { |
|
| 119 | + return null; |
|
| 120 | + } |
|
| 121 | + |
|
| 122 | + $reference = new Reference(array( |
|
| 123 | + 'name' => $release->tag_name, |
|
| 124 | + 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". |
|
| 125 | + 'downloadUrl' => '', |
|
| 126 | + 'updated' => $release->released_at, |
|
| 127 | + 'apiResponse' => $release, |
|
| 128 | + )); |
|
| 129 | + $download_url = false; |
|
| 130 | + |
|
| 131 | + if ( $this->releasePackageEnabled && isset($release->assets, $release->assets->links) ) { |
|
| 132 | + /** |
|
| 133 | + * Use the first asset LINK that is a zip format file generated by a Gitlab Release Pipeline |
|
| 134 | + * |
|
| 135 | + * @link https://gist.github.com/timwiel/9dfd3526c768efad4973254085e065ce |
|
| 136 | + */ |
|
| 137 | + foreach ($release->assets->links as $link) { |
|
| 138 | + //TODO: Check the "format" property instead of the URL suffix. |
|
| 139 | + if ( 'zip' === substr($link->url, -3) ) { |
|
| 140 | + $download_url = $link->url; |
|
| 141 | + break 1; |
|
| 142 | + } |
|
| 143 | + } |
|
| 144 | + if ( empty( $download_url ) ) { |
|
| 145 | + return null; |
|
| 146 | + } |
|
| 147 | + if ( ! empty( $this->accessToken ) ) { |
|
| 148 | + $download_url = add_query_arg('private_token', $this->accessToken, $download_url); |
|
| 149 | + } |
|
| 150 | + $reference->downloadUrl = $download_url; |
|
| 151 | + return $reference; |
|
| 152 | + |
|
| 153 | + } elseif ( isset($release->assets) ) { |
|
| 154 | + /** |
|
| 155 | + * Use the first asset SOURCE file that is a zip format from a Gitlab Release which should be a zip file |
|
| 156 | + */ |
|
| 157 | + foreach ($release->assets->sources as $source) { |
|
| 158 | + if ( 'zip' === $source->format ) { |
|
| 159 | + $download_url = $source->url; |
|
| 160 | + break 1; |
|
| 161 | + } |
|
| 162 | + } |
|
| 163 | + if ( empty( $download_url ) ) { |
|
| 164 | + return null; |
|
| 165 | + } |
|
| 166 | + if ( ! empty( $this->accessToken ) ) { |
|
| 167 | + $download_url = add_query_arg('private_token', $this->accessToken, $download_url); |
|
| 168 | + } |
|
| 169 | + $reference->downloadUrl = $download_url; |
|
| 170 | + return $reference; |
|
| 171 | + |
|
| 172 | + } |
|
| 173 | + |
|
| 174 | + //If we get this far without a return then obviosuly noi release download urls were found |
|
| 175 | + return null; |
|
| 176 | + } |
|
| 177 | + |
|
| 178 | + /** |
|
| 179 | + * Get the tag that looks like the highest version number. |
|
| 180 | + * |
|
| 181 | + * @return Reference|null |
|
| 182 | + */ |
|
| 183 | + public function getLatestTag() { |
|
| 184 | + $tags = $this->api('/:id/repository/tags'); |
|
| 185 | + if ( is_wp_error($tags) || empty($tags) || !is_array($tags) ) { |
|
| 186 | + return null; |
|
| 187 | + } |
|
| 188 | + |
|
| 189 | + $versionTags = $this->sortTagsByVersion($tags); |
|
| 190 | + if ( empty($versionTags) ) { |
|
| 191 | + return null; |
|
| 192 | + } |
|
| 193 | + |
|
| 194 | + $tag = $versionTags[0]; |
|
| 195 | + return new Reference(array( |
|
| 196 | + 'name' => $tag->name, |
|
| 197 | + 'version' => ltrim($tag->name, 'v'), |
|
| 198 | + 'downloadUrl' => $this->buildArchiveDownloadUrl($tag->name), |
|
| 199 | + 'apiResponse' => $tag, |
|
| 200 | + )); |
|
| 201 | + } |
|
| 202 | + |
|
| 203 | + /** |
|
| 204 | + * Get a branch by name. |
|
| 205 | + * |
|
| 206 | + * @param string $branchName |
|
| 207 | + * @return null|Reference |
|
| 208 | + */ |
|
| 209 | + public function getBranch($branchName) { |
|
| 210 | + $branch = $this->api('/:id/repository/branches/' . $branchName); |
|
| 211 | + if ( is_wp_error($branch) || empty($branch) ) { |
|
| 212 | + return null; |
|
| 213 | + } |
|
| 214 | + |
|
| 215 | + $reference = new Reference(array( |
|
| 216 | + 'name' => $branch->name, |
|
| 217 | + 'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name), |
|
| 218 | + 'apiResponse' => $branch, |
|
| 219 | + )); |
|
| 220 | + |
|
| 221 | + if ( isset($branch->commit, $branch->commit->committed_date) ) { |
|
| 222 | + $reference->updated = $branch->commit->committed_date; |
|
| 223 | + } |
|
| 224 | + |
|
| 225 | + return $reference; |
|
| 226 | + } |
|
| 227 | + |
|
| 228 | + /** |
|
| 229 | + * Get the timestamp of the latest commit that changed the specified branch or tag. |
|
| 230 | + * |
|
| 231 | + * @param string $ref Reference name (e.g. branch or tag). |
|
| 232 | + * @return string|null |
|
| 233 | + */ |
|
| 234 | + public function getLatestCommitTime($ref) { |
|
| 235 | + $commits = $this->api('/:id/repository/commits/', array('ref_name' => $ref)); |
|
| 236 | + if ( is_wp_error($commits) || !is_array($commits) || !isset($commits[0]) ) { |
|
| 237 | + return null; |
|
| 238 | + } |
|
| 239 | + |
|
| 240 | + return $commits[0]->committed_date; |
|
| 241 | + } |
|
| 242 | + |
|
| 243 | + /** |
|
| 244 | + * Perform a GitLab API request. |
|
| 245 | + * |
|
| 246 | + * @param string $url |
|
| 247 | + * @param array $queryParams |
|
| 248 | + * @return mixed|\WP_Error |
|
| 249 | + */ |
|
| 250 | + protected function api($url, $queryParams = array()) { |
|
| 251 | + $baseUrl = $url; |
|
| 252 | + $url = $this->buildApiUrl($url, $queryParams); |
|
| 253 | + |
|
| 254 | + $options = array('timeout' => 10); |
|
| 255 | + if ( !empty($this->httpFilterName) ) { |
|
| 256 | + $options = apply_filters($this->httpFilterName, $options); |
|
| 257 | + } |
|
| 258 | + |
|
| 259 | + $response = wp_remote_get($url, $options); |
|
| 260 | + if ( is_wp_error($response) ) { |
|
| 261 | + do_action('puc_api_error', $response, null, $url, $this->slug); |
|
| 262 | + return $response; |
|
| 263 | + } |
|
| 264 | + |
|
| 265 | + $code = wp_remote_retrieve_response_code($response); |
|
| 266 | + $body = wp_remote_retrieve_body($response); |
|
| 267 | + if ( $code === 200 ) { |
|
| 268 | + return json_decode($body); |
|
| 269 | + } |
|
| 270 | + |
|
| 271 | + $error = new \WP_Error( |
|
| 272 | + 'puc-gitlab-http-error', |
|
| 273 | + sprintf('GitLab API error. URL: "%s", HTTP status code: %d.', $baseUrl, $code) |
|
| 274 | + ); |
|
| 275 | + do_action('puc_api_error', $error, $response, $url, $this->slug); |
|
| 276 | + |
|
| 277 | + return $error; |
|
| 278 | + } |
|
| 279 | + |
|
| 280 | + /** |
|
| 281 | + * Build a fully qualified URL for an API request. |
|
| 282 | + * |
|
| 283 | + * @param string $url |
|
| 284 | + * @param array $queryParams |
|
| 285 | + * @return string |
|
| 286 | + */ |
|
| 287 | + protected function buildApiUrl($url, $queryParams) { |
|
| 288 | + $variables = array( |
|
| 289 | + 'user' => $this->userName, |
|
| 290 | + 'repo' => $this->repositoryName, |
|
| 291 | + 'id' => $this->userName . '/' . $this->repositoryName, |
|
| 292 | + ); |
|
| 293 | + |
|
| 294 | + foreach ($variables as $name => $value) { |
|
| 295 | + $url = str_replace("/:{$name}", '/' . urlencode($value), $url); |
|
| 296 | + } |
|
| 297 | + |
|
| 298 | + $url = substr($url, 1); |
|
| 299 | + $url = sprintf('%1$s://%2$s/api/v4/projects/%3$s', $this->repositoryProtocol, $this->repositoryHost, $url); |
|
| 300 | + |
|
| 301 | + if ( !empty($this->accessToken) ) { |
|
| 302 | + $queryParams['private_token'] = $this->accessToken; |
|
| 303 | + } |
|
| 304 | + |
|
| 305 | + if ( !empty($queryParams) ) { |
|
| 306 | + $url = add_query_arg($queryParams, $url); |
|
| 307 | + } |
|
| 308 | + |
|
| 309 | + return $url; |
|
| 310 | + } |
|
| 311 | + |
|
| 312 | + /** |
|
| 313 | + * Get the contents of a file from a specific branch or tag. |
|
| 314 | + * |
|
| 315 | + * @param string $path File name. |
|
| 316 | + * @param string $ref |
|
| 317 | + * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error. |
|
| 318 | + */ |
|
| 319 | + public function getRemoteFile($path, $ref = 'master') { |
|
| 320 | + $response = $this->api('/:id/repository/files/' . $path, array('ref' => $ref)); |
|
| 321 | + if ( is_wp_error($response) || !isset($response->content) || $response->encoding !== 'base64' ) { |
|
| 322 | + return null; |
|
| 323 | + } |
|
| 324 | + |
|
| 325 | + return base64_decode($response->content); |
|
| 326 | + } |
|
| 327 | + |
|
| 328 | + /** |
|
| 329 | + * Generate a URL to download a ZIP archive of the specified branch/tag/etc. |
|
| 330 | + * |
|
| 331 | + * @param string $ref |
|
| 332 | + * @return string |
|
| 333 | + */ |
|
| 334 | + public function buildArchiveDownloadUrl($ref = 'master') { |
|
| 335 | + $url = sprintf( |
|
| 336 | + '%1$s://%2$s/api/v4/projects/%3$s/repository/archive.zip', |
|
| 337 | + $this->repositoryProtocol, |
|
| 338 | + $this->repositoryHost, |
|
| 339 | + urlencode($this->userName . '/' . $this->repositoryName) |
|
| 340 | + ); |
|
| 341 | + $url = add_query_arg('sha', urlencode($ref), $url); |
|
| 342 | + |
|
| 343 | + if ( !empty($this->accessToken) ) { |
|
| 344 | + $url = add_query_arg('private_token', $this->accessToken, $url); |
|
| 345 | + } |
|
| 346 | + |
|
| 347 | + return $url; |
|
| 348 | + } |
|
| 349 | + |
|
| 350 | + /** |
|
| 351 | + * Get a specific tag. |
|
| 352 | + * |
|
| 353 | + * @param string $tagName |
|
| 354 | + * @return void |
|
| 355 | + */ |
|
| 356 | + public function getTag($tagName) { |
|
| 357 | + throw new \LogicException('The ' . __METHOD__ . ' method is not implemented and should not be used.'); |
|
| 358 | + } |
|
| 359 | + |
|
| 360 | + protected function getUpdateDetectionStrategies($configBranch) { |
|
| 361 | + $strategies = array(); |
|
| 362 | + |
|
| 363 | + if ( ($configBranch === 'main') || ($configBranch === 'master') ) { |
|
| 364 | + $strategies[self::STRATEGY_LATEST_RELEASE] = array($this, 'getLatestRelease'); |
|
| 365 | + $strategies[self::STRATEGY_LATEST_TAG] = array($this, 'getLatestTag'); |
|
| 366 | + } |
|
| 367 | + |
|
| 368 | + $strategies[self::STRATEGY_BRANCH] = function() use ($configBranch) { |
|
| 369 | + return $this->getBranch($configBranch); |
|
| 370 | + }; |
|
| 371 | + |
|
| 372 | + return $strategies; |
|
| 373 | + } |
|
| 374 | + |
|
| 375 | + public function setAuthentication($credentials) { |
|
| 376 | + parent::setAuthentication($credentials); |
|
| 377 | + $this->accessToken = is_string($credentials) ? $credentials : null; |
|
| 378 | + } |
|
| 379 | + |
|
| 380 | + public function enableReleaseAssets() { |
|
| 381 | + $this->releaseAssetsEnabled = true; |
|
| 382 | + $this->releasePackageEnabled = false; |
|
| 383 | + } |
|
| 384 | + |
|
| 385 | + public function enableReleasePackages() { |
|
| 386 | + $this->releaseAssetsEnabled = false; |
|
| 387 | + $this->releasePackageEnabled = true; |
|
| 388 | + } |
|
| 389 | + |
|
| 390 | + } |
|
| 391 | 391 | |
| 392 | 392 | endif; |
@@ -1,7 +1,7 @@ discard block |
||
| 1 | 1 | <?php |
| 2 | 2 | namespace YahnisElsts\PluginUpdateChecker\v5p0\Vcs; |
| 3 | 3 | |
| 4 | -if ( !class_exists(GitLabApi::class, false) ): |
|
| 4 | +if (!class_exists(GitLabApi::class, false)): |
|
| 5 | 5 | |
| 6 | 6 | class GitLabApi extends Api { |
| 7 | 7 | /** |
@@ -42,24 +42,24 @@ discard block |
||
| 42 | 42 | public function __construct($repositoryUrl, $accessToken = null, $subgroup = null) { |
| 43 | 43 | //Parse the repository host to support custom hosts. |
| 44 | 44 | $port = wp_parse_url($repositoryUrl, PHP_URL_PORT); |
| 45 | - if ( !empty($port) ) { |
|
| 45 | + if (!empty($port)) { |
|
| 46 | 46 | $port = ':' . $port; |
| 47 | 47 | } |
| 48 | 48 | $this->repositoryHost = wp_parse_url($repositoryUrl, PHP_URL_HOST) . $port; |
| 49 | 49 | |
| 50 | - if ( $this->repositoryHost !== 'gitlab.com' ) { |
|
| 50 | + if ($this->repositoryHost !== 'gitlab.com') { |
|
| 51 | 51 | $this->repositoryProtocol = wp_parse_url($repositoryUrl, PHP_URL_SCHEME); |
| 52 | 52 | } |
| 53 | 53 | |
| 54 | 54 | //Find the repository information |
| 55 | 55 | $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); |
| 56 | - if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) { |
|
| 56 | + if (preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches)) { |
|
| 57 | 57 | $this->userName = $matches['username']; |
| 58 | 58 | $this->repositoryName = $matches['repository']; |
| 59 | - } elseif ( ($this->repositoryHost === 'gitlab.com') ) { |
|
| 59 | + } elseif (($this->repositoryHost === 'gitlab.com')) { |
|
| 60 | 60 | //This is probably a repository in a subgroup, e.g. "/organization/category/repo". |
| 61 | 61 | $parts = explode('/', trim($path, '/')); |
| 62 | - if ( count($parts) < 3 ) { |
|
| 62 | + if (count($parts) < 3) { |
|
| 63 | 63 | throw new \InvalidArgumentException('Invalid GitLab.com repository URL: "' . $repositoryUrl . '"'); |
| 64 | 64 | } |
| 65 | 65 | $lastPart = array_pop($parts); |
@@ -67,7 +67,7 @@ discard block |
||
| 67 | 67 | $this->repositoryName = $lastPart; |
| 68 | 68 | } else { |
| 69 | 69 | //There could be subgroups in the URL: gitlab.domain.com/group/subgroup/subgroup2/repository |
| 70 | - if ( $subgroup !== null ) { |
|
| 70 | + if ($subgroup !== null) { |
|
| 71 | 71 | $path = str_replace(trailingslashit($subgroup), '', $path); |
| 72 | 72 | } |
| 73 | 73 | |
@@ -76,7 +76,7 @@ discard block |
||
| 76 | 76 | $segments = explode('/', untrailingslashit(ltrim($path, '/'))); |
| 77 | 77 | |
| 78 | 78 | //We need at least /user-name/repository-name/ |
| 79 | - if ( count($segments) < 2 ) { |
|
| 79 | + if (count($segments) < 2) { |
|
| 80 | 80 | throw new \InvalidArgumentException('Invalid GitLab repository URL: "' . $repositoryUrl . '"'); |
| 81 | 81 | } |
| 82 | 82 | |
@@ -86,12 +86,12 @@ discard block |
||
| 86 | 86 | $this->repositoryName = $usernameRepo[1]; |
| 87 | 87 | |
| 88 | 88 | //Append the remaining segments to the host if there are segments left. |
| 89 | - if ( count($segments) > 0 ) { |
|
| 89 | + if (count($segments) > 0) { |
|
| 90 | 90 | $this->repositoryHost = trailingslashit($this->repositoryHost) . implode('/', $segments); |
| 91 | 91 | } |
| 92 | 92 | |
| 93 | 93 | //Add subgroups to username. |
| 94 | - if ( $subgroup !== null ) { |
|
| 94 | + if ($subgroup !== null) { |
|
| 95 | 95 | $this->userName = $usernameRepo[0] . '/' . untrailingslashit($subgroup); |
| 96 | 96 | } |
| 97 | 97 | } |
@@ -106,16 +106,16 @@ discard block |
||
| 106 | 106 | */ |
| 107 | 107 | public function getLatestRelease() { |
| 108 | 108 | $releases = $this->api('/:id/releases'); |
| 109 | - if ( is_wp_error($releases) || empty($releases) || !is_array($releases) ) { |
|
| 109 | + if (is_wp_error($releases) || empty($releases) || !is_array($releases)) { |
|
| 110 | 110 | return null; |
| 111 | 111 | } |
| 112 | 112 | |
| 113 | 113 | foreach ($releases as $release) { |
| 114 | - if ( true !== $release->upcoming_release ) { |
|
| 114 | + if (true !== $release->upcoming_release) { |
|
| 115 | 115 | break 1; //Break the loop on the first release we find that isn't an upcoming release |
| 116 | 116 | } |
| 117 | 117 | } |
| 118 | - if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) { |
|
| 118 | + if (is_wp_error($release) || !is_object($release) || !isset($release->tag_name)) { |
|
| 119 | 119 | return null; |
| 120 | 120 | } |
| 121 | 121 | |
@@ -128,7 +128,7 @@ discard block |
||
| 128 | 128 | )); |
| 129 | 129 | $download_url = false; |
| 130 | 130 | |
| 131 | - if ( $this->releasePackageEnabled && isset($release->assets, $release->assets->links) ) { |
|
| 131 | + if ($this->releasePackageEnabled && isset($release->assets, $release->assets->links)) { |
|
| 132 | 132 | /** |
| 133 | 133 | * Use the first asset LINK that is a zip format file generated by a Gitlab Release Pipeline |
| 134 | 134 | * |
@@ -136,34 +136,34 @@ discard block |
||
| 136 | 136 | */ |
| 137 | 137 | foreach ($release->assets->links as $link) { |
| 138 | 138 | //TODO: Check the "format" property instead of the URL suffix. |
| 139 | - if ( 'zip' === substr($link->url, -3) ) { |
|
| 139 | + if ('zip' === substr($link->url, -3)) { |
|
| 140 | 140 | $download_url = $link->url; |
| 141 | 141 | break 1; |
| 142 | 142 | } |
| 143 | 143 | } |
| 144 | - if ( empty( $download_url ) ) { |
|
| 144 | + if (empty($download_url)) { |
|
| 145 | 145 | return null; |
| 146 | 146 | } |
| 147 | - if ( ! empty( $this->accessToken ) ) { |
|
| 147 | + if (!empty($this->accessToken)) { |
|
| 148 | 148 | $download_url = add_query_arg('private_token', $this->accessToken, $download_url); |
| 149 | 149 | } |
| 150 | 150 | $reference->downloadUrl = $download_url; |
| 151 | 151 | return $reference; |
| 152 | 152 | |
| 153 | - } elseif ( isset($release->assets) ) { |
|
| 153 | + } elseif (isset($release->assets)) { |
|
| 154 | 154 | /** |
| 155 | 155 | * Use the first asset SOURCE file that is a zip format from a Gitlab Release which should be a zip file |
| 156 | 156 | */ |
| 157 | 157 | foreach ($release->assets->sources as $source) { |
| 158 | - if ( 'zip' === $source->format ) { |
|
| 158 | + if ('zip' === $source->format) { |
|
| 159 | 159 | $download_url = $source->url; |
| 160 | 160 | break 1; |
| 161 | 161 | } |
| 162 | 162 | } |
| 163 | - if ( empty( $download_url ) ) { |
|
| 163 | + if (empty($download_url)) { |
|
| 164 | 164 | return null; |
| 165 | 165 | } |
| 166 | - if ( ! empty( $this->accessToken ) ) { |
|
| 166 | + if (!empty($this->accessToken)) { |
|
| 167 | 167 | $download_url = add_query_arg('private_token', $this->accessToken, $download_url); |
| 168 | 168 | } |
| 169 | 169 | $reference->downloadUrl = $download_url; |
@@ -182,12 +182,12 @@ discard block |
||
| 182 | 182 | */ |
| 183 | 183 | public function getLatestTag() { |
| 184 | 184 | $tags = $this->api('/:id/repository/tags'); |
| 185 | - if ( is_wp_error($tags) || empty($tags) || !is_array($tags) ) { |
|
| 185 | + if (is_wp_error($tags) || empty($tags) || !is_array($tags)) { |
|
| 186 | 186 | return null; |
| 187 | 187 | } |
| 188 | 188 | |
| 189 | 189 | $versionTags = $this->sortTagsByVersion($tags); |
| 190 | - if ( empty($versionTags) ) { |
|
| 190 | + if (empty($versionTags)) { |
|
| 191 | 191 | return null; |
| 192 | 192 | } |
| 193 | 193 | |
@@ -208,7 +208,7 @@ discard block |
||
| 208 | 208 | */ |
| 209 | 209 | public function getBranch($branchName) { |
| 210 | 210 | $branch = $this->api('/:id/repository/branches/' . $branchName); |
| 211 | - if ( is_wp_error($branch) || empty($branch) ) { |
|
| 211 | + if (is_wp_error($branch) || empty($branch)) { |
|
| 212 | 212 | return null; |
| 213 | 213 | } |
| 214 | 214 | |
@@ -218,7 +218,7 @@ discard block |
||
| 218 | 218 | 'apiResponse' => $branch, |
| 219 | 219 | )); |
| 220 | 220 | |
| 221 | - if ( isset($branch->commit, $branch->commit->committed_date) ) { |
|
| 221 | + if (isset($branch->commit, $branch->commit->committed_date)) { |
|
| 222 | 222 | $reference->updated = $branch->commit->committed_date; |
| 223 | 223 | } |
| 224 | 224 | |
@@ -233,7 +233,7 @@ discard block |
||
| 233 | 233 | */ |
| 234 | 234 | public function getLatestCommitTime($ref) { |
| 235 | 235 | $commits = $this->api('/:id/repository/commits/', array('ref_name' => $ref)); |
| 236 | - if ( is_wp_error($commits) || !is_array($commits) || !isset($commits[0]) ) { |
|
| 236 | + if (is_wp_error($commits) || !is_array($commits) || !isset($commits[0])) { |
|
| 237 | 237 | return null; |
| 238 | 238 | } |
| 239 | 239 | |
@@ -252,19 +252,19 @@ discard block |
||
| 252 | 252 | $url = $this->buildApiUrl($url, $queryParams); |
| 253 | 253 | |
| 254 | 254 | $options = array('timeout' => 10); |
| 255 | - if ( !empty($this->httpFilterName) ) { |
|
| 255 | + if (!empty($this->httpFilterName)) { |
|
| 256 | 256 | $options = apply_filters($this->httpFilterName, $options); |
| 257 | 257 | } |
| 258 | 258 | |
| 259 | 259 | $response = wp_remote_get($url, $options); |
| 260 | - if ( is_wp_error($response) ) { |
|
| 260 | + if (is_wp_error($response)) { |
|
| 261 | 261 | do_action('puc_api_error', $response, null, $url, $this->slug); |
| 262 | 262 | return $response; |
| 263 | 263 | } |
| 264 | 264 | |
| 265 | 265 | $code = wp_remote_retrieve_response_code($response); |
| 266 | 266 | $body = wp_remote_retrieve_body($response); |
| 267 | - if ( $code === 200 ) { |
|
| 267 | + if ($code === 200) { |
|
| 268 | 268 | return json_decode($body); |
| 269 | 269 | } |
| 270 | 270 | |
@@ -298,11 +298,11 @@ discard block |
||
| 298 | 298 | $url = substr($url, 1); |
| 299 | 299 | $url = sprintf('%1$s://%2$s/api/v4/projects/%3$s', $this->repositoryProtocol, $this->repositoryHost, $url); |
| 300 | 300 | |
| 301 | - if ( !empty($this->accessToken) ) { |
|
| 301 | + if (!empty($this->accessToken)) { |
|
| 302 | 302 | $queryParams['private_token'] = $this->accessToken; |
| 303 | 303 | } |
| 304 | 304 | |
| 305 | - if ( !empty($queryParams) ) { |
|
| 305 | + if (!empty($queryParams)) { |
|
| 306 | 306 | $url = add_query_arg($queryParams, $url); |
| 307 | 307 | } |
| 308 | 308 | |
@@ -318,7 +318,7 @@ discard block |
||
| 318 | 318 | */ |
| 319 | 319 | public function getRemoteFile($path, $ref = 'master') { |
| 320 | 320 | $response = $this->api('/:id/repository/files/' . $path, array('ref' => $ref)); |
| 321 | - if ( is_wp_error($response) || !isset($response->content) || $response->encoding !== 'base64' ) { |
|
| 321 | + if (is_wp_error($response) || !isset($response->content) || $response->encoding !== 'base64') { |
|
| 322 | 322 | return null; |
| 323 | 323 | } |
| 324 | 324 | |
@@ -340,7 +340,7 @@ discard block |
||
| 340 | 340 | ); |
| 341 | 341 | $url = add_query_arg('sha', urlencode($ref), $url); |
| 342 | 342 | |
| 343 | - if ( !empty($this->accessToken) ) { |
|
| 343 | + if (!empty($this->accessToken)) { |
|
| 344 | 344 | $url = add_query_arg('private_token', $this->accessToken, $url); |
| 345 | 345 | } |
| 346 | 346 | |
@@ -360,7 +360,7 @@ discard block |
||
| 360 | 360 | protected function getUpdateDetectionStrategies($configBranch) { |
| 361 | 361 | $strategies = array(); |
| 362 | 362 | |
| 363 | - if ( ($configBranch === 'main') || ($configBranch === 'master') ) { |
|
| 363 | + if (($configBranch === 'main') || ($configBranch === 'master')) { |
|
| 364 | 364 | $strategies[self::STRATEGY_LATEST_RELEASE] = array($this, 'getLatestRelease'); |
| 365 | 365 | $strategies[self::STRATEGY_LATEST_TAG] = array($this, 'getLatestTag'); |
| 366 | 366 | } |
@@ -3,49 +3,49 @@ |
||
| 3 | 3 | |
| 4 | 4 | if ( !class_exists(Reference::class, false) ): |
| 5 | 5 | |
| 6 | - /** |
|
| 7 | - * This class represents a VCS branch or tag. It's intended as a read only, short-lived container |
|
| 8 | - * that only exists to provide a limited degree of type checking. |
|
| 9 | - * |
|
| 10 | - * @property string $name |
|
| 11 | - * @property string|null version |
|
| 12 | - * @property string $downloadUrl |
|
| 13 | - * @property string $updated |
|
| 14 | - * |
|
| 15 | - * @property string|null $changelog |
|
| 16 | - * @property int|null $downloadCount |
|
| 17 | - */ |
|
| 18 | - class Reference { |
|
| 19 | - private $properties = array(); |
|
| 20 | - |
|
| 21 | - public function __construct($properties = array()) { |
|
| 22 | - $this->properties = $properties; |
|
| 23 | - } |
|
| 24 | - |
|
| 25 | - /** |
|
| 26 | - * @param string $name |
|
| 27 | - * @return mixed|null |
|
| 28 | - */ |
|
| 29 | - public function __get($name) { |
|
| 30 | - return array_key_exists($name, $this->properties) ? $this->properties[$name] : null; |
|
| 31 | - } |
|
| 32 | - |
|
| 33 | - /** |
|
| 34 | - * @param string $name |
|
| 35 | - * @param mixed $value |
|
| 36 | - */ |
|
| 37 | - public function __set($name, $value) { |
|
| 38 | - $this->properties[$name] = $value; |
|
| 39 | - } |
|
| 40 | - |
|
| 41 | - /** |
|
| 42 | - * @param string $name |
|
| 43 | - * @return bool |
|
| 44 | - */ |
|
| 45 | - public function __isset($name) { |
|
| 46 | - return isset($this->properties[$name]); |
|
| 47 | - } |
|
| 48 | - |
|
| 49 | - } |
|
| 6 | + /** |
|
| 7 | + * This class represents a VCS branch or tag. It's intended as a read only, short-lived container |
|
| 8 | + * that only exists to provide a limited degree of type checking. |
|
| 9 | + * |
|
| 10 | + * @property string $name |
|
| 11 | + * @property string|null version |
|
| 12 | + * @property string $downloadUrl |
|
| 13 | + * @property string $updated |
|
| 14 | + * |
|
| 15 | + * @property string|null $changelog |
|
| 16 | + * @property int|null $downloadCount |
|
| 17 | + */ |
|
| 18 | + class Reference { |
|
| 19 | + private $properties = array(); |
|
| 20 | + |
|
| 21 | + public function __construct($properties = array()) { |
|
| 22 | + $this->properties = $properties; |
|
| 23 | + } |
|
| 24 | + |
|
| 25 | + /** |
|
| 26 | + * @param string $name |
|
| 27 | + * @return mixed|null |
|
| 28 | + */ |
|
| 29 | + public function __get($name) { |
|
| 30 | + return array_key_exists($name, $this->properties) ? $this->properties[$name] : null; |
|
| 31 | + } |
|
| 32 | + |
|
| 33 | + /** |
|
| 34 | + * @param string $name |
|
| 35 | + * @param mixed $value |
|
| 36 | + */ |
|
| 37 | + public function __set($name, $value) { |
|
| 38 | + $this->properties[$name] = $value; |
|
| 39 | + } |
|
| 40 | + |
|
| 41 | + /** |
|
| 42 | + * @param string $name |
|
| 43 | + * @return bool |
|
| 44 | + */ |
|
| 45 | + public function __isset($name) { |
|
| 46 | + return isset($this->properties[$name]); |
|
| 47 | + } |
|
| 48 | + |
|
| 49 | + } |
|
| 50 | 50 | |
| 51 | 51 | endif; |
@@ -1,7 +1,7 @@ |
||
| 1 | 1 | <?php |
| 2 | 2 | namespace YahnisElsts\PluginUpdateChecker\v5p0\Vcs; |
| 3 | 3 | |
| 4 | -if ( !class_exists(Reference::class, false) ): |
|
| 4 | +if (!class_exists(Reference::class, false)): |
|
| 5 | 5 | |
| 6 | 6 | /** |
| 7 | 7 | * This class represents a VCS branch or tag. It's intended as a read only, short-lived container |
@@ -5,433 +5,433 @@ |
||
| 5 | 5 | |
| 6 | 6 | if ( !class_exists(GitHubApi::class, false) ): |
| 7 | 7 | |
| 8 | - class GitHubApi extends Api { |
|
| 9 | - /** |
|
| 10 | - * @var string GitHub username. |
|
| 11 | - */ |
|
| 12 | - protected $userName; |
|
| 13 | - /** |
|
| 14 | - * @var string GitHub repository name. |
|
| 15 | - */ |
|
| 16 | - protected $repositoryName; |
|
| 17 | - |
|
| 18 | - /** |
|
| 19 | - * @var string Either a fully qualified repository URL, or just "user/repo-name". |
|
| 20 | - */ |
|
| 21 | - protected $repositoryUrl; |
|
| 22 | - |
|
| 23 | - /** |
|
| 24 | - * @var string GitHub authentication token. Optional. |
|
| 25 | - */ |
|
| 26 | - protected $accessToken; |
|
| 27 | - |
|
| 28 | - /** |
|
| 29 | - * @var bool Whether to download release assets instead of the auto-generated source code archives. |
|
| 30 | - */ |
|
| 31 | - protected $releaseAssetsEnabled = false; |
|
| 32 | - |
|
| 33 | - /** |
|
| 34 | - * @var string|null Regular expression that's used to filter release assets by name. Optional. |
|
| 35 | - */ |
|
| 36 | - protected $assetFilterRegex = null; |
|
| 37 | - |
|
| 38 | - /** |
|
| 39 | - * @var string|null The unchanging part of a release asset URL. Used to identify download attempts. |
|
| 40 | - */ |
|
| 41 | - protected $assetApiBaseUrl = null; |
|
| 42 | - |
|
| 43 | - /** |
|
| 44 | - * @var bool |
|
| 45 | - */ |
|
| 46 | - private $downloadFilterAdded = false; |
|
| 47 | - |
|
| 48 | - public function __construct($repositoryUrl, $accessToken = null) { |
|
| 49 | - $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); |
|
| 50 | - if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) { |
|
| 51 | - $this->userName = $matches['username']; |
|
| 52 | - $this->repositoryName = $matches['repository']; |
|
| 53 | - } else { |
|
| 54 | - throw new \InvalidArgumentException('Invalid GitHub repository URL: "' . $repositoryUrl . '"'); |
|
| 55 | - } |
|
| 56 | - |
|
| 57 | - parent::__construct($repositoryUrl, $accessToken); |
|
| 58 | - } |
|
| 59 | - |
|
| 60 | - /** |
|
| 61 | - * Get the latest release from GitHub. |
|
| 62 | - * |
|
| 63 | - * @return Reference|null |
|
| 64 | - */ |
|
| 65 | - public function getLatestRelease() { |
|
| 66 | - $release = $this->api('/repos/:user/:repo/releases/latest'); |
|
| 67 | - if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) { |
|
| 68 | - return null; |
|
| 69 | - } |
|
| 70 | - |
|
| 71 | - $reference = new Reference(array( |
|
| 72 | - 'name' => $release->tag_name, |
|
| 73 | - 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". |
|
| 74 | - 'downloadUrl' => $release->zipball_url, |
|
| 75 | - 'updated' => $release->created_at, |
|
| 76 | - 'apiResponse' => $release, |
|
| 77 | - )); |
|
| 78 | - |
|
| 79 | - if ( isset($release->assets[0]) ) { |
|
| 80 | - $reference->downloadCount = $release->assets[0]->download_count; |
|
| 81 | - } |
|
| 82 | - |
|
| 83 | - if ( $this->releaseAssetsEnabled && isset($release->assets, $release->assets[0]) ) { |
|
| 84 | - //Use the first release asset that matches the specified regular expression. |
|
| 85 | - $matchingAssets = array_filter($release->assets, array($this, 'matchesAssetFilter')); |
|
| 86 | - if ( !empty($matchingAssets) ) { |
|
| 87 | - if ( $this->isAuthenticationEnabled() ) { |
|
| 88 | - /** |
|
| 89 | - * Keep in mind that we'll need to add an "Accept" header to download this asset. |
|
| 90 | - * |
|
| 91 | - * @see setUpdateDownloadHeaders() |
|
| 92 | - */ |
|
| 93 | - $reference->downloadUrl = $matchingAssets[0]->url; |
|
| 94 | - } else { |
|
| 95 | - //It seems that browser_download_url only works for public repositories. |
|
| 96 | - //Using an access_token doesn't help. Maybe OAuth would work? |
|
| 97 | - $reference->downloadUrl = $matchingAssets[0]->browser_download_url; |
|
| 98 | - } |
|
| 99 | - |
|
| 100 | - $reference->downloadCount = $matchingAssets[0]->download_count; |
|
| 101 | - } |
|
| 102 | - } |
|
| 103 | - |
|
| 104 | - if ( !empty($release->body) ) { |
|
| 105 | - $reference->changelog = Parsedown::instance()->text($release->body); |
|
| 106 | - } |
|
| 107 | - |
|
| 108 | - return $reference; |
|
| 109 | - } |
|
| 110 | - |
|
| 111 | - /** |
|
| 112 | - * Get the tag that looks like the highest version number. |
|
| 113 | - * |
|
| 114 | - * @return Reference|null |
|
| 115 | - */ |
|
| 116 | - public function getLatestTag() { |
|
| 117 | - $tags = $this->api('/repos/:user/:repo/tags'); |
|
| 118 | - |
|
| 119 | - if ( is_wp_error($tags) || !is_array($tags) ) { |
|
| 120 | - return null; |
|
| 121 | - } |
|
| 122 | - |
|
| 123 | - $versionTags = $this->sortTagsByVersion($tags); |
|
| 124 | - if ( empty($versionTags) ) { |
|
| 125 | - return null; |
|
| 126 | - } |
|
| 127 | - |
|
| 128 | - $tag = $versionTags[0]; |
|
| 129 | - return new Reference(array( |
|
| 130 | - 'name' => $tag->name, |
|
| 131 | - 'version' => ltrim($tag->name, 'v'), |
|
| 132 | - 'downloadUrl' => $tag->zipball_url, |
|
| 133 | - 'apiResponse' => $tag, |
|
| 134 | - )); |
|
| 135 | - } |
|
| 136 | - |
|
| 137 | - /** |
|
| 138 | - * Get a branch by name. |
|
| 139 | - * |
|
| 140 | - * @param string $branchName |
|
| 141 | - * @return null|Reference |
|
| 142 | - */ |
|
| 143 | - public function getBranch($branchName) { |
|
| 144 | - $branch = $this->api('/repos/:user/:repo/branches/' . $branchName); |
|
| 145 | - if ( is_wp_error($branch) || empty($branch) ) { |
|
| 146 | - return null; |
|
| 147 | - } |
|
| 148 | - |
|
| 149 | - $reference = new Reference(array( |
|
| 150 | - 'name' => $branch->name, |
|
| 151 | - 'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name), |
|
| 152 | - 'apiResponse' => $branch, |
|
| 153 | - )); |
|
| 154 | - |
|
| 155 | - if ( isset($branch->commit, $branch->commit->commit, $branch->commit->commit->author->date) ) { |
|
| 156 | - $reference->updated = $branch->commit->commit->author->date; |
|
| 157 | - } |
|
| 158 | - |
|
| 159 | - return $reference; |
|
| 160 | - } |
|
| 161 | - |
|
| 162 | - /** |
|
| 163 | - * Get the latest commit that changed the specified file. |
|
| 164 | - * |
|
| 165 | - * @param string $filename |
|
| 166 | - * @param string $ref Reference name (e.g. branch or tag). |
|
| 167 | - * @return \StdClass|null |
|
| 168 | - */ |
|
| 169 | - public function getLatestCommit($filename, $ref = 'master') { |
|
| 170 | - $commits = $this->api( |
|
| 171 | - '/repos/:user/:repo/commits', |
|
| 172 | - array( |
|
| 173 | - 'path' => $filename, |
|
| 174 | - 'sha' => $ref, |
|
| 175 | - ) |
|
| 176 | - ); |
|
| 177 | - if ( !is_wp_error($commits) && isset($commits[0]) ) { |
|
| 178 | - return $commits[0]; |
|
| 179 | - } |
|
| 180 | - return null; |
|
| 181 | - } |
|
| 182 | - |
|
| 183 | - /** |
|
| 184 | - * Get the timestamp of the latest commit that changed the specified branch or tag. |
|
| 185 | - * |
|
| 186 | - * @param string $ref Reference name (e.g. branch or tag). |
|
| 187 | - * @return string|null |
|
| 188 | - */ |
|
| 189 | - public function getLatestCommitTime($ref) { |
|
| 190 | - $commits = $this->api('/repos/:user/:repo/commits', array('sha' => $ref)); |
|
| 191 | - if ( !is_wp_error($commits) && isset($commits[0]) ) { |
|
| 192 | - return $commits[0]->commit->author->date; |
|
| 193 | - } |
|
| 194 | - return null; |
|
| 195 | - } |
|
| 196 | - |
|
| 197 | - /** |
|
| 198 | - * Perform a GitHub API request. |
|
| 199 | - * |
|
| 200 | - * @param string $url |
|
| 201 | - * @param array $queryParams |
|
| 202 | - * @return mixed|\WP_Error |
|
| 203 | - */ |
|
| 204 | - protected function api($url, $queryParams = array()) { |
|
| 205 | - $baseUrl = $url; |
|
| 206 | - $url = $this->buildApiUrl($url, $queryParams); |
|
| 207 | - |
|
| 208 | - $options = array('timeout' => 10); |
|
| 209 | - if ( $this->isAuthenticationEnabled() ) { |
|
| 210 | - $options['headers'] = array('Authorization' => $this->getAuthorizationHeader()); |
|
| 211 | - } |
|
| 212 | - |
|
| 213 | - if ( !empty($this->httpFilterName) ) { |
|
| 214 | - $options = apply_filters($this->httpFilterName, $options); |
|
| 215 | - } |
|
| 216 | - $response = wp_remote_get($url, $options); |
|
| 217 | - if ( is_wp_error($response) ) { |
|
| 218 | - do_action('puc_api_error', $response, null, $url, $this->slug); |
|
| 219 | - return $response; |
|
| 220 | - } |
|
| 221 | - |
|
| 222 | - $code = wp_remote_retrieve_response_code($response); |
|
| 223 | - $body = wp_remote_retrieve_body($response); |
|
| 224 | - if ( $code === 200 ) { |
|
| 225 | - $document = json_decode($body); |
|
| 226 | - return $document; |
|
| 227 | - } |
|
| 228 | - |
|
| 229 | - $error = new \WP_Error( |
|
| 230 | - 'puc-github-http-error', |
|
| 231 | - sprintf('GitHub API error. Base URL: "%s", HTTP status code: %d.', $baseUrl, $code) |
|
| 232 | - ); |
|
| 233 | - do_action('puc_api_error', $error, $response, $url, $this->slug); |
|
| 234 | - |
|
| 235 | - return $error; |
|
| 236 | - } |
|
| 237 | - |
|
| 238 | - /** |
|
| 239 | - * Build a fully qualified URL for an API request. |
|
| 240 | - * |
|
| 241 | - * @param string $url |
|
| 242 | - * @param array $queryParams |
|
| 243 | - * @return string |
|
| 244 | - */ |
|
| 245 | - protected function buildApiUrl($url, $queryParams) { |
|
| 246 | - $variables = array( |
|
| 247 | - 'user' => $this->userName, |
|
| 248 | - 'repo' => $this->repositoryName, |
|
| 249 | - ); |
|
| 250 | - foreach ($variables as $name => $value) { |
|
| 251 | - $url = str_replace('/:' . $name, '/' . urlencode($value), $url); |
|
| 252 | - } |
|
| 253 | - $url = 'https://api.github.com' . $url; |
|
| 254 | - |
|
| 255 | - if ( !empty($queryParams) ) { |
|
| 256 | - $url = add_query_arg($queryParams, $url); |
|
| 257 | - } |
|
| 258 | - |
|
| 259 | - return $url; |
|
| 260 | - } |
|
| 261 | - |
|
| 262 | - /** |
|
| 263 | - * Get the contents of a file from a specific branch or tag. |
|
| 264 | - * |
|
| 265 | - * @param string $path File name. |
|
| 266 | - * @param string $ref |
|
| 267 | - * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error. |
|
| 268 | - */ |
|
| 269 | - public function getRemoteFile($path, $ref = 'master') { |
|
| 270 | - $apiUrl = '/repos/:user/:repo/contents/' . $path; |
|
| 271 | - $response = $this->api($apiUrl, array('ref' => $ref)); |
|
| 272 | - |
|
| 273 | - if ( is_wp_error($response) || !isset($response->content) || ($response->encoding !== 'base64') ) { |
|
| 274 | - return null; |
|
| 275 | - } |
|
| 276 | - return base64_decode($response->content); |
|
| 277 | - } |
|
| 278 | - |
|
| 279 | - /** |
|
| 280 | - * Generate a URL to download a ZIP archive of the specified branch/tag/etc. |
|
| 281 | - * |
|
| 282 | - * @param string $ref |
|
| 283 | - * @return string |
|
| 284 | - */ |
|
| 285 | - public function buildArchiveDownloadUrl($ref = 'master') { |
|
| 286 | - $url = sprintf( |
|
| 287 | - 'https://api.github.com/repos/%1$s/%2$s/zipball/%3$s', |
|
| 288 | - urlencode($this->userName), |
|
| 289 | - urlencode($this->repositoryName), |
|
| 290 | - urlencode($ref) |
|
| 291 | - ); |
|
| 292 | - return $url; |
|
| 293 | - } |
|
| 294 | - |
|
| 295 | - /** |
|
| 296 | - * Get a specific tag. |
|
| 297 | - * |
|
| 298 | - * @param string $tagName |
|
| 299 | - * @return void |
|
| 300 | - */ |
|
| 301 | - public function getTag($tagName) { |
|
| 302 | - //The current GitHub update checker doesn't use getTag, so I didn't bother to implement it. |
|
| 303 | - throw new \LogicException('The ' . __METHOD__ . ' method is not implemented and should not be used.'); |
|
| 304 | - } |
|
| 305 | - |
|
| 306 | - public function setAuthentication($credentials) { |
|
| 307 | - parent::setAuthentication($credentials); |
|
| 308 | - $this->accessToken = is_string($credentials) ? $credentials : null; |
|
| 309 | - |
|
| 310 | - //Optimization: Instead of filtering all HTTP requests, let's do it only when |
|
| 311 | - //WordPress is about to download an update. |
|
| 312 | - add_filter('upgrader_pre_download', array($this, 'addHttpRequestFilter'), 10, 1); //WP 3.7+ |
|
| 313 | - } |
|
| 314 | - |
|
| 315 | - protected function getUpdateDetectionStrategies($configBranch) { |
|
| 316 | - $strategies = array(); |
|
| 317 | - |
|
| 318 | - if ( $configBranch === 'master' ) { |
|
| 319 | - //Use the latest release. |
|
| 320 | - $strategies[self::STRATEGY_LATEST_RELEASE] = array($this, 'getLatestRelease'); |
|
| 321 | - //Failing that, use the tag with the highest version number. |
|
| 322 | - $strategies[self::STRATEGY_LATEST_TAG] = array($this, 'getLatestTag'); |
|
| 323 | - } |
|
| 324 | - |
|
| 325 | - //Alternatively, just use the branch itself. |
|
| 326 | - $strategies[self::STRATEGY_BRANCH] = function() use ($configBranch) { |
|
| 327 | - return $this->getBranch($configBranch); |
|
| 328 | - }; |
|
| 329 | - |
|
| 330 | - return $strategies; |
|
| 331 | - } |
|
| 332 | - |
|
| 333 | - /** |
|
| 334 | - * Enable updating via release assets. |
|
| 335 | - * |
|
| 336 | - * If the latest release contains no usable assets, the update checker |
|
| 337 | - * will fall back to using the automatically generated ZIP archive. |
|
| 338 | - * |
|
| 339 | - * Private repositories will only work with WordPress 3.7 or later. |
|
| 340 | - * |
|
| 341 | - * @param string|null $fileNameRegex Optional. Use only those assets where the file name matches this regex. |
|
| 342 | - */ |
|
| 343 | - public function enableReleaseAssets($fileNameRegex = null) { |
|
| 344 | - $this->releaseAssetsEnabled = true; |
|
| 345 | - $this->assetFilterRegex = $fileNameRegex; |
|
| 346 | - $this->assetApiBaseUrl = sprintf( |
|
| 347 | - '//api.github.com/repos/%1$s/%2$s/releases/assets/', |
|
| 348 | - $this->userName, |
|
| 349 | - $this->repositoryName |
|
| 350 | - ); |
|
| 351 | - } |
|
| 352 | - |
|
| 353 | - /** |
|
| 354 | - * Does this asset match the file name regex? |
|
| 355 | - * |
|
| 356 | - * @param \stdClass $releaseAsset |
|
| 357 | - * @return bool |
|
| 358 | - */ |
|
| 359 | - protected function matchesAssetFilter($releaseAsset) { |
|
| 360 | - if ( $this->assetFilterRegex === null ) { |
|
| 361 | - //The default is to accept all assets. |
|
| 362 | - return true; |
|
| 363 | - } |
|
| 364 | - return isset($releaseAsset->name) && preg_match($this->assetFilterRegex, $releaseAsset->name); |
|
| 365 | - } |
|
| 366 | - |
|
| 367 | - /** |
|
| 368 | - * @internal |
|
| 369 | - * @param bool $result |
|
| 370 | - * @return bool |
|
| 371 | - */ |
|
| 372 | - public function addHttpRequestFilter($result) { |
|
| 373 | - if ( !$this->downloadFilterAdded && $this->isAuthenticationEnabled() ) { |
|
| 374 | - //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args -- The callback doesn't change the timeout. |
|
| 375 | - add_filter('http_request_args', array($this, 'setUpdateDownloadHeaders'), 10, 2); |
|
| 376 | - add_action('requests-requests.before_redirect', array($this, 'removeAuthHeaderFromRedirects'), 10, 4); |
|
| 377 | - $this->downloadFilterAdded = true; |
|
| 378 | - } |
|
| 379 | - return $result; |
|
| 380 | - } |
|
| 381 | - |
|
| 382 | - /** |
|
| 383 | - * Set the HTTP headers that are necessary to download updates from private repositories. |
|
| 384 | - * |
|
| 385 | - * See GitHub docs: |
|
| 386 | - * @link https://developer.github.com/v3/repos/releases/#get-a-single-release-asset |
|
| 387 | - * @link https://developer.github.com/v3/auth/#basic-authentication |
|
| 388 | - * |
|
| 389 | - * @internal |
|
| 390 | - * @param array $requestArgs |
|
| 391 | - * @param string $url |
|
| 392 | - * @return array |
|
| 393 | - */ |
|
| 394 | - public function setUpdateDownloadHeaders($requestArgs, $url = '') { |
|
| 395 | - //Is WordPress trying to download one of our release assets? |
|
| 396 | - if ( $this->releaseAssetsEnabled && (strpos($url, $this->assetApiBaseUrl) !== false) ) { |
|
| 397 | - $requestArgs['headers']['Accept'] = 'application/octet-stream'; |
|
| 398 | - } |
|
| 399 | - //Use Basic authentication, but only if the download is from our repository. |
|
| 400 | - $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); |
|
| 401 | - if ( $this->isAuthenticationEnabled() && (strpos($url, $repoApiBaseUrl)) === 0 ) { |
|
| 402 | - $requestArgs['headers']['Authorization'] = $this->getAuthorizationHeader(); |
|
| 403 | - } |
|
| 404 | - return $requestArgs; |
|
| 405 | - } |
|
| 406 | - |
|
| 407 | - /** |
|
| 408 | - * When following a redirect, the Requests library will automatically forward |
|
| 409 | - * the authorization header to other hosts. We don't want that because it breaks |
|
| 410 | - * AWS downloads and can leak authorization information. |
|
| 411 | - * |
|
| 412 | - * @internal |
|
| 413 | - * @param string $location |
|
| 414 | - * @param array $headers |
|
| 415 | - */ |
|
| 416 | - public function removeAuthHeaderFromRedirects(&$location, &$headers) { |
|
| 417 | - $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); |
|
| 418 | - if ( strpos($location, $repoApiBaseUrl) === 0 ) { |
|
| 419 | - return; //This request is going to GitHub, so it's fine. |
|
| 420 | - } |
|
| 421 | - //Remove the header. |
|
| 422 | - if ( isset($headers['Authorization']) ) { |
|
| 423 | - unset($headers['Authorization']); |
|
| 424 | - } |
|
| 425 | - } |
|
| 426 | - |
|
| 427 | - /** |
|
| 428 | - * Generate the value of the "Authorization" header. |
|
| 429 | - * |
|
| 430 | - * @return string |
|
| 431 | - */ |
|
| 432 | - protected function getAuthorizationHeader() { |
|
| 433 | - return 'Basic ' . base64_encode($this->userName . ':' . $this->accessToken); |
|
| 434 | - } |
|
| 435 | - } |
|
| 8 | + class GitHubApi extends Api { |
|
| 9 | + /** |
|
| 10 | + * @var string GitHub username. |
|
| 11 | + */ |
|
| 12 | + protected $userName; |
|
| 13 | + /** |
|
| 14 | + * @var string GitHub repository name. |
|
| 15 | + */ |
|
| 16 | + protected $repositoryName; |
|
| 17 | + |
|
| 18 | + /** |
|
| 19 | + * @var string Either a fully qualified repository URL, or just "user/repo-name". |
|
| 20 | + */ |
|
| 21 | + protected $repositoryUrl; |
|
| 22 | + |
|
| 23 | + /** |
|
| 24 | + * @var string GitHub authentication token. Optional. |
|
| 25 | + */ |
|
| 26 | + protected $accessToken; |
|
| 27 | + |
|
| 28 | + /** |
|
| 29 | + * @var bool Whether to download release assets instead of the auto-generated source code archives. |
|
| 30 | + */ |
|
| 31 | + protected $releaseAssetsEnabled = false; |
|
| 32 | + |
|
| 33 | + /** |
|
| 34 | + * @var string|null Regular expression that's used to filter release assets by name. Optional. |
|
| 35 | + */ |
|
| 36 | + protected $assetFilterRegex = null; |
|
| 37 | + |
|
| 38 | + /** |
|
| 39 | + * @var string|null The unchanging part of a release asset URL. Used to identify download attempts. |
|
| 40 | + */ |
|
| 41 | + protected $assetApiBaseUrl = null; |
|
| 42 | + |
|
| 43 | + /** |
|
| 44 | + * @var bool |
|
| 45 | + */ |
|
| 46 | + private $downloadFilterAdded = false; |
|
| 47 | + |
|
| 48 | + public function __construct($repositoryUrl, $accessToken = null) { |
|
| 49 | + $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); |
|
| 50 | + if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) { |
|
| 51 | + $this->userName = $matches['username']; |
|
| 52 | + $this->repositoryName = $matches['repository']; |
|
| 53 | + } else { |
|
| 54 | + throw new \InvalidArgumentException('Invalid GitHub repository URL: "' . $repositoryUrl . '"'); |
|
| 55 | + } |
|
| 56 | + |
|
| 57 | + parent::__construct($repositoryUrl, $accessToken); |
|
| 58 | + } |
|
| 59 | + |
|
| 60 | + /** |
|
| 61 | + * Get the latest release from GitHub. |
|
| 62 | + * |
|
| 63 | + * @return Reference|null |
|
| 64 | + */ |
|
| 65 | + public function getLatestRelease() { |
|
| 66 | + $release = $this->api('/repos/:user/:repo/releases/latest'); |
|
| 67 | + if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) { |
|
| 68 | + return null; |
|
| 69 | + } |
|
| 70 | + |
|
| 71 | + $reference = new Reference(array( |
|
| 72 | + 'name' => $release->tag_name, |
|
| 73 | + 'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3". |
|
| 74 | + 'downloadUrl' => $release->zipball_url, |
|
| 75 | + 'updated' => $release->created_at, |
|
| 76 | + 'apiResponse' => $release, |
|
| 77 | + )); |
|
| 78 | + |
|
| 79 | + if ( isset($release->assets[0]) ) { |
|
| 80 | + $reference->downloadCount = $release->assets[0]->download_count; |
|
| 81 | + } |
|
| 82 | + |
|
| 83 | + if ( $this->releaseAssetsEnabled && isset($release->assets, $release->assets[0]) ) { |
|
| 84 | + //Use the first release asset that matches the specified regular expression. |
|
| 85 | + $matchingAssets = array_filter($release->assets, array($this, 'matchesAssetFilter')); |
|
| 86 | + if ( !empty($matchingAssets) ) { |
|
| 87 | + if ( $this->isAuthenticationEnabled() ) { |
|
| 88 | + /** |
|
| 89 | + * Keep in mind that we'll need to add an "Accept" header to download this asset. |
|
| 90 | + * |
|
| 91 | + * @see setUpdateDownloadHeaders() |
|
| 92 | + */ |
|
| 93 | + $reference->downloadUrl = $matchingAssets[0]->url; |
|
| 94 | + } else { |
|
| 95 | + //It seems that browser_download_url only works for public repositories. |
|
| 96 | + //Using an access_token doesn't help. Maybe OAuth would work? |
|
| 97 | + $reference->downloadUrl = $matchingAssets[0]->browser_download_url; |
|
| 98 | + } |
|
| 99 | + |
|
| 100 | + $reference->downloadCount = $matchingAssets[0]->download_count; |
|
| 101 | + } |
|
| 102 | + } |
|
| 103 | + |
|
| 104 | + if ( !empty($release->body) ) { |
|
| 105 | + $reference->changelog = Parsedown::instance()->text($release->body); |
|
| 106 | + } |
|
| 107 | + |
|
| 108 | + return $reference; |
|
| 109 | + } |
|
| 110 | + |
|
| 111 | + /** |
|
| 112 | + * Get the tag that looks like the highest version number. |
|
| 113 | + * |
|
| 114 | + * @return Reference|null |
|
| 115 | + */ |
|
| 116 | + public function getLatestTag() { |
|
| 117 | + $tags = $this->api('/repos/:user/:repo/tags'); |
|
| 118 | + |
|
| 119 | + if ( is_wp_error($tags) || !is_array($tags) ) { |
|
| 120 | + return null; |
|
| 121 | + } |
|
| 122 | + |
|
| 123 | + $versionTags = $this->sortTagsByVersion($tags); |
|
| 124 | + if ( empty($versionTags) ) { |
|
| 125 | + return null; |
|
| 126 | + } |
|
| 127 | + |
|
| 128 | + $tag = $versionTags[0]; |
|
| 129 | + return new Reference(array( |
|
| 130 | + 'name' => $tag->name, |
|
| 131 | + 'version' => ltrim($tag->name, 'v'), |
|
| 132 | + 'downloadUrl' => $tag->zipball_url, |
|
| 133 | + 'apiResponse' => $tag, |
|
| 134 | + )); |
|
| 135 | + } |
|
| 136 | + |
|
| 137 | + /** |
|
| 138 | + * Get a branch by name. |
|
| 139 | + * |
|
| 140 | + * @param string $branchName |
|
| 141 | + * @return null|Reference |
|
| 142 | + */ |
|
| 143 | + public function getBranch($branchName) { |
|
| 144 | + $branch = $this->api('/repos/:user/:repo/branches/' . $branchName); |
|
| 145 | + if ( is_wp_error($branch) || empty($branch) ) { |
|
| 146 | + return null; |
|
| 147 | + } |
|
| 148 | + |
|
| 149 | + $reference = new Reference(array( |
|
| 150 | + 'name' => $branch->name, |
|
| 151 | + 'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name), |
|
| 152 | + 'apiResponse' => $branch, |
|
| 153 | + )); |
|
| 154 | + |
|
| 155 | + if ( isset($branch->commit, $branch->commit->commit, $branch->commit->commit->author->date) ) { |
|
| 156 | + $reference->updated = $branch->commit->commit->author->date; |
|
| 157 | + } |
|
| 158 | + |
|
| 159 | + return $reference; |
|
| 160 | + } |
|
| 161 | + |
|
| 162 | + /** |
|
| 163 | + * Get the latest commit that changed the specified file. |
|
| 164 | + * |
|
| 165 | + * @param string $filename |
|
| 166 | + * @param string $ref Reference name (e.g. branch or tag). |
|
| 167 | + * @return \StdClass|null |
|
| 168 | + */ |
|
| 169 | + public function getLatestCommit($filename, $ref = 'master') { |
|
| 170 | + $commits = $this->api( |
|
| 171 | + '/repos/:user/:repo/commits', |
|
| 172 | + array( |
|
| 173 | + 'path' => $filename, |
|
| 174 | + 'sha' => $ref, |
|
| 175 | + ) |
|
| 176 | + ); |
|
| 177 | + if ( !is_wp_error($commits) && isset($commits[0]) ) { |
|
| 178 | + return $commits[0]; |
|
| 179 | + } |
|
| 180 | + return null; |
|
| 181 | + } |
|
| 182 | + |
|
| 183 | + /** |
|
| 184 | + * Get the timestamp of the latest commit that changed the specified branch or tag. |
|
| 185 | + * |
|
| 186 | + * @param string $ref Reference name (e.g. branch or tag). |
|
| 187 | + * @return string|null |
|
| 188 | + */ |
|
| 189 | + public function getLatestCommitTime($ref) { |
|
| 190 | + $commits = $this->api('/repos/:user/:repo/commits', array('sha' => $ref)); |
|
| 191 | + if ( !is_wp_error($commits) && isset($commits[0]) ) { |
|
| 192 | + return $commits[0]->commit->author->date; |
|
| 193 | + } |
|
| 194 | + return null; |
|
| 195 | + } |
|
| 196 | + |
|
| 197 | + /** |
|
| 198 | + * Perform a GitHub API request. |
|
| 199 | + * |
|
| 200 | + * @param string $url |
|
| 201 | + * @param array $queryParams |
|
| 202 | + * @return mixed|\WP_Error |
|
| 203 | + */ |
|
| 204 | + protected function api($url, $queryParams = array()) { |
|
| 205 | + $baseUrl = $url; |
|
| 206 | + $url = $this->buildApiUrl($url, $queryParams); |
|
| 207 | + |
|
| 208 | + $options = array('timeout' => 10); |
|
| 209 | + if ( $this->isAuthenticationEnabled() ) { |
|
| 210 | + $options['headers'] = array('Authorization' => $this->getAuthorizationHeader()); |
|
| 211 | + } |
|
| 212 | + |
|
| 213 | + if ( !empty($this->httpFilterName) ) { |
|
| 214 | + $options = apply_filters($this->httpFilterName, $options); |
|
| 215 | + } |
|
| 216 | + $response = wp_remote_get($url, $options); |
|
| 217 | + if ( is_wp_error($response) ) { |
|
| 218 | + do_action('puc_api_error', $response, null, $url, $this->slug); |
|
| 219 | + return $response; |
|
| 220 | + } |
|
| 221 | + |
|
| 222 | + $code = wp_remote_retrieve_response_code($response); |
|
| 223 | + $body = wp_remote_retrieve_body($response); |
|
| 224 | + if ( $code === 200 ) { |
|
| 225 | + $document = json_decode($body); |
|
| 226 | + return $document; |
|
| 227 | + } |
|
| 228 | + |
|
| 229 | + $error = new \WP_Error( |
|
| 230 | + 'puc-github-http-error', |
|
| 231 | + sprintf('GitHub API error. Base URL: "%s", HTTP status code: %d.', $baseUrl, $code) |
|
| 232 | + ); |
|
| 233 | + do_action('puc_api_error', $error, $response, $url, $this->slug); |
|
| 234 | + |
|
| 235 | + return $error; |
|
| 236 | + } |
|
| 237 | + |
|
| 238 | + /** |
|
| 239 | + * Build a fully qualified URL for an API request. |
|
| 240 | + * |
|
| 241 | + * @param string $url |
|
| 242 | + * @param array $queryParams |
|
| 243 | + * @return string |
|
| 244 | + */ |
|
| 245 | + protected function buildApiUrl($url, $queryParams) { |
|
| 246 | + $variables = array( |
|
| 247 | + 'user' => $this->userName, |
|
| 248 | + 'repo' => $this->repositoryName, |
|
| 249 | + ); |
|
| 250 | + foreach ($variables as $name => $value) { |
|
| 251 | + $url = str_replace('/:' . $name, '/' . urlencode($value), $url); |
|
| 252 | + } |
|
| 253 | + $url = 'https://api.github.com' . $url; |
|
| 254 | + |
|
| 255 | + if ( !empty($queryParams) ) { |
|
| 256 | + $url = add_query_arg($queryParams, $url); |
|
| 257 | + } |
|
| 258 | + |
|
| 259 | + return $url; |
|
| 260 | + } |
|
| 261 | + |
|
| 262 | + /** |
|
| 263 | + * Get the contents of a file from a specific branch or tag. |
|
| 264 | + * |
|
| 265 | + * @param string $path File name. |
|
| 266 | + * @param string $ref |
|
| 267 | + * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error. |
|
| 268 | + */ |
|
| 269 | + public function getRemoteFile($path, $ref = 'master') { |
|
| 270 | + $apiUrl = '/repos/:user/:repo/contents/' . $path; |
|
| 271 | + $response = $this->api($apiUrl, array('ref' => $ref)); |
|
| 272 | + |
|
| 273 | + if ( is_wp_error($response) || !isset($response->content) || ($response->encoding !== 'base64') ) { |
|
| 274 | + return null; |
|
| 275 | + } |
|
| 276 | + return base64_decode($response->content); |
|
| 277 | + } |
|
| 278 | + |
|
| 279 | + /** |
|
| 280 | + * Generate a URL to download a ZIP archive of the specified branch/tag/etc. |
|
| 281 | + * |
|
| 282 | + * @param string $ref |
|
| 283 | + * @return string |
|
| 284 | + */ |
|
| 285 | + public function buildArchiveDownloadUrl($ref = 'master') { |
|
| 286 | + $url = sprintf( |
|
| 287 | + 'https://api.github.com/repos/%1$s/%2$s/zipball/%3$s', |
|
| 288 | + urlencode($this->userName), |
|
| 289 | + urlencode($this->repositoryName), |
|
| 290 | + urlencode($ref) |
|
| 291 | + ); |
|
| 292 | + return $url; |
|
| 293 | + } |
|
| 294 | + |
|
| 295 | + /** |
|
| 296 | + * Get a specific tag. |
|
| 297 | + * |
|
| 298 | + * @param string $tagName |
|
| 299 | + * @return void |
|
| 300 | + */ |
|
| 301 | + public function getTag($tagName) { |
|
| 302 | + //The current GitHub update checker doesn't use getTag, so I didn't bother to implement it. |
|
| 303 | + throw new \LogicException('The ' . __METHOD__ . ' method is not implemented and should not be used.'); |
|
| 304 | + } |
|
| 305 | + |
|
| 306 | + public function setAuthentication($credentials) { |
|
| 307 | + parent::setAuthentication($credentials); |
|
| 308 | + $this->accessToken = is_string($credentials) ? $credentials : null; |
|
| 309 | + |
|
| 310 | + //Optimization: Instead of filtering all HTTP requests, let's do it only when |
|
| 311 | + //WordPress is about to download an update. |
|
| 312 | + add_filter('upgrader_pre_download', array($this, 'addHttpRequestFilter'), 10, 1); //WP 3.7+ |
|
| 313 | + } |
|
| 314 | + |
|
| 315 | + protected function getUpdateDetectionStrategies($configBranch) { |
|
| 316 | + $strategies = array(); |
|
| 317 | + |
|
| 318 | + if ( $configBranch === 'master' ) { |
|
| 319 | + //Use the latest release. |
|
| 320 | + $strategies[self::STRATEGY_LATEST_RELEASE] = array($this, 'getLatestRelease'); |
|
| 321 | + //Failing that, use the tag with the highest version number. |
|
| 322 | + $strategies[self::STRATEGY_LATEST_TAG] = array($this, 'getLatestTag'); |
|
| 323 | + } |
|
| 324 | + |
|
| 325 | + //Alternatively, just use the branch itself. |
|
| 326 | + $strategies[self::STRATEGY_BRANCH] = function() use ($configBranch) { |
|
| 327 | + return $this->getBranch($configBranch); |
|
| 328 | + }; |
|
| 329 | + |
|
| 330 | + return $strategies; |
|
| 331 | + } |
|
| 332 | + |
|
| 333 | + /** |
|
| 334 | + * Enable updating via release assets. |
|
| 335 | + * |
|
| 336 | + * If the latest release contains no usable assets, the update checker |
|
| 337 | + * will fall back to using the automatically generated ZIP archive. |
|
| 338 | + * |
|
| 339 | + * Private repositories will only work with WordPress 3.7 or later. |
|
| 340 | + * |
|
| 341 | + * @param string|null $fileNameRegex Optional. Use only those assets where the file name matches this regex. |
|
| 342 | + */ |
|
| 343 | + public function enableReleaseAssets($fileNameRegex = null) { |
|
| 344 | + $this->releaseAssetsEnabled = true; |
|
| 345 | + $this->assetFilterRegex = $fileNameRegex; |
|
| 346 | + $this->assetApiBaseUrl = sprintf( |
|
| 347 | + '//api.github.com/repos/%1$s/%2$s/releases/assets/', |
|
| 348 | + $this->userName, |
|
| 349 | + $this->repositoryName |
|
| 350 | + ); |
|
| 351 | + } |
|
| 352 | + |
|
| 353 | + /** |
|
| 354 | + * Does this asset match the file name regex? |
|
| 355 | + * |
|
| 356 | + * @param \stdClass $releaseAsset |
|
| 357 | + * @return bool |
|
| 358 | + */ |
|
| 359 | + protected function matchesAssetFilter($releaseAsset) { |
|
| 360 | + if ( $this->assetFilterRegex === null ) { |
|
| 361 | + //The default is to accept all assets. |
|
| 362 | + return true; |
|
| 363 | + } |
|
| 364 | + return isset($releaseAsset->name) && preg_match($this->assetFilterRegex, $releaseAsset->name); |
|
| 365 | + } |
|
| 366 | + |
|
| 367 | + /** |
|
| 368 | + * @internal |
|
| 369 | + * @param bool $result |
|
| 370 | + * @return bool |
|
| 371 | + */ |
|
| 372 | + public function addHttpRequestFilter($result) { |
|
| 373 | + if ( !$this->downloadFilterAdded && $this->isAuthenticationEnabled() ) { |
|
| 374 | + //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args -- The callback doesn't change the timeout. |
|
| 375 | + add_filter('http_request_args', array($this, 'setUpdateDownloadHeaders'), 10, 2); |
|
| 376 | + add_action('requests-requests.before_redirect', array($this, 'removeAuthHeaderFromRedirects'), 10, 4); |
|
| 377 | + $this->downloadFilterAdded = true; |
|
| 378 | + } |
|
| 379 | + return $result; |
|
| 380 | + } |
|
| 381 | + |
|
| 382 | + /** |
|
| 383 | + * Set the HTTP headers that are necessary to download updates from private repositories. |
|
| 384 | + * |
|
| 385 | + * See GitHub docs: |
|
| 386 | + * @link https://developer.github.com/v3/repos/releases/#get-a-single-release-asset |
|
| 387 | + * @link https://developer.github.com/v3/auth/#basic-authentication |
|
| 388 | + * |
|
| 389 | + * @internal |
|
| 390 | + * @param array $requestArgs |
|
| 391 | + * @param string $url |
|
| 392 | + * @return array |
|
| 393 | + */ |
|
| 394 | + public function setUpdateDownloadHeaders($requestArgs, $url = '') { |
|
| 395 | + //Is WordPress trying to download one of our release assets? |
|
| 396 | + if ( $this->releaseAssetsEnabled && (strpos($url, $this->assetApiBaseUrl) !== false) ) { |
|
| 397 | + $requestArgs['headers']['Accept'] = 'application/octet-stream'; |
|
| 398 | + } |
|
| 399 | + //Use Basic authentication, but only if the download is from our repository. |
|
| 400 | + $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); |
|
| 401 | + if ( $this->isAuthenticationEnabled() && (strpos($url, $repoApiBaseUrl)) === 0 ) { |
|
| 402 | + $requestArgs['headers']['Authorization'] = $this->getAuthorizationHeader(); |
|
| 403 | + } |
|
| 404 | + return $requestArgs; |
|
| 405 | + } |
|
| 406 | + |
|
| 407 | + /** |
|
| 408 | + * When following a redirect, the Requests library will automatically forward |
|
| 409 | + * the authorization header to other hosts. We don't want that because it breaks |
|
| 410 | + * AWS downloads and can leak authorization information. |
|
| 411 | + * |
|
| 412 | + * @internal |
|
| 413 | + * @param string $location |
|
| 414 | + * @param array $headers |
|
| 415 | + */ |
|
| 416 | + public function removeAuthHeaderFromRedirects(&$location, &$headers) { |
|
| 417 | + $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); |
|
| 418 | + if ( strpos($location, $repoApiBaseUrl) === 0 ) { |
|
| 419 | + return; //This request is going to GitHub, so it's fine. |
|
| 420 | + } |
|
| 421 | + //Remove the header. |
|
| 422 | + if ( isset($headers['Authorization']) ) { |
|
| 423 | + unset($headers['Authorization']); |
|
| 424 | + } |
|
| 425 | + } |
|
| 426 | + |
|
| 427 | + /** |
|
| 428 | + * Generate the value of the "Authorization" header. |
|
| 429 | + * |
|
| 430 | + * @return string |
|
| 431 | + */ |
|
| 432 | + protected function getAuthorizationHeader() { |
|
| 433 | + return 'Basic ' . base64_encode($this->userName . ':' . $this->accessToken); |
|
| 434 | + } |
|
| 435 | + } |
|
| 436 | 436 | |
| 437 | 437 | endif; |
@@ -3,7 +3,7 @@ discard block |
||
| 3 | 3 | |
| 4 | 4 | use Parsedown; |
| 5 | 5 | |
| 6 | -if ( !class_exists(GitHubApi::class, false) ): |
|
| 6 | +if (!class_exists(GitHubApi::class, false)): |
|
| 7 | 7 | |
| 8 | 8 | class GitHubApi extends Api { |
| 9 | 9 | /** |
@@ -47,7 +47,7 @@ discard block |
||
| 47 | 47 | |
| 48 | 48 | public function __construct($repositoryUrl, $accessToken = null) { |
| 49 | 49 | $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); |
| 50 | - if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) { |
|
| 50 | + if (preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches)) { |
|
| 51 | 51 | $this->userName = $matches['username']; |
| 52 | 52 | $this->repositoryName = $matches['repository']; |
| 53 | 53 | } else { |
@@ -64,7 +64,7 @@ discard block |
||
| 64 | 64 | */ |
| 65 | 65 | public function getLatestRelease() { |
| 66 | 66 | $release = $this->api('/repos/:user/:repo/releases/latest'); |
| 67 | - if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) { |
|
| 67 | + if (is_wp_error($release) || !is_object($release) || !isset($release->tag_name)) { |
|
| 68 | 68 | return null; |
| 69 | 69 | } |
| 70 | 70 | |
@@ -76,15 +76,15 @@ discard block |
||
| 76 | 76 | 'apiResponse' => $release, |
| 77 | 77 | )); |
| 78 | 78 | |
| 79 | - if ( isset($release->assets[0]) ) { |
|
| 79 | + if (isset($release->assets[0])) { |
|
| 80 | 80 | $reference->downloadCount = $release->assets[0]->download_count; |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | - if ( $this->releaseAssetsEnabled && isset($release->assets, $release->assets[0]) ) { |
|
| 83 | + if ($this->releaseAssetsEnabled && isset($release->assets, $release->assets[0])) { |
|
| 84 | 84 | //Use the first release asset that matches the specified regular expression. |
| 85 | 85 | $matchingAssets = array_filter($release->assets, array($this, 'matchesAssetFilter')); |
| 86 | - if ( !empty($matchingAssets) ) { |
|
| 87 | - if ( $this->isAuthenticationEnabled() ) { |
|
| 86 | + if (!empty($matchingAssets)) { |
|
| 87 | + if ($this->isAuthenticationEnabled()) { |
|
| 88 | 88 | /** |
| 89 | 89 | * Keep in mind that we'll need to add an "Accept" header to download this asset. |
| 90 | 90 | * |
@@ -101,7 +101,7 @@ discard block |
||
| 101 | 101 | } |
| 102 | 102 | } |
| 103 | 103 | |
| 104 | - if ( !empty($release->body) ) { |
|
| 104 | + if (!empty($release->body)) { |
|
| 105 | 105 | $reference->changelog = Parsedown::instance()->text($release->body); |
| 106 | 106 | } |
| 107 | 107 | |
@@ -116,12 +116,12 @@ discard block |
||
| 116 | 116 | public function getLatestTag() { |
| 117 | 117 | $tags = $this->api('/repos/:user/:repo/tags'); |
| 118 | 118 | |
| 119 | - if ( is_wp_error($tags) || !is_array($tags) ) { |
|
| 119 | + if (is_wp_error($tags) || !is_array($tags)) { |
|
| 120 | 120 | return null; |
| 121 | 121 | } |
| 122 | 122 | |
| 123 | 123 | $versionTags = $this->sortTagsByVersion($tags); |
| 124 | - if ( empty($versionTags) ) { |
|
| 124 | + if (empty($versionTags)) { |
|
| 125 | 125 | return null; |
| 126 | 126 | } |
| 127 | 127 | |
@@ -142,7 +142,7 @@ discard block |
||
| 142 | 142 | */ |
| 143 | 143 | public function getBranch($branchName) { |
| 144 | 144 | $branch = $this->api('/repos/:user/:repo/branches/' . $branchName); |
| 145 | - if ( is_wp_error($branch) || empty($branch) ) { |
|
| 145 | + if (is_wp_error($branch) || empty($branch)) { |
|
| 146 | 146 | return null; |
| 147 | 147 | } |
| 148 | 148 | |
@@ -152,7 +152,7 @@ discard block |
||
| 152 | 152 | 'apiResponse' => $branch, |
| 153 | 153 | )); |
| 154 | 154 | |
| 155 | - if ( isset($branch->commit, $branch->commit->commit, $branch->commit->commit->author->date) ) { |
|
| 155 | + if (isset($branch->commit, $branch->commit->commit, $branch->commit->commit->author->date)) { |
|
| 156 | 156 | $reference->updated = $branch->commit->commit->author->date; |
| 157 | 157 | } |
| 158 | 158 | |
@@ -174,7 +174,7 @@ discard block |
||
| 174 | 174 | 'sha' => $ref, |
| 175 | 175 | ) |
| 176 | 176 | ); |
| 177 | - if ( !is_wp_error($commits) && isset($commits[0]) ) { |
|
| 177 | + if (!is_wp_error($commits) && isset($commits[0])) { |
|
| 178 | 178 | return $commits[0]; |
| 179 | 179 | } |
| 180 | 180 | return null; |
@@ -188,7 +188,7 @@ discard block |
||
| 188 | 188 | */ |
| 189 | 189 | public function getLatestCommitTime($ref) { |
| 190 | 190 | $commits = $this->api('/repos/:user/:repo/commits', array('sha' => $ref)); |
| 191 | - if ( !is_wp_error($commits) && isset($commits[0]) ) { |
|
| 191 | + if (!is_wp_error($commits) && isset($commits[0])) { |
|
| 192 | 192 | return $commits[0]->commit->author->date; |
| 193 | 193 | } |
| 194 | 194 | return null; |
@@ -206,22 +206,22 @@ discard block |
||
| 206 | 206 | $url = $this->buildApiUrl($url, $queryParams); |
| 207 | 207 | |
| 208 | 208 | $options = array('timeout' => 10); |
| 209 | - if ( $this->isAuthenticationEnabled() ) { |
|
| 209 | + if ($this->isAuthenticationEnabled()) { |
|
| 210 | 210 | $options['headers'] = array('Authorization' => $this->getAuthorizationHeader()); |
| 211 | 211 | } |
| 212 | 212 | |
| 213 | - if ( !empty($this->httpFilterName) ) { |
|
| 213 | + if (!empty($this->httpFilterName)) { |
|
| 214 | 214 | $options = apply_filters($this->httpFilterName, $options); |
| 215 | 215 | } |
| 216 | 216 | $response = wp_remote_get($url, $options); |
| 217 | - if ( is_wp_error($response) ) { |
|
| 217 | + if (is_wp_error($response)) { |
|
| 218 | 218 | do_action('puc_api_error', $response, null, $url, $this->slug); |
| 219 | 219 | return $response; |
| 220 | 220 | } |
| 221 | 221 | |
| 222 | 222 | $code = wp_remote_retrieve_response_code($response); |
| 223 | 223 | $body = wp_remote_retrieve_body($response); |
| 224 | - if ( $code === 200 ) { |
|
| 224 | + if ($code === 200) { |
|
| 225 | 225 | $document = json_decode($body); |
| 226 | 226 | return $document; |
| 227 | 227 | } |
@@ -252,7 +252,7 @@ discard block |
||
| 252 | 252 | } |
| 253 | 253 | $url = 'https://api.github.com' . $url; |
| 254 | 254 | |
| 255 | - if ( !empty($queryParams) ) { |
|
| 255 | + if (!empty($queryParams)) { |
|
| 256 | 256 | $url = add_query_arg($queryParams, $url); |
| 257 | 257 | } |
| 258 | 258 | |
@@ -270,7 +270,7 @@ discard block |
||
| 270 | 270 | $apiUrl = '/repos/:user/:repo/contents/' . $path; |
| 271 | 271 | $response = $this->api($apiUrl, array('ref' => $ref)); |
| 272 | 272 | |
| 273 | - if ( is_wp_error($response) || !isset($response->content) || ($response->encoding !== 'base64') ) { |
|
| 273 | + if (is_wp_error($response) || !isset($response->content) || ($response->encoding !== 'base64')) { |
|
| 274 | 274 | return null; |
| 275 | 275 | } |
| 276 | 276 | return base64_decode($response->content); |
@@ -315,7 +315,7 @@ discard block |
||
| 315 | 315 | protected function getUpdateDetectionStrategies($configBranch) { |
| 316 | 316 | $strategies = array(); |
| 317 | 317 | |
| 318 | - if ( $configBranch === 'master' ) { |
|
| 318 | + if ($configBranch === 'master') { |
|
| 319 | 319 | //Use the latest release. |
| 320 | 320 | $strategies[self::STRATEGY_LATEST_RELEASE] = array($this, 'getLatestRelease'); |
| 321 | 321 | //Failing that, use the tag with the highest version number. |
@@ -357,7 +357,7 @@ discard block |
||
| 357 | 357 | * @return bool |
| 358 | 358 | */ |
| 359 | 359 | protected function matchesAssetFilter($releaseAsset) { |
| 360 | - if ( $this->assetFilterRegex === null ) { |
|
| 360 | + if ($this->assetFilterRegex === null) { |
|
| 361 | 361 | //The default is to accept all assets. |
| 362 | 362 | return true; |
| 363 | 363 | } |
@@ -370,7 +370,7 @@ discard block |
||
| 370 | 370 | * @return bool |
| 371 | 371 | */ |
| 372 | 372 | public function addHttpRequestFilter($result) { |
| 373 | - if ( !$this->downloadFilterAdded && $this->isAuthenticationEnabled() ) { |
|
| 373 | + if (!$this->downloadFilterAdded && $this->isAuthenticationEnabled()) { |
|
| 374 | 374 | //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args -- The callback doesn't change the timeout. |
| 375 | 375 | add_filter('http_request_args', array($this, 'setUpdateDownloadHeaders'), 10, 2); |
| 376 | 376 | add_action('requests-requests.before_redirect', array($this, 'removeAuthHeaderFromRedirects'), 10, 4); |
@@ -393,12 +393,12 @@ discard block |
||
| 393 | 393 | */ |
| 394 | 394 | public function setUpdateDownloadHeaders($requestArgs, $url = '') { |
| 395 | 395 | //Is WordPress trying to download one of our release assets? |
| 396 | - if ( $this->releaseAssetsEnabled && (strpos($url, $this->assetApiBaseUrl) !== false) ) { |
|
| 396 | + if ($this->releaseAssetsEnabled && (strpos($url, $this->assetApiBaseUrl) !== false)) { |
|
| 397 | 397 | $requestArgs['headers']['Accept'] = 'application/octet-stream'; |
| 398 | 398 | } |
| 399 | 399 | //Use Basic authentication, but only if the download is from our repository. |
| 400 | 400 | $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); |
| 401 | - if ( $this->isAuthenticationEnabled() && (strpos($url, $repoApiBaseUrl)) === 0 ) { |
|
| 401 | + if ($this->isAuthenticationEnabled() && (strpos($url, $repoApiBaseUrl)) === 0) { |
|
| 402 | 402 | $requestArgs['headers']['Authorization'] = $this->getAuthorizationHeader(); |
| 403 | 403 | } |
| 404 | 404 | return $requestArgs; |
@@ -415,11 +415,11 @@ discard block |
||
| 415 | 415 | */ |
| 416 | 416 | public function removeAuthHeaderFromRedirects(&$location, &$headers) { |
| 417 | 417 | $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); |
| 418 | - if ( strpos($location, $repoApiBaseUrl) === 0 ) { |
|
| 418 | + if (strpos($location, $repoApiBaseUrl) === 0) { |
|
| 419 | 419 | return; //This request is going to GitHub, so it's fine. |
| 420 | 420 | } |
| 421 | 421 | //Remove the header. |
| 422 | - if ( isset($headers['Authorization']) ) { |
|
| 422 | + if (isset($headers['Authorization'])) { |
|
| 423 | 423 | unset($headers['Authorization']); |
| 424 | 424 | } |
| 425 | 425 | } |
@@ -7,266 +7,266 @@ |
||
| 7 | 7 | |
| 8 | 8 | if ( !class_exists(BitBucketApi::class, false) ): |
| 9 | 9 | |
| 10 | - class BitBucketApi extends Api { |
|
| 11 | - /** |
|
| 12 | - * @var OAuthSignature |
|
| 13 | - */ |
|
| 14 | - private $oauth = null; |
|
| 15 | - |
|
| 16 | - /** |
|
| 17 | - * @var string |
|
| 18 | - */ |
|
| 19 | - private $username; |
|
| 20 | - |
|
| 21 | - /** |
|
| 22 | - * @var string |
|
| 23 | - */ |
|
| 24 | - private $repository; |
|
| 25 | - |
|
| 26 | - public function __construct($repositoryUrl, $credentials = array()) { |
|
| 27 | - $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); |
|
| 28 | - if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) { |
|
| 29 | - $this->username = $matches['username']; |
|
| 30 | - $this->repository = $matches['repository']; |
|
| 31 | - } else { |
|
| 32 | - throw new \InvalidArgumentException('Invalid BitBucket repository URL: "' . $repositoryUrl . '"'); |
|
| 33 | - } |
|
| 34 | - |
|
| 35 | - parent::__construct($repositoryUrl, $credentials); |
|
| 36 | - } |
|
| 37 | - |
|
| 38 | - protected function getUpdateDetectionStrategies($configBranch) { |
|
| 39 | - $strategies = array( |
|
| 40 | - self::STRATEGY_STABLE_TAG => function () use ($configBranch) { |
|
| 41 | - return $this->getStableTag($configBranch); |
|
| 42 | - }, |
|
| 43 | - ); |
|
| 44 | - |
|
| 45 | - if ( ($configBranch === 'master' || $configBranch === 'main') ) { |
|
| 46 | - $strategies[self::STRATEGY_LATEST_TAG] = array($this, 'getLatestTag'); |
|
| 47 | - } |
|
| 48 | - |
|
| 49 | - $strategies[self::STRATEGY_BRANCH] = function () use ($configBranch) { |
|
| 50 | - return $this->getBranch($configBranch); |
|
| 51 | - }; |
|
| 52 | - return $strategies; |
|
| 53 | - } |
|
| 54 | - |
|
| 55 | - public function getBranch($branchName) { |
|
| 56 | - $branch = $this->api('/refs/branches/' . $branchName); |
|
| 57 | - if ( is_wp_error($branch) || empty($branch) ) { |
|
| 58 | - return null; |
|
| 59 | - } |
|
| 60 | - |
|
| 61 | - //The "/src/{stuff}/{path}" endpoint doesn't seem to handle branch names that contain slashes. |
|
| 62 | - //If we don't encode the slash, we get a 404. If we encode it as "%2F", we get a 401. |
|
| 63 | - //To avoid issues, if the branch name is not URL-safe, let's use the commit hash instead. |
|
| 64 | - $ref = $branch->name; |
|
| 65 | - if ((urlencode($ref) !== $ref) && isset($branch->target->hash)) { |
|
| 66 | - $ref = $branch->target->hash; |
|
| 67 | - } |
|
| 68 | - |
|
| 69 | - return new Reference(array( |
|
| 70 | - 'name' => $ref, |
|
| 71 | - 'updated' => $branch->target->date, |
|
| 72 | - 'downloadUrl' => $this->getDownloadUrl($branch->name), |
|
| 73 | - )); |
|
| 74 | - } |
|
| 75 | - |
|
| 76 | - /** |
|
| 77 | - * Get a specific tag. |
|
| 78 | - * |
|
| 79 | - * @param string $tagName |
|
| 80 | - * @return Reference|null |
|
| 81 | - */ |
|
| 82 | - public function getTag($tagName) { |
|
| 83 | - $tag = $this->api('/refs/tags/' . $tagName); |
|
| 84 | - if ( is_wp_error($tag) || empty($tag) ) { |
|
| 85 | - return null; |
|
| 86 | - } |
|
| 87 | - |
|
| 88 | - return new Reference(array( |
|
| 89 | - 'name' => $tag->name, |
|
| 90 | - 'version' => ltrim($tag->name, 'v'), |
|
| 91 | - 'updated' => $tag->target->date, |
|
| 92 | - 'downloadUrl' => $this->getDownloadUrl($tag->name), |
|
| 93 | - )); |
|
| 94 | - } |
|
| 95 | - |
|
| 96 | - /** |
|
| 97 | - * Get the tag that looks like the highest version number. |
|
| 98 | - * |
|
| 99 | - * @return Reference|null |
|
| 100 | - */ |
|
| 101 | - public function getLatestTag() { |
|
| 102 | - $tags = $this->api('/refs/tags?sort=-target.date'); |
|
| 103 | - if ( !isset($tags, $tags->values) || !is_array($tags->values) ) { |
|
| 104 | - return null; |
|
| 105 | - } |
|
| 106 | - |
|
| 107 | - //Filter and sort the list of tags. |
|
| 108 | - $versionTags = $this->sortTagsByVersion($tags->values); |
|
| 109 | - |
|
| 110 | - //Return the first result. |
|
| 111 | - if ( !empty($versionTags) ) { |
|
| 112 | - $tag = $versionTags[0]; |
|
| 113 | - return new Reference(array( |
|
| 114 | - 'name' => $tag->name, |
|
| 115 | - 'version' => ltrim($tag->name, 'v'), |
|
| 116 | - 'updated' => $tag->target->date, |
|
| 117 | - 'downloadUrl' => $this->getDownloadUrl($tag->name), |
|
| 118 | - )); |
|
| 119 | - } |
|
| 120 | - return null; |
|
| 121 | - } |
|
| 122 | - |
|
| 123 | - /** |
|
| 124 | - * Get the tag/ref specified by the "Stable tag" header in the readme.txt of a given branch. |
|
| 125 | - * |
|
| 126 | - * @param string $branch |
|
| 127 | - * @return null|Reference |
|
| 128 | - */ |
|
| 129 | - protected function getStableTag($branch) { |
|
| 130 | - $remoteReadme = $this->getRemoteReadme($branch); |
|
| 131 | - if ( !empty($remoteReadme['stable_tag']) ) { |
|
| 132 | - $tag = $remoteReadme['stable_tag']; |
|
| 133 | - |
|
| 134 | - //You can explicitly opt out of using tags by setting "Stable tag" to |
|
| 135 | - //"trunk" or the name of the current branch. |
|
| 136 | - if ( ($tag === $branch) || ($tag === 'trunk') ) { |
|
| 137 | - return $this->getBranch($branch); |
|
| 138 | - } |
|
| 139 | - |
|
| 140 | - return $this->getTag($tag); |
|
| 141 | - } |
|
| 142 | - |
|
| 143 | - return null; |
|
| 144 | - } |
|
| 145 | - |
|
| 146 | - /** |
|
| 147 | - * @param string $ref |
|
| 148 | - * @return string |
|
| 149 | - */ |
|
| 150 | - protected function getDownloadUrl($ref) { |
|
| 151 | - return sprintf( |
|
| 152 | - 'https://bitbucket.org/%s/%s/get/%s.zip', |
|
| 153 | - $this->username, |
|
| 154 | - $this->repository, |
|
| 155 | - $ref |
|
| 156 | - ); |
|
| 157 | - } |
|
| 158 | - |
|
| 159 | - /** |
|
| 160 | - * Get the contents of a file from a specific branch or tag. |
|
| 161 | - * |
|
| 162 | - * @param string $path File name. |
|
| 163 | - * @param string $ref |
|
| 164 | - * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error. |
|
| 165 | - */ |
|
| 166 | - public function getRemoteFile($path, $ref = 'master') { |
|
| 167 | - $response = $this->api('src/' . $ref . '/' . ltrim($path)); |
|
| 168 | - if ( is_wp_error($response) || !is_string($response) ) { |
|
| 169 | - return null; |
|
| 170 | - } |
|
| 171 | - return $response; |
|
| 172 | - } |
|
| 173 | - |
|
| 174 | - /** |
|
| 175 | - * Get the timestamp of the latest commit that changed the specified branch or tag. |
|
| 176 | - * |
|
| 177 | - * @param string $ref Reference name (e.g. branch or tag). |
|
| 178 | - * @return string|null |
|
| 179 | - */ |
|
| 180 | - public function getLatestCommitTime($ref) { |
|
| 181 | - $response = $this->api('commits/' . $ref); |
|
| 182 | - if ( isset($response->values, $response->values[0], $response->values[0]->date) ) { |
|
| 183 | - return $response->values[0]->date; |
|
| 184 | - } |
|
| 185 | - return null; |
|
| 186 | - } |
|
| 187 | - |
|
| 188 | - /** |
|
| 189 | - * Perform a BitBucket API 2.0 request. |
|
| 190 | - * |
|
| 191 | - * @param string $url |
|
| 192 | - * @param string $version |
|
| 193 | - * @return mixed|\WP_Error |
|
| 194 | - */ |
|
| 195 | - public function api($url, $version = '2.0') { |
|
| 196 | - $url = ltrim($url, '/'); |
|
| 197 | - $isSrcResource = Utils::startsWith($url, 'src/'); |
|
| 198 | - |
|
| 199 | - $url = implode('/', array( |
|
| 200 | - 'https://api.bitbucket.org', |
|
| 201 | - $version, |
|
| 202 | - 'repositories', |
|
| 203 | - $this->username, |
|
| 204 | - $this->repository, |
|
| 205 | - $url |
|
| 206 | - )); |
|
| 207 | - $baseUrl = $url; |
|
| 208 | - |
|
| 209 | - if ( $this->oauth ) { |
|
| 210 | - $url = $this->oauth->sign($url,'GET'); |
|
| 211 | - } |
|
| 212 | - |
|
| 213 | - $options = array('timeout' => 10); |
|
| 214 | - if ( !empty($this->httpFilterName) ) { |
|
| 215 | - $options = apply_filters($this->httpFilterName, $options); |
|
| 216 | - } |
|
| 217 | - $response = wp_remote_get($url, $options); |
|
| 218 | - if ( is_wp_error($response) ) { |
|
| 219 | - do_action('puc_api_error', $response, null, $url, $this->slug); |
|
| 220 | - return $response; |
|
| 221 | - } |
|
| 222 | - |
|
| 223 | - $code = wp_remote_retrieve_response_code($response); |
|
| 224 | - $body = wp_remote_retrieve_body($response); |
|
| 225 | - if ( $code === 200 ) { |
|
| 226 | - if ( $isSrcResource ) { |
|
| 227 | - //Most responses are JSON-encoded, but src resources just |
|
| 228 | - //return raw file contents. |
|
| 229 | - $document = $body; |
|
| 230 | - } else { |
|
| 231 | - $document = json_decode($body); |
|
| 232 | - } |
|
| 233 | - return $document; |
|
| 234 | - } |
|
| 235 | - |
|
| 236 | - $error = new \WP_Error( |
|
| 237 | - 'puc-bitbucket-http-error', |
|
| 238 | - sprintf('BitBucket API error. Base URL: "%s", HTTP status code: %d.', $baseUrl, $code) |
|
| 239 | - ); |
|
| 240 | - do_action('puc_api_error', $error, $response, $url, $this->slug); |
|
| 241 | - |
|
| 242 | - return $error; |
|
| 243 | - } |
|
| 244 | - |
|
| 245 | - /** |
|
| 246 | - * @param array $credentials |
|
| 247 | - */ |
|
| 248 | - public function setAuthentication($credentials) { |
|
| 249 | - parent::setAuthentication($credentials); |
|
| 250 | - |
|
| 251 | - if ( !empty($credentials) && !empty($credentials['consumer_key']) ) { |
|
| 252 | - $this->oauth = new OAuthSignature( |
|
| 253 | - $credentials['consumer_key'], |
|
| 254 | - $credentials['consumer_secret'] |
|
| 255 | - ); |
|
| 256 | - } else { |
|
| 257 | - $this->oauth = null; |
|
| 258 | - } |
|
| 259 | - } |
|
| 260 | - |
|
| 261 | - public function signDownloadUrl($url) { |
|
| 262 | - //Add authentication data to download URLs. Since OAuth signatures incorporate |
|
| 263 | - //timestamps, we have to do this immediately before inserting the update. Otherwise, |
|
| 264 | - //authentication could fail due to a stale timestamp. |
|
| 265 | - if ( $this->oauth ) { |
|
| 266 | - $url = $this->oauth->sign($url); |
|
| 267 | - } |
|
| 268 | - return $url; |
|
| 269 | - } |
|
| 270 | - } |
|
| 10 | + class BitBucketApi extends Api { |
|
| 11 | + /** |
|
| 12 | + * @var OAuthSignature |
|
| 13 | + */ |
|
| 14 | + private $oauth = null; |
|
| 15 | + |
|
| 16 | + /** |
|
| 17 | + * @var string |
|
| 18 | + */ |
|
| 19 | + private $username; |
|
| 20 | + |
|
| 21 | + /** |
|
| 22 | + * @var string |
|
| 23 | + */ |
|
| 24 | + private $repository; |
|
| 25 | + |
|
| 26 | + public function __construct($repositoryUrl, $credentials = array()) { |
|
| 27 | + $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); |
|
| 28 | + if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) { |
|
| 29 | + $this->username = $matches['username']; |
|
| 30 | + $this->repository = $matches['repository']; |
|
| 31 | + } else { |
|
| 32 | + throw new \InvalidArgumentException('Invalid BitBucket repository URL: "' . $repositoryUrl . '"'); |
|
| 33 | + } |
|
| 34 | + |
|
| 35 | + parent::__construct($repositoryUrl, $credentials); |
|
| 36 | + } |
|
| 37 | + |
|
| 38 | + protected function getUpdateDetectionStrategies($configBranch) { |
|
| 39 | + $strategies = array( |
|
| 40 | + self::STRATEGY_STABLE_TAG => function () use ($configBranch) { |
|
| 41 | + return $this->getStableTag($configBranch); |
|
| 42 | + }, |
|
| 43 | + ); |
|
| 44 | + |
|
| 45 | + if ( ($configBranch === 'master' || $configBranch === 'main') ) { |
|
| 46 | + $strategies[self::STRATEGY_LATEST_TAG] = array($this, 'getLatestTag'); |
|
| 47 | + } |
|
| 48 | + |
|
| 49 | + $strategies[self::STRATEGY_BRANCH] = function () use ($configBranch) { |
|
| 50 | + return $this->getBranch($configBranch); |
|
| 51 | + }; |
|
| 52 | + return $strategies; |
|
| 53 | + } |
|
| 54 | + |
|
| 55 | + public function getBranch($branchName) { |
|
| 56 | + $branch = $this->api('/refs/branches/' . $branchName); |
|
| 57 | + if ( is_wp_error($branch) || empty($branch) ) { |
|
| 58 | + return null; |
|
| 59 | + } |
|
| 60 | + |
|
| 61 | + //The "/src/{stuff}/{path}" endpoint doesn't seem to handle branch names that contain slashes. |
|
| 62 | + //If we don't encode the slash, we get a 404. If we encode it as "%2F", we get a 401. |
|
| 63 | + //To avoid issues, if the branch name is not URL-safe, let's use the commit hash instead. |
|
| 64 | + $ref = $branch->name; |
|
| 65 | + if ((urlencode($ref) !== $ref) && isset($branch->target->hash)) { |
|
| 66 | + $ref = $branch->target->hash; |
|
| 67 | + } |
|
| 68 | + |
|
| 69 | + return new Reference(array( |
|
| 70 | + 'name' => $ref, |
|
| 71 | + 'updated' => $branch->target->date, |
|
| 72 | + 'downloadUrl' => $this->getDownloadUrl($branch->name), |
|
| 73 | + )); |
|
| 74 | + } |
|
| 75 | + |
|
| 76 | + /** |
|
| 77 | + * Get a specific tag. |
|
| 78 | + * |
|
| 79 | + * @param string $tagName |
|
| 80 | + * @return Reference|null |
|
| 81 | + */ |
|
| 82 | + public function getTag($tagName) { |
|
| 83 | + $tag = $this->api('/refs/tags/' . $tagName); |
|
| 84 | + if ( is_wp_error($tag) || empty($tag) ) { |
|
| 85 | + return null; |
|
| 86 | + } |
|
| 87 | + |
|
| 88 | + return new Reference(array( |
|
| 89 | + 'name' => $tag->name, |
|
| 90 | + 'version' => ltrim($tag->name, 'v'), |
|
| 91 | + 'updated' => $tag->target->date, |
|
| 92 | + 'downloadUrl' => $this->getDownloadUrl($tag->name), |
|
| 93 | + )); |
|
| 94 | + } |
|
| 95 | + |
|
| 96 | + /** |
|
| 97 | + * Get the tag that looks like the highest version number. |
|
| 98 | + * |
|
| 99 | + * @return Reference|null |
|
| 100 | + */ |
|
| 101 | + public function getLatestTag() { |
|
| 102 | + $tags = $this->api('/refs/tags?sort=-target.date'); |
|
| 103 | + if ( !isset($tags, $tags->values) || !is_array($tags->values) ) { |
|
| 104 | + return null; |
|
| 105 | + } |
|
| 106 | + |
|
| 107 | + //Filter and sort the list of tags. |
|
| 108 | + $versionTags = $this->sortTagsByVersion($tags->values); |
|
| 109 | + |
|
| 110 | + //Return the first result. |
|
| 111 | + if ( !empty($versionTags) ) { |
|
| 112 | + $tag = $versionTags[0]; |
|
| 113 | + return new Reference(array( |
|
| 114 | + 'name' => $tag->name, |
|
| 115 | + 'version' => ltrim($tag->name, 'v'), |
|
| 116 | + 'updated' => $tag->target->date, |
|
| 117 | + 'downloadUrl' => $this->getDownloadUrl($tag->name), |
|
| 118 | + )); |
|
| 119 | + } |
|
| 120 | + return null; |
|
| 121 | + } |
|
| 122 | + |
|
| 123 | + /** |
|
| 124 | + * Get the tag/ref specified by the "Stable tag" header in the readme.txt of a given branch. |
|
| 125 | + * |
|
| 126 | + * @param string $branch |
|
| 127 | + * @return null|Reference |
|
| 128 | + */ |
|
| 129 | + protected function getStableTag($branch) { |
|
| 130 | + $remoteReadme = $this->getRemoteReadme($branch); |
|
| 131 | + if ( !empty($remoteReadme['stable_tag']) ) { |
|
| 132 | + $tag = $remoteReadme['stable_tag']; |
|
| 133 | + |
|
| 134 | + //You can explicitly opt out of using tags by setting "Stable tag" to |
|
| 135 | + //"trunk" or the name of the current branch. |
|
| 136 | + if ( ($tag === $branch) || ($tag === 'trunk') ) { |
|
| 137 | + return $this->getBranch($branch); |
|
| 138 | + } |
|
| 139 | + |
|
| 140 | + return $this->getTag($tag); |
|
| 141 | + } |
|
| 142 | + |
|
| 143 | + return null; |
|
| 144 | + } |
|
| 145 | + |
|
| 146 | + /** |
|
| 147 | + * @param string $ref |
|
| 148 | + * @return string |
|
| 149 | + */ |
|
| 150 | + protected function getDownloadUrl($ref) { |
|
| 151 | + return sprintf( |
|
| 152 | + 'https://bitbucket.org/%s/%s/get/%s.zip', |
|
| 153 | + $this->username, |
|
| 154 | + $this->repository, |
|
| 155 | + $ref |
|
| 156 | + ); |
|
| 157 | + } |
|
| 158 | + |
|
| 159 | + /** |
|
| 160 | + * Get the contents of a file from a specific branch or tag. |
|
| 161 | + * |
|
| 162 | + * @param string $path File name. |
|
| 163 | + * @param string $ref |
|
| 164 | + * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error. |
|
| 165 | + */ |
|
| 166 | + public function getRemoteFile($path, $ref = 'master') { |
|
| 167 | + $response = $this->api('src/' . $ref . '/' . ltrim($path)); |
|
| 168 | + if ( is_wp_error($response) || !is_string($response) ) { |
|
| 169 | + return null; |
|
| 170 | + } |
|
| 171 | + return $response; |
|
| 172 | + } |
|
| 173 | + |
|
| 174 | + /** |
|
| 175 | + * Get the timestamp of the latest commit that changed the specified branch or tag. |
|
| 176 | + * |
|
| 177 | + * @param string $ref Reference name (e.g. branch or tag). |
|
| 178 | + * @return string|null |
|
| 179 | + */ |
|
| 180 | + public function getLatestCommitTime($ref) { |
|
| 181 | + $response = $this->api('commits/' . $ref); |
|
| 182 | + if ( isset($response->values, $response->values[0], $response->values[0]->date) ) { |
|
| 183 | + return $response->values[0]->date; |
|
| 184 | + } |
|
| 185 | + return null; |
|
| 186 | + } |
|
| 187 | + |
|
| 188 | + /** |
|
| 189 | + * Perform a BitBucket API 2.0 request. |
|
| 190 | + * |
|
| 191 | + * @param string $url |
|
| 192 | + * @param string $version |
|
| 193 | + * @return mixed|\WP_Error |
|
| 194 | + */ |
|
| 195 | + public function api($url, $version = '2.0') { |
|
| 196 | + $url = ltrim($url, '/'); |
|
| 197 | + $isSrcResource = Utils::startsWith($url, 'src/'); |
|
| 198 | + |
|
| 199 | + $url = implode('/', array( |
|
| 200 | + 'https://api.bitbucket.org', |
|
| 201 | + $version, |
|
| 202 | + 'repositories', |
|
| 203 | + $this->username, |
|
| 204 | + $this->repository, |
|
| 205 | + $url |
|
| 206 | + )); |
|
| 207 | + $baseUrl = $url; |
|
| 208 | + |
|
| 209 | + if ( $this->oauth ) { |
|
| 210 | + $url = $this->oauth->sign($url,'GET'); |
|
| 211 | + } |
|
| 212 | + |
|
| 213 | + $options = array('timeout' => 10); |
|
| 214 | + if ( !empty($this->httpFilterName) ) { |
|
| 215 | + $options = apply_filters($this->httpFilterName, $options); |
|
| 216 | + } |
|
| 217 | + $response = wp_remote_get($url, $options); |
|
| 218 | + if ( is_wp_error($response) ) { |
|
| 219 | + do_action('puc_api_error', $response, null, $url, $this->slug); |
|
| 220 | + return $response; |
|
| 221 | + } |
|
| 222 | + |
|
| 223 | + $code = wp_remote_retrieve_response_code($response); |
|
| 224 | + $body = wp_remote_retrieve_body($response); |
|
| 225 | + if ( $code === 200 ) { |
|
| 226 | + if ( $isSrcResource ) { |
|
| 227 | + //Most responses are JSON-encoded, but src resources just |
|
| 228 | + //return raw file contents. |
|
| 229 | + $document = $body; |
|
| 230 | + } else { |
|
| 231 | + $document = json_decode($body); |
|
| 232 | + } |
|
| 233 | + return $document; |
|
| 234 | + } |
|
| 235 | + |
|
| 236 | + $error = new \WP_Error( |
|
| 237 | + 'puc-bitbucket-http-error', |
|
| 238 | + sprintf('BitBucket API error. Base URL: "%s", HTTP status code: %d.', $baseUrl, $code) |
|
| 239 | + ); |
|
| 240 | + do_action('puc_api_error', $error, $response, $url, $this->slug); |
|
| 241 | + |
|
| 242 | + return $error; |
|
| 243 | + } |
|
| 244 | + |
|
| 245 | + /** |
|
| 246 | + * @param array $credentials |
|
| 247 | + */ |
|
| 248 | + public function setAuthentication($credentials) { |
|
| 249 | + parent::setAuthentication($credentials); |
|
| 250 | + |
|
| 251 | + if ( !empty($credentials) && !empty($credentials['consumer_key']) ) { |
|
| 252 | + $this->oauth = new OAuthSignature( |
|
| 253 | + $credentials['consumer_key'], |
|
| 254 | + $credentials['consumer_secret'] |
|
| 255 | + ); |
|
| 256 | + } else { |
|
| 257 | + $this->oauth = null; |
|
| 258 | + } |
|
| 259 | + } |
|
| 260 | + |
|
| 261 | + public function signDownloadUrl($url) { |
|
| 262 | + //Add authentication data to download URLs. Since OAuth signatures incorporate |
|
| 263 | + //timestamps, we have to do this immediately before inserting the update. Otherwise, |
|
| 264 | + //authentication could fail due to a stale timestamp. |
|
| 265 | + if ( $this->oauth ) { |
|
| 266 | + $url = $this->oauth->sign($url); |
|
| 267 | + } |
|
| 268 | + return $url; |
|
| 269 | + } |
|
| 270 | + } |
|
| 271 | 271 | |
| 272 | 272 | endif; |
@@ -5,7 +5,7 @@ discard block |
||
| 5 | 5 | use YahnisElsts\PluginUpdateChecker\v5p0\OAuthSignature; |
| 6 | 6 | use YahnisElsts\PluginUpdateChecker\v5p0\Utils; |
| 7 | 7 | |
| 8 | -if ( !class_exists(BitBucketApi::class, false) ): |
|
| 8 | +if (!class_exists(BitBucketApi::class, false)): |
|
| 9 | 9 | |
| 10 | 10 | class BitBucketApi extends Api { |
| 11 | 11 | /** |
@@ -25,7 +25,7 @@ discard block |
||
| 25 | 25 | |
| 26 | 26 | public function __construct($repositoryUrl, $credentials = array()) { |
| 27 | 27 | $path = wp_parse_url($repositoryUrl, PHP_URL_PATH); |
| 28 | - if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) { |
|
| 28 | + if (preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches)) { |
|
| 29 | 29 | $this->username = $matches['username']; |
| 30 | 30 | $this->repository = $matches['repository']; |
| 31 | 31 | } else { |
@@ -37,16 +37,16 @@ discard block |
||
| 37 | 37 | |
| 38 | 38 | protected function getUpdateDetectionStrategies($configBranch) { |
| 39 | 39 | $strategies = array( |
| 40 | - self::STRATEGY_STABLE_TAG => function () use ($configBranch) { |
|
| 40 | + self::STRATEGY_STABLE_TAG => function() use ($configBranch) { |
|
| 41 | 41 | return $this->getStableTag($configBranch); |
| 42 | 42 | }, |
| 43 | 43 | ); |
| 44 | 44 | |
| 45 | - if ( ($configBranch === 'master' || $configBranch === 'main') ) { |
|
| 45 | + if (($configBranch === 'master' || $configBranch === 'main')) { |
|
| 46 | 46 | $strategies[self::STRATEGY_LATEST_TAG] = array($this, 'getLatestTag'); |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | - $strategies[self::STRATEGY_BRANCH] = function () use ($configBranch) { |
|
| 49 | + $strategies[self::STRATEGY_BRANCH] = function() use ($configBranch) { |
|
| 50 | 50 | return $this->getBranch($configBranch); |
| 51 | 51 | }; |
| 52 | 52 | return $strategies; |
@@ -54,7 +54,7 @@ discard block |
||
| 54 | 54 | |
| 55 | 55 | public function getBranch($branchName) { |
| 56 | 56 | $branch = $this->api('/refs/branches/' . $branchName); |
| 57 | - if ( is_wp_error($branch) || empty($branch) ) { |
|
| 57 | + if (is_wp_error($branch) || empty($branch)) { |
|
| 58 | 58 | return null; |
| 59 | 59 | } |
| 60 | 60 | |
@@ -81,7 +81,7 @@ discard block |
||
| 81 | 81 | */ |
| 82 | 82 | public function getTag($tagName) { |
| 83 | 83 | $tag = $this->api('/refs/tags/' . $tagName); |
| 84 | - if ( is_wp_error($tag) || empty($tag) ) { |
|
| 84 | + if (is_wp_error($tag) || empty($tag)) { |
|
| 85 | 85 | return null; |
| 86 | 86 | } |
| 87 | 87 | |
@@ -100,7 +100,7 @@ discard block |
||
| 100 | 100 | */ |
| 101 | 101 | public function getLatestTag() { |
| 102 | 102 | $tags = $this->api('/refs/tags?sort=-target.date'); |
| 103 | - if ( !isset($tags, $tags->values) || !is_array($tags->values) ) { |
|
| 103 | + if (!isset($tags, $tags->values) || !is_array($tags->values)) { |
|
| 104 | 104 | return null; |
| 105 | 105 | } |
| 106 | 106 | |
@@ -108,7 +108,7 @@ discard block |
||
| 108 | 108 | $versionTags = $this->sortTagsByVersion($tags->values); |
| 109 | 109 | |
| 110 | 110 | //Return the first result. |
| 111 | - if ( !empty($versionTags) ) { |
|
| 111 | + if (!empty($versionTags)) { |
|
| 112 | 112 | $tag = $versionTags[0]; |
| 113 | 113 | return new Reference(array( |
| 114 | 114 | 'name' => $tag->name, |
@@ -128,12 +128,12 @@ discard block |
||
| 128 | 128 | */ |
| 129 | 129 | protected function getStableTag($branch) { |
| 130 | 130 | $remoteReadme = $this->getRemoteReadme($branch); |
| 131 | - if ( !empty($remoteReadme['stable_tag']) ) { |
|
| 131 | + if (!empty($remoteReadme['stable_tag'])) { |
|
| 132 | 132 | $tag = $remoteReadme['stable_tag']; |
| 133 | 133 | |
| 134 | 134 | //You can explicitly opt out of using tags by setting "Stable tag" to |
| 135 | 135 | //"trunk" or the name of the current branch. |
| 136 | - if ( ($tag === $branch) || ($tag === 'trunk') ) { |
|
| 136 | + if (($tag === $branch) || ($tag === 'trunk')) { |
|
| 137 | 137 | return $this->getBranch($branch); |
| 138 | 138 | } |
| 139 | 139 | |
@@ -165,7 +165,7 @@ discard block |
||
| 165 | 165 | */ |
| 166 | 166 | public function getRemoteFile($path, $ref = 'master') { |
| 167 | 167 | $response = $this->api('src/' . $ref . '/' . ltrim($path)); |
| 168 | - if ( is_wp_error($response) || !is_string($response) ) { |
|
| 168 | + if (is_wp_error($response) || !is_string($response)) { |
|
| 169 | 169 | return null; |
| 170 | 170 | } |
| 171 | 171 | return $response; |
@@ -179,7 +179,7 @@ discard block |
||
| 179 | 179 | */ |
| 180 | 180 | public function getLatestCommitTime($ref) { |
| 181 | 181 | $response = $this->api('commits/' . $ref); |
| 182 | - if ( isset($response->values, $response->values[0], $response->values[0]->date) ) { |
|
| 182 | + if (isset($response->values, $response->values[0], $response->values[0]->date)) { |
|
| 183 | 183 | return $response->values[0]->date; |
| 184 | 184 | } |
| 185 | 185 | return null; |
@@ -206,24 +206,24 @@ discard block |
||
| 206 | 206 | )); |
| 207 | 207 | $baseUrl = $url; |
| 208 | 208 | |
| 209 | - if ( $this->oauth ) { |
|
| 210 | - $url = $this->oauth->sign($url,'GET'); |
|
| 209 | + if ($this->oauth) { |
|
| 210 | + $url = $this->oauth->sign($url, 'GET'); |
|
| 211 | 211 | } |
| 212 | 212 | |
| 213 | 213 | $options = array('timeout' => 10); |
| 214 | - if ( !empty($this->httpFilterName) ) { |
|
| 214 | + if (!empty($this->httpFilterName)) { |
|
| 215 | 215 | $options = apply_filters($this->httpFilterName, $options); |
| 216 | 216 | } |
| 217 | 217 | $response = wp_remote_get($url, $options); |
| 218 | - if ( is_wp_error($response) ) { |
|
| 218 | + if (is_wp_error($response)) { |
|
| 219 | 219 | do_action('puc_api_error', $response, null, $url, $this->slug); |
| 220 | 220 | return $response; |
| 221 | 221 | } |
| 222 | 222 | |
| 223 | 223 | $code = wp_remote_retrieve_response_code($response); |
| 224 | 224 | $body = wp_remote_retrieve_body($response); |
| 225 | - if ( $code === 200 ) { |
|
| 226 | - if ( $isSrcResource ) { |
|
| 225 | + if ($code === 200) { |
|
| 226 | + if ($isSrcResource) { |
|
| 227 | 227 | //Most responses are JSON-encoded, but src resources just |
| 228 | 228 | //return raw file contents. |
| 229 | 229 | $document = $body; |
@@ -248,7 +248,7 @@ discard block |
||
| 248 | 248 | public function setAuthentication($credentials) { |
| 249 | 249 | parent::setAuthentication($credentials); |
| 250 | 250 | |
| 251 | - if ( !empty($credentials) && !empty($credentials['consumer_key']) ) { |
|
| 251 | + if (!empty($credentials) && !empty($credentials['consumer_key'])) { |
|
| 252 | 252 | $this->oauth = new OAuthSignature( |
| 253 | 253 | $credentials['consumer_key'], |
| 254 | 254 | $credentials['consumer_secret'] |
@@ -262,7 +262,7 @@ discard block |
||
| 262 | 262 | //Add authentication data to download URLs. Since OAuth signatures incorporate |
| 263 | 263 | //timestamps, we have to do this immediately before inserting the update. Otherwise, |
| 264 | 264 | //authentication could fail due to a stale timestamp. |
| 265 | - if ( $this->oauth ) { |
|
| 265 | + if ($this->oauth) { |
|
| 266 | 266 | $url = $this->oauth->sign($url); |
| 267 | 267 | } |
| 268 | 268 | return $url; |
@@ -6,348 +6,348 @@ |
||
| 6 | 6 | |
| 7 | 7 | if ( !class_exists(Api::class, false) ): |
| 8 | 8 | |
| 9 | - abstract class Api { |
|
| 10 | - const STRATEGY_LATEST_RELEASE = 'latest_release'; |
|
| 11 | - const STRATEGY_LATEST_TAG = 'latest_tag'; |
|
| 12 | - const STRATEGY_STABLE_TAG = 'stable_tag'; |
|
| 13 | - const STRATEGY_BRANCH = 'branch'; |
|
| 14 | - |
|
| 15 | - protected $tagNameProperty = 'name'; |
|
| 16 | - protected $slug = ''; |
|
| 17 | - |
|
| 18 | - /** |
|
| 19 | - * @var string |
|
| 20 | - */ |
|
| 21 | - protected $repositoryUrl = ''; |
|
| 22 | - |
|
| 23 | - /** |
|
| 24 | - * @var mixed Authentication details for private repositories. Format depends on service. |
|
| 25 | - */ |
|
| 26 | - protected $credentials = null; |
|
| 27 | - |
|
| 28 | - /** |
|
| 29 | - * @var string The filter tag that's used to filter options passed to wp_remote_get. |
|
| 30 | - * For example, "puc_request_info_options-slug" or "puc_request_update_options_theme-slug". |
|
| 31 | - */ |
|
| 32 | - protected $httpFilterName = ''; |
|
| 33 | - |
|
| 34 | - /** |
|
| 35 | - * @var string The filter applied to the list of update detection strategies that |
|
| 36 | - * are used to find the latest version. |
|
| 37 | - */ |
|
| 38 | - protected $strategyFilterName = ''; |
|
| 39 | - |
|
| 40 | - /** |
|
| 41 | - * @var string|null |
|
| 42 | - */ |
|
| 43 | - protected $localDirectory = null; |
|
| 44 | - |
|
| 45 | - /** |
|
| 46 | - * Api constructor. |
|
| 47 | - * |
|
| 48 | - * @param string $repositoryUrl |
|
| 49 | - * @param array|string|null $credentials |
|
| 50 | - */ |
|
| 51 | - public function __construct($repositoryUrl, $credentials = null) { |
|
| 52 | - $this->repositoryUrl = $repositoryUrl; |
|
| 53 | - $this->setAuthentication($credentials); |
|
| 54 | - } |
|
| 55 | - |
|
| 56 | - /** |
|
| 57 | - * @return string |
|
| 58 | - */ |
|
| 59 | - public function getRepositoryUrl() { |
|
| 60 | - return $this->repositoryUrl; |
|
| 61 | - } |
|
| 62 | - |
|
| 63 | - /** |
|
| 64 | - * Figure out which reference (i.e. tag or branch) contains the latest version. |
|
| 65 | - * |
|
| 66 | - * @param string $configBranch Start looking in this branch. |
|
| 67 | - * @return null|Reference |
|
| 68 | - */ |
|
| 69 | - public function chooseReference($configBranch) { |
|
| 70 | - $strategies = $this->getUpdateDetectionStrategies($configBranch); |
|
| 71 | - |
|
| 72 | - if ( !empty($this->strategyFilterName) ) { |
|
| 73 | - $strategies = apply_filters( |
|
| 74 | - $this->strategyFilterName, |
|
| 75 | - $strategies, |
|
| 76 | - $this->slug |
|
| 77 | - ); |
|
| 78 | - } |
|
| 79 | - |
|
| 80 | - foreach ($strategies as $strategy) { |
|
| 81 | - $reference = call_user_func($strategy); |
|
| 82 | - if ( !empty($reference) ) { |
|
| 83 | - return $reference; |
|
| 84 | - } |
|
| 85 | - } |
|
| 86 | - return null; |
|
| 87 | - } |
|
| 88 | - |
|
| 89 | - /** |
|
| 90 | - * Get an ordered list of strategies that can be used to find the latest version. |
|
| 91 | - * |
|
| 92 | - * The update checker will try each strategy in order until one of them |
|
| 93 | - * returns a valid reference. |
|
| 94 | - * |
|
| 95 | - * @param string $configBranch |
|
| 96 | - * @return array<callable> Array of callables that return Vcs_Reference objects. |
|
| 97 | - */ |
|
| 98 | - abstract protected function getUpdateDetectionStrategies($configBranch); |
|
| 99 | - |
|
| 100 | - /** |
|
| 101 | - * Get the readme.txt file from the remote repository and parse it |
|
| 102 | - * according to the plugin readme standard. |
|
| 103 | - * |
|
| 104 | - * @param string $ref Tag or branch name. |
|
| 105 | - * @return array Parsed readme. |
|
| 106 | - */ |
|
| 107 | - public function getRemoteReadme($ref = 'master') { |
|
| 108 | - $fileContents = $this->getRemoteFile($this->getLocalReadmeName(), $ref); |
|
| 109 | - if ( empty($fileContents) ) { |
|
| 110 | - return array(); |
|
| 111 | - } |
|
| 112 | - |
|
| 113 | - $parser = new PucReadmeParser(); |
|
| 114 | - return $parser->parse_readme_contents($fileContents); |
|
| 115 | - } |
|
| 116 | - |
|
| 117 | - /** |
|
| 118 | - * Get the case-sensitive name of the local readme.txt file. |
|
| 119 | - * |
|
| 120 | - * In most cases it should just be called "readme.txt", but some plugins call it "README.txt", |
|
| 121 | - * "README.TXT", or even "Readme.txt". Most VCS are case-sensitive so we need to know the correct |
|
| 122 | - * capitalization. |
|
| 123 | - * |
|
| 124 | - * Defaults to "readme.txt" (all lowercase). |
|
| 125 | - * |
|
| 126 | - * @return string |
|
| 127 | - */ |
|
| 128 | - public function getLocalReadmeName() { |
|
| 129 | - static $fileName = null; |
|
| 130 | - if ( $fileName !== null ) { |
|
| 131 | - return $fileName; |
|
| 132 | - } |
|
| 133 | - |
|
| 134 | - $fileName = 'readme.txt'; |
|
| 135 | - if ( isset($this->localDirectory) ) { |
|
| 136 | - $files = scandir($this->localDirectory); |
|
| 137 | - if ( !empty($files) ) { |
|
| 138 | - foreach ($files as $possibleFileName) { |
|
| 139 | - if ( strcasecmp($possibleFileName, 'readme.txt') === 0 ) { |
|
| 140 | - $fileName = $possibleFileName; |
|
| 141 | - break; |
|
| 142 | - } |
|
| 143 | - } |
|
| 144 | - } |
|
| 145 | - } |
|
| 146 | - return $fileName; |
|
| 147 | - } |
|
| 148 | - |
|
| 149 | - /** |
|
| 150 | - * Get a branch. |
|
| 151 | - * |
|
| 152 | - * @param string $branchName |
|
| 153 | - * @return Reference|null |
|
| 154 | - */ |
|
| 155 | - abstract public function getBranch($branchName); |
|
| 156 | - |
|
| 157 | - /** |
|
| 158 | - * Get a specific tag. |
|
| 159 | - * |
|
| 160 | - * @param string $tagName |
|
| 161 | - * @return Reference|null |
|
| 162 | - */ |
|
| 163 | - abstract public function getTag($tagName); |
|
| 164 | - |
|
| 165 | - /** |
|
| 166 | - * Get the tag that looks like the highest version number. |
|
| 167 | - * (Implementations should skip pre-release versions if possible.) |
|
| 168 | - * |
|
| 169 | - * @return Reference|null |
|
| 170 | - */ |
|
| 171 | - abstract public function getLatestTag(); |
|
| 172 | - |
|
| 173 | - /** |
|
| 174 | - * Check if a tag name string looks like a version number. |
|
| 175 | - * |
|
| 176 | - * @param string $name |
|
| 177 | - * @return bool |
|
| 178 | - */ |
|
| 179 | - protected function looksLikeVersion($name) { |
|
| 180 | - //Tag names may be prefixed with "v", e.g. "v1.2.3". |
|
| 181 | - $name = ltrim($name, 'v'); |
|
| 182 | - |
|
| 183 | - //The version string must start with a number. |
|
| 184 | - if ( !is_numeric(substr($name, 0, 1)) ) { |
|
| 185 | - return false; |
|
| 186 | - } |
|
| 187 | - |
|
| 188 | - //The goal is to accept any SemVer-compatible or "PHP-standardized" version number. |
|
| 189 | - return (preg_match('@^(\d{1,5}?)(\.\d{1,10}?){0,4}?($|[abrdp+_\-]|\s)@i', $name) === 1); |
|
| 190 | - } |
|
| 191 | - |
|
| 192 | - /** |
|
| 193 | - * Check if a tag appears to be named like a version number. |
|
| 194 | - * |
|
| 195 | - * @param \stdClass $tag |
|
| 196 | - * @return bool |
|
| 197 | - */ |
|
| 198 | - protected function isVersionTag($tag) { |
|
| 199 | - $property = $this->tagNameProperty; |
|
| 200 | - return isset($tag->$property) && $this->looksLikeVersion($tag->$property); |
|
| 201 | - } |
|
| 202 | - |
|
| 203 | - /** |
|
| 204 | - * Sort a list of tags as if they were version numbers. |
|
| 205 | - * Tags that don't look like version number will be removed. |
|
| 206 | - * |
|
| 207 | - * @param \stdClass[] $tags Array of tag objects. |
|
| 208 | - * @return \stdClass[] Filtered array of tags sorted in descending order. |
|
| 209 | - */ |
|
| 210 | - protected function sortTagsByVersion($tags) { |
|
| 211 | - //Keep only those tags that look like version numbers. |
|
| 212 | - $versionTags = array_filter($tags, array($this, 'isVersionTag')); |
|
| 213 | - //Sort them in descending order. |
|
| 214 | - usort($versionTags, array($this, 'compareTagNames')); |
|
| 215 | - |
|
| 216 | - return $versionTags; |
|
| 217 | - } |
|
| 218 | - |
|
| 219 | - /** |
|
| 220 | - * Compare two tags as if they were version number. |
|
| 221 | - * |
|
| 222 | - * @param \stdClass $tag1 Tag object. |
|
| 223 | - * @param \stdClass $tag2 Another tag object. |
|
| 224 | - * @return int |
|
| 225 | - */ |
|
| 226 | - protected function compareTagNames($tag1, $tag2) { |
|
| 227 | - $property = $this->tagNameProperty; |
|
| 228 | - if ( !isset($tag1->$property) ) { |
|
| 229 | - return 1; |
|
| 230 | - } |
|
| 231 | - if ( !isset($tag2->$property) ) { |
|
| 232 | - return -1; |
|
| 233 | - } |
|
| 234 | - return -version_compare(ltrim($tag1->$property, 'v'), ltrim($tag2->$property, 'v')); |
|
| 235 | - } |
|
| 236 | - |
|
| 237 | - /** |
|
| 238 | - * Get the contents of a file from a specific branch or tag. |
|
| 239 | - * |
|
| 240 | - * @param string $path File name. |
|
| 241 | - * @param string $ref |
|
| 242 | - * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error. |
|
| 243 | - */ |
|
| 244 | - abstract public function getRemoteFile($path, $ref = 'master'); |
|
| 245 | - |
|
| 246 | - /** |
|
| 247 | - * Get the timestamp of the latest commit that changed the specified branch or tag. |
|
| 248 | - * |
|
| 249 | - * @param string $ref Reference name (e.g. branch or tag). |
|
| 250 | - * @return string|null |
|
| 251 | - */ |
|
| 252 | - abstract public function getLatestCommitTime($ref); |
|
| 253 | - |
|
| 254 | - /** |
|
| 255 | - * Get the contents of the changelog file from the repository. |
|
| 256 | - * |
|
| 257 | - * @param string $ref |
|
| 258 | - * @param string $localDirectory Full path to the local plugin or theme directory. |
|
| 259 | - * @return null|string The HTML contents of the changelog. |
|
| 260 | - */ |
|
| 261 | - public function getRemoteChangelog($ref, $localDirectory) { |
|
| 262 | - $filename = $this->findChangelogName($localDirectory); |
|
| 263 | - if ( empty($filename) ) { |
|
| 264 | - return null; |
|
| 265 | - } |
|
| 266 | - |
|
| 267 | - $changelog = $this->getRemoteFile($filename, $ref); |
|
| 268 | - if ( $changelog === null ) { |
|
| 269 | - return null; |
|
| 270 | - } |
|
| 271 | - |
|
| 272 | - return Parsedown::instance()->text($changelog); |
|
| 273 | - } |
|
| 274 | - |
|
| 275 | - /** |
|
| 276 | - * Guess the name of the changelog file. |
|
| 277 | - * |
|
| 278 | - * @param string $directory |
|
| 279 | - * @return string|null |
|
| 280 | - */ |
|
| 281 | - protected function findChangelogName($directory = null) { |
|
| 282 | - if ( !isset($directory) ) { |
|
| 283 | - $directory = $this->localDirectory; |
|
| 284 | - } |
|
| 285 | - if ( empty($directory) || !is_dir($directory) || ($directory === '.') ) { |
|
| 286 | - return null; |
|
| 287 | - } |
|
| 288 | - |
|
| 289 | - $possibleNames = array('CHANGES.md', 'CHANGELOG.md', 'changes.md', 'changelog.md'); |
|
| 290 | - $files = scandir($directory); |
|
| 291 | - $foundNames = array_intersect($possibleNames, $files); |
|
| 292 | - |
|
| 293 | - if ( !empty($foundNames) ) { |
|
| 294 | - return reset($foundNames); |
|
| 295 | - } |
|
| 296 | - return null; |
|
| 297 | - } |
|
| 298 | - |
|
| 299 | - /** |
|
| 300 | - * Set authentication credentials. |
|
| 301 | - * |
|
| 302 | - * @param $credentials |
|
| 303 | - */ |
|
| 304 | - public function setAuthentication($credentials) { |
|
| 305 | - $this->credentials = $credentials; |
|
| 306 | - } |
|
| 307 | - |
|
| 308 | - public function isAuthenticationEnabled() { |
|
| 309 | - return !empty($this->credentials); |
|
| 310 | - } |
|
| 311 | - |
|
| 312 | - /** |
|
| 313 | - * @param string $url |
|
| 314 | - * @return string |
|
| 315 | - */ |
|
| 316 | - public function signDownloadUrl($url) { |
|
| 317 | - return $url; |
|
| 318 | - } |
|
| 319 | - |
|
| 320 | - /** |
|
| 321 | - * @param string $filterName |
|
| 322 | - */ |
|
| 323 | - public function setHttpFilterName($filterName) { |
|
| 324 | - $this->httpFilterName = $filterName; |
|
| 325 | - } |
|
| 326 | - |
|
| 327 | - /** |
|
| 328 | - * @param string $filterName |
|
| 329 | - */ |
|
| 330 | - public function setStrategyFilterName($filterName) { |
|
| 331 | - $this->strategyFilterName = $filterName; |
|
| 332 | - } |
|
| 333 | - |
|
| 334 | - /** |
|
| 335 | - * @param string $directory |
|
| 336 | - */ |
|
| 337 | - public function setLocalDirectory($directory) { |
|
| 338 | - if ( empty($directory) || !is_dir($directory) || ($directory === '.') ) { |
|
| 339 | - $this->localDirectory = null; |
|
| 340 | - } else { |
|
| 341 | - $this->localDirectory = $directory; |
|
| 342 | - } |
|
| 343 | - } |
|
| 344 | - |
|
| 345 | - /** |
|
| 346 | - * @param string $slug |
|
| 347 | - */ |
|
| 348 | - public function setSlug($slug) { |
|
| 349 | - $this->slug = $slug; |
|
| 350 | - } |
|
| 351 | - } |
|
| 9 | + abstract class Api { |
|
| 10 | + const STRATEGY_LATEST_RELEASE = 'latest_release'; |
|
| 11 | + const STRATEGY_LATEST_TAG = 'latest_tag'; |
|
| 12 | + const STRATEGY_STABLE_TAG = 'stable_tag'; |
|
| 13 | + const STRATEGY_BRANCH = 'branch'; |
|
| 14 | + |
|
| 15 | + protected $tagNameProperty = 'name'; |
|
| 16 | + protected $slug = ''; |
|
| 17 | + |
|
| 18 | + /** |
|
| 19 | + * @var string |
|
| 20 | + */ |
|
| 21 | + protected $repositoryUrl = ''; |
|
| 22 | + |
|
| 23 | + /** |
|
| 24 | + * @var mixed Authentication details for private repositories. Format depends on service. |
|
| 25 | + */ |
|
| 26 | + protected $credentials = null; |
|
| 27 | + |
|
| 28 | + /** |
|
| 29 | + * @var string The filter tag that's used to filter options passed to wp_remote_get. |
|
| 30 | + * For example, "puc_request_info_options-slug" or "puc_request_update_options_theme-slug". |
|
| 31 | + */ |
|
| 32 | + protected $httpFilterName = ''; |
|
| 33 | + |
|
| 34 | + /** |
|
| 35 | + * @var string The filter applied to the list of update detection strategies that |
|
| 36 | + * are used to find the latest version. |
|
| 37 | + */ |
|
| 38 | + protected $strategyFilterName = ''; |
|
| 39 | + |
|
| 40 | + /** |
|
| 41 | + * @var string|null |
|
| 42 | + */ |
|
| 43 | + protected $localDirectory = null; |
|
| 44 | + |
|
| 45 | + /** |
|
| 46 | + * Api constructor. |
|
| 47 | + * |
|
| 48 | + * @param string $repositoryUrl |
|
| 49 | + * @param array|string|null $credentials |
|
| 50 | + */ |
|
| 51 | + public function __construct($repositoryUrl, $credentials = null) { |
|
| 52 | + $this->repositoryUrl = $repositoryUrl; |
|
| 53 | + $this->setAuthentication($credentials); |
|
| 54 | + } |
|
| 55 | + |
|
| 56 | + /** |
|
| 57 | + * @return string |
|
| 58 | + */ |
|
| 59 | + public function getRepositoryUrl() { |
|
| 60 | + return $this->repositoryUrl; |
|
| 61 | + } |
|
| 62 | + |
|
| 63 | + /** |
|
| 64 | + * Figure out which reference (i.e. tag or branch) contains the latest version. |
|
| 65 | + * |
|
| 66 | + * @param string $configBranch Start looking in this branch. |
|
| 67 | + * @return null|Reference |
|
| 68 | + */ |
|
| 69 | + public function chooseReference($configBranch) { |
|
| 70 | + $strategies = $this->getUpdateDetectionStrategies($configBranch); |
|
| 71 | + |
|
| 72 | + if ( !empty($this->strategyFilterName) ) { |
|
| 73 | + $strategies = apply_filters( |
|
| 74 | + $this->strategyFilterName, |
|
| 75 | + $strategies, |
|
| 76 | + $this->slug |
|
| 77 | + ); |
|
| 78 | + } |
|
| 79 | + |
|
| 80 | + foreach ($strategies as $strategy) { |
|
| 81 | + $reference = call_user_func($strategy); |
|
| 82 | + if ( !empty($reference) ) { |
|
| 83 | + return $reference; |
|
| 84 | + } |
|
| 85 | + } |
|
| 86 | + return null; |
|
| 87 | + } |
|
| 88 | + |
|
| 89 | + /** |
|
| 90 | + * Get an ordered list of strategies that can be used to find the latest version. |
|
| 91 | + * |
|
| 92 | + * The update checker will try each strategy in order until one of them |
|
| 93 | + * returns a valid reference. |
|
| 94 | + * |
|
| 95 | + * @param string $configBranch |
|
| 96 | + * @return array<callable> Array of callables that return Vcs_Reference objects. |
|
| 97 | + */ |
|
| 98 | + abstract protected function getUpdateDetectionStrategies($configBranch); |
|
| 99 | + |
|
| 100 | + /** |
|
| 101 | + * Get the readme.txt file from the remote repository and parse it |
|
| 102 | + * according to the plugin readme standard. |
|
| 103 | + * |
|
| 104 | + * @param string $ref Tag or branch name. |
|
| 105 | + * @return array Parsed readme. |
|
| 106 | + */ |
|
| 107 | + public function getRemoteReadme($ref = 'master') { |
|
| 108 | + $fileContents = $this->getRemoteFile($this->getLocalReadmeName(), $ref); |
|
| 109 | + if ( empty($fileContents) ) { |
|
| 110 | + return array(); |
|
| 111 | + } |
|
| 112 | + |
|
| 113 | + $parser = new PucReadmeParser(); |
|
| 114 | + return $parser->parse_readme_contents($fileContents); |
|
| 115 | + } |
|
| 116 | + |
|
| 117 | + /** |
|
| 118 | + * Get the case-sensitive name of the local readme.txt file. |
|
| 119 | + * |
|
| 120 | + * In most cases it should just be called "readme.txt", but some plugins call it "README.txt", |
|
| 121 | + * "README.TXT", or even "Readme.txt". Most VCS are case-sensitive so we need to know the correct |
|
| 122 | + * capitalization. |
|
| 123 | + * |
|
| 124 | + * Defaults to "readme.txt" (all lowercase). |
|
| 125 | + * |
|
| 126 | + * @return string |
|
| 127 | + */ |
|
| 128 | + public function getLocalReadmeName() { |
|
| 129 | + static $fileName = null; |
|
| 130 | + if ( $fileName !== null ) { |
|
| 131 | + return $fileName; |
|
| 132 | + } |
|
| 133 | + |
|
| 134 | + $fileName = 'readme.txt'; |
|
| 135 | + if ( isset($this->localDirectory) ) { |
|
| 136 | + $files = scandir($this->localDirectory); |
|
| 137 | + if ( !empty($files) ) { |
|
| 138 | + foreach ($files as $possibleFileName) { |
|
| 139 | + if ( strcasecmp($possibleFileName, 'readme.txt') === 0 ) { |
|
| 140 | + $fileName = $possibleFileName; |
|
| 141 | + break; |
|
| 142 | + } |
|
| 143 | + } |
|
| 144 | + } |
|
| 145 | + } |
|
| 146 | + return $fileName; |
|
| 147 | + } |
|
| 148 | + |
|
| 149 | + /** |
|
| 150 | + * Get a branch. |
|
| 151 | + * |
|
| 152 | + * @param string $branchName |
|
| 153 | + * @return Reference|null |
|
| 154 | + */ |
|
| 155 | + abstract public function getBranch($branchName); |
|
| 156 | + |
|
| 157 | + /** |
|
| 158 | + * Get a specific tag. |
|
| 159 | + * |
|
| 160 | + * @param string $tagName |
|
| 161 | + * @return Reference|null |
|
| 162 | + */ |
|
| 163 | + abstract public function getTag($tagName); |
|
| 164 | + |
|
| 165 | + /** |
|
| 166 | + * Get the tag that looks like the highest version number. |
|
| 167 | + * (Implementations should skip pre-release versions if possible.) |
|
| 168 | + * |
|
| 169 | + * @return Reference|null |
|
| 170 | + */ |
|
| 171 | + abstract public function getLatestTag(); |
|
| 172 | + |
|
| 173 | + /** |
|
| 174 | + * Check if a tag name string looks like a version number. |
|
| 175 | + * |
|
| 176 | + * @param string $name |
|
| 177 | + * @return bool |
|
| 178 | + */ |
|
| 179 | + protected function looksLikeVersion($name) { |
|
| 180 | + //Tag names may be prefixed with "v", e.g. "v1.2.3". |
|
| 181 | + $name = ltrim($name, 'v'); |
|
| 182 | + |
|
| 183 | + //The version string must start with a number. |
|
| 184 | + if ( !is_numeric(substr($name, 0, 1)) ) { |
|
| 185 | + return false; |
|
| 186 | + } |
|
| 187 | + |
|
| 188 | + //The goal is to accept any SemVer-compatible or "PHP-standardized" version number. |
|
| 189 | + return (preg_match('@^(\d{1,5}?)(\.\d{1,10}?){0,4}?($|[abrdp+_\-]|\s)@i', $name) === 1); |
|
| 190 | + } |
|
| 191 | + |
|
| 192 | + /** |
|
| 193 | + * Check if a tag appears to be named like a version number. |
|
| 194 | + * |
|
| 195 | + * @param \stdClass $tag |
|
| 196 | + * @return bool |
|
| 197 | + */ |
|
| 198 | + protected function isVersionTag($tag) { |
|
| 199 | + $property = $this->tagNameProperty; |
|
| 200 | + return isset($tag->$property) && $this->looksLikeVersion($tag->$property); |
|
| 201 | + } |
|
| 202 | + |
|
| 203 | + /** |
|
| 204 | + * Sort a list of tags as if they were version numbers. |
|
| 205 | + * Tags that don't look like version number will be removed. |
|
| 206 | + * |
|
| 207 | + * @param \stdClass[] $tags Array of tag objects. |
|
| 208 | + * @return \stdClass[] Filtered array of tags sorted in descending order. |
|
| 209 | + */ |
|
| 210 | + protected function sortTagsByVersion($tags) { |
|
| 211 | + //Keep only those tags that look like version numbers. |
|
| 212 | + $versionTags = array_filter($tags, array($this, 'isVersionTag')); |
|
| 213 | + //Sort them in descending order. |
|
| 214 | + usort($versionTags, array($this, 'compareTagNames')); |
|
| 215 | + |
|
| 216 | + return $versionTags; |
|
| 217 | + } |
|
| 218 | + |
|
| 219 | + /** |
|
| 220 | + * Compare two tags as if they were version number. |
|
| 221 | + * |
|
| 222 | + * @param \stdClass $tag1 Tag object. |
|
| 223 | + * @param \stdClass $tag2 Another tag object. |
|
| 224 | + * @return int |
|
| 225 | + */ |
|
| 226 | + protected function compareTagNames($tag1, $tag2) { |
|
| 227 | + $property = $this->tagNameProperty; |
|
| 228 | + if ( !isset($tag1->$property) ) { |
|
| 229 | + return 1; |
|
| 230 | + } |
|
| 231 | + if ( !isset($tag2->$property) ) { |
|
| 232 | + return -1; |
|
| 233 | + } |
|
| 234 | + return -version_compare(ltrim($tag1->$property, 'v'), ltrim($tag2->$property, 'v')); |
|
| 235 | + } |
|
| 236 | + |
|
| 237 | + /** |
|
| 238 | + * Get the contents of a file from a specific branch or tag. |
|
| 239 | + * |
|
| 240 | + * @param string $path File name. |
|
| 241 | + * @param string $ref |
|
| 242 | + * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error. |
|
| 243 | + */ |
|
| 244 | + abstract public function getRemoteFile($path, $ref = 'master'); |
|
| 245 | + |
|
| 246 | + /** |
|
| 247 | + * Get the timestamp of the latest commit that changed the specified branch or tag. |
|
| 248 | + * |
|
| 249 | + * @param string $ref Reference name (e.g. branch or tag). |
|
| 250 | + * @return string|null |
|
| 251 | + */ |
|
| 252 | + abstract public function getLatestCommitTime($ref); |
|
| 253 | + |
|
| 254 | + /** |
|
| 255 | + * Get the contents of the changelog file from the repository. |
|
| 256 | + * |
|
| 257 | + * @param string $ref |
|
| 258 | + * @param string $localDirectory Full path to the local plugin or theme directory. |
|
| 259 | + * @return null|string The HTML contents of the changelog. |
|
| 260 | + */ |
|
| 261 | + public function getRemoteChangelog($ref, $localDirectory) { |
|
| 262 | + $filename = $this->findChangelogName($localDirectory); |
|
| 263 | + if ( empty($filename) ) { |
|
| 264 | + return null; |
|
| 265 | + } |
|
| 266 | + |
|
| 267 | + $changelog = $this->getRemoteFile($filename, $ref); |
|
| 268 | + if ( $changelog === null ) { |
|
| 269 | + return null; |
|
| 270 | + } |
|
| 271 | + |
|
| 272 | + return Parsedown::instance()->text($changelog); |
|
| 273 | + } |
|
| 274 | + |
|
| 275 | + /** |
|
| 276 | + * Guess the name of the changelog file. |
|
| 277 | + * |
|
| 278 | + * @param string $directory |
|
| 279 | + * @return string|null |
|
| 280 | + */ |
|
| 281 | + protected function findChangelogName($directory = null) { |
|
| 282 | + if ( !isset($directory) ) { |
|
| 283 | + $directory = $this->localDirectory; |
|
| 284 | + } |
|
| 285 | + if ( empty($directory) || !is_dir($directory) || ($directory === '.') ) { |
|
| 286 | + return null; |
|
| 287 | + } |
|
| 288 | + |
|
| 289 | + $possibleNames = array('CHANGES.md', 'CHANGELOG.md', 'changes.md', 'changelog.md'); |
|
| 290 | + $files = scandir($directory); |
|
| 291 | + $foundNames = array_intersect($possibleNames, $files); |
|
| 292 | + |
|
| 293 | + if ( !empty($foundNames) ) { |
|
| 294 | + return reset($foundNames); |
|
| 295 | + } |
|
| 296 | + return null; |
|
| 297 | + } |
|
| 298 | + |
|
| 299 | + /** |
|
| 300 | + * Set authentication credentials. |
|
| 301 | + * |
|
| 302 | + * @param $credentials |
|
| 303 | + */ |
|
| 304 | + public function setAuthentication($credentials) { |
|
| 305 | + $this->credentials = $credentials; |
|
| 306 | + } |
|
| 307 | + |
|
| 308 | + public function isAuthenticationEnabled() { |
|
| 309 | + return !empty($this->credentials); |
|
| 310 | + } |
|
| 311 | + |
|
| 312 | + /** |
|
| 313 | + * @param string $url |
|
| 314 | + * @return string |
|
| 315 | + */ |
|
| 316 | + public function signDownloadUrl($url) { |
|
| 317 | + return $url; |
|
| 318 | + } |
|
| 319 | + |
|
| 320 | + /** |
|
| 321 | + * @param string $filterName |
|
| 322 | + */ |
|
| 323 | + public function setHttpFilterName($filterName) { |
|
| 324 | + $this->httpFilterName = $filterName; |
|
| 325 | + } |
|
| 326 | + |
|
| 327 | + /** |
|
| 328 | + * @param string $filterName |
|
| 329 | + */ |
|
| 330 | + public function setStrategyFilterName($filterName) { |
|
| 331 | + $this->strategyFilterName = $filterName; |
|
| 332 | + } |
|
| 333 | + |
|
| 334 | + /** |
|
| 335 | + * @param string $directory |
|
| 336 | + */ |
|
| 337 | + public function setLocalDirectory($directory) { |
|
| 338 | + if ( empty($directory) || !is_dir($directory) || ($directory === '.') ) { |
|
| 339 | + $this->localDirectory = null; |
|
| 340 | + } else { |
|
| 341 | + $this->localDirectory = $directory; |
|
| 342 | + } |
|
| 343 | + } |
|
| 344 | + |
|
| 345 | + /** |
|
| 346 | + * @param string $slug |
|
| 347 | + */ |
|
| 348 | + public function setSlug($slug) { |
|
| 349 | + $this->slug = $slug; |
|
| 350 | + } |
|
| 351 | + } |
|
| 352 | 352 | |
| 353 | 353 | endif; |
@@ -4,7 +4,7 @@ discard block |
||
| 4 | 4 | use Parsedown; |
| 5 | 5 | use PucReadmeParser; |
| 6 | 6 | |
| 7 | -if ( !class_exists(Api::class, false) ): |
|
| 7 | +if (!class_exists(Api::class, false)): |
|
| 8 | 8 | |
| 9 | 9 | abstract class Api { |
| 10 | 10 | const STRATEGY_LATEST_RELEASE = 'latest_release'; |
@@ -69,7 +69,7 @@ discard block |
||
| 69 | 69 | public function chooseReference($configBranch) { |
| 70 | 70 | $strategies = $this->getUpdateDetectionStrategies($configBranch); |
| 71 | 71 | |
| 72 | - if ( !empty($this->strategyFilterName) ) { |
|
| 72 | + if (!empty($this->strategyFilterName)) { |
|
| 73 | 73 | $strategies = apply_filters( |
| 74 | 74 | $this->strategyFilterName, |
| 75 | 75 | $strategies, |
@@ -79,7 +79,7 @@ discard block |
||
| 79 | 79 | |
| 80 | 80 | foreach ($strategies as $strategy) { |
| 81 | 81 | $reference = call_user_func($strategy); |
| 82 | - if ( !empty($reference) ) { |
|
| 82 | + if (!empty($reference)) { |
|
| 83 | 83 | return $reference; |
| 84 | 84 | } |
| 85 | 85 | } |
@@ -106,7 +106,7 @@ discard block |
||
| 106 | 106 | */ |
| 107 | 107 | public function getRemoteReadme($ref = 'master') { |
| 108 | 108 | $fileContents = $this->getRemoteFile($this->getLocalReadmeName(), $ref); |
| 109 | - if ( empty($fileContents) ) { |
|
| 109 | + if (empty($fileContents)) { |
|
| 110 | 110 | return array(); |
| 111 | 111 | } |
| 112 | 112 | |
@@ -127,16 +127,16 @@ discard block |
||
| 127 | 127 | */ |
| 128 | 128 | public function getLocalReadmeName() { |
| 129 | 129 | static $fileName = null; |
| 130 | - if ( $fileName !== null ) { |
|
| 130 | + if ($fileName !== null) { |
|
| 131 | 131 | return $fileName; |
| 132 | 132 | } |
| 133 | 133 | |
| 134 | 134 | $fileName = 'readme.txt'; |
| 135 | - if ( isset($this->localDirectory) ) { |
|
| 135 | + if (isset($this->localDirectory)) { |
|
| 136 | 136 | $files = scandir($this->localDirectory); |
| 137 | - if ( !empty($files) ) { |
|
| 137 | + if (!empty($files)) { |
|
| 138 | 138 | foreach ($files as $possibleFileName) { |
| 139 | - if ( strcasecmp($possibleFileName, 'readme.txt') === 0 ) { |
|
| 139 | + if (strcasecmp($possibleFileName, 'readme.txt') === 0) { |
|
| 140 | 140 | $fileName = $possibleFileName; |
| 141 | 141 | break; |
| 142 | 142 | } |
@@ -181,7 +181,7 @@ discard block |
||
| 181 | 181 | $name = ltrim($name, 'v'); |
| 182 | 182 | |
| 183 | 183 | //The version string must start with a number. |
| 184 | - if ( !is_numeric(substr($name, 0, 1)) ) { |
|
| 184 | + if (!is_numeric(substr($name, 0, 1))) { |
|
| 185 | 185 | return false; |
| 186 | 186 | } |
| 187 | 187 | |
@@ -225,10 +225,10 @@ discard block |
||
| 225 | 225 | */ |
| 226 | 226 | protected function compareTagNames($tag1, $tag2) { |
| 227 | 227 | $property = $this->tagNameProperty; |
| 228 | - if ( !isset($tag1->$property) ) { |
|
| 228 | + if (!isset($tag1->$property)) { |
|
| 229 | 229 | return 1; |
| 230 | 230 | } |
| 231 | - if ( !isset($tag2->$property) ) { |
|
| 231 | + if (!isset($tag2->$property)) { |
|
| 232 | 232 | return -1; |
| 233 | 233 | } |
| 234 | 234 | return -version_compare(ltrim($tag1->$property, 'v'), ltrim($tag2->$property, 'v')); |
@@ -260,12 +260,12 @@ discard block |
||
| 260 | 260 | */ |
| 261 | 261 | public function getRemoteChangelog($ref, $localDirectory) { |
| 262 | 262 | $filename = $this->findChangelogName($localDirectory); |
| 263 | - if ( empty($filename) ) { |
|
| 263 | + if (empty($filename)) { |
|
| 264 | 264 | return null; |
| 265 | 265 | } |
| 266 | 266 | |
| 267 | 267 | $changelog = $this->getRemoteFile($filename, $ref); |
| 268 | - if ( $changelog === null ) { |
|
| 268 | + if ($changelog === null) { |
|
| 269 | 269 | return null; |
| 270 | 270 | } |
| 271 | 271 | |
@@ -279,10 +279,10 @@ discard block |
||
| 279 | 279 | * @return string|null |
| 280 | 280 | */ |
| 281 | 281 | protected function findChangelogName($directory = null) { |
| 282 | - if ( !isset($directory) ) { |
|
| 282 | + if (!isset($directory)) { |
|
| 283 | 283 | $directory = $this->localDirectory; |
| 284 | 284 | } |
| 285 | - if ( empty($directory) || !is_dir($directory) || ($directory === '.') ) { |
|
| 285 | + if (empty($directory) || !is_dir($directory) || ($directory === '.')) { |
|
| 286 | 286 | return null; |
| 287 | 287 | } |
| 288 | 288 | |
@@ -290,7 +290,7 @@ discard block |
||
| 290 | 290 | $files = scandir($directory); |
| 291 | 291 | $foundNames = array_intersect($possibleNames, $files); |
| 292 | 292 | |
| 293 | - if ( !empty($foundNames) ) { |
|
| 293 | + if (!empty($foundNames)) { |
|
| 294 | 294 | return reset($foundNames); |
| 295 | 295 | } |
| 296 | 296 | return null; |
@@ -335,7 +335,7 @@ discard block |
||
| 335 | 335 | * @param string $directory |
| 336 | 336 | */ |
| 337 | 337 | public function setLocalDirectory($directory) { |
| 338 | - if ( empty($directory) || !is_dir($directory) || ($directory === '.') ) { |
|
| 338 | + if (empty($directory) || !is_dir($directory) || ($directory === '.')) { |
|
| 339 | 339 | $this->localDirectory = null; |
| 340 | 340 | } else { |
| 341 | 341 | $this->localDirectory = $directory; |
@@ -3,27 +3,27 @@ |
||
| 3 | 3 | |
| 4 | 4 | if ( !interface_exists(BaseChecker::class, false) ): |
| 5 | 5 | |
| 6 | - interface BaseChecker { |
|
| 7 | - /** |
|
| 8 | - * Set the repository branch to use for updates. Defaults to 'master'. |
|
| 9 | - * |
|
| 10 | - * @param string $branch |
|
| 11 | - * @return $this |
|
| 12 | - */ |
|
| 13 | - public function setBranch($branch); |
|
| 6 | + interface BaseChecker { |
|
| 7 | + /** |
|
| 8 | + * Set the repository branch to use for updates. Defaults to 'master'. |
|
| 9 | + * |
|
| 10 | + * @param string $branch |
|
| 11 | + * @return $this |
|
| 12 | + */ |
|
| 13 | + public function setBranch($branch); |
|
| 14 | 14 | |
| 15 | - /** |
|
| 16 | - * Set authentication credentials. |
|
| 17 | - * |
|
| 18 | - * @param array|string $credentials |
|
| 19 | - * @return $this |
|
| 20 | - */ |
|
| 21 | - public function setAuthentication($credentials); |
|
| 15 | + /** |
|
| 16 | + * Set authentication credentials. |
|
| 17 | + * |
|
| 18 | + * @param array|string $credentials |
|
| 19 | + * @return $this |
|
| 20 | + */ |
|
| 21 | + public function setAuthentication($credentials); |
|
| 22 | 22 | |
| 23 | - /** |
|
| 24 | - * @return Api |
|
| 25 | - */ |
|
| 26 | - public function getVcsApi(); |
|
| 27 | - } |
|
| 23 | + /** |
|
| 24 | + * @return Api |
|
| 25 | + */ |
|
| 26 | + public function getVcsApi(); |
|
| 27 | + } |
|
| 28 | 28 | |
| 29 | 29 | endif; |
@@ -1,7 +1,7 @@ |
||
| 1 | 1 | <?php |
| 2 | 2 | namespace YahnisElsts\PluginUpdateChecker\v5p0\Vcs; |
| 3 | 3 | |
| 4 | -if ( !interface_exists(BaseChecker::class, false) ): |
|
| 4 | +if (!interface_exists(BaseChecker::class, false)): |
|
| 5 | 5 | |
| 6 | 6 | interface BaseChecker { |
| 7 | 7 | /** |
@@ -4,56 +4,56 @@ |
||
| 4 | 4 | |
| 5 | 5 | if ( !trait_exists(VcsCheckerMethods::class, false) ) : |
| 6 | 6 | |
| 7 | - trait VcsCheckerMethods { |
|
| 8 | - /** |
|
| 9 | - * @var string The branch where to look for updates. Defaults to "master". |
|
| 10 | - */ |
|
| 11 | - protected $branch = 'master'; |
|
| 12 | - |
|
| 13 | - /** |
|
| 14 | - * @var Api Repository API client. |
|
| 15 | - */ |
|
| 16 | - protected $api = null; |
|
| 17 | - |
|
| 18 | - public function setBranch($branch) { |
|
| 19 | - $this->branch = $branch; |
|
| 20 | - return $this; |
|
| 21 | - } |
|
| 22 | - |
|
| 23 | - /** |
|
| 24 | - * Set authentication credentials. |
|
| 25 | - * |
|
| 26 | - * @param array|string $credentials |
|
| 27 | - * @return $this |
|
| 28 | - */ |
|
| 29 | - public function setAuthentication($credentials) { |
|
| 30 | - $this->api->setAuthentication($credentials); |
|
| 31 | - return $this; |
|
| 32 | - } |
|
| 33 | - |
|
| 34 | - /** |
|
| 35 | - * @return Api |
|
| 36 | - */ |
|
| 37 | - public function getVcsApi() { |
|
| 38 | - return $this->api; |
|
| 39 | - } |
|
| 40 | - |
|
| 41 | - public function getUpdate() { |
|
| 42 | - $update = parent::getUpdate(); |
|
| 43 | - |
|
| 44 | - if ( isset($update) && !empty($update->download_url) ) { |
|
| 45 | - $update->download_url = $this->api->signDownloadUrl($update->download_url); |
|
| 46 | - } |
|
| 47 | - |
|
| 48 | - return $update; |
|
| 49 | - } |
|
| 50 | - |
|
| 51 | - public function onDisplayConfiguration($panel) { |
|
| 52 | - parent::onDisplayConfiguration($panel); |
|
| 53 | - $panel->row('Branch', $this->branch); |
|
| 54 | - $panel->row('Authentication enabled', $this->api->isAuthenticationEnabled() ? 'Yes' : 'No'); |
|
| 55 | - $panel->row('API client', get_class($this->api)); |
|
| 56 | - } |
|
| 57 | - } |
|
| 7 | + trait VcsCheckerMethods { |
|
| 8 | + /** |
|
| 9 | + * @var string The branch where to look for updates. Defaults to "master". |
|
| 10 | + */ |
|
| 11 | + protected $branch = 'master'; |
|
| 12 | + |
|
| 13 | + /** |
|
| 14 | + * @var Api Repository API client. |
|
| 15 | + */ |
|
| 16 | + protected $api = null; |
|
| 17 | + |
|
| 18 | + public function setBranch($branch) { |
|
| 19 | + $this->branch = $branch; |
|
| 20 | + return $this; |
|
| 21 | + } |
|
| 22 | + |
|
| 23 | + /** |
|
| 24 | + * Set authentication credentials. |
|
| 25 | + * |
|
| 26 | + * @param array|string $credentials |
|
| 27 | + * @return $this |
|
| 28 | + */ |
|
| 29 | + public function setAuthentication($credentials) { |
|
| 30 | + $this->api->setAuthentication($credentials); |
|
| 31 | + return $this; |
|
| 32 | + } |
|
| 33 | + |
|
| 34 | + /** |
|
| 35 | + * @return Api |
|
| 36 | + */ |
|
| 37 | + public function getVcsApi() { |
|
| 38 | + return $this->api; |
|
| 39 | + } |
|
| 40 | + |
|
| 41 | + public function getUpdate() { |
|
| 42 | + $update = parent::getUpdate(); |
|
| 43 | + |
|
| 44 | + if ( isset($update) && !empty($update->download_url) ) { |
|
| 45 | + $update->download_url = $this->api->signDownloadUrl($update->download_url); |
|
| 46 | + } |
|
| 47 | + |
|
| 48 | + return $update; |
|
| 49 | + } |
|
| 50 | + |
|
| 51 | + public function onDisplayConfiguration($panel) { |
|
| 52 | + parent::onDisplayConfiguration($panel); |
|
| 53 | + $panel->row('Branch', $this->branch); |
|
| 54 | + $panel->row('Authentication enabled', $this->api->isAuthenticationEnabled() ? 'Yes' : 'No'); |
|
| 55 | + $panel->row('API client', get_class($this->api)); |
|
| 56 | + } |
|
| 57 | + } |
|
| 58 | 58 | |
| 59 | 59 | endif; |
| 60 | 60 | \ No newline at end of file |
@@ -2,7 +2,7 @@ discard block |
||
| 2 | 2 | |
| 3 | 3 | namespace YahnisElsts\PluginUpdateChecker\v5p0\Vcs; |
| 4 | 4 | |
| 5 | -if ( !trait_exists(VcsCheckerMethods::class, false) ) : |
|
| 5 | +if (!trait_exists(VcsCheckerMethods::class, false)) : |
|
| 6 | 6 | |
| 7 | 7 | trait VcsCheckerMethods { |
| 8 | 8 | /** |
@@ -41,7 +41,7 @@ discard block |
||
| 41 | 41 | public function getUpdate() { |
| 42 | 42 | $update = parent::getUpdate(); |
| 43 | 43 | |
| 44 | - if ( isset($update) && !empty($update->download_url) ) { |
|
| 44 | + if (isset($update) && !empty($update->download_url)) { |
|
| 45 | 45 | $update->download_url = $this->api->signDownloadUrl($update->download_url); |
| 46 | 46 | } |
| 47 | 47 | |