├── .gitignore ├── changelog.md ├── readme.md ├── src ├── plugin-updater.php ├── settings.php ├── theme-updater.php └── updater-base.php ├── uninstall.php └── wp-gitlab-updater.php /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.0.2 - 25.07.2017 4 | 5 | ### Fixed 6 | 7 | * Issue with renaming plugins or themes which should not get updates from GitLab updater. The issue leads in renamed 8 | directories, so WordPress cannot find the plugin/theme after an update and deactivates it. 9 | 10 | ## 2.0.1 - 08.07.2017 11 | 12 | ### Fixed 13 | 14 | * Works now with multisite. 15 | 16 | ## 2.0.0 - 06.07.2017 17 | 18 | **This is a major update. If you used the plugin or lib version 1.0.0, you need to modify your code. See 19 | updated readme for details.** 20 | 21 | ### Added 22 | 23 | * Check to prevent crash with plugin and theme updates with same slug from W.org or other sources. 24 | * GitHub updater support. 25 | * Settings page. 26 | 27 | ### Changed 28 | 29 | * Use `plugins_loaded` hook in readme for calling the plugin. 30 | 31 | ## 1.0.0 - 30.06.2017 32 | * Initial release 33 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # WordPress plugin and theme updates from private GitLab repos 2 | 3 | You can use this as a WordPress plugin (for example, if you have 4 | multiple themes and/or plugins in one WP installation, which 5 | should be updated with the script), or include it directly in a theme or plugin. 6 | The script uses the GitLab tags to check for a new version. 7 | 8 | ## Usage as a WordPress plugin 9 | 10 | Just download the repo and upload the ZIP as a new plugin to 11 | your WordPress install or use the [GitHub updater](https://github.com/afragen/github-updater) plugin. 12 | 13 | After that, you will find a new options page under *Settings* › *GitLab Updater*. There 14 | you can find all installed themes and plugins, and fields to insert the needed data 15 | to make one or more of them use your GitLab repo as update source. 16 | 17 | Search the theme or plugin in the list and insert the following data: 18 | 19 | * **Access token** is the GitLab API access token 20 | (needs »api« and »read_registry« scope. If you use the gitlab.com version, you can create the token here: [gitlab.com/profile/personal_access_tokens](https://gitlab.com/profile/personal_access_tokens)). The safest way 21 | might be to create the access token for an external user with 22 | the role »reporter«, who has only access to the theme repo. 23 | Project features like wiki and issues can be hidden from external users. 24 | * **GitLab URL** needs to be the URL of your GitLab install. For example `https://gitlab.com` 25 | * **Repo** needs to be the identifier of the repo in the format `username/repo` or `group/repo` 26 | 27 | ## Bundled inside a plugin or theme 28 | 29 | ### Inside a theme 30 | 31 | To bundle it into a theme, you can just grab the `src/theme-updater.php` 32 | and `src/updater-base.php` and put it into your theme, for example, 33 | into a `wp-gitlab-updater` folder. After that, you can call it like that: 34 | 35 | ```php 36 | /** 37 | * Include the file with the ThemeUpdater class. 38 | */ 39 | require_once 'wp-gitlab-updater/theme-updater.php'; 40 | 41 | /** 42 | * Init the theme updater. 43 | */ 44 | new Moenus\GitLabUpdater\ThemeUpdater( [ 45 | 'slug' => 'SlugOfTheTheme', 46 | 'access_token' => 'YourGitLabAccessToken', 47 | 'gitlab_url' => 'URLtoGitLabInstall', 48 | 'repo' => 'RepoIdentifier', 49 | ] ); 50 | ``` 51 | 52 | The params are the same as explained in the _Usage as a WordPress plugin_ part — `slug` must be the 53 | directory of the theme. 54 | 55 | ### Inside a plugin 56 | 57 | For that, take the `src/plugin-updater.php` and `src/updater-base.php`, 58 | put it into your plugin and call it: 59 | 60 | ```php 61 | /** 62 | * Include the file with the PluginUpdater class. 63 | */ 64 | require_once 'wp-gitlab-updater/plugin-updater.php'; 65 | 66 | /** 67 | * Init the plugin updater with the plugin base name. 68 | */ 69 | new Moenus\GitLabUpdater\PluginUpdater( [ 70 | 'slug' => 'SlugOfPlugin', 71 | 'plugin_base_name' => 'BaseNameOfThePlugin', 72 | 'access_token' => 'YourGitLabAccessToken', 73 | 'gitlab_url' => 'URLtoGitLabInstall', 74 | 'repo' => 'RepoIdentifier', 75 | ] ); 76 | ``` 77 | 78 | Same params as explained in _Usage as a WordPress plugin_ — `slug` is plugin directory 79 | and `plugin_base_name` the basename (for example, `svg-social-menu/svg-social-menu.php`). 80 | -------------------------------------------------------------------------------- /src/plugin-updater.php: -------------------------------------------------------------------------------- 1 | plugin_data = $plugin_data; 59 | } 60 | 61 | // Check if we have values. 62 | if ( isset( $args['slug'] ) && isset( $args['plugin_base_name'] ) && isset( $args['access_token'] ) && isset( $args['gitlab_url'] ) && isset( $args['repo'] ) ) { 63 | // Create array to insert them into plugin_data. 64 | $tmp_array = [ 65 | 'settings-array-key' => $args['plugin_base_name'], 66 | 'slug' => $args['slug'], 67 | 'access-token' => $args['access_token'], 68 | 'gitlab-url' => untrailingslashit( $args['gitlab_url'] ), 69 | 'repo' => str_replace( '/', '%2F', $args['repo'] ), 70 | ]; 71 | 72 | // Insert it. 73 | $this->plugin_data[ $args['slug'] ] = $tmp_array; 74 | } 75 | 76 | /** 77 | * Hook into pre_set_site_transient_update_plugins to modify the update_plugins 78 | * transient if a new plugin version is available. 79 | */ 80 | add_filter( 'pre_set_site_transient_update_plugins', function ( $transient ) { 81 | $transient = $this->plugin_update( $transient ); 82 | 83 | return $transient; 84 | } ); 85 | 86 | /** 87 | * Check for plugins with the same slug (for example from W.org) and remove 88 | * update notifications for them. 89 | * 90 | * For whatever reason, it seems to be working better here in an anonymous function 91 | * rather than in a method… 92 | */ 93 | add_filter( 'site_transient_update_plugins', function ( $transient ) { 94 | if ( empty ( $transient->response ) ) { 95 | return $transient; 96 | } 97 | 98 | // Check if we have an update for a plugin with the same slug from W.org 99 | // and remove it. 100 | // 101 | // At first, we loop the GitLab updater plugins. 102 | foreach ( $this->plugin_data as $plugin ) { 103 | $plugin_basename = $plugin['settings-array-key']; 104 | // Check if we have a plugin with the same slug and another package URL 105 | // than our GitLab URL. 106 | if ( array_key_exists( $plugin_basename, $transient->response ) && false === strpos( $transient->response[ $plugin_basename ]->package, $plugin['gitlab-url'] ) ) { 107 | // Unset the response key for that plugin. 108 | unset( $transient->response[ $plugin_basename ] ); 109 | } 110 | } 111 | 112 | return $transient; 113 | } ); 114 | 115 | /** 116 | * Before the files are copied to wp-content/plugins, we need to rename the 117 | * folder of the plugin, so it matches the slug. That is because WordPress 118 | * uses the folder name for the destination inside wp-content/plugins, not 119 | * the plugin slug. And the name of the ZIP we get from the GitLab API call 120 | * is something with the project name, the tag number and the commit SHA 121 | * (so everything but matching the plugin slug). 122 | */ 123 | add_filter( 'upgrader_source_selection', function ( $source, $remote_source, $wp_upgrader, $args ) { 124 | foreach ( $this->plugin_data as $plugin ) { 125 | // Check if the currently updated plugin matches our plugin base name. 126 | if ( $args['plugin'] === $plugin['settings-array-key'] && false !== $plugin ) { 127 | $source = $this->filter_source_name( $source, $remote_source, $plugin['slug'] ); 128 | } 129 | } 130 | 131 | return $source; 132 | }, 10, 4 ); 133 | } 134 | 135 | /** 136 | * Checking for updates and updating the transient for plugin updates. 137 | * 138 | * @param object $transient Transient object for plugin updates. 139 | * 140 | * @return object plugin update transient. 141 | */ 142 | private function plugin_update( $transient ) { 143 | if ( empty( $transient->checked ) ) { 144 | return $transient; 145 | } 146 | 147 | foreach ( $this->plugin_data as $plugin ) { 148 | // Get data from array which we need to build package URL. 149 | $gitlab_url = $plugin['gitlab-url']; 150 | $repo = $plugin['repo']; 151 | $access_token = $plugin['access-token']; 152 | 153 | // Get tag list from GitLab repo. 154 | $request = $this->fetch_tags_from_repo( $gitlab_url, $repo, $access_token ); 155 | 156 | // Get response code of the request. 157 | $response_code = wp_remote_retrieve_response_code( $request ); 158 | 159 | // Check if request is not valid and return the $transient. 160 | // Otherwise get the data body. 161 | if ( is_wp_error( $request ) || 200 !== $response_code ) { 162 | continue; 163 | } else { 164 | $response = wp_remote_retrieve_body( $request ); 165 | } 166 | 167 | // Decode json. 168 | $data = json_decode( $response ); 169 | 170 | // Check if we have no tags and return the transient. 171 | if ( empty( $data ) ) { 172 | continue; 173 | } 174 | 175 | // Get the latest tag. 176 | $latest_version = $data[0]->name; 177 | 178 | // Check if new version is available. 179 | if ( ! isset( $transient->checked[ $plugin['settings-array-key'] ] ) ) { 180 | continue; 181 | } 182 | 183 | if ( version_compare( $transient->checked[ $plugin['settings-array-key'] ], $latest_version, '<' ) ) { 184 | // Get the package URL. 185 | $plugin_package = "$gitlab_url/api/v4/projects/$repo/repository/archive.zip?sha=$latest_version&private_token=$access_token"; 186 | 187 | // Check the response. 188 | $response = wp_safe_remote_get( $plugin_package ); 189 | $response_code = wp_remote_retrieve_response_code( $response ); 190 | if ( is_wp_error( $response ) || 200 !== $response_code ) { 191 | continue; 192 | } else { 193 | // Build stdClass 194 | $info = new \stdClass(); 195 | $info->slug = $plugin['slug']; 196 | $info->plugin = $plugin['settings-array-key']; 197 | $info->package = $plugin_package; 198 | $info->new_version = $latest_version; 199 | 200 | // Add data to transient. 201 | $transient->response[ $plugin['settings-array-key'] ] = $info; 202 | } 203 | } 204 | } 205 | 206 | return $transient; 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/settings.php: -------------------------------------------------------------------------------- 1 | 82 |
83 |

