├── assets ├── banner-772x250.png ├── banner-1544x500.png ├── github-logo.svg └── icon.svg ├── vendor ├── composer │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── autoload_classmap.php │ ├── platform_check.php │ ├── LICENSE │ ├── autoload_real.php │ ├── installed.php │ ├── autoload_static.php │ ├── installed.json │ ├── InstalledVersions.php │ └── ClassLoader.php ├── afragen │ └── singleton │ │ ├── composer.json │ │ ├── LICENSE │ │ ├── README.md │ │ └── Singleton.php └── autoload.php ├── src └── Git_Updater_PRO │ ├── WP_CLI │ ├── CLI_Common.php │ ├── CLI.php │ └── CLI_Integration.php │ ├── Bootstrap.php │ ├── REST │ ├── Rest_Upgrader_Skin.php │ ├── REST_API.php │ └── Rest_Update.php │ ├── Remote_Management.php │ ├── Install.php │ └── Branch.php ├── LICENSE ├── readme.txt ├── js ├── ghu-install.js └── gu-install-vanilla.js ├── composer.json ├── git-updater-pro.php ├── README.md ├── .phpcs.xml.dist ├── CHANGES.md └── languages └── git-updater-pro.pot /assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afragen/git-updater-pro/develop/assets/banner-772x250.png -------------------------------------------------------------------------------- /assets/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afragen/git-updater-pro/develop/assets/banner-1544x500.png -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($baseDir . '/src/Git_Updater_PRO'), 10 | 'Fragen\\Git_Updater\\' => array($baseDir . '/src/Git_Updater'), 11 | ); 12 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | 'Fragen\\Singleton' => $vendorDir . '/afragen/singleton/Singleton.php', 11 | ); 12 | -------------------------------------------------------------------------------- /vendor/afragen/singleton/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afragen/singleton", 3 | "description": "A singleton static proxy generator.", 4 | "version": "1.0.1", 5 | "type": "library", 6 | "keywords": [ 7 | "wordpress", 8 | "singleton" 9 | ], 10 | "license": "MIT", 11 | "repositories": [ 12 | { 13 | "type": "vcs", 14 | "url": "https://github.com/afragen/singleton" 15 | } 16 | ], 17 | "authors": [ 18 | { 19 | "name": "Andy Fragen", 20 | "email": "andy@thefragens.com", 21 | "homepage": "https://github.com/afragen/singleton", 22 | "role": "Developer" 23 | } 24 | ], 25 | "prefer-stable": true, 26 | "require": { 27 | "php": ">=5.4" 28 | }, 29 | "autoload": { 30 | "classmap": [ 31 | "Singleton.php" 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | base_prefix . 'sitemeta' : $wpdb->base_prefix . 'options'; 26 | $column = is_multisite() ? 'meta_key' : 'option_name'; 27 | $delete_string = 'DELETE FROM ' . $table . ' WHERE ' . $column . ' LIKE %s LIMIT 1000'; 28 | 29 | $wpdb->query( $wpdb->prepare( $delete_string, [ '%ghu-%' ] ) ); // phpcs:ignore 30 | 31 | return true; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /assets/github-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vendor/composer/platform_check.php: -------------------------------------------------------------------------------- 1 | = 70200)) { 8 | $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.0". You are running ' . PHP_VERSION . '.'; 9 | } 10 | 11 | if ($issues) { 12 | if (!headers_sent()) { 13 | header('HTTP/1.1 500 Internal Server Error'); 14 | } 15 | if (!ini_get('display_errors')) { 16 | if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { 17 | fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); 18 | } elseif (!headers_sent()) { 19 | echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; 20 | } 21 | } 22 | trigger_error( 23 | 'Composer detected issues in your platform: ' . implode(' ', $issues), 24 | E_USER_ERROR 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Andy Fragen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vendor/composer/LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) Nils Adermann, Jordi Boggiano 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is furnished 9 | to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | # Git Updater PRO 2 | 3 | * Contributors: afragen 4 | * Tags: branch switching, remote install, REST API, Webhooks, WP-CLI 5 | * Requires at least: 5.9 6 | * Requires PHP: 7.2 7 | * Tested up to: trunk 8 | * Stable tag: main 9 | * Donate link: 10 | * License: MIT 11 | 12 | ## Description 13 | 14 | This is a Git Updater add-on plugin that unlocks PRO features of branch switching, remote installation of plugins and themes, Remote Management, REST API, Webhooks, WP-CLI, and more. 15 | 16 | #### Sponsor 17 | 18 | You can [sponsor me on GitHub](https://github.com/sponsors/afragen) to help with continued development and support. 19 | 20 | ## Frequently Asked Questions 21 | 22 | #### Knowledge Base 23 | 24 | [Comprehensive information regarding Git Updater is available in the Knowledge Base.](https://git-updater.com/knowledge-base) 25 | 26 | #### Slack 27 | 28 | We now have a [Slack team for Git Updater](https://git-updater.slack.com). Please [click here for an invite](https://git-updater.herokuapp.com). You will be automatically added to the _#general_ and _#support_ channels. Please take a look at other channels too. 29 | -------------------------------------------------------------------------------- /vendor/afragen/singleton/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Andy Fragen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /js/ghu-install.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Javascript to show and hide the API specific settings 3 | * for the remote install feature. 4 | * 5 | * @class Fragen\GitHub_Updater\Install 6 | * @since 4.6.0 7 | * @access public 8 | * @package git-updater 9 | */ 10 | 11 | jQuery(document).ready( 12 | function ($) { 13 | // Hide non-default (Bitbucket & GitLab) settings on page load. 14 | $.each(['bitbucket', 'gitlab', 'gitea', 'zipfile'], 15 | function () { 16 | $('input.'.concat(this, '_setting')).parents('tr').hide(); 17 | }); 18 | 19 | // When the api selector changes. 20 | $('select[ name="github_updater_api" ]').on('change', 21 | function () { 22 | 23 | // create difference array. 24 | var hideMe = $(['github', 'bitbucket', 'gitlab', 'gitea', 'zipfile']).not([this.value]).get(); 25 | 26 | /* 27 | * Show/hide all settings that have the selected api's class. 28 | * this.value equals either 'github', 'bitbucket', or 'gitlab'. 29 | */ 30 | $.each( 31 | hideMe, 32 | function () { 33 | $('input.'.concat(this, '_setting')).parents('tr').hide(); 34 | } 35 | ); 36 | 37 | $('input.'.concat(this.value, '_setting')).parents('tr').show(); 38 | 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 35 | 36 | return $loader; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/composer/installed.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => 'afragen/git-updater-pro', 4 | 'pretty_version' => 'dev-develop', 5 | 'version' => 'dev-develop', 6 | 'reference' => '0929f5035c9e0e6b22acd90a03d907d3ae5eb8c2', 7 | 'type' => 'wordpress-plugin', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => false, 11 | ), 12 | 'versions' => array( 13 | 'afragen/git-updater-pro' => array( 14 | 'pretty_version' => 'dev-develop', 15 | 'version' => 'dev-develop', 16 | 'reference' => '0929f5035c9e0e6b22acd90a03d907d3ae5eb8c2', 17 | 'type' => 'wordpress-plugin', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | 'afragen/singleton' => array( 23 | 'pretty_version' => 'dev-master', 24 | 'version' => 'dev-master', 25 | 'reference' => 'be8e3c3b3a53ba30db9f77f5b3bcf2d5e58ed9c0', 26 | 'type' => 'library', 27 | 'install_path' => __DIR__ . '/../afragen/singleton', 28 | 'aliases' => array( 29 | 0 => '9999999-dev', 30 | ), 31 | 'dev_requirement' => false, 32 | ), 33 | ), 34 | ); 35 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afragen/git-updater-pro", 3 | "description": "A Git Updater add-on plugin that unlocks PRO features of branch switching, installing plugins and themes, REST API, WP-CLI, and more.", 4 | "type": "wordpress-plugin", 5 | "keywords": [ 6 | "wordpress", 7 | "plugin", 8 | "theme", 9 | "branch switch", 10 | "install" 11 | ], 12 | "license": "MIT", 13 | "authors": [ 14 | { 15 | "name": "Andy Fragen", 16 | "email": "andy@thefragens.com", 17 | "homepage": "https://thefragens.com", 18 | "role": "Developer" 19 | } 20 | ], 21 | "repositories": [ 22 | { 23 | "type": "vcs", 24 | "url": "https://github.com/afragen/git-updater-pro" 25 | } 26 | ], 27 | "support": { 28 | "issues": "https://github.com/afragen/git-updater-pro/issues", 29 | "source": "https://github.com/afragen/git-updater-pro" 30 | }, 31 | "minimum-stability": "dev", 32 | "prefer-stable": true, 33 | "require-dev": { 34 | "afragen/github-updater": "^11" 35 | }, 36 | "require": { 37 | "php": ">=7.2", 38 | "afragen/singleton": "dev-master" 39 | }, 40 | "autoload": { 41 | "psr-4": { 42 | "Fragen\\Git_Updater\\PRO\\": "src/Git_Updater_PRO/", 43 | "Fragen\\Git_Updater\\": "src/Git_Updater" 44 | } 45 | }, 46 | "scripts": { 47 | "post-update-cmd": [ 48 | "wp i18n make-pot . languages/git-updater-pro.pot" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'Fragen\\Git_Updater\\PRO\\' => 23, 13 | 'Fragen\\Git_Updater\\' => 19, 14 | ), 15 | ); 16 | 17 | public static $prefixDirsPsr4 = array ( 18 | 'Fragen\\Git_Updater\\PRO\\' => 19 | array ( 20 | 0 => __DIR__ . '/../..' . '/src/Git_Updater_PRO', 21 | ), 22 | 'Fragen\\Git_Updater\\' => 23 | array ( 24 | 0 => __DIR__ . '/../..' . '/src/Git_Updater', 25 | ), 26 | ); 27 | 28 | public static $classMap = array ( 29 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 30 | 'Fragen\\Singleton' => __DIR__ . '/..' . '/afragen/singleton/Singleton.php', 31 | ); 32 | 33 | public static function getInitializer(ClassLoader $loader) 34 | { 35 | return \Closure::bind(function () use ($loader) { 36 | $loader->prefixLengthsPsr4 = ComposerStaticInit388fe1ef227f29d2ec552eefda83d5a9::$prefixLengthsPsr4; 37 | $loader->prefixDirsPsr4 = ComposerStaticInit388fe1ef227f29d2ec552eefda83d5a9::$prefixDirsPsr4; 38 | $loader->classMap = ComposerStaticInit388fe1ef227f29d2ec552eefda83d5a9::$classMap; 39 | 40 | }, null, ClassLoader::class); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /vendor/afragen/singleton/README.md: -------------------------------------------------------------------------------- 1 | # singleton 2 | 3 | This is a singleton static proxy generator that I use in several projects instead of creating true Singletons. It was inspired by [Alain Schlesser’s post on Singletons](https://www.alainschlesser.com/singletons-shared-instances/). 4 | 5 | I’ve moved this library into it’s own repository so that I will be better able to include it via composer. 6 | 7 | I have written it to work with PSR-4. 8 | 9 | `composer require afragen/singleton:dev-master` 10 | 11 | When using this Singleton class in your project you will create an array of class instances. 12 | 13 | ## Usage 14 | 15 | ```php 16 | @param string $class_name 17 | @param object $caller Originating object. 18 | @param null|array|\stdClass $options 19 | 20 | Singleton::get_instance( $class_name, $calling_class, $options ); 21 | ``` 22 | 23 | This will usually be called as follows. 24 | 25 | `Singleton::get_instance( 'MyClass', $this );` 26 | 27 | The class object created will also pass the calling object as `$instance[$class_name]->caller`. 28 | 29 | I do my best to automatically determine the namespace of the class. If the class is in a subfolder of `src` it will need to be designated in the call as follows. 30 | 31 | If PSR-4 is set for the `src` directory and the class lives in `src/MySubDir/MyClass` the corresponding call would be as follows. 32 | 33 | `Singleton::get_instance( 'MySubDir\MyClass', $this );` 34 | 35 | I’m still learning how to properly set up using composer so this may be updated along the way. 36 | -------------------------------------------------------------------------------- /src/Git_Updater_PRO/Bootstrap.php: -------------------------------------------------------------------------------- 1 | load_hooks(); 37 | 38 | if ( static::is_wp_cli() ) { 39 | include_once __DIR__ . '/WP_CLI/CLI.php'; 40 | include_once __DIR__ . '/WP_CLI/CLI_Integration.php'; 41 | } 42 | 43 | // Need to ensure these classes are activated here for hooks to fire. 44 | if ( $this->is_current_page( [ 'options.php', 'options-general.php', 'settings.php', 'edit.php' ] ) ) { 45 | Singleton::get_instance( 'Install', $this )->run(); 46 | Singleton::get_instance( 'Remote_Management', $this )->load_hooks(); 47 | } 48 | } 49 | 50 | /** 51 | * Load hooks. 52 | * 53 | * @return void 54 | */ 55 | public function load_hooks() { 56 | add_action( 'rest_api_init', [ new REST_API(), 'register_endpoints' ] ); 57 | 58 | // Deprecated AJAX request. 59 | add_action( 'wp_ajax_git-updater-update', [ Singleton::get_instance( 'REST\Rest_Update', $this ), 'process_request' ] ); 60 | add_action( 'wp_ajax_nopriv_git-updater-update', [ Singleton::get_instance( 'REST\Rest_Update', $this ), 'process_request' ] ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /git-updater-pro.php: -------------------------------------------------------------------------------- 1 | run(); 59 | } 60 | ); 61 | -------------------------------------------------------------------------------- /assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 19 | 20 | 21 | 22 | 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Updater PRO 2 | 3 | ![WordPress Tests](https://github.com/afragen/git-updater-pro/workflows/WordPress%20Tests/badge.svg) 4 | 5 | * Contributors: [Andy Fragen](https://github.com/afragen), [contributors](https://github.com/afragen/git-updater-pro/graphs/contributors) 6 | * Tags: branch switch, remote install, REST API, Webhooks, WP-CLI 7 | * Requires at least: 5.9 8 | * Requires PHP: 7.2 9 | * Tested up to: trunk 10 | * Stable tag: [master](https://github.com/afragen/git-updater-pro/releases/latest) 11 | * Donate link: 12 | * License: MIT 13 | 14 | A [Git Updater](https://github.com/afragen/git-updater) add-on plugin that unlocks PRO features of branch switching, remote installing plugins and themes, Remote Management, REST API, Webhooks, WP-CLI, and more. 15 | 16 | [Comprehensive information regarding Git Updater is available in the Knowledge Base.](https://git-updater.com/knowledge-base) 17 | 18 | ## Description 19 | 20 | This is a Git Updater add-on plugin. It allows for branch switching, remote installation of plugins or themes, etc. It also unlocks the REST API and WP-CLI features. 21 | 22 | ### API plugins 23 | 24 | API plugins for Bitbucket, GitLab, Gitea, and Gist are available. API plugins are available for a one-click install from the **Add-Ons** tab. 25 | 26 | * [Git Updater - Bitbucket](https://github.com/afragen/git-updater-bitbucket/releases/latest) 27 | * [Git Updater - GitLab](https://github.com/afragen/git-updater-gitlab/releases/latest) 28 | * [Git Updater - Gitea](https://github.com/afragen/git-updater-gitea/releases/latest) 29 | * [Git Updater - Gist](https://github.com/afragen/git-updater-gist/releases/latest) 30 | 31 | ### Sponsor 32 | 33 | You can [sponsor me on GitHub](https://github.com/sponsors/afragen) to help with continued development and support. 34 | 35 | ## Slack 36 | 37 | We now have a [Slack team for Git Updater](https://git-updater.slack.com). Please [click here for an invite](https://git-updater.herokuapp.com). You will be automatically added to the _#general_ and _#support_ channels. Please take a look at other channels too. 38 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "name": "afragen/singleton", 5 | "version": "dev-master", 6 | "version_normalized": "dev-master", 7 | "source": { 8 | "type": "git", 9 | "url": "https://github.com/afragen/singleton.git", 10 | "reference": "be8e3c3b3a53ba30db9f77f5b3bcf2d5e58ed9c0" 11 | }, 12 | "dist": { 13 | "type": "zip", 14 | "url": "https://api.github.com/repos/afragen/singleton/zipball/be8e3c3b3a53ba30db9f77f5b3bcf2d5e58ed9c0", 15 | "reference": "be8e3c3b3a53ba30db9f77f5b3bcf2d5e58ed9c0", 16 | "shasum": "" 17 | }, 18 | "require": { 19 | "php": ">=5.4" 20 | }, 21 | "time": "2020-12-24T03:45:00+00:00", 22 | "default-branch": true, 23 | "type": "library", 24 | "installation-source": "dist", 25 | "autoload": { 26 | "classmap": [ 27 | "Singleton.php" 28 | ] 29 | }, 30 | "notification-url": "https://packagist.org/downloads/", 31 | "license": [ 32 | "MIT" 33 | ], 34 | "authors": [ 35 | { 36 | "name": "Andy Fragen", 37 | "email": "andy@thefragens.com", 38 | "homepage": "https://github.com/afragen/singleton", 39 | "role": "Developer" 40 | } 41 | ], 42 | "description": "A singleton static proxy generator.", 43 | "keywords": [ 44 | "singleton", 45 | "wordpress" 46 | ], 47 | "support": { 48 | "issues": "https://github.com/afragen/singleton/issues", 49 | "source": "https://github.com/afragen/singleton/tree/master" 50 | }, 51 | "install-path": "../afragen/singleton" 52 | } 53 | ], 54 | "dev": false, 55 | "dev-package-names": [] 56 | } 57 | -------------------------------------------------------------------------------- /.phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | Generally-applicable sniffs for WordPress plugins. 4 | 5 | 6 | . 7 | /vendor/ 8 | /node_modules/ 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/Git_Updater_PRO/WP_CLI/CLI.php: -------------------------------------------------------------------------------- 1 | 33 | * : delete the cache 34 | * 35 | * ## EXAMPLES 36 | * 37 | * wp git-updater cache delete 38 | * 39 | * @param array $args Array of arguments. 40 | * 41 | * @subcommand cache 42 | */ 43 | public function cache( $args ) { 44 | list($action) = $args; 45 | if ( 'delete' === $action ) { 46 | Singleton::get_instance( 'CLI_Common', $this )->delete_all_cached_data(); 47 | WP_CLI::success( 'Git Updater cache has been cleared.' ); 48 | } else { 49 | WP_CLI::error( sprintf( 'Incorrect command syntax, see %s for proper syntax.', '`wp help git-updater cache`' ) ); 50 | } 51 | WP_CLI::success( 'WP-Cron is now running.' ); 52 | WP_CLI::runcommand( 'cron event run --due-now' ); 53 | } 54 | 55 | /** 56 | * Reset Git Updater REST API key. 57 | * 58 | * ## EXAMPLES 59 | * 60 | * wp git-updater reset-api-key 61 | * 62 | * @subcommand reset-api-key 63 | */ 64 | public function reset_api_key() { 65 | delete_site_option( 'git_updater_api_key' ); 66 | Singleton::get_instance( 'Fragen\Git_Updater\PRO\Remote_Management', $this )->ensure_api_key_is_set(); 67 | $namespace = Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_class_vars( 'Fragen\Git_Updater\PRO\REST\REST_API', 'namespace' ); 68 | $api_key = get_site_option( 'git_updater_api_key' ); 69 | $api_url = add_query_arg( 70 | [ 'key' => $api_key ], 71 | \home_url( "wp-json/$namespace/update/" ) 72 | ); 73 | 74 | WP_CLI::success( 'Git Updater REST API key has been reset.' ); 75 | WP_CLI::success( sprintf( 'The new REST API key is: `%s`', $api_key ) ); 76 | WP_CLI::success( sprintf( 'The current REST API endpoint for updating is `%s`', $api_url ) ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Git_Updater_PRO/REST/Rest_Upgrader_Skin.php: -------------------------------------------------------------------------------- 1 | upgrader->strings[ $string ] ) ) { 53 | $string = $this->upgrader->strings[ $string ]; 54 | } 55 | 56 | if ( false !== strpos( $string, '%' ) ) { 57 | if ( $args ) { 58 | $args = array_map( 'strip_tags', $args ); 59 | $args = array_map( 'esc_html', $args ); 60 | $string = vsprintf( $string, $args ); 61 | } 62 | } 63 | if ( empty( $string ) ) { 64 | return; 65 | } 66 | 67 | $this->messages[] = $string; 68 | } 69 | 70 | /** 71 | * Set the error flag to true, then let the base class handle the rest. 72 | * 73 | * @param mixed $errors Error messages. 74 | */ 75 | public function error( $errors ) { 76 | $this->error = true; 77 | parent::error( $errors ); 78 | } 79 | 80 | /** 81 | * Do nothing. 82 | * 83 | * @param mixed $type I don't know, not used. 84 | */ 85 | protected function decrement_update_count( $type ) { 86 | } 87 | 88 | /** 89 | * Do nothing. 90 | */ 91 | public function header() { 92 | } 93 | 94 | /** 95 | * Do nothing. 96 | */ 97 | public function footer() { 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | [unreleased] 2 | 3 | #### 2.2.0 / 2022-10-30 4 | * remove Freemius 5 | 6 | #### 2.1.1 / 2022-10-28 7 | * reset Freemius to update 8 | 9 | #### 2.1.0 / 2022-10-25 10 | * strip Freemius, prelude to removal 11 | 12 | #### 2.0.1 / 2022-08-27 13 | * update Freemius/wordpress-sdk 14 | 15 | #### 2.0.0 / 2022-04-24 16 | * update error message return for REST route 17 | * require PHP7.2+ 18 | 19 | #### 1.4.1 / 2022-03-12 20 | * fix PHP warnings in REST route if certain data not set 21 | * update error checking and reporting in REST route 22 | 23 | #### 1.4.0 / 2022-03-06 24 | * add REST route to return `plugins_api()` like data for any installed plugin 25 | 26 | #### 1.3.6 / 2022-03-02 27 | * update Freemius/wordpress-sdk 28 | 29 | #### 1.3.5 / 2022-02-07 30 | * remove errant comma 31 | * update GitHub Action 32 | 33 | #### 1.3.4 / 2021-10-27 34 | * update REST API routes 35 | * update `Remote Management` tab information 36 | 37 | #### 1.3.3 / 2021-10-22 38 | * more efficient removal of `current_branch` from cache 39 | 40 | #### 1.3.2 / 2021-10-22 41 | * use `try/catch` and `UnexpectedValueException` for error messaging in `reset-branch` 42 | * correctly remove `current_branch` from cache for `reset-branch` REST endpoint 43 | 44 | #### 1.3.1 / 2021-10-21 45 | * use `Rest_Update::log_exit()` for better messaging with `reset-branch` REST endpoint 46 | 47 | #### 1.3.0 / 2021-10-21 48 | * use `sanitize_title_with_dashes()` as `sanitize_file_name()` maybe have attached filter that changes output 49 | * update `class REST_API` with new endpoint `reset-branch` to reset the saved branch of a plugin or theme 50 | 51 | #### 1.2.3 / 2021-09-04 52 | * fix for PHP 5.6 compatibility 53 | * add error checking to `Branch::set_branch_on_switch()` when directory renamed 54 | 55 | #### 1.2.2 / 2021-08-18 56 | * only use `esc_attr_e` for translating strings 57 | * un-deprecate `github-updater/v1/update`, issue deprecation message if used 58 | 59 | #### 1.2.1 / 2021-07-21 60 | * use `Primary Branch` header for default in `REST_Update` if available 61 | * load `site_transient` hooks in Git Updater during WP-CLI 62 | 63 | #### 1.2.0 / 2021-07-05 64 | * remove Freemius from the autoloader 65 | * ensure `is_plugin_active()` is available when needed 66 | * utilize new `class Ignore` from Git Updater 67 | * uses new `class Fragen\Git_Updater\Shim` for PHP 5.6 compatibility, will remove when WP core changes minimum requirement 68 | 69 | #### 1.1.1 / 2021-06-14 70 | * utilize new `class Ignore` in Git Updater 71 | * update Freemius menu for multisite 72 | 73 | #### 1.1.0 / 2021-06-02 74 | * add filter to skip updating from Git Updater 75 | * add filter to display this plugin in GitHub subtab without errors 76 | 77 | #### 1.0.3 / 2021-05-22 78 | * update constant for WPCS 79 | 80 | #### 1.0.2 / 2021-05-21 81 | * add language pack updates 82 | * add check when Git Updater loaded as mu-plugin 83 | 84 | #### 1.0.1 / 2021-05-18 85 | * ensure custom icon shows in update notice from Freemius 86 | 87 | #### 1.0.0 / 2021-05-11 88 | * migrate update, install, REST API, WP-CLI, remote management code over 89 | * add Zipfile_API code over 90 | * add Branch class code over 91 | * add Freemius integration 92 | * update logo branding 93 | -------------------------------------------------------------------------------- /vendor/afragen/singleton/Singleton.php: -------------------------------------------------------------------------------- 1 | caller = $caller; 49 | 50 | return $instance[ $class ]; 51 | } 52 | 53 | /** 54 | * Determine correct class name with namespace and return. 55 | * 56 | * @param string $class_name 57 | * @param string $class 58 | * 59 | * @return string Namespaced class name. 60 | */ 61 | private static function get_class( $class_name, $class ) { 62 | $reflection = self::get_reflection( $class ); 63 | $namespace = $reflection->getNamespaceName(); 64 | $namespace_parts = explode( '\\', $namespace ); 65 | $count = count( $namespace_parts ); 66 | $classes[-1] = null; 67 | 68 | for ( $i = 0; $i < $count; $i++ ) { 69 | $classes[ $i ] = ltrim( $classes[ $i - 1 ] . '\\' . $namespace_parts[ $i ], '\\' ); 70 | } 71 | 72 | $classes = array_reverse( $classes ); 73 | foreach ( $classes as $namespace ) { 74 | $namespaced_class = $namespace . '\\' . $class_name; 75 | if ( class_exists( $namespaced_class ) ) { 76 | return $namespaced_class; 77 | } 78 | } 79 | 80 | try { 81 | throw new \Exception( "Undefined class '{$class_name}'" ); 82 | } catch ( \Exception $e ) { 83 | $message = "PHP Fatal error: {$e->getMessage()}\nPHP Stack trace:\n"; 84 | $trace = $e->getTraceAsString(); 85 | error_log( $message . $trace ); 86 | die( "
{$message}{$trace}
" ); 87 | } 88 | } 89 | 90 | /** 91 | * Get ReflectionClass of passed class name. 92 | * 93 | * @param string $class 94 | * 95 | * @return \ReflectionClass $reflection 96 | */ 97 | private static function get_reflection( $class ) { 98 | try { 99 | $reflection = new \ReflectionClass( $class ); 100 | } catch ( \ReflectionException $Exception ) { 101 | die( '' . $Exception->xdebug_message . '
' ); 102 | } 103 | 104 | return $reflection; 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /js/gu-install-vanilla.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vanilla Javascript to show and hide the API specific settings 3 | * for the remote install feature. 4 | * 5 | * @class Fragen\GitHub_Updater\Install, now Fragen\Git_Updater\PRO\Install 6 | * @since 8.5.0 7 | * @access public 8 | * @package git-updater-pro 9 | */ 10 | 11 | (function () { 12 | 13 | //polyfill for NodeList.forEach browsers that supports ES5 14 | if (window.NodeList && !NodeList.prototype.forEach) { 15 | NodeList.prototype.forEach = function (callback, thisArg) { 16 | thisArg = thisArg || window; 17 | for (var i = 0; i < this.length; i++) { 18 | callback.call(thisArg, this[i], i, this); 19 | } 20 | }; 21 | } 22 | 23 | //polyfill for Element.matches and Element.closest in IE 24 | if (!Element.prototype.matches) 25 | Element.prototype.matches = Element.prototype.msMatchesSelector || 26 | Element.prototype.webkitMatchesSelector; 27 | if (!Element.prototype.closest) 28 | Element.prototype.closest = function(s) { 29 | var el = this; 30 | if (!document.documentElement.contains(el)) return null; 31 | do { 32 | if (el.matches(s)) return el; 33 | el = el.parentElement || el.parentNode; 34 | } while (el !== null && el.nodeType == 1); 35 | return null; 36 | }; 37 | 38 | // Hide non-default (Bitbucket & GitLab) settings on page load. 39 | let nonDefault = ['bitbucket', 'gitlab', 'gitea', 'zipfile', 'gist']; 40 | 41 | nonDefault.forEach(function (item) { 42 | let parents = getParents(item, 'tr'); 43 | displayNone(parents); 44 | }); 45 | 46 | // When the api selector changes. 47 | let selects = document.querySelector('select[ name="git_updater_api" ]'); 48 | 49 | // Only run when on proper tab. 50 | if (selects !== null) { 51 | selects.addEventListener('change', function () { 52 | let defaults = ['github', 'bitbucket', 'gitlab', 'gitea', 'zipfile', 'gist']; 53 | 54 | // Create difference array. 55 | let hideMe = remove(defaults, this.value); 56 | 57 | // Hide items with unselected api's classes. 58 | hideMe.forEach(function (item) { 59 | let parents = getParents(item, 'tr'); 60 | displayNone(parents); 61 | }); 62 | 63 | // Show selected setting. 64 | [this.value].forEach(function (item) { 65 | let parents = getParents(item, 'tr'); 66 | display(parents); 67 | }); 68 | 69 | console.log('selected', this.value); 70 | console.log('hideMe', hideMe); 71 | }); 72 | } 73 | 74 | // Remove selected element from array and return array. 75 | function remove(array, element) { 76 | const index = array.indexOf(element); 77 | if (index !== -1) { 78 | array.splice(index, 1); 79 | } 80 | return array; 81 | } 82 | 83 | // Hide element. 84 | function displayNone(array) { 85 | array.forEach(function (item) { 86 | item.style.display = 'none'; 87 | }); 88 | } 89 | 90 | // Display element. 91 | function display(array) { 92 | array.forEach(function (item) { 93 | item.style.display = ''; 94 | }); 95 | } 96 | 97 | // Return query and selector for `$(query).parents.(selector)`. 98 | function getParents(item, selector) { 99 | return vanillaParents(document.querySelectorAll('input.'.concat(item, '_setting')), selector); 100 | } 101 | 102 | // Vanilla JS version of jQuery `$(query).parents(selector)`. 103 | function vanillaParents(element, selector) { 104 | let parents = []; 105 | if (NodeList.prototype.isPrototypeOf(element)) { 106 | element.forEach(function (item) { 107 | element = item.parentElement.closest(selector); 108 | parents.push(element); 109 | }); 110 | } else { 111 | element = item.parentElement.closest(selector); 112 | parents.push(element); 113 | } 114 | return parents; 115 | } 116 | 117 | })(); 118 | -------------------------------------------------------------------------------- /languages/git-updater-pro.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Andy Fragen 2 | # This file is distributed under the MIT. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Git Updater PRO 2.0..0\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/git-updater-pro\n" 7 | "Last-Translator: FULL NAME \n" 8 | "Language-Team: LANGUAGE \n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "POT-Creation-Date: 2022-04-25T05:38:16+00:00\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "X-Generator: WP-CLI 2.6.0\n" 15 | "X-Domain: git-updater-pro\n" 16 | 17 | #. Plugin Name of the plugin 18 | msgid "Git Updater PRO" 19 | msgstr "" 20 | 21 | #. Plugin URI of the plugin 22 | msgid "https://github.com/afragen/git-updater-pro" 23 | msgstr "" 24 | 25 | #. Description of the plugin 26 | msgid "A Git Updater add-on plugin that unlocks PRO features of branch switching, remote installation of plugins and themes, REST API, Webhooks, WP-CLI, and more." 27 | msgstr "" 28 | 29 | #. Author of the plugin 30 | msgid "Andy Fragen" 31 | msgstr "" 32 | 33 | #. translators: 1: branch name, 2: jQuery dropdown, 3: closing tag 34 | #: src/Git_Updater_PRO/Branch.php:312 35 | #: src/Git_Updater_PRO/Branch.php:414 36 | msgid "Current branch is `%1$s`, try %2$sanother version%3$s" 37 | msgstr "" 38 | 39 | #: src/Git_Updater_PRO/Branch.php:320 40 | msgid "Choose a Version" 41 | msgstr "" 42 | 43 | #: src/Git_Updater_PRO/Branch.php:363 44 | #: src/Git_Updater_PRO/Branch.php:521 45 | msgid "No previous tags to rollback to." 46 | msgstr "" 47 | 48 | #: src/Git_Updater_PRO/Branch.php:367 49 | msgid "Install" 50 | msgstr "" 51 | 52 | #: src/Git_Updater_PRO/Branch.php:453 53 | msgid "Switch to branch " 54 | msgstr "" 55 | 56 | #: src/Git_Updater_PRO/Branch.php:513 57 | msgid "Switch to release " 58 | msgstr "" 59 | 60 | #: src/Git_Updater_PRO/Install.php:104 61 | #: src/Git_Updater_PRO/Install.php:318 62 | msgid "Install Plugin" 63 | msgstr "" 64 | 65 | #: src/Git_Updater_PRO/Install.php:107 66 | #: src/Git_Updater_PRO/Install.php:321 67 | msgid "Install Theme" 68 | msgstr "" 69 | 70 | #: src/Git_Updater_PRO/Install.php:163 71 | msgid "A repository URI is required." 72 | msgstr "" 73 | 74 | #: src/Git_Updater_PRO/Install.php:338 75 | msgid "Plugin" 76 | msgstr "" 77 | 78 | #: src/Git_Updater_PRO/Install.php:341 79 | msgid "Theme" 80 | msgstr "" 81 | 82 | #. translators: variable is 'Plugin' or 'Theme' 83 | #: src/Git_Updater_PRO/Install.php:353 84 | msgid "Git Updater Install %s" 85 | msgstr "" 86 | 87 | #. translators: variable is 'Plugin' or 'Theme' 88 | #: src/Git_Updater_PRO/Install.php:361 89 | msgid "%s URI" 90 | msgstr "" 91 | 92 | #: src/Git_Updater_PRO/Install.php:369 93 | msgid "Repository Branch" 94 | msgstr "" 95 | 96 | #: src/Git_Updater_PRO/Install.php:377 97 | msgid "Remote Repository Host" 98 | msgstr "" 99 | 100 | #: src/Git_Updater_PRO/Install.php:412 101 | msgid "URI is case sensitive." 102 | msgstr "" 103 | 104 | #: src/Git_Updater_PRO/Install.php:427 105 | msgid "Enter branch name or leave empty for `master`" 106 | msgstr "" 107 | 108 | #: src/Git_Updater_PRO/Install.php:477 109 | msgid "Activate" 110 | msgstr "" 111 | 112 | #: src/Git_Updater_PRO/Install.php:489 113 | msgctxt "This refers to a network activation in a multisite installation" 114 | msgid "Network Enable" 115 | msgstr "" 116 | 117 | #: src/Git_Updater_PRO/Remote_Management.php:58 118 | #: src/Git_Updater_PRO/Remote_Management.php:123 119 | msgid "Remote Management" 120 | msgstr "" 121 | 122 | #: src/Git_Updater_PRO/Remote_Management.php:92 123 | msgid "Reset REST API key" 124 | msgstr "" 125 | 126 | #: src/Git_Updater_PRO/Remote_Management.php:106 127 | msgid "REST API key reset." 128 | msgstr "" 129 | 130 | #: src/Git_Updater_PRO/Remote_Management.php:146 131 | msgid "Remote Management services should just work for plugins like MainWP, ManageWP, InfiniteWP, iThemes Sync and others." 132 | msgstr "" 133 | 134 | #. translators: %s: Link to Git Remote Updater repository 135 | #: src/Git_Updater_PRO/Remote_Management.php:153 136 | msgid "The Git Remote Updater plugin was specifically created to make the remote management of Git Updater supported plugins and themes much simpler. You will need the Site URL and REST API key to use with Git Remote Updater settings." 137 | msgstr "" 138 | 139 | #. translators: 1: home URL, 2: REST API key 140 | #: src/Git_Updater_PRO/Remote_Management.php:163 141 | msgid "Site URL: %1$s
REST API key: %2$s" 142 | msgstr "" 143 | 144 | #. translators: 1: Link to wiki, 2: RESTful API URL 145 | #: src/Git_Updater_PRO/Remote_Management.php:174 146 | msgid "Please refer to the Git Updater Knowledge Base for complete list of attributes." 147 | msgstr "" 148 | 149 | #. translators: link to REST API endpoint for updating 150 | #: src/Git_Updater_PRO/Remote_Management.php:184 151 | msgid "REST API endpoints for webhook updating begin at: %s" 152 | msgstr "" 153 | 154 | #. translators: link to REST API endpoint for branch resetting 155 | #: src/Git_Updater_PRO/Remote_Management.php:194 156 | msgid "REST API endpoints for webhook branch resetting begin at: %s" 157 | msgstr "" 158 | -------------------------------------------------------------------------------- /src/Git_Updater_PRO/Remote_Management.php: -------------------------------------------------------------------------------- 1 | ensure_api_key_is_set(); 34 | } 35 | 36 | /** 37 | * Ensure api key is set. 38 | */ 39 | public function ensure_api_key_is_set() { 40 | if ( ! self::$api_key ) { 41 | update_site_option( 'git_updater_api_key', md5( uniqid( \wp_rand(), true ) ) ); 42 | } 43 | } 44 | 45 | /** 46 | * Load needed action/filter hooks. 47 | */ 48 | public function load_hooks() { 49 | add_action( 'admin_init', [ $this, 'remote_management_page_init' ] ); 50 | 51 | $this->add_settings_tabs(); 52 | } 53 | 54 | /** 55 | * Adds Remote Management tab to Settings page. 56 | */ 57 | public function add_settings_tabs() { 58 | $install_tabs = [ 'git_updater_remote_management' => esc_html__( 'Remote Management', 'git-updater-pro' ) ]; 59 | add_filter( 60 | 'gu_add_settings_tabs', 61 | function ( $tabs ) use ( $install_tabs ) { 62 | return array_merge( $tabs, $install_tabs ); 63 | } 64 | ); 65 | add_filter( 66 | 'gu_add_admin_page', 67 | function ( $tab, $action ) { 68 | $this->add_admin_page( $tab, $action ); 69 | }, 70 | 10, 71 | 2 72 | ); 73 | } 74 | 75 | /** 76 | * Add Settings page data via action hook. 77 | * 78 | * @uses 'gu_add_admin_page' action hook 79 | * 80 | * @param string $tab Tab name. 81 | * @param string $action Form action. 82 | */ 83 | public function add_admin_page( $tab, $action ) { 84 | if ( 'git_updater_remote_management' === $tab ) { 85 | $action = add_query_arg( 'tab', $tab, $action ); 86 | $this->admin_page_notices(); ?> 87 |
88 | 89 |
90 | true ], $action ); ?> 91 |
92 | 93 |
94 |

