├── .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 |
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 |