84 | 90 |
91 |

92 | 93 | 104 | 140 |
141 | 155 |
156 |
157 | 'wp-gitlab-updater', 197 | 'tab' => $tab, 198 | 'updated' => $update, 199 | ], 200 | network_admin_url( 'settings.php' ) 201 | ); 202 | wp_safe_redirect( $location ); 203 | exit; 204 | } 205 | 206 | /** 207 | * Displays section instructions. 208 | * 209 | * @param string $tab The currently displayed tab. 210 | */ 211 | private function section_instructions( $tab ) { 212 | if ( 'themes' === $tab ) { ?> 213 |

214 | 215 |

216 | installed_themes = wp_get_themes(); 225 | $this->installed_plugins = get_plugins(); 226 | 227 | // Register settings. 228 | register_setting( 'wp-gitlab-updater-themes', 'wp-gitlab-updater-themes', [ 229 | 'sanitize_callback' => [ $this, 'sanitize_theme_settings' ], 230 | ] ); 231 | 232 | register_setting( 'wp-gitlab-updater-plugins', 'wp-gitlab-updater-plugins', [ 233 | 'sanitize_callback' => [ $this, 'sanitize_plugin_settings' ], 234 | ] ); 235 | 236 | // Check if we need the plugin settings. 237 | if ( isset( $_GET['tab'] ) && 'plugins' === $_GET['tab'] ) { 238 | // Loop them. 239 | foreach ( $this->installed_plugins as $plugin_basename => $plugin_data ) { 240 | // Get slug. 241 | $arr = explode( "/", $plugin_basename, 2 ); 242 | $slug = $arr[0]; 243 | 244 | // Get name. 245 | $name = $plugin_data['Name']; 246 | 247 | // Section for plugins. 248 | add_settings_section( 249 | "wp-gitlab-updater-plugins-$slug-section", 250 | sprintf( 251 | '%s (%s)', 252 | $slug, 253 | $name, 254 | $plugin_basename 255 | ), 256 | [ $this, 'section_cb' ], 257 | 'wp-gitlab-updater' 258 | ); 259 | 260 | // Create slug field. 261 | add_settings_field( 262 | "wp-gitlab-updater-plugin-$slug-slug-field", 263 | __( 'Plugin slug', 'wp-gitlab-updater' ), 264 | [ $this, 'field_cb' ], 265 | 'wp-gitlab-updater', 266 | "wp-gitlab-updater-plugins-$slug-section", 267 | [ 268 | 'label_for' => "wp-gitlab-updater-plugin-$slug-slug-field", 269 | 'class' => 'hidden', 270 | 'settings-array-key' => $plugin_basename, 271 | 'value' => $slug, 272 | 'type' => 'plugins', 273 | 'readonly' => true, 274 | 'value_array_key' => 'slug', 275 | ] 276 | ); 277 | 278 | // Create basename field. 279 | add_settings_field( 280 | "wp-gitlab-updater-plugin-$slug-basename-field", 281 | __( 'Plugin basename', 'wp-gitlab-updater' ), 282 | [ $this, 'field_cb' ], 283 | 'wp-gitlab-updater', 284 | "wp-gitlab-updater-plugins-$slug-section", 285 | [ 286 | 'label_for' => "wp-gitlab-updater-plugin-$slug-basename-field", 287 | 'class' => 'hidden', 288 | 'settings-array-key' => $plugin_basename, 289 | 'value' => $plugin_basename, 290 | 'type' => 'plugins', 291 | 'readonly' => true, 292 | 'value_array_key' => 'settings-array-key', 293 | ] 294 | ); 295 | 296 | // Create Access token field. 297 | add_settings_field( 298 | "wp-gitlab-updater-plugin-$slug-access-token-field", 299 | __( 'Access token', 'wp-gitlab-updater' ), 300 | [ $this, 'field_cb' ], 301 | 'wp-gitlab-updater', 302 | "wp-gitlab-updater-plugins-$slug-section", 303 | [ 304 | 'label_for' => "wp-gitlab-updater-plugin-$slug-access-token-field", 305 | 'settings-array-key' => $plugin_basename, 306 | 'type' => 'plugins', 307 | 'value_array_key' => 'access-token', 308 | 'description' => __( 'GitLab Access token. Needs »api« and »read_registry« scope.', 'wp-gitlab-updater' ), 309 | ] 310 | ); 311 | 312 | // GitLab URL. 313 | add_settings_field( 314 | "wp-gitlab-updater-plugin-$slug-gitlab-url", 315 | __( 'GitLab URL', 'wp-gitlab-updater' ), 316 | [ $this, 'field_cb' ], 317 | 'wp-gitlab-updater', 318 | "wp-gitlab-updater-plugins-$slug-section", 319 | [ 320 | 'label_for' => "wp-gitlab-updater-plugin-$slug-gitlab-url", 321 | 'settings-array-key' => $plugin_basename, 322 | 'type' => 'plugins', 323 | 'value_array_key' => 'gitlab-url', 324 | 'description' => __( 'URL of the GitLab instance (for example, https://gitlab.com).', 'wp-gitlab-updater' ), 325 | ] 326 | ); 327 | 328 | // Repo information. 329 | add_settings_field( 330 | "wp-gitlab-updater-plugin-$slug-repo-information", 331 | __( 'Repo', 'wp-gitlab-updater' ), 332 | [ $this, 'field_cb' ], 333 | 'wp-gitlab-updater', 334 | "wp-gitlab-updater-plugins-$slug-section", 335 | [ 336 | 'label_for' => "wp-gitlab-updater-plugin-$slug-repo-information", 337 | 'settings-array-key' => $plugin_basename, 338 | 'type' => 'plugins', 339 | 'value_array_key' => 'repo', 340 | 'description' => __( 'Username or group and name of repository. For example: username/repo-name or group/repo-name.', 'wp-gitlab-updater' ), 341 | ] 342 | ); 343 | } 344 | } else { 345 | // Loop through the themes and create a option for each of it. 346 | foreach ( $this->installed_themes as $installed_theme ) { 347 | $slug = $installed_theme->stylesheet; 348 | $name = $installed_theme->get( 'Name' ); 349 | 350 | // Register new sections in »wp-gitlab-updater« page. 351 | add_settings_section( 352 | "wp-gitlab-updater-themes-$slug-section", 353 | sprintf( 354 | '%2$s (%1$s)', 355 | $slug, 356 | $name 357 | ), 358 | [ $this, 'section_cb' ], 359 | 'wp-gitlab-updater' 360 | ); 361 | 362 | // Create slug field. 363 | add_settings_field( 364 | "wp-gitlab-updater-theme-$slug", 365 | __( 'Theme slug', 'wp-gitlab-updater' ), 366 | [ $this, 'field_cb' ], 367 | 'wp-gitlab-updater', 368 | "wp-gitlab-updater-themes-$slug-section", 369 | [ 370 | 'label_for' => "theme-slug-field-$slug", 371 | 'class' => 'hidden', 372 | 'settings-array-key' => $slug, 373 | 'value' => $slug, 374 | 'type' => 'themes', 375 | 'readonly' => true, 376 | 'value_array_key' => 'settings-array-key', 377 | ] 378 | ); 379 | 380 | // Create Access token field. 381 | add_settings_field( 382 | "wp-gitlab-updater-theme-$slug-access-token-field", 383 | __( 'Access token', 'wp-gitlab-updater' ), 384 | [ $this, 'field_cb' ], 385 | 'wp-gitlab-updater', 386 | "wp-gitlab-updater-themes-$slug-section", 387 | [ 388 | 'label_for' => "wp-gitlab-updater-theme-$slug-access-token-field", 389 | 'settings-array-key' => $slug, 390 | 'type' => 'themes', 391 | 'value_array_key' => 'access-token', 392 | 'description' => __( 'GitLab Access token. Needs »api« and »read_registry« scope.', 'wp-gitlab-updater' ), 393 | ] 394 | ); 395 | 396 | // GitLab URL. 397 | add_settings_field( 398 | "wp-gitlab-updater-theme-$slug-gitlab-url", 399 | __( 'GitLab URL', 'wp-gitlab-updater' ), 400 | [ $this, 'field_cb' ], 401 | 'wp-gitlab-updater', 402 | "wp-gitlab-updater-themes-$slug-section", 403 | [ 404 | 'label_for' => "wp-gitlab-updater-theme-$slug-gitlab-url", 405 | 'settings-array-key' => $slug, 406 | 'type' => 'themes', 407 | 'value_array_key' => 'gitlab-url', 408 | 'description' => __( 'URL of the GitLab instance (for example, https://gitlab.com).', 'wp-gitlab-updater' ), 409 | ] 410 | ); 411 | 412 | // Repo information. 413 | add_settings_field( 414 | "wp-gitlab-updater-theme-$slug-repo-information", 415 | __( 'Repo', 'wp-gitlab-updater' ), 416 | [ $this, 'field_cb' ], 417 | 'wp-gitlab-updater', 418 | "wp-gitlab-updater-themes-$slug-section", 419 | [ 420 | 'label_for' => "wp-gitlab-updater-theme-$slug-repo-information", 421 | 'settings-array-key' => $slug, 422 | 'type' => 'themes', 423 | 'value_array_key' => 'repo', 424 | 'description' => __( 'Username or group and name of repository. For example: username/repo-name or group/repo-name.', 'wp-gitlab-updater' ), 425 | ] 426 | ); 427 | } 428 | } 429 | } 430 | 431 | /** 432 | * Sanitize theme settings. 433 | * 434 | * @param $input 435 | * 436 | * @return mixed 437 | */ 438 | public function sanitize_theme_settings( $input ) { 439 | // Loop through the themes. 440 | // $key is theme slug. 441 | foreach ( $input as $key => $value ) { 442 | // Check if the theme exists. 443 | if ( $this->installed_themes[ $key ]->exists() ) { 444 | // Check if one of the fields is empty. 445 | if ( '' === $value['settings-array-key'] || '' === $value['access-token'] || '' === $value['gitlab-url'] || '' === $value['repo'] ) { 446 | // Unset the array with the theme info. 447 | unset( $input[ $key ] ); 448 | } else { 449 | // We have all information, now we need to 450 | // replace the slash in the repo info with %2F 451 | // for usage in the API URL. 452 | $input[ $key ]['repo'] = str_replace( '/', '%2F', $input[ $key ]['repo'] ); 453 | // And remove a trailing slash from the URL (if set). 454 | $input[ $key ]['gitlab-url'] = untrailingslashit( $input[ $key ]['gitlab-url'] ); 455 | } 456 | } else { 457 | // We do not have that theme installed, so we unset the array. 458 | unset( $input[ $key ] ); 459 | } 460 | } 461 | 462 | return $input; 463 | } 464 | 465 | /** 466 | * Sanitize plugin settings. 467 | * 468 | * @param array $input Array with submitted options. 469 | * 470 | * @return mixed 471 | */ 472 | public function sanitize_plugin_settings( $input ) { 473 | // Loop through the plugins. 474 | // $key is plugin basename. 475 | foreach ( $input as $key => $value ) { 476 | // Check if plugin is installed. 477 | if ( isset( $this->installed_plugins[ $key ] ) ) { 478 | // Check if one of the fields is empty. 479 | if ( '' === $value['slug'] || '' === $value['settings-array-key'] || '' === $value['access-token'] || '' === $value['gitlab-url'] || '' === $value['repo'] ) { 480 | // Unset the array with the theme info. 481 | unset( $input[ $key ] ); 482 | } else { 483 | // We have all information, now we need to 484 | // replace the slash in the repo info with %2F 485 | // for usage in the API URL. 486 | $input[ $key ]['repo'] = str_replace( '/', '%2F', $input[ $key ]['repo'] ); 487 | 488 | // And remove a trailing slash from the URL (if set). 489 | $input[ $key ]['gitlab-url'] = untrailingslashit( $input[ $key ]['gitlab-url'] ); 490 | } 491 | } else { 492 | // Plugins is not installed, so we unser the array key. 493 | unset( $input[ $key ] ); 494 | } 495 | } 496 | 497 | return $input; 498 | } 499 | 500 | /** 501 | * Section callback. 502 | * 503 | * @param array $args 504 | */ 505 | public function section_cb( $args ) { 506 | } 507 | 508 | /** 509 | * Field callback. 510 | * 511 | * @param array $args { 512 | * Argument array. 513 | * 514 | * @type string $type (Required) »themes« or »plugins«. 515 | * @type string $label_for (Required) Value for the for attribute. 516 | * @type string $settings (Required) Theme slug or plugin basename. 517 | * @type string $value_array_key (Required) array key for the value. 518 | * } 519 | */ 520 | public function field_cb( $args ) { 521 | // Get type (plugins or themes). 522 | $type = $args['type']; 523 | 524 | // Get the value of the setting we've registered with register_setting() 525 | // 526 | // @link https://konstantin.blog/2012/the-wordpress-settings-api/ 527 | $options = ( is_multisite() ? (array) get_site_option( "wp-gitlab-updater-$type" ) : (array) get_option( "wp-gitlab-updater-$type" ) ); 528 | 529 | // Get label for. 530 | $label_for = esc_attr( $args['label_for'] ); 531 | 532 | // Get settings array key. 533 | $settings_array_key = $args['settings-array-key']; 534 | 535 | // Get array key for value. 536 | $value_array_key = $args['value_array_key']; 537 | 538 | // Get description. 539 | $description = isset( $args['description'] ) ? $args['description'] : ''; 540 | 541 | // Get value 542 | if ( isset( $args['value'] ) ) { 543 | $value = esc_attr( $args['value'] ); 544 | } else { 545 | // Check if we have a value in the settings array. 546 | $value = isset( $options[ $settings_array_key ][ $value_array_key ] ) ? $options[ $settings_array_key ][ $value_array_key ] : ''; 547 | 548 | // Check if this is the repo. 549 | // Then we replace the %2F with / again. 550 | if ( 1 === preg_match( '/-repo-information$/', $label_for ) ) { 551 | $value = str_replace( '%2F', '/', $value ); 552 | } 553 | } ?> 554 | 556 | type="text" value="" 557 | name="wp-gitlab-updater-[][]"> 558 | 561 |

562 | 563 |

564 | theme_data = $theme_data; 58 | } 59 | 60 | // Check if we have values. 61 | if ( isset( $args['slug'] ) && isset( $args['access_token'] ) && isset( $args['gitlab_url'] ) && isset( $args['repo'] ) ) { 62 | // Create array to insert them into theme_data. 63 | $tmp_array = [ 64 | 'settings-array-key' => $args['slug'], 65 | 'access-token' => $args['access_token'], 66 | 'gitlab-url' => untrailingslashit( $args['gitlab_url'] ), 67 | 'repo' => str_replace( '/', '%2F', $args['repo'] ), 68 | ]; 69 | 70 | // Insert it. 71 | $this->theme_data[ $args['slug'] ] = $tmp_array; 72 | } 73 | 74 | /** 75 | * Hook into pre_set_site_transient_update_themes to modify the update_themes 76 | * transient if a new theme version is available. 77 | */ 78 | add_filter( 'pre_set_site_transient_update_themes', function ( $transient ) { 79 | $transient = $this->theme_update( $transient ); 80 | 81 | return $transient; 82 | } ); 83 | 84 | /** 85 | * Check for themes with the same slug (for example from W.org) and remove 86 | * update notifications for them. 87 | * 88 | * For whatever reason, it seems to be working better here in an anonymous function 89 | * rather than in a method… 90 | */ 91 | add_filter( 'site_transient_update_themes', function ( $transient ) { 92 | if ( empty ( $transient->response ) ) { 93 | return $transient; 94 | } 95 | 96 | // Check if we have an update for a theme with the same slug from W.org 97 | // and remove it. 98 | // 99 | // At first, we loop the GitLab updater themes. 100 | foreach ( $this->theme_data as $theme ) { 101 | $theme_slug = $theme['settings-array-key']; 102 | 103 | // Check if we have a theme with the same slug and another package URL 104 | // than our GitLab URL. 105 | if ( array_key_exists( $theme_slug, $transient->response ) && false === strpos( $transient->response[ $theme_slug ]['package'], $theme['gitlab-url'] ) ) { 106 | // Unset the response key for that theme. 107 | unset( $transient->response[ $theme_slug ] ); 108 | } 109 | } 110 | 111 | return $transient; 112 | } ); 113 | 114 | /** 115 | * Before the files are copied to wp-content/themes, we need to rename the 116 | * folder of the theme, so it matches the slug. That is because WordPress 117 | * uses the folder name for the destination inside wp-content/themes, not 118 | * the theme slug. And the name of the ZIP we get from the GitLab API call 119 | * is something with the project name, the tag number and the commit SHA 120 | * (so everything but matching the theme slug). 121 | */ 122 | add_filter( 'upgrader_source_selection', function ( $source, $remote_source, $wp_upgrader, $args ) { 123 | foreach ( $this->theme_data as $theme ) { 124 | // Check if the currently updated theme matches our theme slug. 125 | if ( $args['theme'] === $theme['settings-array-key'] && false !== $theme ) { 126 | $source = $this->filter_source_name( $source, $remote_source, $theme['settings-array-key'] ); 127 | } 128 | } 129 | 130 | return $source; 131 | }, 10, 4 ); 132 | } 133 | 134 | /** 135 | * Checking for updates and updating the transient for theme updates. 136 | * 137 | * @param object $transient Transient object for theme updates. 138 | * 139 | * @return object Theme update transient. 140 | */ 141 | private function theme_update( $transient ) { 142 | if ( empty( $transient->checked ) ) { 143 | return $transient; 144 | } 145 | 146 | foreach ( $this->theme_data as $theme ) { 147 | // Get data from array. 148 | $gitlab_url = $theme['gitlab-url']; 149 | $repo = $theme['repo']; 150 | $access_token = $theme['access-token']; 151 | 152 | // Get tag list from GitLab repo. 153 | $request = $this->fetch_tags_from_repo( $gitlab_url, $repo, $access_token ); 154 | 155 | // Get response code of the request. 156 | $response_code = wp_remote_retrieve_response_code( $request ); 157 | 158 | // Check if request is not valid and return the $transient. 159 | // Otherwise get the data body. 160 | if ( is_wp_error( $request ) || 200 !== $response_code ) { 161 | continue; 162 | } else { 163 | $response = wp_remote_retrieve_body( $request ); 164 | } 165 | 166 | // Decode json. 167 | $data = json_decode( $response ); 168 | 169 | // Check if we have no tags and return the transient. 170 | if ( empty( $data ) ) { 171 | continue; 172 | } 173 | 174 | // Get the latest tag. 175 | $latest_version = $data[0]->name; 176 | 177 | // Check if new version is available. 178 | if ( ! isset( $transient->checked[ $theme['settings-array-key'] ] ) ) { 179 | continue; 180 | } 181 | 182 | if ( version_compare( $transient->checked[ $theme['settings-array-key'] ], $latest_version, '<' ) ) { 183 | // Get the package URL. 184 | $theme_package = "$gitlab_url/api/v4/projects/$repo/repository/archive.zip?sha=$latest_version&private_token=$access_token"; 185 | 186 | // Check the response. 187 | $response = wp_safe_remote_get( $theme_package ); 188 | $response_code = wp_remote_retrieve_response_code( $response ); 189 | if ( is_wp_error( $response ) || 200 !== $response_code ) { 190 | continue; 191 | } else { 192 | // Add data to response array. 193 | $transient->response[ $theme['settings-array-key'] ]['theme'] = $theme['settings-array-key']; 194 | $transient->response[ $theme['settings-array-key'] ]['new_version'] = $latest_version; 195 | $transient->response[ $theme['settings-array-key'] ]['package'] = $theme_package; 196 | } 197 | } 198 | } 199 | 200 | return $transient; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/updater-base.php: -------------------------------------------------------------------------------- 1 | exists( $remote_source ) ) { 66 | // Create a folder with slug as name inside the folder. 67 | $upgrade_theme_folder = $remote_source . "/$slug"; 68 | $wp_filesystem->mkdir( $upgrade_theme_folder ); 69 | 70 | // Copy files from $source in new $upgrade_theme_folder 71 | copy_dir( $source, $upgrade_theme_folder ); 72 | 73 | // Remove the old $source directory. 74 | $wp_filesystem->delete( $source, true ); 75 | 76 | // Set new folder as $source. 77 | $source = $upgrade_theme_folder; 78 | } 79 | 80 | return $source; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /uninstall.php: -------------------------------------------------------------------------------- 1 |