Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
95.93% |
118 / 123 |
|
50.00% |
4 / 8 |
CRAP | |
0.00% |
0 / 1 |
| GitHub | |
95.93% |
118 / 123 |
|
50.00% |
4 / 8 |
16 | |
0.00% |
0 / 1 |
| __construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
| update | |
96.15% |
50 / 52 |
|
0.00% |
0 / 1 |
8 | |||
| get_release | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| activate_licence | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| deactivate_licence | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| refresh_licence_details | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| get_remote_check_update | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
1 | |||
| get_remote_product_information | |
100.00% |
48 / 48 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | /** |
| 3 | * GitHub integration. |
| 4 | * |
| 5 | * If a plugin's header contains a GitHub URI, this class will be used to fetch the latest release from GitHub. |
| 6 | * |
| 7 | * @package brianhenryie/bh-wp-plugin-updater |
| 8 | */ |
| 9 | |
| 10 | namespace BrianHenryIE\WP_Plugin_Updater\Integrations\GitHub; |
| 11 | |
| 12 | use BrianHenryIE\WP_Plugin_Updater\Integrations\GitHub\Model\Release; |
| 13 | use BrianHenryIE\WP_Plugin_Updater\Integrations\Integration_Interface; |
| 14 | use BrianHenryIE\WP_Plugin_Updater\Licence; |
| 15 | use BrianHenryIE\WP_Plugin_Updater\Model\Plugin_Headers; |
| 16 | use BrianHenryIE\WP_Plugin_Updater\Model\Plugin_Info; |
| 17 | use BrianHenryIE\WP_Plugin_Updater\Model\Plugin_Info_Interface; |
| 18 | use BrianHenryIE\WP_Plugin_Updater\Model\Plugin_Update_Interface; |
| 19 | use BrianHenryIE\WP_Plugin_Updater\Model\Plugin_Update; |
| 20 | use BrianHenryIE\WP_Plugin_Updater\Settings_Interface; |
| 21 | use Github\Client as GitHub_Client; |
| 22 | use JsonMapper\Handler\FactoryRegistry; |
| 23 | use JsonMapper\Handler\PropertyMapper; |
| 24 | use JsonMapper\JsonMapperBuilder; |
| 25 | use Psr\Http\Client\ClientInterface; |
| 26 | use Psr\Log\LoggerAwareTrait; |
| 27 | use Psr\Log\LoggerInterface; |
| 28 | use Syntatis\WPPluginReadMeParser\Parser as Readme_Parser; |
| 29 | |
| 30 | class GitHub implements Integration_Interface { |
| 31 | use LoggerAwareTrait; |
| 32 | |
| 33 | protected GitHub_Client $client; |
| 34 | |
| 35 | protected Release $release; |
| 36 | protected ?string $changelog_text = null; |
| 37 | |
| 38 | protected ?Readme_Parser $readme = null; |
| 39 | protected ?Plugin_Headers $plugin_headers = null; |
| 40 | |
| 41 | |
| 42 | public function __construct( |
| 43 | ClientInterface $http_client, |
| 44 | protected Settings_Interface $settings, |
| 45 | LoggerInterface $logger, |
| 46 | ) { |
| 47 | $this->client = GitHub_Client::createWithHttpClient( $http_client ); |
| 48 | $this->setLogger( $logger ); |
| 49 | } |
| 50 | |
| 51 | protected function update(): void { |
| 52 | |
| 53 | if ( 1 !== preg_match( '/github.com\/(?<user>.*?)\/(?<repo>[^\/]*)/', $this->settings->get_licence_server_host(), $output_array ) ) { |
| 54 | throw new \Exception( 'Failed to parse GitHub URI user and repo from ' . $this->settings->get_licence_server_host() ); |
| 55 | } |
| 56 | |
| 57 | $user = $output_array['user']; |
| 58 | $repo = $output_array['repo']; |
| 59 | |
| 60 | // TODO: catch not-found exception, no connection etc. |
| 61 | /** |
| 62 | * @see Client::api() |
| 63 | * @see \Github\Api\Repo::releases() |
| 64 | * @see \Github\Api\Repository\Releases::all() |
| 65 | */ |
| 66 | $response = $this->client->api( 'repo' )->releases()->all( $user, $repo ); |
| 67 | |
| 68 | // TODO: remove '->with' that are not needed. |
| 69 | $factory_registry = new FactoryRegistry(); |
| 70 | $mapper = JsonMapperBuilder::new() |
| 71 | ->withDocBlockAnnotationsMiddleware() |
| 72 | ->withObjectConstructorMiddleware( $factory_registry ) |
| 73 | ->withPropertyMapper( new PropertyMapper( $factory_registry ) ) |
| 74 | ->withTypedPropertiesMiddleware() |
| 75 | ->withNamespaceResolverMiddleware() |
| 76 | ->build(); |
| 77 | |
| 78 | /** @var Release[] $release_object */ |
| 79 | $releases = $mapper->mapToClassArrayFromString( json_encode( $response ), Release::class ); |
| 80 | |
| 81 | $allow_beta = false; |
| 82 | |
| 83 | if ( $allow_beta ) { |
| 84 | $this->release = $releases[0]; |
| 85 | } else { |
| 86 | foreach ( $releases as $release ) { |
| 87 | if ( ! $release->is_prerelease() ) { |
| 88 | $this->release = $release; |
| 89 | break; |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | // get changelog.md from GitHub at tag commit |
| 95 | $changelog_url = "https://raw.githubusercontent.com/{$user}/{$repo}/{$this->release->get_tag_name()}/CHANGELOG.md"; |
| 96 | $changelog_request_response = wp_remote_get( $changelog_url ); |
| 97 | if ( 200 === wp_remote_retrieve_response_code( $changelog_request_response ) ) { |
| 98 | $this->changelog_text = $changelog_request_response['body']; |
| 99 | } |
| 100 | |
| 101 | // TODO: this is case sensitive. |
| 102 | // get readme.txt from GitHub at tag commit |
| 103 | $readme_url = "https://raw.githubusercontent.com/{$user}/{$repo}/{$this->release->get_tag_name()}/README.txt"; |
| 104 | $readme_request_response = wp_remote_get( $readme_url ); |
| 105 | if ( 200 === wp_remote_retrieve_response_code( $readme_request_response ) ) { |
| 106 | $this->readme = new Readme_Parser( $readme_request_response['body'] ); |
| 107 | } |
| 108 | |
| 109 | $plugin_file_name = explode( '/', $this->settings->get_plugin_basename() )[1]; |
| 110 | $plugin_file_url = "https://raw.githubusercontent.com/{$user}/{$repo}/{$this->release->get_tag_name()}/{$plugin_file_name}"; |
| 111 | $plugin_file_request_response = wp_remote_get( $plugin_file_url ); |
| 112 | if ( 200 === wp_remote_retrieve_response_code( $plugin_file_request_response ) ) { |
| 113 | |
| 114 | /** |
| 115 | * TODO: remove unused. |
| 116 | * |
| 117 | * @see get_plugin_data() |
| 118 | */ |
| 119 | $default_headers = array( |
| 120 | 'Name' => 'Plugin Name', |
| 121 | 'PluginURI' => 'Plugin URI', |
| 122 | 'Version' => 'Version', |
| 123 | 'Description' => 'Description', |
| 124 | 'Author' => 'Author', |
| 125 | 'AuthorURI' => 'Author URI', |
| 126 | 'TextDomain' => 'Text Domain', |
| 127 | 'DomainPath' => 'Domain Path', |
| 128 | 'Network' => 'Network', |
| 129 | 'RequiresWP' => 'Requires at least', |
| 130 | 'RequiresPHP' => 'Requires PHP', |
| 131 | 'UpdateURI' => 'Update URI', |
| 132 | 'RequiresPlugins' => 'Requires Plugins', |
| 133 | ); |
| 134 | |
| 135 | // write to tmp dir |
| 136 | $tmp_plugin_file_path = get_temp_dir() . $plugin_file_name; |
| 137 | file_put_contents( $tmp_plugin_file_path, $plugin_file_request_response['body'] ); |
| 138 | $this->plugin_headers = new Plugin_Headers( get_file_data( $tmp_plugin_file_path, $default_headers ) ); |
| 139 | unlink( $tmp_plugin_file_path ); |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | protected function get_release(): Release { |
| 144 | if ( ! isset( $this->release ) ) { |
| 145 | $this->update(); |
| 146 | } |
| 147 | return $this->release; |
| 148 | } |
| 149 | |
| 150 | public function activate_licence( Licence $licence ) { |
| 151 | // TODO: Implement activate_licence() method. |
| 152 | return new Licence(); |
| 153 | } |
| 154 | |
| 155 | public function deactivate_licence( Licence $licence ) { |
| 156 | // TODO: Implement deactivate_licence() method. |
| 157 | return new Licence(); |
| 158 | } |
| 159 | |
| 160 | public function refresh_licence_details( Licence $licence ): Licence { |
| 161 | // TODO: Implement refresh_licence_details() method. |
| 162 | return new Licence(); |
| 163 | } |
| 164 | |
| 165 | public function get_remote_check_update( Licence $licence ): ?Plugin_Update_Interface { |
| 166 | |
| 167 | $release = $this->get_release(); |
| 168 | |
| 169 | return new Plugin_Update( |
| 170 | id: null, |
| 171 | slug: $this->settings->get_plugin_slug(), |
| 172 | version: ltrim( $release->get_tag_name(), 'v' ), |
| 173 | url: $this->plugin_headers->get_plugin_uri(), |
| 174 | package: $release->get_assets()[0]->get_browser_download_url(), |
| 175 | tested: $this->readme->tested, |
| 176 | requires_php: $this->readme->requires_php ?? $this->plugin_headers->get_requires_php() ?? null, |
| 177 | autoupdate: null, |
| 178 | icons: null, |
| 179 | banners: null, |
| 180 | banners_rtl: null, |
| 181 | translations: null, |
| 182 | ); |
| 183 | } |
| 184 | |
| 185 | public function get_remote_product_information( Licence $licence ): ?Plugin_Info_Interface { |
| 186 | |
| 187 | $release = $this->get_release(); |
| 188 | |
| 189 | return new Plugin_Info( |
| 190 | sections: array(), |
| 191 | name: $this->plugin_headers->get_name(), |
| 192 | slug: $this->settings->get_plugin_slug(), |
| 193 | version: ltrim( $release->get_tag_name(), 'v' ), |
| 194 | author: $this->plugin_headers->get_author(), |
| 195 | author_profile: $this->plugin_headers->get_author_uri(), |
| 196 | contributors: array(), |
| 197 | requires: $this->plugin_headers->get_requires_wp(), |
| 198 | tested: null, |
| 199 | requires_php: $this->plugin_headers->get_requires_php(), |
| 200 | requires_plugins: $this->plugin_headers->get_requires_plugins(), |
| 201 | compatibility: array(), |
| 202 | rating: 0, |
| 203 | ratings: array(), |
| 204 | num_ratings: 0, |
| 205 | support_url: 'support_url string', |
| 206 | support_threads: 0, |
| 207 | support_threads_resolved: 0, |
| 208 | active_installs: 0, |
| 209 | downloaded: 0, |
| 210 | last_updated: $this->release->get_created_at(), |
| 211 | added: 'added string', |
| 212 | homepage: 'homepage string', |
| 213 | short_description: 'short_description string', |
| 214 | description: 'description string', |
| 215 | download_link: $release->get_assets()[0]->get_browser_download_url(), |
| 216 | upgrade_notice: 'upgrade_notice string', |
| 217 | screenshots: array(), |
| 218 | tags: array(), |
| 219 | stable_tag: $this->release->get_tag_name(), |
| 220 | versions: array(), |
| 221 | business_model: null, |
| 222 | repository_url: $this->plugin_headers->get_plugin_uri(), |
| 223 | commercial_support_url: 'commercial_support_url string', |
| 224 | donate_link: 'donate_link string', |
| 225 | banners: array(), |
| 226 | icons: array(), |
| 227 | blocks: array(), |
| 228 | block_assets: array(), |
| 229 | author_block_count: 0, |
| 230 | author_block_rating: 0, |
| 231 | blueprints: array(), |
| 232 | preview_link: array(), |
| 233 | language_packs: array(), |
| 234 | block_translations: array(), |
| 235 | ); |
| 236 | } |
| 237 | } |