'; 106 | esc_html_e( 'REST API key reset.', 'git-updater-pro' ); 107 | echo '

'; 108 | } 109 | } 110 | 111 | /** 112 | * Settings for Remote Management. 113 | */ 114 | public function remote_management_page_init() { 115 | register_setting( 116 | 'git_updater_remote_management', 117 | 'git_updater_remote_settings', 118 | [ $this, 'sanitize' ] 119 | ); 120 | 121 | add_settings_section( 122 | 'remote_management', 123 | esc_html__( 'Remote Management', 'git-updater-pro' ), 124 | [ $this, 'print_section_remote_management' ], 125 | 'git_updater_remote_settings' 126 | ); 127 | } 128 | 129 | /** 130 | * Print the Remote Management text. 131 | */ 132 | public function print_section_remote_management() { 133 | if ( empty( self::$api_key ) ) { 134 | $this->load_options(); 135 | } 136 | $update_endpoint = add_query_arg( 137 | [ 'key' => self::$api_key ], 138 | home_url( 'wp-json/' . $this->get_class_vars( 'REST\REST_API', 'namespace' ) . '/update/' ) 139 | ); 140 | $branch_reset_endpoint = add_query_arg( 141 | [ 'key' => self::$api_key ], 142 | home_url( 'wp-json/' . $this->get_class_vars( 'REST\REST_API', 'namespace' ) . '/reset-branch/' ) 143 | ); 144 | 145 | echo '

'; 146 | esc_html_e( 'Remote Management services should just work for plugins like MainWP, ManageWP, InfiniteWP, iThemes Sync and others.', 'git-updater-pro' ); 147 | echo '

'; 148 | 149 | echo '

'; 150 | printf( 151 | wp_kses_post( 152 | /* translators: %s: Link to Git Remote Updater repository */ 153 | __( 'The Git Remote Updater plugin was specifically created to make the remote management of Git Updater supported plugins and themes much simpler. You will need the Site URL and REST API key to use with Git Remote Updater settings.', 'git-updater-pro' ) 154 | ), 155 | 'https://git-updater.com/knowledge-base/git-remote-updater/' 156 | ); 157 | echo '

'; 158 | 159 | echo '

'; 160 | printf( 161 | wp_kses_post( 162 | /* translators: 1: home URL, 2: REST API key */ 163 | __( 'Site URL: %1$s
REST API key: %2$s', 'git-updater-pro' ) 164 | ), 165 | '' . esc_url( home_url() ) . '', 166 | '' . esc_attr( self::$api_key ) . '' 167 | ); 168 | echo '

'; 169 | 170 | echo '

'; 171 | printf( 172 | wp_kses_post( 173 | /* translators: 1: Link to wiki, 2: RESTful API URL */ 174 | __( 'Please refer to the Git Updater Knowledge Base for complete list of attributes.', 'git-updater-pro' ) 175 | ), 176 | 'https://git-updater.com/knowledge-base/remote-management-restful-endpoints/' 177 | ); 178 | echo '

'; 179 | 180 | echo '

'; 181 | printf( 182 | wp_kses_post( 183 | /* translators: link to REST API endpoint for updating */ 184 | __( 'REST API endpoints for webhook updating begin at: %s', 'git-updater-pro' ) 185 | ), 186 | '
' . esc_url( $update_endpoint ) . '' 187 | ); 188 | echo '

'; 189 | 190 | echo '

'; 191 | printf( 192 | wp_kses_post( 193 | /* translators: link to REST API endpoint for branch resetting */ 194 | __( 'REST API endpoints for webhook branch resetting begin at: %s', 'git-updater-pro' ) 195 | ), 196 | '
' . esc_url( $branch_reset_endpoint ) . '' 197 | ); 198 | echo '

'; 199 | } 200 | 201 | /** 202 | * Reset RESTful API key. 203 | * Deleting site option will cause it to be re-created. 204 | * 205 | * @return bool 206 | */ 207 | public function reset_api_key() { 208 | // phpcs:disable WordPress.Security.NonceVerification.Recommended 209 | if ( isset( $_REQUEST['tab'], $_REQUEST['git_updater_reset_api_key'] ) 210 | && 'git_updater_remote_management' === sanitize_title_with_dashes( wp_unslash( $_REQUEST['tab'] ) ) 211 | ) { 212 | $_POST = $_REQUEST; 213 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 214 | $_POST['_wp_http_referer'] = isset( $_SERVER['HTTP_REFERER'] ) ? esc_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : null; 215 | // phpcs:enable 216 | delete_site_option( 'git_updater_api_key' ); 217 | 218 | return true; 219 | } 220 | 221 | return false; 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/Git_Updater_PRO/WP_CLI/CLI_Integration.php: -------------------------------------------------------------------------------- 1 | 36 | * : URI to the repo being installed 37 | * 38 | * [--branch=] 39 | * : String indicating the branch name to be installed 40 | * --- 41 | * default: master 42 | * --- 43 | * 44 | * [--token=] 45 | * : GitHub, Bitbucket, GitLab, or Gitea access token if not already saved 46 | * Bitbucket pseudo-token in format `username:password` 47 | * 48 | * [--slug=] 49 | * : Optional string indicating the plugin slug 50 | 51 | * [--github] 52 | * : Optional to denote a GitHub repository 53 | * Required when installing from a self-hosted GitHub installation 54 | * 55 | * [--bitbucket] 56 | * : Optional switch to denote a Bitbucket repository 57 | * Required when installing from a self-hosted Bitbucket installation 58 | * 59 | * [--gitlab] 60 | * : Optional switch to denote a GitLab repository 61 | * Required when installing from a self-hosted GitLab installation 62 | * 63 | * [--gitea] 64 | * : Optional switch to denote a Gitea repository 65 | * Required when installing from a Gitea installation 66 | * 67 | * [--gist] 68 | * : Optional switch to denote a GitHub Gist repository 69 | * Required when installing from a GitHub Gist installation 70 | * 71 | * [--zipfile] 72 | * : Optional switch to denote a Zipfile 73 | * Required when installing from a Zipfile 74 | * 75 | * ## EXAMPLES 76 | * 77 | * wp plugin install-git https://github.com/afragen/my-plugin 78 | * 79 | * wp plugin install-git https://github.com/afragen/my-plugin --branch=develop --github 80 | * 81 | * wp plugin install-git https://bitbucket.org/afragen/my-private-plugin --token=username:password 82 | * 83 | * wp plugin install-git https://github.com/afragen/my-private-plugin --token=lks9823evalki 84 | * 85 | * @param array $args An array of $uri. 86 | * @param array $assoc_args Array of optional arguments. 87 | * 88 | * @subcommand install-git 89 | */ 90 | public function install_plugin( $args, $assoc_args ) { 91 | list($uri) = $args; 92 | $cli_config = $this->process_args( $uri, $assoc_args ); 93 | ( new Install() )->install( 'plugin', $cli_config ); 94 | 95 | $headers = parse_url( $uri, PHP_URL_PATH ); 96 | $slug = basename( $headers ); 97 | $this->process_branch( $cli_config, $slug ); 98 | WP_CLI::success( sprintf( 'Plugin %s installed.', "'{$slug}'" ) ); 99 | } 100 | 101 | /** 102 | * Install theme from GitHub, Bitbucket, GitLab, Gitea, Gist, or Zipfile using Git Updater PRO. Appropriate API plugin is required. 103 | * 104 | * ## OPTIONS 105 | * 106 | * 107 | * : URI to the repo being installed 108 | * 109 | * [--branch=] 110 | * : String indicating the branch name to be installed 111 | * --- 112 | * default: master 113 | * --- 114 | * 115 | * [--token=] 116 | * : GitHub, Bitbucket, GitLab, or Gitea access token if not already saved 117 | * Bitbucket pseudo-token in format `username:password` 118 | * 119 | * [--slug=] 120 | * : Optional string indicating the theme slug 121 | * 122 | * [--github] 123 | * : Optional to denote a GitHub repository 124 | * Required when installing from a self-hosted GitHub installation 125 | * 126 | * [--bitbucket] 127 | * : Optional switch to denote a Bitbucket repository 128 | * Required when installing from a self-hosted Bitbucket installation 129 | * 130 | * [--gitlab] 131 | * : Optional switch to denote a GitLab repository 132 | * Required when installing from a self-hosted GitLab installation 133 | * 134 | * [--gitea] 135 | * : Optional switch to denote a Gitea repository 136 | * Required when installing from a Gitea installation 137 | * 138 | * [--gist] 139 | * : Optional switch to denote a GitHub Gist repository 140 | * Required when installing from a GitHub Gist installation 141 | * 142 | * [--zipfile] 143 | * : Optional switch to denote a Zipfile 144 | * Required when installing from a Zipfile 145 | * 146 | * ## EXAMPLES 147 | * 148 | * wp theme install-git https://github.com/afragen/my-theme 149 | * 150 | * wp theme install-git https://bitbucket.org/afragen/my-theme --branch=develop --bitbucket 151 | * 152 | * wp theme install-git https://bitbucket.org/afragen/my-private-theme --token=username:password 153 | * 154 | * wp theme install-git https://github.com/afragen/my-private-theme --token=lks9823evalki 155 | * 156 | * @param array $args An array of $uri. 157 | * @param array $assoc_args Array of optional arguments. 158 | * 159 | * @subcommand install-git 160 | */ 161 | public function install_theme( $args, $assoc_args ) { 162 | list($uri) = $args; 163 | $cli_config = $this->process_args( $uri, $assoc_args ); 164 | ( new Install() )->install( 'theme', $cli_config ); 165 | 166 | $headers = parse_url( $uri, PHP_URL_PATH ); 167 | $slug = basename( $headers ); 168 | $this->process_branch( $cli_config, $slug ); 169 | WP_CLI::success( sprintf( 'Theme %s installed.', "'$slug'" ) ); 170 | } 171 | 172 | /** 173 | * Branch switching via WP-CLI. 174 | * 175 | * ## OPTIONS 176 | * 177 | * 178 | * : Slug of the repo being installed 179 | * 180 | * 181 | * : String indicating the branch name to be installed 182 | * --- 183 | * default: master 184 | * --- 185 | * 186 | * ## EXAMPLES 187 | * 188 | * wp plugin branch-switch 189 | * 190 | * wp theme branch-switch 191 | * 192 | * @param string $args Repository slug. 193 | * 194 | * @subcommand branch-switch 195 | */ 196 | public function branch_switch( $args = null ) { 197 | list( $slug, $branch ) = $args; 198 | $plugins = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs(); 199 | $themes = Singleton::get_instance( 'Fragen\Git_Updater\Theme', $this )->get_theme_configs(); 200 | $configs = array_merge( $plugins, $themes ); 201 | 202 | $repo = isset( $configs[ $slug ] ) ? $configs[ $slug ] : false; 203 | if ( ! $repo ) { 204 | WP_CLI::error( sprintf( 'There is no repository with slug: %s installed.', "'{$slug}'" ) ); 205 | exit; 206 | } 207 | 208 | $rest_api_key = Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_class_vars( 'Remote_Management', 'api_key' ); 209 | $api_url = add_query_arg( 210 | [ 211 | 'key' => $rest_api_key, 212 | $repo->type => $repo->slug, 213 | 'branch' => $branch, 214 | 'override' => true, 215 | ], 216 | home_url( 'wp-json/' . Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_class_vars( 'REST\REST_API', 'namespace' ) . '/update/' ) 217 | ); 218 | $response = wp_remote_get( $api_url, [ 'timeout' => 10 ] ); 219 | 220 | if ( is_wp_error( $response ) ) { 221 | WP_CLI::error( $response->errors['http_request_failed'][0] ); 222 | exit; 223 | } 224 | if ( 200 === \wp_remote_retrieve_response_code( $response ) ) { 225 | WP_CLI::success( $response['body'] ); 226 | } else { 227 | WP_CLI::warning( 'Branch switching resulted in an error.' ); 228 | WP_CLI::warning( $response['body'] ); 229 | } 230 | } 231 | 232 | /** 233 | * Process WP-CLI config data. 234 | * 235 | * @param string $uri URI to process. 236 | * @param array $assoc_args Args to process. 237 | * 238 | * @return array $cli_config 239 | */ 240 | private function process_args( $uri, $assoc_args ) { 241 | $token = isset( $assoc_args['token'] ) ? $assoc_args['token'] : false; 242 | $cli_config = []; 243 | $cli_config['uri'] = $uri; 244 | $cli_config['private'] = $token; 245 | $cli_config['branch'] = isset( $assoc_args['branch'] ) ? $assoc_args['branch'] : 'master'; 246 | $cli_config['slug'] = isset( $assoc_args['slug'] ) ? $assoc_args['slug'] : null; 247 | 248 | switch ( $assoc_args ) { 249 | case isset( $assoc_args['github'] ): 250 | $cli_config['git'] = 'github'; 251 | break; 252 | case isset( $assoc_args['bitbucket'] ): 253 | $cli_config['git'] = 'bitbucket'; 254 | break; 255 | case isset( $assoc_args['gitlab'] ): 256 | $cli_config['git'] = 'gitlab'; 257 | break; 258 | case isset( $assoc_args['gitea'] ): 259 | $cli_config['git'] = 'gitea'; 260 | break; 261 | case isset( $assoc_args['gist'] ): 262 | $cli_config['git'] = 'gist'; 263 | break; 264 | case isset( $assoc_args['zipfile'] ): 265 | $cli_config['git'] = 'zipfile'; 266 | break; 267 | } 268 | 269 | return $cli_config; 270 | } 271 | 272 | /** 273 | * Process branch setting for WP-CLI. 274 | * 275 | * @param array $cli_config Config args. 276 | * @param string $slug Repository slug. 277 | */ 278 | private function process_branch( $cli_config, $slug ) { 279 | $branch_data['git_updater_branch'] = $cli_config['branch']; 280 | $branch_data['repo'] = $slug; 281 | 282 | ( new Branch() )->set_branch_on_install( $branch_data ); 283 | } 284 | } 285 | 286 | /** 287 | * Use custom installer skins to display error messages. 288 | */ 289 | require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; 290 | 291 | /** 292 | * Class GitHub_Upgrader_CLI_Plugin_Installer_Skin 293 | */ 294 | // phpcs:ignore 295 | class CLI_Plugin_Installer_Skin extends \Plugin_Installer_Skin { 296 | 297 | /** Skin feeback. */ 298 | public function header() { 299 | } 300 | 301 | /** Skin footer. */ 302 | public function footer() { 303 | } 304 | 305 | /** 306 | * Skin error. 307 | * 308 | * @param \stdClass $errors Error object. 309 | * 310 | * @return void 311 | */ 312 | public function error( $errors ) { 313 | if ( is_wp_error( $errors ) ) { 314 | WP_CLI::error( $errors->get_error_message() . "\n" . $errors->get_error_data() ); 315 | } 316 | } 317 | 318 | /** 319 | * Skin feedback. 320 | * 321 | * @param string $string Feedback message. 322 | * @param array ...$args Feedback args. 323 | * 324 | * @return void 325 | */ 326 | public function feedback( $string, ...$args ) { 327 | } 328 | } 329 | 330 | /** 331 | * Class GitHub_Upgrader_CLI_Theme_Installer_Skin 332 | */ 333 | // phpcs:ignore 334 | class CLI_Theme_Installer_Skin extends \Theme_Installer_Skin { 335 | /** Skin header. */ 336 | public function header() { 337 | } 338 | 339 | /** Skin footer. */ 340 | public function footer() { 341 | } 342 | 343 | /** 344 | * Skin error. 345 | * 346 | * @param \stdClass $errors Error object. 347 | * 348 | * @return void 349 | */ 350 | public function error( $errors ) { 351 | if ( is_wp_error( $errors ) ) { 352 | WP_CLI::error( $errors->get_error_message() . "\n" . $errors->get_error_data() ); 353 | } 354 | } 355 | 356 | /** 357 | * Skin feedback. 358 | * 359 | * @param string $string Feedback message. 360 | * @param array ...$args Feedback args. 361 | * 362 | * @return void 363 | */ 364 | public function feedback( $string, ...$args ) { 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/Git_Updater_PRO/REST/REST_API.php: -------------------------------------------------------------------------------- 1 | true, 47 | 'methods' => \WP_REST_Server::READABLE, 48 | 'callback' => [ $this, 'test' ], 49 | 'permission_callback' => '__return_true', 50 | ] 51 | ); 52 | 53 | register_rest_route( 54 | 'git-updater', 55 | 'namespace', 56 | [ 57 | 'show_in_index' => true, 58 | 'methods' => \WP_REST_Server::READABLE, 59 | 'callback' => [ $this, 'get_namespace' ], 60 | 'permission_callback' => '__return_true', 61 | ] 62 | ); 63 | 64 | register_rest_route( 65 | self::$namespace, 66 | 'repos', 67 | [ 68 | 'show_in_index' => false, 69 | 'methods' => \WP_REST_Server::READABLE, 70 | 'callback' => [ $this, 'get_remote_repo_data' ], 71 | 'permission_callback' => '__return_true', 72 | 'args' => [ 73 | 'key' => [ 74 | 'default' => null, 75 | 'required' => true, 76 | 'validate_callback' => 'sanitize_text_field', 77 | ], 78 | ], 79 | ] 80 | ); 81 | 82 | register_rest_route( 83 | self::$namespace, 84 | 'plugins-api', 85 | [ 86 | 'show_in_index' => true, 87 | 'methods' => \WP_REST_Server::READABLE, 88 | 'callback' => [ $this, 'get_plugins_api_data' ], 89 | 'permission_callback' => '__return_true', 90 | 'args' => [ 91 | 'slug' => [ 92 | 'default' => false, 93 | 'required' => true, 94 | 'validate_callback' => 'sanitize_title_with_dashes', 95 | ], 96 | ], 97 | ] 98 | ); 99 | 100 | $update_args = [ 101 | 'key' => [ 102 | 'default' => false, 103 | 'required' => true, 104 | 'validate_callback' => 'sanitize_text_field', 105 | ], 106 | 'plugin' => [ 107 | 'default' => false, 108 | 'validate_callback' => 'sanitize_title_with_dashes', 109 | ], 110 | 'theme' => [ 111 | 'default' => false, 112 | 'validate_callback' => 'sanitize_title_with_dashes', 113 | ], 114 | 'tag' => [ 115 | 'default' => false, 116 | 'validate_callback' => 'sanitize_text_field', 117 | ], 118 | 'branch' => [ 119 | 'default' => false, 120 | 'validate_callback' => 'sanitize_text_field', 121 | ], 122 | 'committish' => [ 123 | 'default' => false, 124 | 'validate_callback' => 'sanitize_text_field', 125 | ], 126 | 'override' => [ 127 | 'default' => false, 128 | ], 129 | ]; 130 | 131 | register_rest_route( 132 | self::$namespace, 133 | 'update', 134 | [ 135 | [ 136 | 'show_in_index' => true, 137 | 'methods' => \WP_REST_Server::READABLE, 138 | 'callback' => [ new REST_Update(), 'process_request' ], 139 | 'permission_callback' => '__return_true', 140 | 'args' => $update_args, 141 | ], 142 | [ 143 | 'show_in_index' => false, 144 | 'methods' => \WP_REST_Server::CREATABLE, 145 | 'callback' => [ new REST_Update(), 'process_request' ], 146 | 'permission_callback' => '__return_true', 147 | 'args' => $update_args, 148 | ], 149 | ] 150 | ); 151 | 152 | register_rest_route( 153 | self::$namespace, 154 | 'reset-branch', 155 | [ 156 | 'show_in_index' => true, 157 | 'methods' => \WP_REST_Server::READABLE, 158 | 'callback' => [ $this, 'reset_branch' ], 159 | 'permission_callback' => '__return_true', 160 | 'args' => [ 161 | 'key' => [ 162 | 'default' => null, 163 | 'required' => true, 164 | 'validate_callback' => 'sanitize_text_field', 165 | ], 166 | 'plugin' => [ 167 | 'default' => false, 168 | 'validate_callback' => 'sanitize_title_with_dashes', 169 | ], 170 | 'theme' => [ 171 | 'default' => false, 172 | 'validate_callback' => 'sanitize_title_with_dashes', 173 | ], 174 | ], 175 | ] 176 | ); 177 | 178 | register_rest_route( 179 | 'github-updater/v1', 180 | 'test', 181 | [ 182 | 'methods' => \WP_REST_Server::READABLE, 183 | 'callback' => [ $this, 'deprecated' ], 184 | 'permission_callback' => '__return_true', 185 | ] 186 | ); 187 | 188 | register_rest_route( 189 | 'github-updater/v1', 190 | 'repos', 191 | [ 192 | 'methods' => \WP_REST_Server::READABLE, 193 | 'callback' => [ $this, 'deprecated' ], 194 | 'permission_callback' => '__return_true', 195 | ] 196 | ); 197 | register_rest_route( 198 | 'github-updater/v1', 199 | 'update', 200 | [ 201 | [ 202 | 'show_in_index' => false, 203 | 'methods' => \WP_REST_Server::READABLE, 204 | 'callback' => [ new REST_Update(), 'process_request' ], 205 | 'permission_callback' => '__return_true', 206 | 'args' => $update_args, 207 | ], 208 | [ 209 | 'show_in_index' => false, 210 | 'methods' => \WP_REST_Server::CREATABLE, 211 | 'callback' => [ new REST_Update(), 'process_request' ], 212 | 'permission_callback' => '__return_true', 213 | 'args' => $update_args, 214 | ], 215 | ] 216 | ); 217 | } 218 | 219 | /** 220 | * Return deprecation notice. 221 | * 222 | * @return array 223 | */ 224 | public function deprecated() { 225 | $namespace = self::$namespace; 226 | return [ 227 | 'success' => false, 228 | 'error' => "The 'github-updater/v1' REST route namespace has been deprecated. Please use '{$namespace}'", 229 | ]; 230 | } 231 | 232 | /** 233 | * Simple REST endpoint return. 234 | * 235 | * @return string 236 | */ 237 | public function test() { 238 | return 'Connected to Git Updater PRO!'; 239 | } 240 | 241 | /** 242 | * Return current REST namespace. 243 | * 244 | * @return array 245 | */ 246 | public function get_namespace() { 247 | return [ 'namespace' => self::$namespace ]; 248 | } 249 | 250 | /** 251 | * Get repo data for Git Remote Updater. 252 | * 253 | * @param \WP_REST_Request $request REST API response. 254 | * 255 | * @return array 256 | */ 257 | public function get_remote_repo_data( \WP_REST_Request $request ) { 258 | // Test for API key and exit if incorrect. 259 | if ( $this->get_class_vars( 'Remote_Management', 'api_key' ) !== $request->get_param( 'key' ) ) { 260 | return [ 'error' => 'Bad API key. No repo data for you.' ]; 261 | } 262 | $gu_plugins = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs(); 263 | $gu_themes = Singleton::get_instance( 'Fragen\Git_Updater\Theme', $this )->get_theme_configs(); 264 | $gu_tokens = array_merge( $gu_plugins, $gu_themes ); 265 | 266 | $plugin_updates = get_site_option( 'git_updater_plugin_updates' ); 267 | $theme_updates = get_site_option( 'git_updater_theme_updates' ); 268 | 269 | $site = $request->get_header( 'host' ); 270 | $api_url = add_query_arg( 271 | [ 272 | 'key' => $request->get_param( 'key' ), 273 | ], 274 | home_url( 'wp-json/' . self::$namespace . '/update/' ) 275 | ); 276 | foreach ( $gu_tokens as $token ) { 277 | $update_package = false; 278 | if ( 'plugin' === $token->type && array_key_exists( $token->file, (array) $plugin_updates ) ) { 279 | $update_package = $plugin_updates[ $token->file ]; 280 | } 281 | if ( 'theme' === $token->type && array_key_exists( $token->slug, (array) $theme_updates ) ) { 282 | $update_package = $theme_updates[ $token->slug ]; 283 | } 284 | $slugs[] = [ 285 | 'slug' => $token->slug, 286 | 'type' => $token->type, 287 | 'primary_branch' => $token->primary_branch, 288 | 'branch' => $token->branch, 289 | 'version' => $token->local_version, 290 | 'update_package' => $update_package, 291 | ]; 292 | } 293 | $json = [ 294 | 'sites' => [ 295 | 'site' => $site, 296 | 'restful_start' => $api_url, 297 | 'slugs' => $slugs, 298 | ], 299 | ]; 300 | 301 | return $json; 302 | } 303 | 304 | /** 305 | * Get specific repo plugin API data. 306 | * 307 | * Returns data consistent with `plugins_api()` request. 308 | * 309 | * @param \WP_REST_Request $request REST API response. 310 | * 311 | * @return array|\WP_Error 312 | */ 313 | public function get_plugins_api_data( \WP_REST_Request $request ) { 314 | $slug = $request->get_param( 'slug' ); 315 | if ( ! $slug ) { 316 | return (object) [ 'error' => 'The REST request likely has an invalid query argument.' ]; 317 | } 318 | $repo_cache = $this->get_repo_cache( $slug ); 319 | $gu_plugins = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs(); 320 | 321 | if ( ! \array_key_exists( $slug, $gu_plugins ) ) { 322 | return (object) [ 'error' => 'Specified plugin does not exist.' ]; 323 | } 324 | 325 | add_filter( 'gu_disable_wpcron', '__return_false' ); 326 | $repo_data = Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_remote_repo_meta( $gu_plugins[ $slug ] ); 327 | 328 | if ( ! is_object( $repo_data ) ) { 329 | return (object) [ 'error' => 'Plugin data response is incorrect.' ]; 330 | } 331 | 332 | $plugins_api_data = [ 333 | 'name' => $repo_data->name, 334 | 'slug' => $repo_data->slug, 335 | 'git' => $repo_data->git, 336 | 'type' => $repo_data->type, 337 | 'version' => $repo_data->remote_version, 338 | 'author' => $repo_data->author, 339 | 'contributors' => $repo_data->contributors, 340 | 'requires' => $repo_data->requires, 341 | 'tested' => $repo_data->tested, 342 | 'requires_php' => $repo_data->requires_php, 343 | 'sections' => $repo_data->sections, 344 | // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags 345 | 'short_description' => substr( strip_tags( trim( $repo_data->sections['description'] ) ), 0, 175 ) . '...', 346 | 'primary_branch' => $repo_data->primary_branch, 347 | 'branch' => $repo_data->branch, 348 | 'download_link' => $repo_data->download_link, 349 | 'banners' => $repo_data->banners, 350 | 'icons' => $repo_data->icons, 351 | 'last_updated' => $repo_data->last_updated, 352 | 'num_ratings' => $repo_data->num_ratings, 353 | 'rating' => $repo_data->rating, 354 | 'active_installs' => $repo_data->downloaded, 355 | ]; 356 | 357 | if ( $repo_data->release_asset ) { 358 | if ( property_exists( $repo_cache['release_asset_response'], 'browser_download_url' ) ) { 359 | $plugins_api_data['download_link'] = $repo_cache['release_asset_response']->browser_download_url; 360 | $plugins_api_data['active_installs'] = $repo_cache['release_asset_response']->download_count; 361 | } elseif ( $repo_cache['release_asset'] ) { 362 | $plugins_api_data['download_link'] = $repo_cache['release_asset']; 363 | } 364 | } 365 | 366 | return $plugins_api_data; 367 | } 368 | 369 | /** 370 | * Reset branch of plugin/theme by removing from saved options. 371 | * 372 | * @param \WP_REST_Request $request REST API response. 373 | * 374 | * @throws \UnexpectedValueException Under multiple bad or missing params. 375 | * @return void 376 | */ 377 | public function reset_branch( \WP_REST_Request $request ) { 378 | $rest_update = new Rest_Update(); 379 | $start = microtime( true ); 380 | 381 | try { 382 | // Test for API key and exit if incorrect. 383 | if ( $this->get_class_vars( 'Remote_Management', 'api_key' ) !== $request->get_param( 'key' ) ) { 384 | throw new \UnexpectedValueException( 'Bad API key. No branch reset for you.' ); 385 | } 386 | 387 | $plugin_slug = $request->get_param( 'plugin' ); 388 | $theme_slug = $request->get_param( 'theme' ); 389 | $options = $this->get_class_vars( 'Base', 'options' ); 390 | $slug = ! empty( $plugin_slug ) ? $plugin_slug : $theme_slug; 391 | 392 | if ( empty( $plugin_slug ) && empty( $theme_slug ) || ! isset( $options[ $slug ] ) ) { 393 | throw new \UnexpectedValueException( 'No plugin or theme specified for branch reset.' ); 394 | } 395 | 396 | $this->set_repo_cache( 'current_branch', '', $slug ); 397 | unset( $options[ "current_branch_$slug" ] ); 398 | update_site_option( 'git_updater', $options ); 399 | 400 | $response = [ 401 | 'success' => true, 402 | 'messages' => 'Reset to primary branch complete.', 403 | 'webhook' => $_GET, // phpcs:ignore WordPress.Security.NonceVerification 404 | 'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms', 405 | ]; 406 | $rest_update->log_exit( $response, 200 ); 407 | 408 | } catch ( \Exception $e ) { 409 | $response = [ 410 | 'success' => false, 411 | 'messages' => $e->getMessage(), 412 | 'webhook' => $_GET, // phpcs:ignore WordPress.Security.NonceVerification 413 | 'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms', 414 | ]; 415 | $rest_update->log_exit( $response, 417 ); 416 | } 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/Git_Updater_PRO/Install.php: -------------------------------------------------------------------------------- 1 | / directly from Git Updater. 30 | */ 31 | class Install { 32 | use GU_Trait, Basic_Auth_Loader; 33 | 34 | /** 35 | * Class options. 36 | * 37 | * @var array 38 | */ 39 | protected static $install = []; 40 | 41 | /** 42 | * Hold local copy of Git Updater options. 43 | * 44 | * @var mixed 45 | */ 46 | private static $options; 47 | 48 | /** 49 | * Hold local copy of installed APIs. 50 | * 51 | * @var mixed 52 | */ 53 | private static $installed_apis; 54 | 55 | /** 56 | * Hold local copy of git servers. 57 | * 58 | * @var mixed 59 | */ 60 | private static $git_servers; 61 | 62 | /** 63 | * Constructor. 64 | */ 65 | public function __construct() { 66 | self::$options = $this->get_class_vars( 'Fragen\Git_Updater\Base', 'options' ); 67 | self::$installed_apis = $this->get_class_vars( 'Fragen\Git_Updater\Base', 'installed_apis' ); 68 | self::$git_servers = $this->get_class_vars( 'Fragen\Git_Updater\Base', 'git_servers' ); 69 | } 70 | 71 | /** 72 | * Let's set up the Install tabs. 73 | * Need class-wp-upgrader.php for upgrade classes. 74 | * 75 | * @return void 76 | */ 77 | public function run() { 78 | $this->load_js(); 79 | $this->add_settings_tabs(); 80 | require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; 81 | } 82 | 83 | /** 84 | * Load javascript for Install. 85 | * 86 | * @return void 87 | */ 88 | public function load_js() { 89 | add_action( 90 | 'admin_enqueue_scripts', 91 | function () { 92 | wp_register_script( 'gu-install', plugins_url( basename( dirname( __DIR__, 2 ) ) . '/js/gu-install-vanilla.js' ), [], $this->get_plugin_version(), true ); 93 | wp_enqueue_script( 'gu-install' ); 94 | } 95 | ); 96 | } 97 | 98 | /** 99 | * Adds Install tabs to Settings page. 100 | */ 101 | public function add_settings_tabs() { 102 | $install_tabs = []; 103 | if ( current_user_can( 'install_plugins' ) ) { 104 | $install_tabs['git_updater_install_plugin'] = esc_html__( 'Install Plugin', 'git-updater-pro' ); 105 | } 106 | if ( current_user_can( 'install_themes' ) ) { 107 | $install_tabs['git_updater_install_theme'] = esc_html__( 'Install Theme', 'git-updater-pro' ); 108 | } 109 | add_filter( 110 | 'gu_add_settings_tabs', 111 | function ( $tabs ) use ( $install_tabs ) { 112 | return array_merge( $tabs, $install_tabs ); 113 | } 114 | ); 115 | add_action( 116 | 'gu_add_admin_page', 117 | function ( $tab ) { 118 | $this->add_admin_page( $tab ); 119 | } 120 | ); 121 | } 122 | 123 | /** 124 | * Add Settings page data via action hook. 125 | * 126 | * @uses 'gu_add_admin_page' action hook 127 | * 128 | * @param string $tab Name of tab. 129 | */ 130 | public function add_admin_page( $tab ) { 131 | if ( 'git_updater_install_plugin' === $tab ) { 132 | $this->install( 'plugin' ); 133 | $this->create_form( 'plugin' ); 134 | } 135 | if ( 'git_updater_install_theme' === $tab ) { 136 | $this->install( 'theme' ); 137 | $this->create_form( 'theme' ); 138 | } 139 | } 140 | 141 | /** 142 | * Install remote plugin or theme. 143 | * 144 | * @param string $type plugin|theme. 145 | * @param array $config Array of data. 146 | * 147 | * @return bool 148 | */ 149 | public function install( $type, $config = null ) { 150 | if ( self::is_wp_cli() ) { 151 | $this->set_install_post_data( $config ); 152 | } 153 | 154 | // phpcs:disable WordPress.Security.NonceVerification.Missing 155 | if ( isset( $_POST['option_page'] ) && 'git_updater_install' === $_POST['option_page'] ) { 156 | if ( empty( $_POST['git_updater_branch'] ) ) { 157 | $_POST['git_updater_branch'] = 'master'; 158 | } 159 | 160 | // Exit early if no repo entered. 161 | if ( empty( $_POST['git_updater_repo'] ) ) { 162 | echo '

'; 163 | esc_html_e( 'A repository URI is required.', 'git-updater-pro' ); 164 | echo '

'; 165 | 166 | return false; 167 | } 168 | 169 | // Transform URI to owner/repo. 170 | $headers = $this->parse_header_uri( sanitize_text_field( wp_unslash( $_POST['git_updater_repo'] ) ) ); 171 | $_POST['git_updater_repo'] = $headers['owner_repo']; 172 | 173 | self::$install = $this->sanitize( $_POST ); 174 | // phpcs:ignore Squiz.PHP.DisallowMultipleAssignments.Found 175 | self::$install['repo'] = self::$install['git_updater_install_repo'] = $headers['repo']; 176 | // phpcs:enable 177 | 178 | /* 179 | * Create GitHub endpoint. 180 | * Save Access Token if present. 181 | * Check for GitHub Self-Hosted. 182 | */ 183 | if ( 'github' === self::$install['git_updater_api'] ) { 184 | self::$install = Singleton::get_instance( 'Fragen\Git_Updater\API\GitHub_API', $this, new \stdClass() )->remote_install( $headers, self::$install ); 185 | } 186 | 187 | /** 188 | * Filter to create git host specific endpoint. 189 | * 190 | * @since 10.0.0 191 | * @param array self::$install Array of installation data. 192 | * @param array $headers Array of repo header data. 193 | */ 194 | self::$install = apply_filters( 'gu_install_remote_install', self::$install, $headers ); 195 | 196 | if ( isset( self::$install['options'] ) ) { 197 | $this->save_options_on_install( self::$install['options'] ); 198 | } 199 | 200 | $url = self::$install['download_link']; 201 | $upgrader = $this->get_upgrader( $type, $url ); 202 | 203 | // Ensure authentication headers are present for download packages. 204 | add_filter( 'http_request_args', [ $this, 'download_package' ], 15, 2 ); 205 | 206 | // Install the repo from the $source urldecode() and save branch setting. 207 | if ( $upgrader && $upgrader->install( $url ) ) { 208 | ( new Branch() )->set_branch_on_install( self::$install ); 209 | } else { 210 | return false; 211 | } 212 | } 213 | 214 | return true; 215 | } 216 | 217 | /** 218 | * Save options set during installation. 219 | * 220 | * @param array $install_options Array of options from remote install process. 221 | * @return void 222 | */ 223 | private function save_options_on_install( $install_options ) { 224 | self::$options = array_merge( self::$options, $install_options ); 225 | update_site_option( 'git_updater', self::$options ); 226 | } 227 | 228 | /** 229 | * Set remote install data into $_POST. 230 | * 231 | * @param array $config Data for a remote install. 232 | */ 233 | private function set_install_post_data( $config ) { 234 | if ( ! isset( $config['uri'] ) ) { 235 | return; 236 | } 237 | 238 | $headers = $this->parse_header_uri( $config['uri'] ); 239 | $api = false !== strpos( $headers['host'], '.com' ) 240 | ? rtrim( $headers['host'], '.com' ) 241 | : rtrim( $headers['host'], '.org' ); 242 | 243 | $api = isset( $config['git'] ) ? $config['git'] : $api; 244 | 245 | $_POST['git_updater_repo'] = $config['uri']; 246 | $_POST['git_updater_branch'] = $config['branch']; 247 | $_POST['git_updater_api'] = $api; 248 | $_POST['option_page'] = 'git_updater_install'; 249 | $_POST[ "{$api}_access_token" ] = $config['private'] ?: null; 250 | 251 | if ( 'zipfile' === $config['git'] ) { 252 | $_POST['zipfile_slug'] = $config['slug']; 253 | } 254 | } 255 | 256 | /** 257 | * Get the appropriate upgrader for remote installation. 258 | * 259 | * @param string $type 'plugin' | 'theme'. 260 | * @param string $url URL of the repository to be installed. 261 | * 262 | * @return bool|\Plugin_Upgrader|\Theme_Upgrader 263 | */ 264 | private function get_upgrader( $type, $url ) { 265 | $nonce = wp_nonce_url( $url ); 266 | $upgrader = false; 267 | 268 | if ( 'plugin' === $type ) { 269 | $plugin = self::$install['repo']; 270 | 271 | // Create a new instance of Plugin_Upgrader. 272 | $skin = static::is_wp_cli() 273 | ? new CLI_Plugin_Installer_Skin() 274 | : new \Plugin_Installer_Skin( compact( 'type', 'url', 'nonce', 'plugin' ) ); 275 | $upgrader = new \Plugin_Upgrader( $skin ); 276 | } 277 | 278 | if ( 'theme' === $type ) { 279 | $theme = self::$install['repo']; 280 | 281 | // Create a new instance of Theme_Upgrader. 282 | $skin = static::is_wp_cli() 283 | ? new CLI_Theme_Installer_Skin() 284 | : new \Theme_Installer_Skin( compact( 'type', 'url', 'nonce', 'theme' ) ); 285 | $upgrader = new \Theme_Upgrader( $skin ); 286 | add_filter( 287 | 'install_theme_complete_actions', 288 | [ 289 | $this, 290 | 'install_theme_complete_actions', 291 | ], 292 | 10, 293 | 3 294 | ); 295 | } 296 | 297 | return $upgrader; 298 | } 299 | 300 | /** 301 | * Create Install Plugin or Install Theme page. 302 | * 303 | * @param string $type (plugin|theme). 304 | */ 305 | public function create_form( $type ) { 306 | // Bail if installing. 307 | // phpcs:ignore WordPress.Security.NonceVerification.Missing 308 | if ( isset( $_POST['option_page'] ) && 'git_updater_install' === $_POST['option_page'] ) { 309 | return; 310 | } 311 | 312 | $this->register_settings( $type ); ?> 313 |
314 | 324 |
325 | get_running_git_servers(); 394 | $servers_not_running = array_diff( array_flip( self::$git_servers ), $running_servers ); 395 | if ( ! empty( $servers_not_running ) ) { 396 | foreach ( array_keys( $servers_not_running ) as $server ) { 397 | $class = 'Fragen\\Git_Updater\\API\\' . $server . '_API'; 398 | Singleton::get_instance( $class, $this )->add_install_settings_fields( $type ); 399 | } 400 | } 401 | } 402 | 403 | /** 404 | * Repo setting. 405 | */ 406 | public function get_repo() { 407 | ?> 408 | 415 | 423 | 430 | 438 | 449 | 'activate', 470 | // 'template' => rawurlencode( $template ), 471 | 'stylesheet' => rawurlencode( $stylesheet ), 472 | ], 473 | admin_url( 'themes.php' ) 474 | ); 475 | $activate_link = esc_url( wp_nonce_url( $activate_link, 'switch-theme_' . $stylesheet ) ); 476 | 477 | $install_actions['activate'] = '' . esc_attr__( 'Activate', 'git-updater-pro' ) . ' “' . $stylesheet . '”'; 478 | 479 | if ( is_network_admin() && current_user_can( 'manage_network_themes' ) ) { 480 | $network_activate_link = add_query_arg( 481 | [ 482 | 'action' => 'enable', 483 | 'theme' => rawurlencode( $stylesheet ), 484 | ], 485 | network_admin_url( 'themes.php' ) 486 | ); 487 | $network_activate_link = esc_url( wp_nonce_url( $network_activate_link, 'enable-theme_' . $stylesheet ) ); 488 | 489 | $install_actions['network_enable'] = '' . esc_attr_x( 'Network Enable', 'This refers to a network activation in a multisite installation', 'git-updater-pro' ) . ''; 490 | unset( $install_actions['activate'] ); 491 | } 492 | ksort( $install_actions ); 493 | 494 | return $install_actions; 495 | } 496 | } 497 | -------------------------------------------------------------------------------- /src/Git_Updater_PRO/REST/Rest_Update.php: -------------------------------------------------------------------------------- 1 | load_options(); 63 | $this->upgrader_skin = new Rest_Upgrader_Skin(); 64 | 65 | // phpcs:ignore WordPress.Security.NonceVerification 66 | self::$request = $this->sanitize( $_REQUEST ); 67 | } 68 | 69 | /** 70 | * Update plugin. 71 | * 72 | * @param string $plugin_slug Plugin slug. 73 | * @param string $tag Plugin tag/branch. 74 | * 75 | * @throws \UnexpectedValueException Plugin not found or not updatable. 76 | */ 77 | public function update_plugin( $plugin_slug, $tag = 'master' ) { 78 | $plugin = null; 79 | $is_plugin_active = false; 80 | 81 | foreach ( (array) Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs() as $config_entry ) { 82 | if ( $config_entry->slug === $plugin_slug ) { 83 | $plugin = $config_entry; 84 | break; 85 | } 86 | } 87 | 88 | if ( ! $plugin ) { 89 | throw new \UnexpectedValueException( 'Plugin not found or not updatable with Git Updater: ' . $plugin_slug ); 90 | } 91 | 92 | if ( is_plugin_active( $plugin->file ) ) { 93 | $is_plugin_active = true; 94 | } 95 | 96 | Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_remote_repo_meta( $plugin ); 97 | $repo_api = Singleton::get_instance( 'Fragen\Git_Updater\API\API', $this )->get_repo_api( $plugin->git, $plugin ); 98 | 99 | $update = [ 100 | 'slug' => $plugin->slug, 101 | 'plugin' => $plugin->file, 102 | 'new_version' => null, 103 | 'url' => $plugin->uri, 104 | 'package' => $repo_api->construct_download_link( $tag ), 105 | ]; 106 | 107 | add_filter( 108 | 'site_transient_update_plugins', 109 | function ( $current ) use ( $plugin, $update ) { 110 | // needed to fix PHP 7.4 warning. 111 | if ( ! \is_object( $current ) ) { 112 | $current = new \stdClass(); 113 | $current->response = null; 114 | } elseif ( ! \property_exists( $current, 'response' ) ) { 115 | $current->response = null; 116 | } 117 | 118 | $current->response[ $plugin->file ] = (object) $update; 119 | 120 | return $current; 121 | } 122 | ); 123 | 124 | // Add authentication header to download package. 125 | add_filter( 'http_request_args', [ Singleton::get_instance( 'Fragen\Git_Updater\API\API', $this ), 'download_package' ], 15, 2 ); 126 | 127 | $upgrader = new \Plugin_Upgrader( $this->upgrader_skin ); 128 | $upgrader->upgrade( $plugin->file ); 129 | 130 | if ( $is_plugin_active ) { 131 | $activate = is_multisite() ? activate_plugin( $plugin->file, null, true ) : activate_plugin( $plugin->file ); 132 | if ( ! $activate ) { 133 | $this->upgrader_skin->messages[] = 'Plugin reactivated successfully.'; 134 | } 135 | } 136 | } 137 | 138 | /** 139 | * Update a single theme. 140 | * 141 | * @param string $theme_slug Theme slug. 142 | * @param string $tag Theme tag/branch. 143 | * 144 | * @throws \UnexpectedValueException Theme not found or not updatable. 145 | */ 146 | public function update_theme( $theme_slug, $tag = 'master' ) { 147 | $theme = null; 148 | 149 | foreach ( (array) Singleton::get_instance( 'Fragen\Git_Updater\Theme', $this )->get_theme_configs() as $config_entry ) { 150 | if ( $config_entry->slug === $theme_slug ) { 151 | $theme = $config_entry; 152 | break; 153 | } 154 | } 155 | 156 | if ( ! $theme ) { 157 | throw new \UnexpectedValueException( 'Theme not found or not updatable with Git Updater: ' . $theme_slug ); 158 | } 159 | 160 | Singleton::get_instance( 'Fragen\Git_Updater\Base', $this )->get_remote_repo_meta( $theme ); 161 | $repo_api = Singleton::get_instance( 'Fragen\Git_Updater\API\API', $this )->get_repo_api( $theme->git, $theme ); 162 | 163 | $update = [ 164 | 'theme' => $theme->slug, 165 | 'new_version' => null, 166 | 'url' => $theme->uri, 167 | 'package' => $repo_api->construct_download_link( $tag ), 168 | ]; 169 | 170 | add_filter( 171 | 'site_transient_update_themes', 172 | function ( $current ) use ( $theme, $update ) { 173 | // needed to fix PHP 7.4 warning. 174 | if ( ! \is_object( $current ) ) { 175 | $current = new \stdClass(); 176 | $current->response = null; 177 | } elseif ( ! \property_exists( $current, 'response' ) ) { 178 | $current->response = null; 179 | } 180 | 181 | $current->response[ $theme->slug ] = $update; 182 | 183 | return $current; 184 | } 185 | ); 186 | 187 | // Add authentication header to download package. 188 | add_filter( 'http_request_args', [ Singleton::get_instance( 'Fragen\Git_Updater\API\API', $this ), 'download_package' ], 15, 2 ); 189 | 190 | $upgrader = new \Theme_Upgrader( $this->upgrader_skin ); 191 | $upgrader->upgrade( $theme->slug ); 192 | } 193 | 194 | /** 195 | * Is there an error? 196 | */ 197 | public function is_error() { 198 | return $this->upgrader_skin->error; 199 | } 200 | 201 | /** 202 | * Get messages during update. 203 | */ 204 | public function get_messages() { 205 | return $this->upgrader_skin->messages; 206 | } 207 | 208 | /** 209 | * Process request. 210 | * 211 | * Relies on data in $_REQUEST, prints out json and exits. 212 | * If the request came through a webhook, and if the branch in the 213 | * webhook matches the branch specified by the url, use the latest 214 | * update available as specified in the webhook payload. 215 | * 216 | * @param \WP_REST_Request|null $request Request data from update webhook. 217 | * 218 | * @throws \UnexpectedValueException Under multiple bad or missing params. 219 | */ 220 | public function process_request( $request = null ) { 221 | $args = $this->process_request_data( $request ); 222 | extract( $args ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract 223 | 224 | $start = microtime( true ); 225 | try { 226 | if ( ! $key 227 | || get_site_option( 'git_updater_api_key' ) !== $key 228 | ) { 229 | throw new \UnexpectedValueException( 'Bad API key.' ); 230 | } 231 | 232 | /** 233 | * Allow access into the REST Update process. 234 | * 235 | * @since 10.0.0 236 | * @access public 237 | */ 238 | do_action_deprecated( 'github_updater_pre_rest_process_request', [], '10.0.0', 'gu_pre_rest_process_request' ); 239 | 240 | /** 241 | * Allow access into the REST Update process. 242 | * 243 | * @since 7.6.0 244 | * @access public 245 | */ 246 | do_action( 'gu_pre_rest_process_request' ); 247 | 248 | $this->get_webhook_source(); 249 | $tag = $committish ? $committish : $tag; 250 | $current_branch = $this->get_local_branch( $plugin, $theme ); 251 | 252 | if ( ! ( 0 === preg_match( self::$version_number_regex, $tag ) ) ) { 253 | $remote_branch = 'master'; 254 | } 255 | if ( $branch ) { 256 | $tag = $branch; 257 | $remote_branch = $branch; 258 | } 259 | $remote_branch = isset( $remote_branch ) ? $remote_branch : $tag; 260 | $current_branch = $override ? $remote_branch : $current_branch; 261 | if ( $remote_branch !== $current_branch && ! $override ) { 262 | throw new \UnexpectedValueException( 'Webhook tag and current branch are not matching. Consider using `override` query arg.' ); 263 | } 264 | 265 | if ( $plugin ) { 266 | $this->update_plugin( $plugin, $tag ); 267 | } elseif ( $theme ) { 268 | $this->update_theme( $theme, $tag ); 269 | } else { 270 | throw new \UnexpectedValueException( 'No plugin or theme specified for update.' ); 271 | } 272 | } catch ( \Exception $e ) { 273 | $http_response = [ 274 | 'success' => false, 275 | 'messages' => $e->getMessage(), 276 | 'webhook' => $_GET, // phpcs:ignore WordPress.Security.NonceVerification 277 | 'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms', 278 | 'deprecated' => $deprecated, 279 | ]; 280 | $this->log_exit( $http_response, 417 ); 281 | } 282 | 283 | // Only set branch on successful update. 284 | if ( ! $this->is_error() ) { 285 | $slug = $plugin ? $plugin : false; 286 | $slug = $theme ? $theme : $slug; 287 | $file = $plugin ? $plugin . '.php' : 'style.css'; 288 | $options = $this->get_class_vars( 'Base', 'options' ); 289 | $cache = $this->get_repo_cache( $slug ); 290 | $cache_key = 'ghu-' . md5( $slug ); 291 | 292 | $cache['current_branch'] = $current_branch; 293 | unset( $cache[ $file ] ); 294 | update_site_option( $cache_key, $cache ); 295 | 296 | $options[ 'current_branch_' . $slug ] = $current_branch; 297 | update_site_option( 'git_updater', $options ); 298 | } 299 | 300 | $response = [ 301 | 'success' => true, 302 | 'messages' => $this->get_messages(), 303 | 'webhook' => $_GET, // phpcs:ignore WordPress.Security.NonceVerification 304 | 'elapsed_time' => round( ( microtime( true ) - $start ) * 1000, 2 ) . ' ms', 305 | 'deprecated' => $deprecated, 306 | ]; 307 | 308 | if ( $this->is_error() ) { 309 | $response['success'] = false; 310 | $this->log_exit( $response, 417 ); 311 | } 312 | $this->log_exit( $response, 200 ); 313 | } 314 | 315 | /** 316 | * Process request data from REST API or RESTful endpoint. 317 | * 318 | * @param \WP_REST_Request|array $request Request data from update webhook. 319 | * 320 | * @return array 321 | */ 322 | public function process_request_data( $request = null ) { 323 | if ( $request instanceof \WP_REST_Request ) { 324 | $params = $request->get_params(); 325 | $slug = $params['plugin'] ?: $params['theme']; 326 | $params['tag'] = $params['tag'] ?: $this->get_primary_branch( $slug ); 327 | extract( $params ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract 328 | $override = false === $override ? false : true; 329 | // TODO: Update for PHP 7.0 330 | // $deprecated = strpos( $request->get_route(), ( new REST_API() )::$namespace ) ? false : true; 331 | $rest_api = new REST_API(); 332 | $deprecated = strpos( $request->get_route(), $rest_api::$namespace ) ? false : true; 333 | if ( $deprecated ) { 334 | $this->upgrader_skin->feedback( ( new REST_API() )->deprecated()['error'] ); 335 | } 336 | } else { // call from admin-ajax.php. 337 | $key = empty( self::$request['key'] ) ? false : self::$request['key']; 338 | $plugin = empty( self::$request['plugin'] ) ? false : self::$request['plugin']; 339 | $theme = empty( self::$request['theme'] ) ? false : self::$request['theme']; 340 | $tag = empty( self::$request['tag'] ) ? 'master' : self::$request['tag']; 341 | $committish = empty( self::$request['committish'] ) ? false : self::$request['committish']; 342 | $branch = empty( self::$request['branch'] ) ? false : self::$request['branch']; 343 | $override = empty( self::$request['override'] ) ? false : self::$request['override']; 344 | $override = false === $override ? false : true; 345 | $deprecated = 'Please update to using the new REST API endpoint. This is now deprecated.'; 346 | } 347 | 348 | $args = compact( 'key', 'plugin', 'theme', 'tag', 'committish', 'branch', 'override', 'deprecated' ); 349 | 350 | return $args; 351 | } 352 | 353 | /** 354 | * Returns the current branch of the local repository referenced in the webhook. 355 | * 356 | * @param string|bool $plugin Plugin slug or false. 357 | * @param string|bool $theme Theme slug or false. 358 | * 359 | * @return string $current_branch Default return is 'master'. 360 | */ 361 | private function get_local_branch( $plugin, $theme ) { 362 | $repo = false; 363 | if ( $plugin ) { 364 | $repos = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this )->get_plugin_configs(); 365 | $repo = isset( $repos[ $plugin ] ) ? $repos[ $plugin ] : false; 366 | } 367 | if ( $theme ) { 368 | $repos = Singleton::get_instance( 'Fragen\Git_Updater\Theme', $this )->get_theme_configs(); 369 | $repo = isset( $repos[ $theme ] ) ? $repos[ $theme ] : false; 370 | } 371 | $current_branch = $repo ? 372 | ( new Branch() )->get_current_branch( $repo ) : 373 | 'master'; 374 | 375 | return $current_branch; 376 | } 377 | 378 | /** 379 | * Returns the Primary Branch header if set, otherwise 'master'. 380 | * 381 | * @param string $slug Repository slug. 382 | * 383 | * @return string 384 | */ 385 | private function get_primary_branch( $slug ) { 386 | $cache = $this->get_repo_cache( $slug ); 387 | $primary_branch = isset( $cache[ $slug ]['PrimaryBranch'] ) ? $cache[ $slug ]['PrimaryBranch'] : 'master'; 388 | 389 | return $primary_branch; 390 | } 391 | 392 | /** 393 | * Sets the source of the webhook to $_GET variable. 394 | */ 395 | private function get_webhook_source() { 396 | switch ( $_SERVER ) { 397 | case isset( $_SERVER['HTTP_X_GITHUB_EVENT'] ): 398 | $webhook_source = 'GitHub webhook'; 399 | break; 400 | case isset( $_SERVER['HTTP_X_EVENT_KEY'] ): 401 | $webhook_source = 'Bitbucket webhook'; 402 | break; 403 | case isset( $_SERVER['HTTP_X_GITLAB_EVENT'] ): 404 | $webhook_source = 'GitLab webhook'; 405 | break; 406 | case isset( $_SERVER['HTTP_X_GITEA_EVENT'] ): 407 | $webhook_source = 'Gitea webhook'; 408 | break; 409 | default: 410 | $webhook_source = 'browser'; 411 | break; 412 | } 413 | $_GET['webhook_source'] = $webhook_source; 414 | } 415 | 416 | /** 417 | * Append $response to debug.log and wp_die(). 418 | * 419 | * 128 == JSON_PRETTY_PRINT 420 | * 64 == JSON_UNESCAPED_SLASHES 421 | * 422 | * @param array $response Response array. 423 | * @param int $code Response code. 424 | */ 425 | public function log_exit( $response, $code ) { 426 | $json_encode_flags = 128 | 64; 427 | 428 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log 429 | error_log( json_encode( $response, $json_encode_flags ) ); 430 | 431 | /** 432 | * Action hook after processing REST process. 433 | * 434 | * @since 8.6.0 435 | * 436 | * @param array $response 437 | * @param int $code HTTP response. 438 | */ 439 | do_action_deprecated( 'github_updater_post_rest_process_request', [ $response, $code ], '10.0.0', 'gu_post_rest_process_request' ); 440 | 441 | /** 442 | * Action hook after processing REST process. 443 | * 444 | * @since 10.0.0 445 | * 446 | * @param array $response 447 | * @param int $code HTTP response. 448 | */ 449 | do_action( 'gu_post_rest_process_request', $response, $code ); 450 | 451 | unset( $response['success'] ); 452 | // phpcs:disable WordPress.Security.EscapeOutput.OutputNotEscaped 453 | if ( 200 === $code ) { 454 | wp_die( wp_send_json_success( $response, $code ) ); 455 | } else { 456 | wp_die( wp_send_json_error( $response, $code ) ); 457 | } 458 | // phpcs:enable 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /vendor/composer/InstalledVersions.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer; 14 | 15 | use Composer\Autoload\ClassLoader; 16 | use Composer\Semver\VersionParser; 17 | 18 | /** 19 | * This class is copied in every Composer installed project and available to all 20 | * 21 | * See also https://getcomposer.org/doc/07-runtime.md#installed-versions 22 | * 23 | * To require its presence, you can require `composer-runtime-api ^2.0` 24 | * 25 | * @final 26 | */ 27 | class InstalledVersions 28 | { 29 | /** 30 | * @var mixed[]|null 31 | * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null 32 | */ 33 | private static $installed; 34 | 35 | /** 36 | * @var bool|null 37 | */ 38 | private static $canGetVendors; 39 | 40 | /** 41 | * @var array[] 42 | * @psalm-var array}> 43 | */ 44 | private static $installedByVendor = array(); 45 | 46 | /** 47 | * Returns a list of all package names which are present, either by being installed, replaced or provided 48 | * 49 | * @return string[] 50 | * @psalm-return list 51 | */ 52 | public static function getInstalledPackages() 53 | { 54 | $packages = array(); 55 | foreach (self::getInstalled() as $installed) { 56 | $packages[] = array_keys($installed['versions']); 57 | } 58 | 59 | if (1 === \count($packages)) { 60 | return $packages[0]; 61 | } 62 | 63 | return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 64 | } 65 | 66 | /** 67 | * Returns a list of all package names with a specific type e.g. 'library' 68 | * 69 | * @param string $type 70 | * @return string[] 71 | * @psalm-return list 72 | */ 73 | public static function getInstalledPackagesByType($type) 74 | { 75 | $packagesByType = array(); 76 | 77 | foreach (self::getInstalled() as $installed) { 78 | foreach ($installed['versions'] as $name => $package) { 79 | if (isset($package['type']) && $package['type'] === $type) { 80 | $packagesByType[] = $name; 81 | } 82 | } 83 | } 84 | 85 | return $packagesByType; 86 | } 87 | 88 | /** 89 | * Checks whether the given package is installed 90 | * 91 | * This also returns true if the package name is provided or replaced by another package 92 | * 93 | * @param string $packageName 94 | * @param bool $includeDevRequirements 95 | * @return bool 96 | */ 97 | public static function isInstalled($packageName, $includeDevRequirements = true) 98 | { 99 | foreach (self::getInstalled() as $installed) { 100 | if (isset($installed['versions'][$packageName])) { 101 | return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); 102 | } 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * Checks whether the given package satisfies a version constraint 110 | * 111 | * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: 112 | * 113 | * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') 114 | * 115 | * @param VersionParser $parser Install composer/semver to have access to this class and functionality 116 | * @param string $packageName 117 | * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package 118 | * @return bool 119 | */ 120 | public static function satisfies(VersionParser $parser, $packageName, $constraint) 121 | { 122 | $constraint = $parser->parseConstraints($constraint); 123 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 124 | 125 | return $provided->matches($constraint); 126 | } 127 | 128 | /** 129 | * Returns a version constraint representing all the range(s) which are installed for a given package 130 | * 131 | * It is easier to use this via isInstalled() with the $constraint argument if you need to check 132 | * whether a given version of a package is installed, and not just whether it exists 133 | * 134 | * @param string $packageName 135 | * @return string Version constraint usable with composer/semver 136 | */ 137 | public static function getVersionRanges($packageName) 138 | { 139 | foreach (self::getInstalled() as $installed) { 140 | if (!isset($installed['versions'][$packageName])) { 141 | continue; 142 | } 143 | 144 | $ranges = array(); 145 | if (isset($installed['versions'][$packageName]['pretty_version'])) { 146 | $ranges[] = $installed['versions'][$packageName]['pretty_version']; 147 | } 148 | if (array_key_exists('aliases', $installed['versions'][$packageName])) { 149 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 150 | } 151 | if (array_key_exists('replaced', $installed['versions'][$packageName])) { 152 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 153 | } 154 | if (array_key_exists('provided', $installed['versions'][$packageName])) { 155 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 156 | } 157 | 158 | return implode(' || ', $ranges); 159 | } 160 | 161 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 162 | } 163 | 164 | /** 165 | * @param string $packageName 166 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 167 | */ 168 | public static function getVersion($packageName) 169 | { 170 | foreach (self::getInstalled() as $installed) { 171 | if (!isset($installed['versions'][$packageName])) { 172 | continue; 173 | } 174 | 175 | if (!isset($installed['versions'][$packageName]['version'])) { 176 | return null; 177 | } 178 | 179 | return $installed['versions'][$packageName]['version']; 180 | } 181 | 182 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 183 | } 184 | 185 | /** 186 | * @param string $packageName 187 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present 188 | */ 189 | public static function getPrettyVersion($packageName) 190 | { 191 | foreach (self::getInstalled() as $installed) { 192 | if (!isset($installed['versions'][$packageName])) { 193 | continue; 194 | } 195 | 196 | if (!isset($installed['versions'][$packageName]['pretty_version'])) { 197 | return null; 198 | } 199 | 200 | return $installed['versions'][$packageName]['pretty_version']; 201 | } 202 | 203 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 204 | } 205 | 206 | /** 207 | * @param string $packageName 208 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference 209 | */ 210 | public static function getReference($packageName) 211 | { 212 | foreach (self::getInstalled() as $installed) { 213 | if (!isset($installed['versions'][$packageName])) { 214 | continue; 215 | } 216 | 217 | if (!isset($installed['versions'][$packageName]['reference'])) { 218 | return null; 219 | } 220 | 221 | return $installed['versions'][$packageName]['reference']; 222 | } 223 | 224 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 225 | } 226 | 227 | /** 228 | * @param string $packageName 229 | * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. 230 | */ 231 | public static function getInstallPath($packageName) 232 | { 233 | foreach (self::getInstalled() as $installed) { 234 | if (!isset($installed['versions'][$packageName])) { 235 | continue; 236 | } 237 | 238 | return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; 239 | } 240 | 241 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 242 | } 243 | 244 | /** 245 | * @return array 246 | * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} 247 | */ 248 | public static function getRootPackage() 249 | { 250 | $installed = self::getInstalled(); 251 | 252 | return $installed[0]['root']; 253 | } 254 | 255 | /** 256 | * Returns the raw installed.php data for custom implementations 257 | * 258 | * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. 259 | * @return array[] 260 | * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} 261 | */ 262 | public static function getRawData() 263 | { 264 | @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); 265 | 266 | if (null === self::$installed) { 267 | // only require the installed.php file if this file is loaded from its dumped location, 268 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 269 | if (substr(__DIR__, -8, 1) !== 'C') { 270 | self::$installed = include __DIR__ . '/installed.php'; 271 | } else { 272 | self::$installed = array(); 273 | } 274 | } 275 | 276 | return self::$installed; 277 | } 278 | 279 | /** 280 | * Returns the raw data of all installed.php which are currently loaded for custom implementations 281 | * 282 | * @return array[] 283 | * @psalm-return list}> 284 | */ 285 | public static function getAllRawData() 286 | { 287 | return self::getInstalled(); 288 | } 289 | 290 | /** 291 | * Lets you reload the static array from another file 292 | * 293 | * This is only useful for complex integrations in which a project needs to use 294 | * this class but then also needs to execute another project's autoloader in process, 295 | * and wants to ensure both projects have access to their version of installed.php. 296 | * 297 | * A typical case would be PHPUnit, where it would need to make sure it reads all 298 | * the data it needs from this class, then call reload() with 299 | * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure 300 | * the project in which it runs can then also use this class safely, without 301 | * interference between PHPUnit's dependencies and the project's dependencies. 302 | * 303 | * @param array[] $data A vendor/composer/installed.php data set 304 | * @return void 305 | * 306 | * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data 307 | */ 308 | public static function reload($data) 309 | { 310 | self::$installed = $data; 311 | self::$installedByVendor = array(); 312 | } 313 | 314 | /** 315 | * @return array[] 316 | * @psalm-return list}> 317 | */ 318 | private static function getInstalled() 319 | { 320 | if (null === self::$canGetVendors) { 321 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 322 | } 323 | 324 | $installed = array(); 325 | 326 | if (self::$canGetVendors) { 327 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 328 | if (isset(self::$installedByVendor[$vendorDir])) { 329 | $installed[] = self::$installedByVendor[$vendorDir]; 330 | } elseif (is_file($vendorDir.'/composer/installed.php')) { 331 | $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; 332 | if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 333 | self::$installed = $installed[count($installed) - 1]; 334 | } 335 | } 336 | } 337 | } 338 | 339 | if (null === self::$installed) { 340 | // only require the installed.php file if this file is loaded from its dumped location, 341 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 342 | if (substr(__DIR__, -8, 1) !== 'C') { 343 | self::$installed = require __DIR__ . '/installed.php'; 344 | } else { 345 | self::$installed = array(); 346 | } 347 | } 348 | $installed[] = self::$installed; 349 | 350 | return $installed; 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /vendor/composer/ClassLoader.php: -------------------------------------------------------------------------------- 1 | 7 | * Jordi Boggiano 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | namespace Composer\Autoload; 14 | 15 | /** 16 | * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. 17 | * 18 | * $loader = new \Composer\Autoload\ClassLoader(); 19 | * 20 | * // register classes with namespaces 21 | * $loader->add('Symfony\Component', __DIR__.'/component'); 22 | * $loader->add('Symfony', __DIR__.'/framework'); 23 | * 24 | * // activate the autoloader 25 | * $loader->register(); 26 | * 27 | * // to enable searching the include path (eg. for PEAR packages) 28 | * $loader->setUseIncludePath(true); 29 | * 30 | * In this example, if you try to use a class in the Symfony\Component 31 | * namespace or one of its children (Symfony\Component\Console for instance), 32 | * the autoloader will first look for the class under the component/ 33 | * directory, and it will then fallback to the framework/ directory if not 34 | * found before giving up. 35 | * 36 | * This class is loosely based on the Symfony UniversalClassLoader. 37 | * 38 | * @author Fabien Potencier 39 | * @author Jordi Boggiano 40 | * @see https://www.php-fig.org/psr/psr-0/ 41 | * @see https://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | /** @var ?string */ 46 | private $vendorDir; 47 | 48 | // PSR-4 49 | /** 50 | * @var array[] 51 | * @psalm-var array> 52 | */ 53 | private $prefixLengthsPsr4 = array(); 54 | /** 55 | * @var array[] 56 | * @psalm-var array> 57 | */ 58 | private $prefixDirsPsr4 = array(); 59 | /** 60 | * @var array[] 61 | * @psalm-var array 62 | */ 63 | private $fallbackDirsPsr4 = array(); 64 | 65 | // PSR-0 66 | /** 67 | * @var array[] 68 | * @psalm-var array> 69 | */ 70 | private $prefixesPsr0 = array(); 71 | /** 72 | * @var array[] 73 | * @psalm-var array 74 | */ 75 | private $fallbackDirsPsr0 = array(); 76 | 77 | /** @var bool */ 78 | private $useIncludePath = false; 79 | 80 | /** 81 | * @var string[] 82 | * @psalm-var array 83 | */ 84 | private $classMap = array(); 85 | 86 | /** @var bool */ 87 | private $classMapAuthoritative = false; 88 | 89 | /** 90 | * @var bool[] 91 | * @psalm-var array 92 | */ 93 | private $missingClasses = array(); 94 | 95 | /** @var ?string */ 96 | private $apcuPrefix; 97 | 98 | /** 99 | * @var self[] 100 | */ 101 | private static $registeredLoaders = array(); 102 | 103 | /** 104 | * @param ?string $vendorDir 105 | */ 106 | public function __construct($vendorDir = null) 107 | { 108 | $this->vendorDir = $vendorDir; 109 | } 110 | 111 | /** 112 | * @return string[] 113 | */ 114 | public function getPrefixes() 115 | { 116 | if (!empty($this->prefixesPsr0)) { 117 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 118 | } 119 | 120 | return array(); 121 | } 122 | 123 | /** 124 | * @return array[] 125 | * @psalm-return array> 126 | */ 127 | public function getPrefixesPsr4() 128 | { 129 | return $this->prefixDirsPsr4; 130 | } 131 | 132 | /** 133 | * @return array[] 134 | * @psalm-return array 135 | */ 136 | public function getFallbackDirs() 137 | { 138 | return $this->fallbackDirsPsr0; 139 | } 140 | 141 | /** 142 | * @return array[] 143 | * @psalm-return array 144 | */ 145 | public function getFallbackDirsPsr4() 146 | { 147 | return $this->fallbackDirsPsr4; 148 | } 149 | 150 | /** 151 | * @return string[] Array of classname => path 152 | * @psalm-return array 153 | */ 154 | public function getClassMap() 155 | { 156 | return $this->classMap; 157 | } 158 | 159 | /** 160 | * @param string[] $classMap Class to filename map 161 | * @psalm-param array $classMap 162 | * 163 | * @return void 164 | */ 165 | public function addClassMap(array $classMap) 166 | { 167 | if ($this->classMap) { 168 | $this->classMap = array_merge($this->classMap, $classMap); 169 | } else { 170 | $this->classMap = $classMap; 171 | } 172 | } 173 | 174 | /** 175 | * Registers a set of PSR-0 directories for a given prefix, either 176 | * appending or prepending to the ones previously set for this prefix. 177 | * 178 | * @param string $prefix The prefix 179 | * @param string[]|string $paths The PSR-0 root directories 180 | * @param bool $prepend Whether to prepend the directories 181 | * 182 | * @return void 183 | */ 184 | public function add($prefix, $paths, $prepend = false) 185 | { 186 | if (!$prefix) { 187 | if ($prepend) { 188 | $this->fallbackDirsPsr0 = array_merge( 189 | (array) $paths, 190 | $this->fallbackDirsPsr0 191 | ); 192 | } else { 193 | $this->fallbackDirsPsr0 = array_merge( 194 | $this->fallbackDirsPsr0, 195 | (array) $paths 196 | ); 197 | } 198 | 199 | return; 200 | } 201 | 202 | $first = $prefix[0]; 203 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 204 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 205 | 206 | return; 207 | } 208 | if ($prepend) { 209 | $this->prefixesPsr0[$first][$prefix] = array_merge( 210 | (array) $paths, 211 | $this->prefixesPsr0[$first][$prefix] 212 | ); 213 | } else { 214 | $this->prefixesPsr0[$first][$prefix] = array_merge( 215 | $this->prefixesPsr0[$first][$prefix], 216 | (array) $paths 217 | ); 218 | } 219 | } 220 | 221 | /** 222 | * Registers a set of PSR-4 directories for a given namespace, either 223 | * appending or prepending to the ones previously set for this namespace. 224 | * 225 | * @param string $prefix The prefix/namespace, with trailing '\\' 226 | * @param string[]|string $paths The PSR-4 base directories 227 | * @param bool $prepend Whether to prepend the directories 228 | * 229 | * @throws \InvalidArgumentException 230 | * 231 | * @return void 232 | */ 233 | public function addPsr4($prefix, $paths, $prepend = false) 234 | { 235 | if (!$prefix) { 236 | // Register directories for the root namespace. 237 | if ($prepend) { 238 | $this->fallbackDirsPsr4 = array_merge( 239 | (array) $paths, 240 | $this->fallbackDirsPsr4 241 | ); 242 | } else { 243 | $this->fallbackDirsPsr4 = array_merge( 244 | $this->fallbackDirsPsr4, 245 | (array) $paths 246 | ); 247 | } 248 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 249 | // Register directories for a new namespace. 250 | $length = strlen($prefix); 251 | if ('\\' !== $prefix[$length - 1]) { 252 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 253 | } 254 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 255 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 256 | } elseif ($prepend) { 257 | // Prepend directories for an already registered namespace. 258 | $this->prefixDirsPsr4[$prefix] = array_merge( 259 | (array) $paths, 260 | $this->prefixDirsPsr4[$prefix] 261 | ); 262 | } else { 263 | // Append directories for an already registered namespace. 264 | $this->prefixDirsPsr4[$prefix] = array_merge( 265 | $this->prefixDirsPsr4[$prefix], 266 | (array) $paths 267 | ); 268 | } 269 | } 270 | 271 | /** 272 | * Registers a set of PSR-0 directories for a given prefix, 273 | * replacing any others previously set for this prefix. 274 | * 275 | * @param string $prefix The prefix 276 | * @param string[]|string $paths The PSR-0 base directories 277 | * 278 | * @return void 279 | */ 280 | public function set($prefix, $paths) 281 | { 282 | if (!$prefix) { 283 | $this->fallbackDirsPsr0 = (array) $paths; 284 | } else { 285 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 286 | } 287 | } 288 | 289 | /** 290 | * Registers a set of PSR-4 directories for a given namespace, 291 | * replacing any others previously set for this namespace. 292 | * 293 | * @param string $prefix The prefix/namespace, with trailing '\\' 294 | * @param string[]|string $paths The PSR-4 base directories 295 | * 296 | * @throws \InvalidArgumentException 297 | * 298 | * @return void 299 | */ 300 | public function setPsr4($prefix, $paths) 301 | { 302 | if (!$prefix) { 303 | $this->fallbackDirsPsr4 = (array) $paths; 304 | } else { 305 | $length = strlen($prefix); 306 | if ('\\' !== $prefix[$length - 1]) { 307 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 308 | } 309 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 310 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 311 | } 312 | } 313 | 314 | /** 315 | * Turns on searching the include path for class files. 316 | * 317 | * @param bool $useIncludePath 318 | * 319 | * @return void 320 | */ 321 | public function setUseIncludePath($useIncludePath) 322 | { 323 | $this->useIncludePath = $useIncludePath; 324 | } 325 | 326 | /** 327 | * Can be used to check if the autoloader uses the include path to check 328 | * for classes. 329 | * 330 | * @return bool 331 | */ 332 | public function getUseIncludePath() 333 | { 334 | return $this->useIncludePath; 335 | } 336 | 337 | /** 338 | * Turns off searching the prefix and fallback directories for classes 339 | * that have not been registered with the class map. 340 | * 341 | * @param bool $classMapAuthoritative 342 | * 343 | * @return void 344 | */ 345 | public function setClassMapAuthoritative($classMapAuthoritative) 346 | { 347 | $this->classMapAuthoritative = $classMapAuthoritative; 348 | } 349 | 350 | /** 351 | * Should class lookup fail if not found in the current class map? 352 | * 353 | * @return bool 354 | */ 355 | public function isClassMapAuthoritative() 356 | { 357 | return $this->classMapAuthoritative; 358 | } 359 | 360 | /** 361 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 362 | * 363 | * @param string|null $apcuPrefix 364 | * 365 | * @return void 366 | */ 367 | public function setApcuPrefix($apcuPrefix) 368 | { 369 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 370 | } 371 | 372 | /** 373 | * The APCu prefix in use, or null if APCu caching is not enabled. 374 | * 375 | * @return string|null 376 | */ 377 | public function getApcuPrefix() 378 | { 379 | return $this->apcuPrefix; 380 | } 381 | 382 | /** 383 | * Registers this instance as an autoloader. 384 | * 385 | * @param bool $prepend Whether to prepend the autoloader or not 386 | * 387 | * @return void 388 | */ 389 | public function register($prepend = false) 390 | { 391 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 392 | 393 | if (null === $this->vendorDir) { 394 | return; 395 | } 396 | 397 | if ($prepend) { 398 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 399 | } else { 400 | unset(self::$registeredLoaders[$this->vendorDir]); 401 | self::$registeredLoaders[$this->vendorDir] = $this; 402 | } 403 | } 404 | 405 | /** 406 | * Unregisters this instance as an autoloader. 407 | * 408 | * @return void 409 | */ 410 | public function unregister() 411 | { 412 | spl_autoload_unregister(array($this, 'loadClass')); 413 | 414 | if (null !== $this->vendorDir) { 415 | unset(self::$registeredLoaders[$this->vendorDir]); 416 | } 417 | } 418 | 419 | /** 420 | * Loads the given class or interface. 421 | * 422 | * @param string $class The name of the class 423 | * @return true|null True if loaded, null otherwise 424 | */ 425 | public function loadClass($class) 426 | { 427 | if ($file = $this->findFile($class)) { 428 | includeFile($file); 429 | 430 | return true; 431 | } 432 | 433 | return null; 434 | } 435 | 436 | /** 437 | * Finds the path to the file where the class is defined. 438 | * 439 | * @param string $class The name of the class 440 | * 441 | * @return string|false The path if found, false otherwise 442 | */ 443 | public function findFile($class) 444 | { 445 | // class map lookup 446 | if (isset($this->classMap[$class])) { 447 | return $this->classMap[$class]; 448 | } 449 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 450 | return false; 451 | } 452 | if (null !== $this->apcuPrefix) { 453 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 454 | if ($hit) { 455 | return $file; 456 | } 457 | } 458 | 459 | $file = $this->findFileWithExtension($class, '.php'); 460 | 461 | // Search for Hack files if we are running on HHVM 462 | if (false === $file && defined('HHVM_VERSION')) { 463 | $file = $this->findFileWithExtension($class, '.hh'); 464 | } 465 | 466 | if (null !== $this->apcuPrefix) { 467 | apcu_add($this->apcuPrefix.$class, $file); 468 | } 469 | 470 | if (false === $file) { 471 | // Remember that this class does not exist. 472 | $this->missingClasses[$class] = true; 473 | } 474 | 475 | return $file; 476 | } 477 | 478 | /** 479 | * Returns the currently registered loaders indexed by their corresponding vendor directories. 480 | * 481 | * @return self[] 482 | */ 483 | public static function getRegisteredLoaders() 484 | { 485 | return self::$registeredLoaders; 486 | } 487 | 488 | /** 489 | * @param string $class 490 | * @param string $ext 491 | * @return string|false 492 | */ 493 | private function findFileWithExtension($class, $ext) 494 | { 495 | // PSR-4 lookup 496 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 497 | 498 | $first = $class[0]; 499 | if (isset($this->prefixLengthsPsr4[$first])) { 500 | $subPath = $class; 501 | while (false !== $lastPos = strrpos($subPath, '\\')) { 502 | $subPath = substr($subPath, 0, $lastPos); 503 | $search = $subPath . '\\'; 504 | if (isset($this->prefixDirsPsr4[$search])) { 505 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 506 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 507 | if (file_exists($file = $dir . $pathEnd)) { 508 | return $file; 509 | } 510 | } 511 | } 512 | } 513 | } 514 | 515 | // PSR-4 fallback dirs 516 | foreach ($this->fallbackDirsPsr4 as $dir) { 517 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 518 | return $file; 519 | } 520 | } 521 | 522 | // PSR-0 lookup 523 | if (false !== $pos = strrpos($class, '\\')) { 524 | // namespaced class name 525 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 526 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 527 | } else { 528 | // PEAR-like class name 529 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 530 | } 531 | 532 | if (isset($this->prefixesPsr0[$first])) { 533 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 534 | if (0 === strpos($class, $prefix)) { 535 | foreach ($dirs as $dir) { 536 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 537 | return $file; 538 | } 539 | } 540 | } 541 | } 542 | } 543 | 544 | // PSR-0 fallback dirs 545 | foreach ($this->fallbackDirsPsr0 as $dir) { 546 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 547 | return $file; 548 | } 549 | } 550 | 551 | // PSR-0 include paths. 552 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 553 | return $file; 554 | } 555 | 556 | return false; 557 | } 558 | } 559 | 560 | /** 561 | * Scope isolated include. 562 | * 563 | * Prevents access to $this/self from included files. 564 | * 565 | * @param string $file 566 | * @return void 567 | * @private 568 | */ 569 | function includeFile($file) 570 | { 571 | include $file; 572 | } 573 | -------------------------------------------------------------------------------- /src/Git_Updater_PRO/Branch.php: -------------------------------------------------------------------------------- 1 | get_class_vars( 'Fragen\Git_Updater\Base', 'options' ); 48 | $this->base = Singleton::get_instance( 'Fragen\Git_Updater\Base', $this ); 49 | } 50 | 51 | /** 52 | * Get the current repo branch. 53 | * 54 | * @access public 55 | * 56 | * @param \stdClass $repo Repository object. 57 | * 58 | * @return mixed 59 | */ 60 | public function get_current_branch( $repo ) { 61 | $cache = $this->get_repo_cache( $repo->slug ); 62 | $current_branch = ! empty( $cache['current_branch'] ) 63 | ? $cache['current_branch'] 64 | : $repo->branch; 65 | 66 | return $current_branch; 67 | } 68 | 69 | /** 70 | * Update transient for rollback or branch switch. 71 | * 72 | * @param string $type plugin|theme. 73 | * @param \stdClass $repo Repo object. 74 | * 75 | * @return array $rollback Rollback transient. 76 | */ 77 | public function set_rollback_transient( $type, $repo ) { 78 | $repo_api = Singleton::get_instance( 'API\API', $this )->get_repo_api( $repo->git, $repo ); 79 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended 80 | $this->tag = isset( $_GET['rollback'] ) ? sanitize_text_field( wp_unslash( $_GET['rollback'] ) ) : false; 81 | $slug = 'plugin' === $type ? $repo->file : $repo->slug; 82 | $download_link = $repo_api->construct_download_link( $this->tag ); 83 | 84 | /** 85 | * Filter download link so developers can point to specific ZipFile 86 | * to use as a download link during a branch switch. 87 | * 88 | * @since 8.6.0 89 | * 90 | * @param string $download_link Download URL. 91 | * @param /stdClass $repo 92 | * @param string $this->tag Branch or tag for rollback. 93 | */ 94 | $download_link = apply_filters_deprecated( 'github_updater_post_construct_download_link', [ $download_link, $repo, $this->tag ], '10.0.0', 'gu_post_construct_download_link' ); 95 | 96 | /** 97 | * Filter download link so developers can point to specific ZipFile 98 | * to use as a download link during a branch switch. 99 | * 100 | * @since 10.0.0 101 | * 102 | * @param string $download_link Download URL. 103 | * @param /stdClass $repo 104 | * @param string $this->tag Branch or tag for rollback. 105 | */ 106 | $download_link = apply_filters( 'gu_post_construct_download_link', $download_link, $repo, $this->tag ); 107 | 108 | $repo->download_link = $download_link; 109 | $rollback = [ 110 | $type => $slug, 111 | 'new_version' => $this->tag, 112 | 'url' => $repo->uri, 113 | 'package' => $repo->download_link, 114 | 'branch' => $repo->branch, 115 | 'branches' => $repo->branches, 116 | 'type' => $repo->type, 117 | ]; 118 | 119 | if ( 'plugin' === $type ) { 120 | $rollback['slug'] = $repo->slug; 121 | $rollback = (object) $rollback; 122 | } 123 | 124 | return $rollback; 125 | } 126 | 127 | /** 128 | * Set current branch on branch switch. 129 | * Exit early if not a rollback. 130 | * 131 | * @access public 132 | * 133 | * @param string $repo Repository slug. 134 | * @return void 135 | */ 136 | public function set_branch_on_switch( $repo ) { 137 | $this->cache = $this->get_repo_cache( $repo ); 138 | 139 | // phpcs:disable WordPress.Security.NonceVerification.Recommended 140 | // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized 141 | $rollback = isset( $_GET['rollback'] ) ? wp_unslash( $_GET['rollback'] ) : false; 142 | // Exit early if not a rollback, ie normal update. 143 | if ( ! $rollback ) { 144 | return; 145 | } 146 | 147 | $tag_array = isset( $this->cache['tags'] ) && is_array( $this->cache['tags'] ); 148 | $in_tag_array = $tag_array && in_array( $rollback, $this->cache['tags'], true ); 149 | if ( $in_tag_array ) { 150 | $current_branch = isset( $this->cache[ $repo ]['PrimaryBranch'] ) ? $this->cache[ $repo ]['PrimaryBranch'] : 'master'; 151 | } 152 | 153 | if ( ! $in_tag_array && isset( $_GET['action'], $this->cache['branches'] ) 154 | && in_array( $_GET['action'], [ 'upgrade-plugin', 'upgrade-theme' ], true ) 155 | ) { 156 | // phpcs:enable 157 | $current_branch = array_key_exists( $rollback, $this->cache['branches'] ) 158 | ? sanitize_text_field( $rollback ) 159 | : 'master'; 160 | } 161 | if ( isset( $current_branch ) ) { 162 | $this->set_repo_cache( 'current_branch', $current_branch, $repo ); 163 | self::$options[ 'current_branch_' . $repo ] = $current_branch; 164 | update_site_option( 'git_updater', self::$options ); 165 | } 166 | } 167 | 168 | /** 169 | * Set current branch on install and update options. 170 | * 171 | * @access public 172 | * 173 | * @param array $install Array of install data. 174 | */ 175 | public function set_branch_on_install( $install ) { 176 | $this->set_repo_cache( 'current_branch', $install['git_updater_branch'], $install['repo'] ); 177 | self::$options[ 'current_branch_' . $install['repo'] ] = $install['git_updater_branch']; 178 | self::$options = isset( $install['options'] ) && is_array( $install['options'] ) 179 | ? array_merge( self::$options, $install['options'] ) 180 | : self::$options; 181 | update_site_option( 'git_updater', self::$options ); 182 | } 183 | 184 | /** 185 | * Add branch switch row to plugins page. 186 | * 187 | * @param string $plugin_file Plugin file. 188 | * @param \stdClass $plugin_data Plugin repo data. 189 | * 190 | * @return bool 191 | */ 192 | public function plugin_branch_switcher( $plugin_file, $plugin_data ) { 193 | if ( empty( self::$options['branch_switch'] ) ) { 194 | return false; 195 | } 196 | $plugin_obj = Singleton::get_instance( 'Fragen\Git_Updater\Plugin', $this ); 197 | $config = $this->get_class_vars( 'Fragen\Git_Updater\Plugin', 'config' ); 198 | $plugin = $this->get_repo_slugs( dirname( $plugin_file ), $plugin_obj ); 199 | 200 | $this->base->get_remote_repo_meta( $config[ $plugin['slug'] ] ); 201 | 202 | $enclosure = $this->base->update_row_enclosure( $plugin_file, 'plugin', true ); 203 | $nonced_update_url = wp_nonce_url( 204 | $this->base->get_update_url( 'plugin', 'upgrade-plugin', $plugin_file ), 205 | 'upgrade-plugin_' . $plugin_file 206 | ); 207 | 208 | if ( ! empty( $plugin ) ) { 209 | $id = $plugin['slug'] . '-id'; 210 | $branches = isset( $config[ $plugin['slug'] ]->branches ) 211 | ? $config[ $plugin['slug'] ]->branches 212 | : null; 213 | } else { 214 | return false; 215 | } 216 | 217 | // Get current branch. 218 | $repo = $config[ $plugin['slug'] ]; 219 | $branch = $this->get_current_branch( $repo ); 220 | 221 | $branch_switch_data = []; 222 | $branch_switch_data['slug'] = $plugin['slug']; 223 | $branch_switch_data['nonced_update_url'] = $nonced_update_url; 224 | $branch_switch_data['id'] = $id; 225 | $branch_switch_data['branch'] = $branch; 226 | $branch_switch_data['branches'] = $branches; 227 | $branch_switch_data['release_asset'] = $repo->release_asset; 228 | $branch_switch_data['primary_branch'] = $repo->primary_branch; 229 | 230 | /* 231 | * Create after_plugin_row_ 232 | */ 233 | echo wp_kses_post( $enclosure['open'] ); 234 | $this->make_branch_switch_row( $branch_switch_data, $config ); 235 | echo wp_kses_post( $enclosure['close'] ); 236 | 237 | return true; 238 | } 239 | 240 | /** 241 | * Create branch switcher row for theme multisite installation. 242 | * 243 | * @param string $theme_key Theme slug. 244 | * @param array $theme Array of theme data. 245 | * 246 | * @return bool 247 | */ 248 | public function multisite_branch_switcher( $theme_key, $theme ) { 249 | if ( empty( self::$options['branch_switch'] ) ) { 250 | return false; 251 | } 252 | 253 | $config = $this->get_class_vars( 'Fragen\Git_Updater\Theme', 'config' ); 254 | 255 | $this->base->get_remote_repo_meta( $config[ $theme_key ] ); 256 | 257 | $enclosure = $this->base->update_row_enclosure( $theme_key, 'theme', true ); 258 | $id = $theme_key . '-id'; 259 | $branches = isset( $config[ $theme_key ]->branches ) 260 | ? $config[ $theme_key ]->branches 261 | : null; 262 | $nonced_update_url = wp_nonce_url( 263 | $this->base->get_update_url( 'theme', 'upgrade-theme', $theme_key ), 264 | 'upgrade-theme_' . $theme_key 265 | ); 266 | 267 | // Get current branch. 268 | $repo = $config[ $theme_key ]; 269 | $branch = $this->get_current_branch( $repo ); 270 | 271 | $branch_switch_data = []; 272 | $branch_switch_data['slug'] = $theme_key; 273 | $branch_switch_data['nonced_update_url'] = $nonced_update_url; 274 | $branch_switch_data['id'] = $id; 275 | $branch_switch_data['branch'] = $branch; 276 | $branch_switch_data['branches'] = $branches; 277 | $branch_switch_data['release_asset'] = $repo->release_asset; 278 | $branch_switch_data['primary_branch'] = $repo->primary_branch; 279 | 280 | /* 281 | * Create after_theme_row_ 282 | */ 283 | echo wp_kses_post( $enclosure['open'] ); 284 | $this->make_branch_switch_row( $branch_switch_data, $config ); 285 | echo wp_kses_post( $enclosure['close'] ); 286 | 287 | return true; 288 | } 289 | 290 | /** 291 | * Display rollback/branch switcher for theme single site installation. 292 | * 293 | * @param \stdClass $theme Theme object. 294 | * 295 | * @return string 296 | */ 297 | public function single_install_switcher( $theme ) { 298 | $nonced_update_url = wp_nonce_url( 299 | $this->base->get_update_url( 'theme', 'upgrade-theme', $theme->slug ), 300 | 'upgrade-theme_' . $theme->slug 301 | ); 302 | $rollback_url = sprintf( '%s%s', $nonced_update_url, '&rollback=' ); 303 | 304 | if ( ! isset( self::$options['branch_switch'] ) ) { 305 | return; 306 | } 307 | 308 | ob_start(); 309 | if ( '1' === self::$options['branch_switch'] ) { 310 | printf( 311 | /* translators: 1: branch name, 2: jQuery dropdown, 3: closing tag */ 312 | '

' . esc_html__( 'Current branch is `%1$s`, try %2$sanother version%3$s', 'git-updater-pro' ), 313 | esc_attr( $theme->branch ), 314 | '', 315 | '.

' 316 | ); 317 | ?> 318 | 369 | rollback ) ? [] : $config[ $data['slug'] ]->rollback; 385 | 386 | // Make the branch switch row visually appear as if it is contained with the plugin/theme's row. 387 | // We have to use JS for this because of the way: 388 | // 1) the @class of the list table row is not filterabled; and 389 | // 2) the list table CSS is written. 390 | if ( 'plugin' === $config[ $data['slug'] ]->type ) { 391 | $data_attr = 'data-plugin'; 392 | $file = $config[ $data['slug'] ]->file; 393 | } else { 394 | $data_attr = 'data-slug'; 395 | $file = $config[ $data['slug'] ]->slug; 396 | } 397 | echo ''; 409 | 410 | echo '

'; 411 | echo wp_kses_post( $this->base->get_git_icon( $file, true ) ); 412 | printf( 413 | /* translators: 1: branch name, 2: jQuery dropdown, 3: closing tag */ 414 | esc_html__( 'Current branch is `%1$s`, try %2$sanother version%3$s', 'git-updater-pro' ), 415 | esc_attr( $data['branch'] ), 416 | '', 417 | '.' 418 | ); 419 | echo '

'; 420 | 421 | print ''; 525 | } 526 | 527 | } 528 | --------------------------------------------------------------------------------