├── assets ├── screenshot-1.png ├── screenshot-2.png ├── screenshot-3.png ├── screenshot-4.png ├── banner-1544x500.png ├── banner-772x250.png └── icon.svg ├── vendor ├── composer │ ├── autoload_namespaces.php │ ├── autoload_psr4.php │ ├── platform_check.php │ ├── LICENSE │ ├── autoload_real.php │ ├── installed.php │ ├── autoload_classmap.php │ ├── autoload_static.php │ ├── InstalledVersions.php │ ├── installed.json │ └── ClassLoader.php └── autoload.php ├── LICENSE ├── js └── git-remote-updater-switcher.js ├── composer.json ├── git-remote-updater.php ├── src └── Git_Remote_Updater │ ├── Actions.php │ ├── Updater.php │ ├── Settings_Row.php │ ├── Settings.php │ ├── Webhooks.php │ └── Site_List_Table.php ├── README.md ├── CHANGES.md ├── readme.txt ├── phpcs.xml └── languages └── git-remote-updater.pot /assets/screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afragen/git-remote-updater/develop/assets/screenshot-1.png -------------------------------------------------------------------------------- /assets/screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afragen/git-remote-updater/develop/assets/screenshot-2.png -------------------------------------------------------------------------------- /assets/screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afragen/git-remote-updater/develop/assets/screenshot-3.png -------------------------------------------------------------------------------- /assets/screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afragen/git-remote-updater/develop/assets/screenshot-4.png -------------------------------------------------------------------------------- /assets/banner-1544x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afragen/git-remote-updater/develop/assets/banner-1544x500.png -------------------------------------------------------------------------------- /assets/banner-772x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afragen/git-remote-updater/develop/assets/banner-772x250.png -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/dealerdirect/phpcodesniffer-composer-installer/src'), 10 | 'Fragen\\Git_Updater\\' => array($baseDir . '/src/Git_Updater'), 11 | 'Fragen\\Git_Remote_Updater\\' => array($baseDir . '/src/Git_Remote_Updater'), 12 | ); 13 | -------------------------------------------------------------------------------- /vendor/autoload.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) 2019 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 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 35 | 36 | return $loader; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /js/git-remote-updater-switcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Vanilla Javascript to show and hide the update choices. 3 | * 4 | * @access public 5 | * @package git-remote-updater 6 | */ 7 | 8 | (function () { 9 | 10 | // Hide non-default settings on page load. 11 | let nonDefault = ['git-remote-updater-site']; 12 | 13 | nonDefault.forEach(function (item) { 14 | displayNone(item); 15 | }); 16 | 17 | // When the selector changes. 18 | let selects = document.querySelector('select[ name="git-remote-updater" ]'); 19 | 20 | // Only run when on proper tab. 21 | selects.addEventListener('change', function () { 22 | let defaults = ['git-remote-updater-site', 'git-remote-updater-repo']; 23 | 24 | // Create difference array. 25 | let hideMe = remove(defaults, this.value); 26 | 27 | // Hide items with unselected classes. 28 | hideMe.forEach(function (item) { 29 | displayNone(item); 30 | }); 31 | 32 | // Show selected setting. 33 | [this.value].forEach(function (item) { 34 | display(item); 35 | }); 36 | }); 37 | 38 | // Remove selected element from array and return array. 39 | function remove(array, element) { 40 | const index = array.indexOf(element); 41 | if (index !== -1) { 42 | array.splice(index, 1); 43 | } 44 | return array; 45 | } 46 | 47 | // Hide element. 48 | function displayNone(item) { 49 | x = document.getElementsByClassName(item)[0]; 50 | x.style.display = 'none'; 51 | } 52 | 53 | // Display element. 54 | function display(item) { 55 | x = document.getElementsByClassName(item)[0]; 56 | x.style.display = ''; 57 | } 58 | 59 | })(); 60 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afragen/git-remote-updater", 3 | "description": "Allows you to easily update Git Updater repositories in bulk via REST API endpoint updating.", 4 | "type": "wordpress-plugin", 5 | "keywords": [ 6 | "git-updater", 7 | "bulk", 8 | "update", 9 | "webhook" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Andy Fragen", 15 | "email": "andy@thefragens.com", 16 | "homepage": "https://thefragens.com", 17 | "role": "Developer" 18 | } 19 | ], 20 | "repositories": [ 21 | { 22 | "type": "vcs", 23 | "url": "https://github.com/afragen/git-remote-updater" 24 | } 25 | ], 26 | "support": { 27 | "issues": "https://github.com/afragen/git-remote-updater/issues", 28 | "source": "https://github.com/afragen/git-remote-updater" 29 | }, 30 | "prefer-stable": true, 31 | "require": { 32 | "php": ">=7.2" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Fragen\\Git_Remote_Updater\\": "src/Git_Remote_Updater/", 37 | "Fragen\\Git_Updater\\": "src/Git_Updater" 38 | } 39 | }, 40 | "config": { 41 | "allow-plugins": { 42 | "dealerdirect/phpcodesniffer-composer-installer": true 43 | } 44 | }, 45 | "require-dev": { 46 | "wp-coding-standards/wpcs": "^3.0.0" 47 | }, 48 | "scripts": { 49 | "wpcs": [ 50 | "vendor/bin/phpcbf .; vendor/bin/phpcs ." 51 | ] 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /git-remote-updater.php: -------------------------------------------------------------------------------- 1 | load_hooks(); 54 | } 55 | ); 56 | -------------------------------------------------------------------------------- /assets/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 19 | 20 | 21 | 22 | 43 | 44 | -------------------------------------------------------------------------------- /src/Git_Remote_Updater/Actions.php: -------------------------------------------------------------------------------- 1 | '; 35 | $this->show_feedback(); 36 | $this->repo_or_site_selector(); 37 | 38 | echo '
'; 39 | echo ''; 40 | 41 | echo ''; 42 | ( new Settings_Row() )->add_repo_rows(); 43 | echo ''; 44 | 45 | echo ''; 46 | ( new Settings_Row() )->add_site_rows(); 47 | echo ''; 48 | 49 | echo '
'; 50 | echo '
'; 51 | } 52 | 53 | 54 | /** 55 | * Display update feedback. 56 | * 57 | * @return void 58 | */ 59 | private function show_feedback() { 60 | $feedback = get_site_transient( 'git_remote_updater_feedback' ); 61 | if ( $feedback ) { 62 | echo '
'; 63 | echo '

' . esc_html__( 'Update Feedback', 'git-remote-updater' ) . '

'; 64 | foreach ( $feedback as $repo_feedback ) { 65 | echo '

'; 66 | foreach ( $repo_feedback as $message ) { 67 | echo wp_kses_post( $message ) . '
'; 68 | } 69 | echo '

'; 70 | } 71 | echo '
'; 72 | } 73 | } 74 | 75 | /** 76 | * Repo or Site option. 77 | */ 78 | private function repo_or_site_selector() { 79 | $options = [ 80 | 'git-remote-updater-repo' => esc_html__( 'Show Repositories', 'git-remote-updater' ), 81 | 'git-remote-updater-site' => esc_html__( 'Show Sites', 'git-remote-updater' ), 82 | ]; ?> 83 | 92 | array( 3 | 'name' => 'afragen/git-remote-updater', 4 | 'pretty_version' => 'dev-develop', 5 | 'version' => 'dev-develop', 6 | 'reference' => '333b5f17a8de88e5d48b78e41abb7e534033b21f', 7 | 'type' => 'wordpress-plugin', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => true, 11 | ), 12 | 'versions' => array( 13 | 'afragen/git-remote-updater' => array( 14 | 'pretty_version' => 'dev-develop', 15 | 'version' => 'dev-develop', 16 | 'reference' => '333b5f17a8de88e5d48b78e41abb7e534033b21f', 17 | 'type' => 'wordpress-plugin', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | 'dealerdirect/phpcodesniffer-composer-installer' => array( 23 | 'pretty_version' => 'v1.0.0', 24 | 'version' => '1.0.0.0', 25 | 'reference' => '4be43904336affa5c2f70744a348312336afd0da', 26 | 'type' => 'composer-plugin', 27 | 'install_path' => __DIR__ . '/../dealerdirect/phpcodesniffer-composer-installer', 28 | 'aliases' => array(), 29 | 'dev_requirement' => true, 30 | ), 31 | 'phpcsstandards/phpcsextra' => array( 32 | 'pretty_version' => '1.2.1', 33 | 'version' => '1.2.1.0', 34 | 'reference' => '11d387c6642b6e4acaf0bd9bf5203b8cca1ec489', 35 | 'type' => 'phpcodesniffer-standard', 36 | 'install_path' => __DIR__ . '/../phpcsstandards/phpcsextra', 37 | 'aliases' => array(), 38 | 'dev_requirement' => true, 39 | ), 40 | 'phpcsstandards/phpcsutils' => array( 41 | 'pretty_version' => '1.0.12', 42 | 'version' => '1.0.12.0', 43 | 'reference' => '87b233b00daf83fb70f40c9a28692be017ea7c6c', 44 | 'type' => 'phpcodesniffer-standard', 45 | 'install_path' => __DIR__ . '/../phpcsstandards/phpcsutils', 46 | 'aliases' => array(), 47 | 'dev_requirement' => true, 48 | ), 49 | 'squizlabs/php_codesniffer' => array( 50 | 'pretty_version' => '3.11.2', 51 | 'version' => '3.11.2.0', 52 | 'reference' => '1368f4a58c3c52114b86b1abe8f4098869cb0079', 53 | 'type' => 'library', 54 | 'install_path' => __DIR__ . '/../squizlabs/php_codesniffer', 55 | 'aliases' => array(), 56 | 'dev_requirement' => true, 57 | ), 58 | 'wp-coding-standards/wpcs' => array( 59 | 'pretty_version' => '3.1.0', 60 | 'version' => '3.1.0.0', 61 | 'reference' => '9333efcbff231f10dfd9c56bb7b65818b4733ca7', 62 | 'type' => 'phpcodesniffer-standard', 63 | 'install_path' => __DIR__ . '/../wp-coding-standards/wpcs', 64 | 'aliases' => array(), 65 | 'dev_requirement' => true, 66 | ), 67 | ), 68 | ); 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Git Remote Updater 2 | 3 | * Author: Andy Fragen 4 | * Author URI: https://github.com/afragen 5 | * Donate link: https://thefragens.com/git-updater-donate 6 | * License: MIT 7 | * Requires PHP: 7.2 8 | * Requires at least: 5.9 9 | 10 | ## Description 11 | 12 | Allows you to easily update Git Updater repositories in bulk via [REST API endpoint updating](https://github.com/afragen/git-updater/wiki/Remote-Management---RESTful-Endpoints). 13 | 14 | The **Git Remote Updater** page allows updating via repository or site. 15 | 16 | ## Setup 17 | 18 | Requires [Git Updater](https://github.com/afragen/git-updater) v12.0.0 or higher and an active license running on the sites you use with Git Remote Updater. 19 | 20 | You can obtain the needed data from your `Git Updater > Remote Management` tab and then copy it into the `Settings > Add Site` form. You will only need to update data if your REST API key changes. 21 | 22 | ![Git Remote Updater Settings](https://github.com/afragen/git-updater/raw/develop/wiki-assets/screenshot-14.png) 23 | 24 | Update feedback will show at the top of the page. If you have debug logging set on your site, it is also added to the `debug.log`. 25 | 26 | I recommend running Git Remote Updater from a local development environment installation of WordPress, though you can run it from any WordPress site. 27 | 28 | ## Usage 29 | 30 | By using the `Site URL` and the `REST API key` you can add sites to the **Settings** page. 31 | 32 | ![Settings](./assets/screenshot-3.png) 33 | 34 | Once you have site data added, the **Update** page will present options for you to update repositories. 35 | 36 | You can update individual repositories across all the sites the repositories exist on. 37 | 38 | ![Update individual repos](./assets/screenshot-1.png) 39 | 40 | Or you can update all the repositories on an individual site all at once. 41 | 42 | ![Update whole site](./assets/screenshot-2.png) 43 | 44 | The update process will continue until it succeeds. Feedback is presented at the top of the page when the process has completed. Feedback will also be added the the site's `debug.log`, if debug logging is set for the site. 45 | 46 | ![Update Feedback](./assets/screenshot-4.png) 47 | 48 | If there is a timeout error in the update process, Git Remote Upater will continue to attempt updating until it succeeds. 49 | 50 | Other errors will not attempt to retry the update an will display the appropriate error. 51 | 52 | Any site that is unreachable will not display any repository data in the **Update** page. 53 | 54 | ## Filters and Hooks 55 | 56 | If you have specific repositories that you wish to exclude you can add them using the filter hook as follows. 57 | 58 | ```php 59 | add_filter( 60 | 'git_remote_updater_remove_site_data', 61 | function() { 62 | return [ 'caldera-forms', 'github-updater' ]; 63 | } 64 | ); 65 | ``` 66 | 67 | If you wish to modify the transient timeout for repo data; return the number of seconds, as an integer, to the following filter. Default is 600 seconds. Less is better for testing. 68 | 69 | ```php 70 | add_filter( 71 | 'git_remote_updater_repo_transient_timeout', 72 | function() { 73 | return 5; 74 | } 75 | ); 76 | ``` 77 | 78 | PRs are welcome against the `develop` branch. 79 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | #### [unreleased] 2 | 3 | #### 3.2.0 / 2024-12-11 4 | * add GA to generate POT 5 | * add GA to add release asset 6 | * load in `init` for `_load_textdomain_just_in_time` 7 | * remove Bootstrap.php 8 | 9 | #### 3.1.1 / 2023-09-08 10 | * update to WPCS 3.0.0 11 | 12 | #### 3.1.0 / 2022-10-24 13 | * require WP 5.9+ 14 | * check for `is_plugins_active` function and load `wp-admin/includes/plugin.php` if needed 15 | * remove Freemius SDK 16 | 17 | #### 3.0.1 / 2022-08-27 18 | * update Freemius/wordpress-sdk 19 | 20 | #### 3.0.0 / 2022-04-24 21 | * require PHP7.2+ 22 | 23 | #### 2.4.3 / 2022-03-10 24 | * composer update 25 | 26 | #### 2.4.2 / 2022-03-02 27 | * update `class Site_List_Table` and `Settings` nonces 28 | * move check for Git Updater to hook 29 | * update Freemius/wordpress-sdk to 2.4.3 30 | 31 | #### 2.4.1 / 2021-11-16 32 | * remove checkbox from Settings List table 33 | * use `sanitize_title_with_dashes()` as `sanitize_file_name()` maybe have attached filter that changes output 34 | 35 | #### 2.4.0 / 2021-07-05 36 | * remove Freemius from the autoloader 37 | * utilize new `class Ignore` from Git Updater 38 | * uses new `class Fragen\Git_Updater\Shim` for PHP 5.6 compatibility, will remove when WP core changes minimum requirement 39 | 40 | #### 2.3.1 / 2021-06-14 41 | * utilize new `class Ignore` in Git Updater 42 | 43 | #### 2.3.0 / 2021-06-02 44 | * add filter to skip updating from Git Updater 45 | * add filter to display this plugin in GitHub subtab without errors 46 | 47 | #### 2.2.3 / 2021-05-21 48 | * add language pack updating 49 | 50 | #### 2.2.2 / 2021-05-18 51 | * ensure custom icon shows in update notice from Freemius 52 | 53 | #### 2.2.0 / 2021-05-11 54 | * update assets 55 | 56 | #### 2.1.0 / 2021-05-03 57 | * add Freemius integration 58 | 59 | #### 2.0.0 / 2021-04-15 60 | * get REST namespace from site 61 | * fix settings page site query 62 | * remove constant and require PHP 7+ 63 | * requires Git Updater v10+ **and** Git Updater PRO, now just Git Updater v12+ 64 | 65 | #### 1.0.1 / 2020-08-07 66 | * add some error checking 67 | 68 | #### 1.0.0 / 2020-07-31 - 💥 69 | * initial pass 70 | * add rows for both update entire site or update all sites with specific repository 71 | * add ability to use multiple JSON files. 72 | * update for use with downloadable JSON files from GitHub Updater 73 | * add javascript to switch display from repos/sites 74 | * add update feedback messaging 75 | * create JSON storage directory in `wp-content/uploads/git-remote-updater` 76 | * sort repositories by name 77 | * more error checking during update process 78 | * rebrand to Git Remote Updater 79 | * keep retrying if remote response is WP_Error 80 | * added filter and function to remove slugs from appearing for updates 81 | * added a Settings tab to add site data, removed JSON storage directory 82 | * added nonce checks for WP_List_Table 83 | * move Git Remote Updater a top level menu 84 | * added `git_remote_updater_repo_transient_timeout` to set transient timeout, returns int with default of 600 seconds. I use this for testing. 85 | * escape, sanitize & ignore 86 | * add error messaging when getting repo data 87 | * increase `wp_remote_get` timeout for updating, helps to avoid a timeout loop 88 | * with GitHub Updater v 9.7.2 or later, repos with tags and set to primary branch will use tag for remote update 89 | -------------------------------------------------------------------------------- /src/Git_Remote_Updater/Updater.php: -------------------------------------------------------------------------------- 1 | init(); 37 | $message = null; 38 | $webhooks = []; 39 | $update = array_search( 'Update', $_POST, true ); 40 | $type = false !== strpos( $update, 'plugin_' ) ? 'plugin' : null; 41 | $type = false !== strpos( $update, 'theme_' ) ? 'theme' : $type; 42 | $update = 'plugin' === $type ? str_replace( 'plugin_', '', $update ) : $update; 43 | $update = 'theme' === $type ? str_replace( 'theme_', '', $update ) : $update; 44 | $site = null === $type ? str_replace( '_', '.', $update ) : null; 45 | $webhooks = null !== $site ? $this->all_webhooks[ $site ] : $webhooks; 46 | if ( null === $site ) { 47 | foreach ( $this->repos[ $update ]['urls'] as $url ) { 48 | $webhooks[] = $url; 49 | } 50 | } 51 | 52 | // Try again if response is WP_Error. 53 | foreach ( $webhooks as $webhook ) { 54 | $args['timeout'] = 10; 55 | do { 56 | $response = wp_remote_get( $webhook, $args ); 57 | $message[] = $this->parse_response( $response, $webhook ); 58 | } while ( is_wp_error( $response ) ); 59 | } 60 | if ( null !== $message ) { 61 | set_site_transient( 'git_remote_updater_feedback', $message, 10 ); 62 | } 63 | ( new Settings() )->redirect(); 64 | } 65 | 66 | /** 67 | * Parse wp_remote_get() response for feedback reporting. 68 | * 69 | * @param \stdClass $response JSON response to wp_remote_get(). 70 | * @param string $webhook URL of webhook. 71 | * 72 | * @return array $message 73 | */ 74 | private function parse_response( $response, $webhook ) { 75 | $parsed = parse_url( $webhook ); 76 | $site = $parsed['host']; 77 | parse_str( $parsed['query'], $query ); 78 | $repo = isset( $query['plugin'] ) ? $query['plugin'] : $query['theme']; 79 | 80 | switch ( $response ) { 81 | case is_wp_error( $response ): 82 | $error = $response->errors; 83 | $message[] = isset( $error['http_request_failed'] ) ? 'WP_Error - ' . $error['http_request_failed'][0] : []; 84 | break; 85 | default: 86 | $message = wp_remote_retrieve_body( $response ); 87 | $message = json_decode( $message, true ); 88 | $message = isset( $message['data']['messages'] ) ? $message['data']['messages'] : []; 89 | if ( 200 !== (int) $response['response']['code'] ) { 90 | $message = is_array( $message ) ? $message : (array) $message; 91 | array_unshift( $message, "{$response['response']['code']} {$response['response']['message']}" ); 92 | } 93 | break; 94 | } 95 | array_unshift( $message, "{$site}: {$repo}" ); 96 | 97 | return $message; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | # Git Remote Updater 2 | 3 | Plugin Name: Git Remote Updater 4 | Plugin URI: https://github.com/afragen/git-remote-updater 5 | Author: Andy Fragen 6 | Author URI: https://github.com/afragen 7 | Donate link: https://thefragens.com/github-updater-donate 8 | License: MIT 9 | Stable tag: master 10 | Requires PHP: 7.2 11 | Requires at least: 5.9 12 | Tested up to: trunk 13 | 14 | ## Description 15 | 16 | Allows you to easily update Git Updater repositories in bulk via [REST API endpoint updating](https://github.com/afragen/git-updater/wiki/Remote-Management---RESTful-Endpoints). 17 | 18 | The **Git Remote Updater** page allows updating via repository or site. 19 | 20 | ## Setup 21 | 22 | Requires [Git Updater](https://github.com/afragen/git-updater) v12.0.0 or higher and an active license running on the sites you use with Git Remote Updater. 23 | 24 | You can obtain the needed data from your `Git Updater > Remote Management` tab and then copy it into the `Settings > Add Site` form. You will only need to update data if your REST API key changes. 25 | 26 | ![Git Remote Updater Settings](https://github.com/afragen/git-updater/raw/develop/wiki-assets/screenshot-14.png) 27 | 28 | Update feedback will show at the top of the page. If you have debug logging set on your site, it is also added to the `debug.log`. 29 | 30 | I recommend running Git Remote Updater from a local development environment installation of WordPress, though you can run it from any WordPress site. 31 | 32 | ## Usage 33 | 34 | By using the `Site URL` and the `REST API key` you can add sites to the **Settings** page. 35 | 36 | ![Settings](./assets/screenshot-3.png) 37 | 38 | Once you have site data added, the **Update** page will present options for you to update repositories. 39 | 40 | You can update individual repositories across all the sites the repositories exist on. 41 | 42 | ![Update individual repos](./assets/screenshot-1.png) 43 | 44 | Or you can update all the repositories on an individual site all at once. 45 | 46 | ![Update whole site](./assets/screenshot-2.png) 47 | 48 | The update process will continue until it succeeds. Feedback is presented at the top of the page when the process has completed. Feedback will also be added the the site's `debug.log`, if debug logging is set for the site. 49 | 50 | ![Update Feedback](./assets/screenshot-4.png) 51 | 52 | If there is a timeout error in the update process, Git Remote Upater will continue to attempt updating until it succeeds. 53 | 54 | Other errors will not attempt to retry the update an will display the appropriate error. 55 | 56 | Any site that is unreachable will not display any repository data in the **Actions** tab. 57 | 58 | ## Filters and Hooks 59 | 60 | If you have specific repositories that you wish to exclude you can add them using the filter hook as follows. 61 | 62 | ```php 63 | add_filter( 64 | 'git_remote_updater_remove_site_data', 65 | function() { 66 | return [ 'caldera-forms', 'github-updater' ]; 67 | } 68 | ); 69 | ``` 70 | 71 | If you wish to modify the transient timeout for repo data; return the number of seconds, as an integer, to the following filter. Default is 600 seconds. Less is better for testing. 72 | 73 | ```php 74 | add_filter( 75 | 'git_remote_updater_repo_transient_timeout', 76 | function() { 77 | return 5; 78 | } 79 | ); 80 | ``` 81 | 82 | PRs are welcome against the `develop` branch. 83 | 84 | ## Screenshots 85 | 86 | 1. Update by Repository 87 | 2. Update by Site 88 | 3. Settings 89 | 4. Feedback 90 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | A custom ruleset to take in account both WordPress and personal standards. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | /vendor/* 13 | /node_modules/* 14 | /tests/* 15 | *.js 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 | 5 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /languages/git-remote-updater.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2022 Andy Fragen 2 | # This file is distributed under the MIT. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Git Remote Updater 3.0.0\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/git-remote-updater\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:46:19+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-remote-updater\n" 16 | 17 | #. Plugin Name of the plugin 18 | #: src/Git_Remote_Updater/Settings.php:46 19 | #: src/Git_Remote_Updater/Settings.php:84 20 | msgid "Git Remote Updater" 21 | msgstr "" 22 | 23 | #. Plugin URI of the plugin 24 | msgid "https://github.com/afragen/git-remote-updater" 25 | msgstr "" 26 | 27 | #. Description of the plugin 28 | msgid "Allows you to easily update Git Updater repositories in bulk via REST API endpoint updating. Requires Git Updater PRO." 29 | msgstr "" 30 | 31 | #. Author of the plugin 32 | msgid "Andy Fragen" 33 | msgstr "" 34 | 35 | #. Author URI of the plugin 36 | msgid "https://github.com/afragen" 37 | msgstr "" 38 | 39 | #: src/Git_Remote_Updater/Actions.php:63 40 | msgid "Update Feedback" 41 | msgstr "" 42 | 43 | #: src/Git_Remote_Updater/Actions.php:80 44 | msgid "Show Repositories" 45 | msgstr "" 46 | 47 | #: src/Git_Remote_Updater/Actions.php:81 48 | msgid "Show Sites" 49 | msgstr "" 50 | 51 | #: src/Git_Remote_Updater/Settings.php:47 52 | msgctxt "Menu item" 53 | msgid "Git Remote Updater" 54 | msgstr "" 55 | 56 | #: src/Git_Remote_Updater/Settings.php:57 57 | #: src/Git_Remote_Updater/Settings.php:58 58 | #: src/Git_Remote_Updater/Settings_Row.php:77 59 | #: src/Git_Remote_Updater/Settings_Row.php:116 60 | msgid "Update" 61 | msgstr "" 62 | 63 | #: src/Git_Remote_Updater/Settings.php:66 64 | #: src/Git_Remote_Updater/Settings.php:67 65 | #: src/Git_Remote_Updater/Site_List_Table.php:392 66 | msgid "Settings" 67 | msgstr "" 68 | 69 | #: src/Git_Remote_Updater/Settings.php:96 70 | msgid "Add Site" 71 | msgstr "" 72 | 73 | #: src/Git_Remote_Updater/Settings.php:217 74 | msgid "Add Site Data" 75 | msgstr "" 76 | 77 | #: src/Git_Remote_Updater/Settings.php:224 78 | msgid "Site URL" 79 | msgstr "" 80 | 81 | #: src/Git_Remote_Updater/Settings.php:232 82 | #: src/Git_Remote_Updater/Site_List_Table.php:164 83 | msgid "REST API key" 84 | msgstr "" 85 | 86 | #: src/Git_Remote_Updater/Settings.php:248 87 | msgid "URI is case sensitive." 88 | msgstr "" 89 | 90 | #: src/Git_Remote_Updater/Settings_Row.php:42 91 | msgid "Please add a site config to your Settings." 92 | msgstr "" 93 | 94 | #: src/Git_Remote_Updater/Settings_Row.php:54 95 | #: src/Git_Remote_Updater/Site_List_Table.php:163 96 | msgid "Site" 97 | msgstr "" 98 | 99 | #: src/Git_Remote_Updater/Settings_Row.php:56 100 | msgid "Repositories" 101 | msgstr "" 102 | 103 | #: src/Git_Remote_Updater/Settings_Row.php:58 104 | #: src/Git_Remote_Updater/Settings_Row.php:95 105 | msgid "Action" 106 | msgstr "" 107 | 108 | #: src/Git_Remote_Updater/Settings_Row.php:91 109 | msgid "Repository" 110 | msgstr "" 111 | 112 | #: src/Git_Remote_Updater/Settings_Row.php:93 113 | msgid "Sites" 114 | msgstr "" 115 | 116 | #: src/Git_Remote_Updater/Site_List_Table.php:209 117 | msgid "Delete" 118 | msgstr "" 119 | 120 | #: src/Git_Remote_Updater/Site_List_Table.php:240 121 | msgid "Items would go to edit when we write that code." 122 | msgstr "" 123 | -------------------------------------------------------------------------------- /src/Git_Remote_Updater/Settings_Row.php: -------------------------------------------------------------------------------- 1 | init(); 32 | $this->can_run(); 33 | } 34 | 35 | /** 36 | * Are there basic data to run? 37 | * 38 | * @return void 39 | */ 40 | private function can_run() { 41 | if ( empty( $this->sites ) || empty( $this->repos ) ) { 42 | wp_die( esc_html__( 'Please add a site config to your Settings.', 'git-remote-updater' ) ); 43 | } 44 | } 45 | 46 | /** 47 | * Add row to Action page for sites. 48 | * 49 | * @return void 50 | */ 51 | public function add_site_rows() { 52 | $this->can_run(); 53 | echo ''; 54 | esc_html_e( 'Site', 'git-remote-updater' ); 55 | echo ''; 56 | esc_html_e( 'Repositories', 'git-remote-updater' ); 57 | echo ''; 58 | esc_html_e( 'Action', 'git-remote-updater' ); 59 | echo ''; 60 | 61 | foreach ( $this->sites as $site => $elements ) { 62 | wp_nonce_field( 'git_remote_updater_nonce', 'git_remote_updater_nonce' ); 63 | echo ''; 64 | echo ''; 65 | echo wp_kses_post( $site ); 66 | echo ''; 67 | 68 | foreach ( $elements as $type => $repo ) { 69 | $dashicon = 'plugin' === $type ? '  ' : ' '; 70 | $dashicon = 'theme' === $type ? '  ' : $dashicon; 71 | foreach ( $repo as $slug => $url ) { 72 | echo '

' . wp_kses_post( $dashicon . ' ' . $slug ) . '

'; 73 | } 74 | } 75 | 76 | echo ''; 77 | echo ''; 78 | echo ''; 79 | echo ''; 80 | } 81 | } 82 | 83 | /** 84 | * Add row to Actions page for repositories. 85 | * 86 | * @return void 87 | */ 88 | public function add_repo_rows() { 89 | $this->can_run(); 90 | echo ''; 91 | esc_html_e( 'Repository', 'git-remote-updater' ); 92 | echo ''; 93 | esc_html_e( 'Sites', 'git-remote-updater' ); 94 | echo ''; 95 | esc_html_e( 'Action', 'git-remote-updater' ); 96 | echo ''; 97 | 98 | ksort( $this->repos ); 99 | foreach ( $this->repos as $slug => $elements ) { 100 | wp_nonce_field( 'git_remote_updater_nonce', 'git_remote_updater_nonce' ); 101 | $sites = $elements['sites']; 102 | unset( $elements['sites'] ); 103 | $type = $elements['type']; 104 | $dashicon = 'plugin' === $type ? '  ' : ' '; 105 | $dashicon = 'theme' === $type ? '  ' : $dashicon; 106 | echo ''; 107 | echo ''; 108 | echo wp_kses_post( $dashicon . ' ' . $slug ); 109 | echo ''; 110 | 111 | foreach ( $sites as $site ) { 112 | echo '

' . wp_kses_post( $site ) . '

'; 113 | } 114 | 115 | echo ''; 116 | echo ''; 117 | echo ''; 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /vendor/composer/autoload_classmap.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | 'PHPCSUtils\\AbstractSniffs\\AbstractArrayDeclarationSniff' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php', 11 | 'PHPCSUtils\\BackCompat\\BCFile' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCFile.php', 12 | 'PHPCSUtils\\BackCompat\\BCTokens' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCTokens.php', 13 | 'PHPCSUtils\\BackCompat\\Helper' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/Helper.php', 14 | 'PHPCSUtils\\Exceptions\\InvalidTokenArray' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/InvalidTokenArray.php', 15 | 'PHPCSUtils\\Exceptions\\TestFileNotFound' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestFileNotFound.php', 16 | 'PHPCSUtils\\Exceptions\\TestMarkerNotFound' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestMarkerNotFound.php', 17 | 'PHPCSUtils\\Exceptions\\TestTargetNotFound' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestTargetNotFound.php', 18 | 'PHPCSUtils\\Fixers\\SpacesFixer' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Fixers/SpacesFixer.php', 19 | 'PHPCSUtils\\Internal\\Cache' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/Cache.php', 20 | 'PHPCSUtils\\Internal\\IsShortArrayOrList' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrList.php', 21 | 'PHPCSUtils\\Internal\\IsShortArrayOrListWithCache' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrListWithCache.php', 22 | 'PHPCSUtils\\Internal\\NoFileCache' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/NoFileCache.php', 23 | 'PHPCSUtils\\Internal\\StableCollections' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/StableCollections.php', 24 | 'PHPCSUtils\\TestUtils\\UtilityMethodTestCase' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/UtilityMethodTestCase.php', 25 | 'PHPCSUtils\\Tokens\\Collections' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/Collections.php', 26 | 'PHPCSUtils\\Tokens\\TokenHelper' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/TokenHelper.php', 27 | 'PHPCSUtils\\Utils\\Arrays' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Arrays.php', 28 | 'PHPCSUtils\\Utils\\Conditions' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Conditions.php', 29 | 'PHPCSUtils\\Utils\\Context' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Context.php', 30 | 'PHPCSUtils\\Utils\\ControlStructures' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ControlStructures.php', 31 | 'PHPCSUtils\\Utils\\FunctionDeclarations' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FunctionDeclarations.php', 32 | 'PHPCSUtils\\Utils\\GetTokensAsString' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/GetTokensAsString.php', 33 | 'PHPCSUtils\\Utils\\Lists' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Lists.php', 34 | 'PHPCSUtils\\Utils\\MessageHelper' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/MessageHelper.php', 35 | 'PHPCSUtils\\Utils\\Namespaces' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Namespaces.php', 36 | 'PHPCSUtils\\Utils\\NamingConventions' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/NamingConventions.php', 37 | 'PHPCSUtils\\Utils\\Numbers' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Numbers.php', 38 | 'PHPCSUtils\\Utils\\ObjectDeclarations' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ObjectDeclarations.php', 39 | 'PHPCSUtils\\Utils\\Operators' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Operators.php', 40 | 'PHPCSUtils\\Utils\\Orthography' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Orthography.php', 41 | 'PHPCSUtils\\Utils\\Parentheses' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Parentheses.php', 42 | 'PHPCSUtils\\Utils\\PassedParameters' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/PassedParameters.php', 43 | 'PHPCSUtils\\Utils\\Scopes' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Scopes.php', 44 | 'PHPCSUtils\\Utils\\TextStrings' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/TextStrings.php', 45 | 'PHPCSUtils\\Utils\\UseStatements' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/UseStatements.php', 46 | 'PHPCSUtils\\Utils\\Variables' => $vendorDir . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Variables.php', 47 | ); 48 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => 57, 13 | ), 14 | 'F' => 15 | array ( 16 | 'Fragen\\Git_Updater\\' => 19, 17 | 'Fragen\\Git_Remote_Updater\\' => 26, 18 | ), 19 | ); 20 | 21 | public static $prefixDirsPsr4 = array ( 22 | 'PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\' => 23 | array ( 24 | 0 => __DIR__ . '/..' . '/dealerdirect/phpcodesniffer-composer-installer/src', 25 | ), 26 | 'Fragen\\Git_Updater\\' => 27 | array ( 28 | 0 => __DIR__ . '/../..' . '/src/Git_Updater', 29 | ), 30 | 'Fragen\\Git_Remote_Updater\\' => 31 | array ( 32 | 0 => __DIR__ . '/../..' . '/src/Git_Remote_Updater', 33 | ), 34 | ); 35 | 36 | public static $classMap = array ( 37 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 38 | 'PHPCSUtils\\AbstractSniffs\\AbstractArrayDeclarationSniff' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/AbstractSniffs/AbstractArrayDeclarationSniff.php', 39 | 'PHPCSUtils\\BackCompat\\BCFile' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCFile.php', 40 | 'PHPCSUtils\\BackCompat\\BCTokens' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/BCTokens.php', 41 | 'PHPCSUtils\\BackCompat\\Helper' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/BackCompat/Helper.php', 42 | 'PHPCSUtils\\Exceptions\\InvalidTokenArray' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/InvalidTokenArray.php', 43 | 'PHPCSUtils\\Exceptions\\TestFileNotFound' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestFileNotFound.php', 44 | 'PHPCSUtils\\Exceptions\\TestMarkerNotFound' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestMarkerNotFound.php', 45 | 'PHPCSUtils\\Exceptions\\TestTargetNotFound' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Exceptions/TestTargetNotFound.php', 46 | 'PHPCSUtils\\Fixers\\SpacesFixer' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Fixers/SpacesFixer.php', 47 | 'PHPCSUtils\\Internal\\Cache' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/Cache.php', 48 | 'PHPCSUtils\\Internal\\IsShortArrayOrList' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrList.php', 49 | 'PHPCSUtils\\Internal\\IsShortArrayOrListWithCache' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/IsShortArrayOrListWithCache.php', 50 | 'PHPCSUtils\\Internal\\NoFileCache' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/NoFileCache.php', 51 | 'PHPCSUtils\\Internal\\StableCollections' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Internal/StableCollections.php', 52 | 'PHPCSUtils\\TestUtils\\UtilityMethodTestCase' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/TestUtils/UtilityMethodTestCase.php', 53 | 'PHPCSUtils\\Tokens\\Collections' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/Collections.php', 54 | 'PHPCSUtils\\Tokens\\TokenHelper' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Tokens/TokenHelper.php', 55 | 'PHPCSUtils\\Utils\\Arrays' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Arrays.php', 56 | 'PHPCSUtils\\Utils\\Conditions' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Conditions.php', 57 | 'PHPCSUtils\\Utils\\Context' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Context.php', 58 | 'PHPCSUtils\\Utils\\ControlStructures' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ControlStructures.php', 59 | 'PHPCSUtils\\Utils\\FunctionDeclarations' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/FunctionDeclarations.php', 60 | 'PHPCSUtils\\Utils\\GetTokensAsString' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/GetTokensAsString.php', 61 | 'PHPCSUtils\\Utils\\Lists' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Lists.php', 62 | 'PHPCSUtils\\Utils\\MessageHelper' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/MessageHelper.php', 63 | 'PHPCSUtils\\Utils\\Namespaces' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Namespaces.php', 64 | 'PHPCSUtils\\Utils\\NamingConventions' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/NamingConventions.php', 65 | 'PHPCSUtils\\Utils\\Numbers' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Numbers.php', 66 | 'PHPCSUtils\\Utils\\ObjectDeclarations' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/ObjectDeclarations.php', 67 | 'PHPCSUtils\\Utils\\Operators' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Operators.php', 68 | 'PHPCSUtils\\Utils\\Orthography' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Orthography.php', 69 | 'PHPCSUtils\\Utils\\Parentheses' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Parentheses.php', 70 | 'PHPCSUtils\\Utils\\PassedParameters' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/PassedParameters.php', 71 | 'PHPCSUtils\\Utils\\Scopes' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Scopes.php', 72 | 'PHPCSUtils\\Utils\\TextStrings' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/TextStrings.php', 73 | 'PHPCSUtils\\Utils\\UseStatements' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/UseStatements.php', 74 | 'PHPCSUtils\\Utils\\Variables' => __DIR__ . '/..' . '/phpcsstandards/phpcsutils/PHPCSUtils/Utils/Variables.php', 75 | ); 76 | 77 | public static function getInitializer(ClassLoader $loader) 78 | { 79 | return \Closure::bind(function () use ($loader) { 80 | $loader->prefixLengthsPsr4 = ComposerStaticInit916525bb03247909b6809c3419155c61::$prefixLengthsPsr4; 81 | $loader->prefixDirsPsr4 = ComposerStaticInit916525bb03247909b6809c3419155c61::$prefixDirsPsr4; 82 | $loader->classMap = ComposerStaticInit916525bb03247909b6809c3419155c61::$classMap; 83 | 84 | }, null, ClassLoader::class); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Git_Remote_Updater/Settings.php: -------------------------------------------------------------------------------- 1 | load_js(); 33 | add_action( is_multisite() ? 'network_admin_menu' : 'admin_menu', [ $this, 'add_plugin_menu' ] ); 34 | add_action( 'admin_init', [ new Updater(), 'update' ] ); 35 | add_action( 'admin_init', [ $this, 'update_settings' ] ); 36 | add_action( 'network_admin_edit_git-remote-updater', [ $this, 'update_settings' ] ); 37 | } 38 | 39 | /** 40 | * Add options page. 41 | */ 42 | public function add_plugin_menu() { 43 | $capability = is_multisite() ? 'manage_network_options' : 'manage_options'; 44 | 45 | add_menu_page( 46 | esc_html__( 'Git Remote Updater', 'git-remote-updater' ), 47 | esc_html_x( 'Git Remote Updater', 'Menu item', 'git-remote-updater' ), 48 | $capability, 49 | 'git-remote-updater', 50 | [ $this, 'create_admin_page' ], 51 | 'dashicons-update', 52 | null 53 | ); 54 | 55 | add_submenu_page( 56 | 'git-remote-updater', 57 | esc_html__( 'Update', 'git-remote-updater' ), 58 | esc_html__( 'Update', 'git-remote-updater' ), 59 | $capability, 60 | 'git-remote-updater', 61 | [ $this, 'create_admin_page' ] 62 | ); 63 | 64 | add_submenu_page( 65 | 'git-remote-updater', 66 | esc_html__( 'Settings', 'git-remote-updater' ), 67 | esc_html__( 'Settings', 'git-remote-updater' ), 68 | $capability, 69 | 'git-remote-updater-settings', 70 | [ $this, 'create_admin_page' ] 71 | ); 72 | } 73 | 74 | /** 75 | * Options page callback. 76 | */ 77 | public function create_admin_page() { 78 | $action = is_multisite() ? 'edit.php?action=git-remote-updater' : 'options.php'; 79 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended 80 | $page = isset( $_GET['page'] ) ? sanitize_title_with_dashes( wp_unslash( $_GET['page'] ) ) : 'git-remote-updater'; 81 | 82 | echo '

'; 83 | esc_html_e( 'Git Remote Updater', 'git-remote-updater' ); 84 | echo '

'; 85 | 86 | if ( 'git-remote-updater' === $page ) { 87 | ( new Actions() )->display( $action ); 88 | } 89 | if ( 'git-remote-updater-settings' === $page ) { 90 | ( new Site_List_Table() )->render_list_table(); 91 | $this->register_settings(); 92 | echo '
'; 93 | settings_fields( 'git_remote_updater' ); 94 | do_settings_sections( 'git_remote_updater' ); 95 | submit_button( esc_html__( 'Add Site', 'git-remote-updater' ) ); 96 | echo '
'; 97 | } 98 | } 99 | 100 | /** 101 | * Update settings for single site or network activated. 102 | * 103 | * @link http://wordpress.stackexchange.com/questions/64968/settings-api-in-multisite-missing-update-message 104 | * @link http://benohead.com/wordpress-network-wide-plugin-settings/ 105 | */ 106 | public function update_settings() { 107 | $options = get_site_option( 'git_remote_updater', [] ); 108 | $duplicate = false; 109 | 110 | if ( ! isset( $_POST['_wpnonce'] ) || ! \wp_verify_nonce( \sanitize_key( \wp_unslash( $_POST['_wpnonce'] ) ), 'git_remote_updater-options' ) ) { 111 | return; 112 | } 113 | 114 | if ( isset( $_POST['option_page'], $_POST['git_remote_updater_site'], $_POST['git_remote_updater_key'] ) 115 | && 'git_remote_updater' === $_POST['option_page'] 116 | ) { 117 | $new_options = [ 118 | 'site' => sanitize_url( wp_unslash( $_POST['git_remote_updater_site'] ) ), 119 | 'api_key' => sanitize_key( wp_unslash( $_POST['git_remote_updater_key'] ) ), 120 | ]; 121 | $new_options = $this->sanitize( $new_options ); 122 | $empty_add = empty( $_POST['git_remote_updater_site'] ); 123 | 124 | foreach ( $options as $option ) { 125 | $duplicate = in_array( $new_options[0]['ID'], $option, true ); 126 | if ( $duplicate ) { 127 | break; 128 | } 129 | } 130 | if ( ! $duplicate && ! $empty_add ) { 131 | $options = array_merge( $options, $new_options ); 132 | update_site_option( 'git_remote_updater', $options ); 133 | } 134 | $this->redirect(); 135 | } 136 | } 137 | 138 | /** 139 | * Sanitize each setting field as needed. 140 | * 141 | * @param array $input Contains all settings fields as array keys. 142 | * 143 | * @return array 144 | */ 145 | public function sanitize( $input ) { 146 | $new_input = []; 147 | 148 | foreach ( (array) $input as $key => $value ) { 149 | $new_input[0][ $key ] = 'site' === $key ? untrailingslashit( sanitize_url( trim( $value ) ) ) : sanitize_text_field( $value ); 150 | $new_input[0]['ID'] = md5( $new_input[0]['site'] ); 151 | } 152 | 153 | return $new_input; 154 | } 155 | 156 | /** 157 | * Redirect to where we came from. 158 | * 159 | * @return void 160 | */ 161 | public function redirect() { 162 | if ( ! ( ( isset( $_POST['git_remote_updater_nonce'] ) 163 | && wp_verify_nonce( sanitize_key( wp_unslash( $_POST['git_remote_updater_nonce'] ) ), 'git_remote_updater_nonce' ) ) 164 | || ( isset( $_POST['_wpnonce'] ) 165 | && wp_verify_nonce( sanitize_key( wp_unslash( $_POST['_wpnonce'] ) ), 'git_remote_updater-options' ) ) ) 166 | ) { 167 | return; 168 | } 169 | 170 | $redirect_url = is_multisite() ? network_admin_url( 'admin.php' ) : admin_url( 'admin.php' ); 171 | $query = isset( $_POST['_wp_http_referer'] ) ? parse_url( html_entity_decode( sanitize_url( wp_unslash( $_POST['_wp_http_referer'] ) ) ), PHP_URL_QUERY ) : ''; 172 | parse_str( (string) $query, $arr ); 173 | 174 | $location = add_query_arg( 175 | [ 176 | 'page' => isset( $arr['page'] ) ? $arr['page'] : 'git-remote-updater', 177 | 'updated' => true, 178 | ], 179 | $redirect_url 180 | ); 181 | wp_safe_redirect( $location ); 182 | exit(); 183 | } 184 | 185 | /** 186 | * Load javascript for Install. 187 | * 188 | * @return void 189 | */ 190 | public function load_js() { 191 | // phpcs:ignore WordPress.Security.NonceVerification 192 | if ( isset( $_GET['page'] ) && 'git-remote-updater' === $_GET['page'] ) { 193 | add_action( 194 | 'admin_enqueue_scripts', 195 | function () { 196 | // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.NoExplicitVersion 197 | wp_register_script( 'git-remote-updater-actions', plugins_url( basename( dirname( __DIR__, 2 ) ) . '/js/git-remote-updater-switcher.js' ), [], false, true ); 198 | wp_enqueue_script( 'git-remote-updater-actions' ); 199 | } 200 | ); 201 | } 202 | } 203 | 204 | /** 205 | * Add settings sections. 206 | */ 207 | public function register_settings() { 208 | register_setting( 209 | 'git_remote_updater', 210 | 'git_remote_updater', 211 | [ $this, 'sanitize' ] 212 | ); 213 | 214 | add_settings_section( 215 | 'git_remote_updater', 216 | esc_html__( 'Add Site Data', 'git-remote-updater' ), 217 | [], 218 | 'git_remote_updater' 219 | ); 220 | 221 | add_settings_field( 222 | 'site', 223 | esc_html__( 'Site URL', 'git-remote-updater' ), 224 | [ $this, 'get_site' ], 225 | 'git_remote_updater', 226 | 'git_remote_updater' 227 | ); 228 | 229 | add_settings_field( 230 | 'api_key', 231 | esc_html__( 'REST API key', 'git-remote-updater' ), 232 | [ $this, 'get_api_key' ], 233 | 'git_remote_updater', 234 | 'git_remote_updater' 235 | ); 236 | } 237 | 238 | /** 239 | * Site setting. 240 | */ 241 | public function get_site() { 242 | ?> 243 | 250 | 258 | 261 | process_options( $options ); 54 | $config = $this->get_site_data( $config ); 55 | $this->get_webhooks( $config ); 56 | $this->get_all_webhooks(); 57 | } 58 | 59 | /** 60 | * Process options for config object. 61 | * 62 | * @param array $options Site options. 63 | * 64 | * @return \stdClass 65 | */ 66 | private function process_options( $options ) { 67 | $config = []; 68 | 69 | foreach ( $options as $option ) { 70 | $site = new \stdClass(); 71 | $site->site = new \stdClass(); 72 | $site->site->host = $option['site']; 73 | $site->site->rest_api_key = $option['api_key']; 74 | $config[] = $site; 75 | } 76 | 77 | return (object) $config; 78 | } 79 | 80 | /** 81 | * Get remote REST namespace. 82 | * 83 | * @param string $site Site URL. 84 | * @param string $api_key REST API key. 85 | * 86 | * @return string 87 | */ 88 | private function get_namespace( $site, $api_key ) { 89 | $namespace = 'git-updater'; 90 | $route = 'namespace'; 91 | $url = "$site/wp-json/$namespace/$route"; 92 | $url = \add_query_arg( 'key', $api_key, $url ); 93 | $response = wp_remote_get( $url ); 94 | $code = wp_remote_retrieve_response_code( $response ); 95 | if ( 200 === $code ) { 96 | $response = wp_remote_retrieve_body( $response ); 97 | $response = json_decode( $response ); 98 | } 99 | $namespace = isset( $response->namespace ) ? $response->namespace : 'github-updater/v1'; 100 | 101 | return $namespace; 102 | } 103 | 104 | /** 105 | * Get directory listing of JSON files. 106 | * 107 | * @param string $dir Directory path the JSON files. 108 | * 109 | * @return array $arr_dir 110 | */ 111 | private function list_directory( $dir ) { 112 | $arr_dir = []; 113 | foreach ( glob( "{$dir}/*.json" ) as $file ) { 114 | array_push( $arr_dir, basename( $file ) ); 115 | } 116 | 117 | return $arr_dir; 118 | } 119 | 120 | /** 121 | * Get remote site repo data via REST API endpoint. 122 | * 123 | * @param \stdClass $config Parsed JSON file created from Git Updater. 124 | * 125 | * @return \stdClass 126 | */ 127 | final public function get_site_data( \stdClass $config ) { 128 | $json = get_site_transient( 'git_remote_updater_repo_data' ); 129 | $message = null; 130 | 131 | if ( ! $json ) { 132 | $json = []; 133 | foreach ( $config as $sites ) { 134 | $namespace = $this->get_namespace( $sites->site->host, $sites->site->rest_api_key ); 135 | $namespace_route = "$namespace/repos"; 136 | 137 | $rest_url = $sites->site->host . '/wp-json/' . $namespace_route; 138 | $rest_url = add_query_arg( [ 'key' => $sites->site->rest_api_key ], $rest_url ); 139 | $response = wp_remote_get( $rest_url ); 140 | $code = wp_remote_retrieve_response_code( $response ); 141 | if ( 200 !== $code ) { 142 | continue; 143 | } 144 | $response = wp_remote_retrieve_body( $response ); 145 | $response = json_decode( $response ); 146 | 147 | if ( is_object( $response ) && \property_exists( $response, 'error' ) ) { 148 | $message[] = "{$sites->site->host}
{$response->error}"; 149 | continue; 150 | } 151 | 152 | $json[ $sites->site->host ] = $response; 153 | } 154 | /** 155 | * Filter to add slugs to be removed from updating. 156 | * 157 | * @since 0.3.6 158 | * 159 | * @param array Array of slugs to remove from site data. 160 | */ 161 | $remove_slugs = \apply_filters( 'git_remote_updater_remove_site_data', [] ); 162 | if ( ! empty( $remove_slugs ) ) { 163 | $json = $this->remove_slugs_from_json( $json, $remove_slugs ); 164 | } 165 | 166 | /** 167 | * Filter the transient timeout. 168 | * Useful for testing. Default is 600 seconds. 169 | * 170 | * @since 0.4.7 171 | * @return int 172 | */ 173 | $timeout = \apply_filters( 'git_remote_updater_repo_transient_timeout', 600 ); 174 | set_site_transient( 'git_remote_updater_repo_data', $json, $timeout ); 175 | 176 | // Display error feedback. 177 | if ( null !== $message ) { 178 | echo '

'; 179 | foreach ( $message as $feedback ) { 180 | echo wp_kses_post( $feedback ) . '
'; 181 | } 182 | echo '

'; 183 | } 184 | } 185 | 186 | return (object) $json; 187 | } 188 | 189 | /** 190 | * Create array of webhooks (supports multiple instances). 191 | * 192 | * @param \stdClass $config JSON config as string. 193 | * 194 | * @return void 195 | */ 196 | final public function get_webhooks( \stdClass $config ) { 197 | $parsed_sites = []; 198 | $repos = []; 199 | foreach ( $config as $sites ) { 200 | if ( empty( $sites ) ) { 201 | return; 202 | } 203 | foreach ( $sites as $site ) { 204 | foreach ( $site->slugs as $repo ) { 205 | $repo_sites = []; 206 | $url = []; 207 | 208 | $parsed_sites[ $site->site ][ $repo->type ][ $repo->slug ] = $this->get_endpoint( $site, $repo ); 209 | 210 | $repos[ $repo->slug ] = [ 211 | 'slug' => $repo->slug, 212 | 'type' => $repo->type, 213 | 'url' => $this->get_endpoint( $site, $repo ), 214 | ]; 215 | 216 | $url = isset( $this->repos[ $repo->slug ]['urls'] ) ? $this->repos[ $repo->slug ]['urls'] : []; 217 | $url[] = $repos[ $repo->slug ]['url']; 218 | 219 | $repo_sites = isset( $this->repos[ $repo->slug ]['sites'] ) ? $this->repos[ $repo->slug ]['sites'] : []; 220 | $repo_sites[] = $site->site; 221 | $repo_sites = array_unique( $repo_sites ); 222 | 223 | $repos[ $repo->slug ]['urls'] = $url; 224 | $repos[ $repo->slug ]['sites'] = isset( $repos[ $repo->slug ]['sites'] ) ? array_merge( $repos[ $repo->slug ]['sites'], $repo_sites ) : $repo_sites; 225 | } 226 | $parsed_sites = array_merge( (array) $this->sites, $parsed_sites ); 227 | $repos = array_merge( (array) $this->repos, $repos ); 228 | } 229 | $this->sites = $parsed_sites; 230 | $this->repos = $repos; 231 | } 232 | } 233 | 234 | /** 235 | * Get RESTful endpoint. 236 | * 237 | * @param \stdClass $site Object of site. 238 | * @param \stdClass $repo Object of repo. 239 | * 240 | * @return string $endpoint 241 | */ 242 | private function get_endpoint( $site, $repo ) { 243 | $endpoint = add_query_arg( $repo->type, $repo->slug, "{$site->restful_start}" ); 244 | $endpoint = isset( $repo->branch ) ? add_query_arg( 'branch', $repo->branch, $endpoint ) : $endpoint; 245 | if ( isset( $repo->branch, $repo->primary_branch, $repo->tag ) 246 | && $repo->primary_branch === $repo->branch 247 | && $repo->tag 248 | ) { 249 | $endpoint = remove_query_arg( 'branch', $endpoint ); 250 | $endpoint = add_query_arg( 'tag', $repo->tag, $endpoint ); 251 | } 252 | $endpoint = add_query_arg( 'override', true, $endpoint ); 253 | 254 | return $endpoint; 255 | } 256 | 257 | /** 258 | * Parse the webhooks. 259 | * 260 | * @return void 261 | */ 262 | final public function get_all_webhooks() { 263 | $parsed = null; 264 | $all_webhooks = null; 265 | $parsed_webhooks = null; 266 | foreach ( (array) $this->sites as $site => $repos ) { 267 | foreach ( $repos as $repo ) { 268 | foreach ( $repo as $url ) { 269 | $all_webhooks[ $site ][] = $url; 270 | } 271 | } 272 | } 273 | 274 | $this->all_webhooks = $all_webhooks; 275 | } 276 | 277 | /** 278 | * Filter JSON data to remove specific slugs from updating. 279 | * 280 | * @param array $json Array of sites data. 281 | * @param array $remove_slugs Array of slugs to remove from updating. 282 | * 283 | * @return array 284 | */ 285 | private function remove_slugs_from_json( $json, $remove_slugs ) { 286 | if ( ! $json ) { 287 | return false; 288 | } 289 | foreach ( $json as $sites ) { 290 | if ( is_object( $sites ) && \property_exists( $sites, 'sites' ) ) { 291 | foreach ( $sites->sites->slugs as $key => $slug ) { 292 | if ( in_array( $slug->slug, $remove_slugs, true ) ) { 293 | unset( $sites->sites->slugs[ $key ] ); 294 | } 295 | } 296 | } 297 | } 298 | 299 | return $json; 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /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 || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false; 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((string) $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 | /** @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} $required */ 332 | $required = require $vendorDir.'/composer/installed.php'; 333 | $installed[] = self::$installedByVendor[$vendorDir] = $required; 334 | if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { 335 | self::$installed = $installed[count($installed) - 1]; 336 | } 337 | } 338 | } 339 | } 340 | 341 | if (null === self::$installed) { 342 | // only require the installed.php file if this file is loaded from its dumped location, 343 | // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 344 | if (substr(__DIR__, -8, 1) !== 'C') { 345 | /** @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} $required */ 346 | $required = require __DIR__ . '/installed.php'; 347 | self::$installed = $required; 348 | } else { 349 | self::$installed = array(); 350 | } 351 | } 352 | 353 | if (self::$installed !== array()) { 354 | $installed[] = self::$installed; 355 | } 356 | 357 | return $installed; 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "name": "dealerdirect/phpcodesniffer-composer-installer", 5 | "version": "v1.0.0", 6 | "version_normalized": "1.0.0.0", 7 | "source": { 8 | "type": "git", 9 | "url": "https://github.com/PHPCSStandards/composer-installer.git", 10 | "reference": "4be43904336affa5c2f70744a348312336afd0da" 11 | }, 12 | "dist": { 13 | "type": "zip", 14 | "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", 15 | "reference": "4be43904336affa5c2f70744a348312336afd0da", 16 | "shasum": "" 17 | }, 18 | "require": { 19 | "composer-plugin-api": "^1.0 || ^2.0", 20 | "php": ">=5.4", 21 | "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" 22 | }, 23 | "require-dev": { 24 | "composer/composer": "*", 25 | "ext-json": "*", 26 | "ext-zip": "*", 27 | "php-parallel-lint/php-parallel-lint": "^1.3.1", 28 | "phpcompatibility/php-compatibility": "^9.0", 29 | "yoast/phpunit-polyfills": "^1.0" 30 | }, 31 | "time": "2023-01-05T11:28:13+00:00", 32 | "type": "composer-plugin", 33 | "extra": { 34 | "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" 35 | }, 36 | "installation-source": "dist", 37 | "autoload": { 38 | "psr-4": { 39 | "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" 40 | } 41 | }, 42 | "notification-url": "https://packagist.org/downloads/", 43 | "license": [ 44 | "MIT" 45 | ], 46 | "authors": [ 47 | { 48 | "name": "Franck Nijhof", 49 | "email": "franck.nijhof@dealerdirect.com", 50 | "homepage": "http://www.frenck.nl", 51 | "role": "Developer / IT Manager" 52 | }, 53 | { 54 | "name": "Contributors", 55 | "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" 56 | } 57 | ], 58 | "description": "PHP_CodeSniffer Standards Composer Installer Plugin", 59 | "homepage": "http://www.dealerdirect.com", 60 | "keywords": [ 61 | "PHPCodeSniffer", 62 | "PHP_CodeSniffer", 63 | "code quality", 64 | "codesniffer", 65 | "composer", 66 | "installer", 67 | "phpcbf", 68 | "phpcs", 69 | "plugin", 70 | "qa", 71 | "quality", 72 | "standard", 73 | "standards", 74 | "style guide", 75 | "stylecheck", 76 | "tests" 77 | ], 78 | "support": { 79 | "issues": "https://github.com/PHPCSStandards/composer-installer/issues", 80 | "source": "https://github.com/PHPCSStandards/composer-installer" 81 | }, 82 | "install-path": "../dealerdirect/phpcodesniffer-composer-installer" 83 | }, 84 | { 85 | "name": "phpcsstandards/phpcsextra", 86 | "version": "1.2.1", 87 | "version_normalized": "1.2.1.0", 88 | "source": { 89 | "type": "git", 90 | "url": "https://github.com/PHPCSStandards/PHPCSExtra.git", 91 | "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489" 92 | }, 93 | "dist": { 94 | "type": "zip", 95 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSExtra/zipball/11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", 96 | "reference": "11d387c6642b6e4acaf0bd9bf5203b8cca1ec489", 97 | "shasum": "" 98 | }, 99 | "require": { 100 | "php": ">=5.4", 101 | "phpcsstandards/phpcsutils": "^1.0.9", 102 | "squizlabs/php_codesniffer": "^3.8.0" 103 | }, 104 | "require-dev": { 105 | "php-parallel-lint/php-console-highlighter": "^1.0", 106 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 107 | "phpcsstandards/phpcsdevcs": "^1.1.6", 108 | "phpcsstandards/phpcsdevtools": "^1.2.1", 109 | "phpunit/phpunit": "^4.5 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" 110 | }, 111 | "time": "2023-12-08T16:49:07+00:00", 112 | "type": "phpcodesniffer-standard", 113 | "extra": { 114 | "branch-alias": { 115 | "dev-stable": "1.x-dev", 116 | "dev-develop": "1.x-dev" 117 | } 118 | }, 119 | "installation-source": "dist", 120 | "notification-url": "https://packagist.org/downloads/", 121 | "license": [ 122 | "LGPL-3.0-or-later" 123 | ], 124 | "authors": [ 125 | { 126 | "name": "Juliette Reinders Folmer", 127 | "homepage": "https://github.com/jrfnl", 128 | "role": "lead" 129 | }, 130 | { 131 | "name": "Contributors", 132 | "homepage": "https://github.com/PHPCSStandards/PHPCSExtra/graphs/contributors" 133 | } 134 | ], 135 | "description": "A collection of sniffs and standards for use with PHP_CodeSniffer.", 136 | "keywords": [ 137 | "PHP_CodeSniffer", 138 | "phpcbf", 139 | "phpcodesniffer-standard", 140 | "phpcs", 141 | "standards", 142 | "static analysis" 143 | ], 144 | "support": { 145 | "issues": "https://github.com/PHPCSStandards/PHPCSExtra/issues", 146 | "security": "https://github.com/PHPCSStandards/PHPCSExtra/security/policy", 147 | "source": "https://github.com/PHPCSStandards/PHPCSExtra" 148 | }, 149 | "funding": [ 150 | { 151 | "url": "https://github.com/PHPCSStandards", 152 | "type": "github" 153 | }, 154 | { 155 | "url": "https://github.com/jrfnl", 156 | "type": "github" 157 | }, 158 | { 159 | "url": "https://opencollective.com/php_codesniffer", 160 | "type": "open_collective" 161 | } 162 | ], 163 | "install-path": "../phpcsstandards/phpcsextra" 164 | }, 165 | { 166 | "name": "phpcsstandards/phpcsutils", 167 | "version": "1.0.12", 168 | "version_normalized": "1.0.12.0", 169 | "source": { 170 | "type": "git", 171 | "url": "https://github.com/PHPCSStandards/PHPCSUtils.git", 172 | "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c" 173 | }, 174 | "dist": { 175 | "type": "zip", 176 | "url": "https://api.github.com/repos/PHPCSStandards/PHPCSUtils/zipball/87b233b00daf83fb70f40c9a28692be017ea7c6c", 177 | "reference": "87b233b00daf83fb70f40c9a28692be017ea7c6c", 178 | "shasum": "" 179 | }, 180 | "require": { 181 | "dealerdirect/phpcodesniffer-composer-installer": "^0.4.1 || ^0.5 || ^0.6.2 || ^0.7 || ^1.0", 182 | "php": ">=5.4", 183 | "squizlabs/php_codesniffer": "^3.10.0 || 4.0.x-dev@dev" 184 | }, 185 | "require-dev": { 186 | "ext-filter": "*", 187 | "php-parallel-lint/php-console-highlighter": "^1.0", 188 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 189 | "phpcsstandards/phpcsdevcs": "^1.1.6", 190 | "yoast/phpunit-polyfills": "^1.1.0 || ^2.0.0" 191 | }, 192 | "time": "2024-05-20T13:34:27+00:00", 193 | "type": "phpcodesniffer-standard", 194 | "extra": { 195 | "branch-alias": { 196 | "dev-stable": "1.x-dev", 197 | "dev-develop": "1.x-dev" 198 | } 199 | }, 200 | "installation-source": "dist", 201 | "autoload": { 202 | "classmap": [ 203 | "PHPCSUtils/" 204 | ] 205 | }, 206 | "notification-url": "https://packagist.org/downloads/", 207 | "license": [ 208 | "LGPL-3.0-or-later" 209 | ], 210 | "authors": [ 211 | { 212 | "name": "Juliette Reinders Folmer", 213 | "homepage": "https://github.com/jrfnl", 214 | "role": "lead" 215 | }, 216 | { 217 | "name": "Contributors", 218 | "homepage": "https://github.com/PHPCSStandards/PHPCSUtils/graphs/contributors" 219 | } 220 | ], 221 | "description": "A suite of utility functions for use with PHP_CodeSniffer", 222 | "homepage": "https://phpcsutils.com/", 223 | "keywords": [ 224 | "PHP_CodeSniffer", 225 | "phpcbf", 226 | "phpcodesniffer-standard", 227 | "phpcs", 228 | "phpcs3", 229 | "standards", 230 | "static analysis", 231 | "tokens", 232 | "utility" 233 | ], 234 | "support": { 235 | "docs": "https://phpcsutils.com/", 236 | "issues": "https://github.com/PHPCSStandards/PHPCSUtils/issues", 237 | "security": "https://github.com/PHPCSStandards/PHPCSUtils/security/policy", 238 | "source": "https://github.com/PHPCSStandards/PHPCSUtils" 239 | }, 240 | "funding": [ 241 | { 242 | "url": "https://github.com/PHPCSStandards", 243 | "type": "github" 244 | }, 245 | { 246 | "url": "https://github.com/jrfnl", 247 | "type": "github" 248 | }, 249 | { 250 | "url": "https://opencollective.com/php_codesniffer", 251 | "type": "open_collective" 252 | } 253 | ], 254 | "install-path": "../phpcsstandards/phpcsutils" 255 | }, 256 | { 257 | "name": "squizlabs/php_codesniffer", 258 | "version": "3.11.2", 259 | "version_normalized": "3.11.2.0", 260 | "source": { 261 | "type": "git", 262 | "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", 263 | "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079" 264 | }, 265 | "dist": { 266 | "type": "zip", 267 | "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/1368f4a58c3c52114b86b1abe8f4098869cb0079", 268 | "reference": "1368f4a58c3c52114b86b1abe8f4098869cb0079", 269 | "shasum": "" 270 | }, 271 | "require": { 272 | "ext-simplexml": "*", 273 | "ext-tokenizer": "*", 274 | "ext-xmlwriter": "*", 275 | "php": ">=5.4.0" 276 | }, 277 | "require-dev": { 278 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" 279 | }, 280 | "time": "2024-12-11T16:04:26+00:00", 281 | "bin": [ 282 | "bin/phpcbf", 283 | "bin/phpcs" 284 | ], 285 | "type": "library", 286 | "extra": { 287 | "branch-alias": { 288 | "dev-master": "3.x-dev" 289 | } 290 | }, 291 | "installation-source": "dist", 292 | "notification-url": "https://packagist.org/downloads/", 293 | "license": [ 294 | "BSD-3-Clause" 295 | ], 296 | "authors": [ 297 | { 298 | "name": "Greg Sherwood", 299 | "role": "Former lead" 300 | }, 301 | { 302 | "name": "Juliette Reinders Folmer", 303 | "role": "Current lead" 304 | }, 305 | { 306 | "name": "Contributors", 307 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" 308 | } 309 | ], 310 | "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", 311 | "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 312 | "keywords": [ 313 | "phpcs", 314 | "standards", 315 | "static analysis" 316 | ], 317 | "support": { 318 | "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", 319 | "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", 320 | "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", 321 | "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" 322 | }, 323 | "funding": [ 324 | { 325 | "url": "https://github.com/PHPCSStandards", 326 | "type": "github" 327 | }, 328 | { 329 | "url": "https://github.com/jrfnl", 330 | "type": "github" 331 | }, 332 | { 333 | "url": "https://opencollective.com/php_codesniffer", 334 | "type": "open_collective" 335 | } 336 | ], 337 | "install-path": "../squizlabs/php_codesniffer" 338 | }, 339 | { 340 | "name": "wp-coding-standards/wpcs", 341 | "version": "3.1.0", 342 | "version_normalized": "3.1.0.0", 343 | "source": { 344 | "type": "git", 345 | "url": "https://github.com/WordPress/WordPress-Coding-Standards.git", 346 | "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7" 347 | }, 348 | "dist": { 349 | "type": "zip", 350 | "url": "https://api.github.com/repos/WordPress/WordPress-Coding-Standards/zipball/9333efcbff231f10dfd9c56bb7b65818b4733ca7", 351 | "reference": "9333efcbff231f10dfd9c56bb7b65818b4733ca7", 352 | "shasum": "" 353 | }, 354 | "require": { 355 | "ext-filter": "*", 356 | "ext-libxml": "*", 357 | "ext-tokenizer": "*", 358 | "ext-xmlreader": "*", 359 | "php": ">=5.4", 360 | "phpcsstandards/phpcsextra": "^1.2.1", 361 | "phpcsstandards/phpcsutils": "^1.0.10", 362 | "squizlabs/php_codesniffer": "^3.9.0" 363 | }, 364 | "require-dev": { 365 | "php-parallel-lint/php-console-highlighter": "^1.0.0", 366 | "php-parallel-lint/php-parallel-lint": "^1.3.2", 367 | "phpcompatibility/php-compatibility": "^9.0", 368 | "phpcsstandards/phpcsdevtools": "^1.2.0", 369 | "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" 370 | }, 371 | "suggest": { 372 | "ext-iconv": "For improved results", 373 | "ext-mbstring": "For improved results" 374 | }, 375 | "time": "2024-03-25T16:39:00+00:00", 376 | "type": "phpcodesniffer-standard", 377 | "installation-source": "dist", 378 | "notification-url": "https://packagist.org/downloads/", 379 | "license": [ 380 | "MIT" 381 | ], 382 | "authors": [ 383 | { 384 | "name": "Contributors", 385 | "homepage": "https://github.com/WordPress/WordPress-Coding-Standards/graphs/contributors" 386 | } 387 | ], 388 | "description": "PHP_CodeSniffer rules (sniffs) to enforce WordPress coding conventions", 389 | "keywords": [ 390 | "phpcs", 391 | "standards", 392 | "static analysis", 393 | "wordpress" 394 | ], 395 | "support": { 396 | "issues": "https://github.com/WordPress/WordPress-Coding-Standards/issues", 397 | "source": "https://github.com/WordPress/WordPress-Coding-Standards", 398 | "wiki": "https://github.com/WordPress/WordPress-Coding-Standards/wiki" 399 | }, 400 | "funding": [ 401 | { 402 | "url": "https://opencollective.com/php_codesniffer", 403 | "type": "custom" 404 | } 405 | ], 406 | "install-path": "../wp-coding-standards/wpcs" 407 | } 408 | ], 409 | "dev": true, 410 | "dev-package-names": [ 411 | "dealerdirect/phpcodesniffer-composer-installer", 412 | "phpcsstandards/phpcsextra", 413 | "phpcsstandards/phpcsutils", 414 | "squizlabs/php_codesniffer", 415 | "wp-coding-standards/wpcs" 416 | ] 417 | } 418 | -------------------------------------------------------------------------------- /src/Git_Remote_Updater/Site_List_Table.php: -------------------------------------------------------------------------------- 1 | 'site', // singular name of the listed records. 51 | 'plural' => 'sites', // plural name of the listed records. 52 | 'ajax' => false, // does this table support ajax? 53 | ] 54 | ); 55 | } 56 | 57 | /** ************************************************************************ 58 | * Recommended. This method is called when the parent class can't find a method 59 | * specifically build for a given column. Generally, it's recommended to include 60 | * one method for each column you want to render, keeping your package class 61 | * neat and organized. For example, if the class needs to process a column 62 | * named 'title', it would first see if a method named $this->column_title() 63 | * exists - if it does, that method will be used. If it doesn't, this one will 64 | * be used. Generally, you should try to use custom column methods as much as 65 | * possible. 66 | * 67 | * Since we have defined a column_title() method later on, this method doesn't 68 | * need to concern itself with any column with a name of 'title'. Instead, it 69 | * needs to handle everything else. 70 | * 71 | * For more detailed insight into how columns are handled, take a look at 72 | * WP_List_Table::single_row_columns() 73 | * 74 | * @param array $item A singular item (one full row's worth of data). 75 | * @param array $column_name The name/slug of the column to be processed. 76 | * @return string Text or HTML to be placed inside the column 77 | **************************************************************************/ 78 | public function column_default( $item, $column_name ) { 79 | switch ( $column_name ) { 80 | case 'site': 81 | case 'api_key': 82 | return $item[ $column_name ]; 83 | default: 84 | // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r 85 | return print_r( $item, true ); // Show the whole array for troubleshooting purposes. 86 | } 87 | } 88 | 89 | /** ************************************************************************ 90 | * Recommended. This is a custom column method and is responsible for what 91 | * is rendered in any column with a name/slug of 'site'. Every time the class 92 | * needs to render a column, it first looks for a method named 93 | * column_{$column_title} - if it exists, that method is run. If it doesn't 94 | * exist, column_default() is called instead. 95 | * 96 | * This example also illustrates how to implement rollover actions. Actions 97 | * should be an associative array formatted as 'slug'=>'link html' - and you 98 | * will need to generate the URLs yourself. You could even ensure the links 99 | * 100 | * @see WP_List_Table::::single_row_columns() 101 | * @param array $item A singular item (one full row's worth of data). 102 | * @return string Text to be placed inside the column (site title only) 103 | **************************************************************************/ 104 | public function column_site( $item ) { 105 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended 106 | $page = isset( $_REQUEST['page'] ) ? sanitize_title_with_dashes( wp_unslash( $_REQUEST['page'] ) ) : null; 107 | $location = add_query_arg( 'page', $page, '' ); 108 | 109 | // Build row actions. 110 | $actions = [ 111 | // 'edit' => sprintf( 'Edit', $location, 'edit', $item['ID'] ), 112 | 'delete' => sprintf( 'Delete', wp_nonce_url( $location, 'delete_row_item', '_wpnonce_row_action_delete' ), 'delete_row_item', $item['ID'] ), 113 | ]; 114 | 115 | // Return the title contents. 116 | return sprintf( 117 | /* translators: 1: title, 2: ID, 3: row actions */ 118 | '%1$s (id:%2$s)%3$s', 119 | /*$1%s*/ 120 | $item['site'], 121 | /*$2%s*/ 122 | $item['ID'], 123 | /*$3%s*/ 124 | $this->row_actions( $actions ) 125 | ); 126 | } 127 | 128 | /** ************************************************************************ 129 | * REQUIRED if displaying checkboxes or using bulk actions! The 'cb' column 130 | * is given special treatment when columns are processed. It ALWAYS needs to 131 | * have it's own method. 132 | * 133 | * @see WP_List_Table::::single_row_columns() 134 | * @param array $item A singular item (one full row's worth of data). 135 | * @return string Text to be placed inside the column (movie title only) 136 | **************************************************************************/ 137 | public function column_cb( $item ) { 138 | return sprintf( 139 | '', 140 | /*$1%s*/ 141 | $this->_args['singular'], // Let's simply repurpose the table's singular label ("site"). 142 | /*$2%s*/ 143 | $item['ID'] // The value of the checkbox should be the record's id. 144 | ); 145 | } 146 | 147 | /** ************************************************************************ 148 | * REQUIRED! This method dictates the table's columns and titles. This should 149 | * return an array where the key is the column slug (and class) and the value 150 | * is the column's title text. If you need a checkbox for bulk actions, refer 151 | * to the $columns array below. 152 | * 153 | * The 'cb' column is treated differently than the rest. If including a checkbox 154 | * column in your table you must create a column_cb() method. If you don't need 155 | * bulk actions or checkboxes, simply leave the 'cb' entry out of your array. 156 | * 157 | * @see WP_List_Table::::single_row_columns() 158 | * @return array An associative array containing column information: 'slugs'=>'Visible Titles' 159 | **************************************************************************/ 160 | public function get_columns() { 161 | $columns = [ 162 | // 'cb' => '', // Render a checkbox instead of text. 163 | 'site' => esc_html__( 'Site', 'git-remote-updater' ), 164 | 'api_key' => esc_html__( 'REST API key', 'git-remote-updater' ), 165 | ]; 166 | 167 | return $columns; 168 | } 169 | 170 | /** ************************************************************************ 171 | * Optional. If you want one or more columns to be sortable (ASC/DESC toggle), 172 | * you will need to register it here. This should return an array where the 173 | * key is the column that needs to be sortable, and the value is db column to 174 | * sort by. Often, the key and value will be the same, but this is not always 175 | * the case (as the value is a column name from the database, not the list table). 176 | * 177 | * This method merely defines which columns should be sortable and makes them 178 | * clickable - it does not handle the actual sorting. You still need to detect 179 | * the ORDERBY and ORDER querystring variables within prepare_items() and sort 180 | * your data accordingly (usually by modifying your query). 181 | * 182 | * @return array An associative array containing all the columns that should be sortable: 'slugs'=>array('data_values',bool) 183 | **************************************************************************/ 184 | public function get_sortable_columns() { 185 | $sortable_columns = [ 186 | 'site' => [ 'site', false ], // true means it's already sorted. 187 | // 'api_key' => [ 'api_key', false ], 188 | ]; 189 | 190 | return $sortable_columns; 191 | } 192 | 193 | /** ************************************************************************ 194 | * Optional. If you need to include bulk actions in your list table, this is 195 | * the place to define them. Bulk actions are an associative array in the format 196 | * 'slug'=>'Visible Title' 197 | * 198 | * If this method returns an empty value, no bulk action will be rendered. If 199 | * you specify any bulk actions, the bulk actions box will be rendered with 200 | * the table automatically on display(). 201 | * 202 | * Also note that list tables are not automatically wrapped in
elements, 203 | * so you will need to create those manually in order for bulk actions to function. 204 | * 205 | * @return array An associative array containing all the bulk actions: 'slugs'=>'Visible Titles' 206 | **************************************************************************/ 207 | public function get_bulk_actions() { 208 | $actions = [ 209 | 'delete' => esc_html__( 'Delete', 'git-remote-updater' ), 210 | ]; 211 | 212 | return $actions; 213 | } 214 | 215 | /** ************************************************************************ 216 | * Optional. You can handle your bulk actions anywhere or anyhow you prefer. 217 | * For this example package, we will handle it in the class to keep things 218 | * clean and organized. 219 | * 220 | * @see $this->prepare_items() 221 | **************************************************************************/ 222 | public function process_bulk_action() { 223 | // Detect when a bulk action is being triggered... 224 | if ( ! isset( $_REQUEST['_wpnonce_row_action_delete'] ) 225 | || ! \wp_verify_nonce( \sanitize_key( \wp_unslash( $_REQUEST['_wpnonce_row_action_delete'] ) ), 'delete_row_item' ) 226 | ) { 227 | return; 228 | } 229 | $slugs = isset( $_REQUEST['slug'] ) ? sanitize_key( wp_unslash( $_REQUEST['slug'] ) ) : null; 230 | $slugs = is_array( $slugs ) ? $slugs : (array) $slugs; 231 | foreach ( $slugs as $slug ) { 232 | foreach ( self::$options as $key => $option ) { 233 | if ( in_array( $slug, $option, true ) ) { 234 | unset( self::$options[ $key ] ); 235 | update_site_option( 'git_remote_updater', self::$options ); 236 | } 237 | } 238 | } 239 | if ( 'edit' === $this->current_action() ) { 240 | wp_die( esc_html__( 'Items would go to edit when we write that code.', 'git-remote-updater' ) ); 241 | } 242 | } 243 | 244 | /** ************************************************************************ 245 | * REQUIRED! This is where you prepare your data for display. This method will 246 | * usually be used to query the database, sort and filter the data, and generally 247 | * get it ready to be displayed. At a minimum, we should set $this->items and 248 | * $this->set_pagination_args(), although the following properties and methods 249 | * are frequently interacted with here... 250 | * 251 | * @global WPDB $wpdb 252 | * @uses $this->_column_headers 253 | * @uses $this->items 254 | * @uses $this->get_columns() 255 | * @uses $this->get_sortable_columns() 256 | * @uses $this->get_pagenum() 257 | * @uses $this->set_pagination_args() 258 | **************************************************************************/ 259 | public function prepare_items() { 260 | global $wpdb; // This is used only if making any database queries. 261 | 262 | /** 263 | * First, lets decide how many records per page to show. 264 | */ 265 | $per_page = 5; 266 | 267 | /** 268 | * REQUIRED. Now we need to define our column headers. This includes a complete 269 | * array of columns to be displayed (slugs & titles), a list of columns 270 | * to keep hidden, and a list of columns that are sortable. Each of these 271 | * can be defined in another method (as we've done here) before being 272 | * used to build the value for our _column_headers property. 273 | */ 274 | $columns = $this->get_columns(); 275 | $hidden = []; 276 | $sortable = $this->get_sortable_columns(); 277 | 278 | /** 279 | * REQUIRED. Finally, we build an array to be used by the class for column 280 | * headers. The $this->_column_headers property takes an array which contains 281 | * 3 other arrays. One for all columns, one for hidden columns, and one 282 | * for sortable columns. 283 | */ 284 | $this->_column_headers = [ $columns, $hidden, $sortable ]; 285 | 286 | /** 287 | * Optional. You can handle your bulk actions however you see fit. In this 288 | * case, we'll handle them within our package just to keep things clean. 289 | */ 290 | $this->process_bulk_action(); 291 | 292 | /** 293 | * Instead of querying a database, we're going to fetch the example data 294 | * property we created for use in this plugin. This makes this example 295 | * package slightly different than one you might build on your own. In 296 | * this example, we'll be using array manipulation to sort and paginate 297 | * our data. In a real-world implementation, you will probably want to 298 | * use sort and pagination data to build a custom query instead, as you'll 299 | * be able to use your precisely-queried data immediately. 300 | */ 301 | $data = self::$options; 302 | 303 | usort( $data, [ $this, 'usort_reorder' ] ); 304 | 305 | /*********************************************************************** 306 | * --------------------------------------------------------------------- 307 | * vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 308 | * 309 | * In a real-world situation, this is where you would place your query. 310 | * 311 | * For information on making queries in WordPress, see this Codex entry: 312 | * http://codex.wordpress.org/Class_Reference/wpdb 313 | * 314 | * ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 315 | * --------------------------------------------------------------------- 316 | */ 317 | 318 | /** 319 | * REQUIRED for pagination. Let's figure out what page the user is currently 320 | * looking at. We'll need this later, so you should always include it in 321 | * your own package classes. 322 | */ 323 | $current_page = $this->get_pagenum(); 324 | 325 | /** 326 | * REQUIRED for pagination. Let's check how many items are in our data array. 327 | * In real-world use, this would be the total number of items in your database, 328 | * without filtering. We'll need this later, so you should always include it 329 | * in your own package classes. 330 | */ 331 | $total_items = count( $data ); 332 | 333 | /** 334 | * The WP_List_Table class does not handle pagination for us, so we need 335 | * to ensure that the data is trimmed to only the current page. We can use 336 | * array_slice() to 337 | */ 338 | $data = array_slice( $data, ( ( $current_page - 1 ) * $per_page ), $per_page ); 339 | 340 | /** 341 | * REQUIRED. Now we can add our *sorted* data to the items property, where 342 | * it can be used by the rest of the class. 343 | */ 344 | $this->items = $data; 345 | 346 | /** 347 | * REQUIRED. We also have to register our pagination options & calculations. 348 | */ 349 | $this->set_pagination_args( 350 | [ 351 | 'total_items' => $total_items, // WE have to calculate the total number of items. 352 | 'per_page' => $per_page, // WE have to determine how many items to show on a page. 353 | 'total_pages' => ceil( $total_items / $per_page ), // WE have to calculate the total number of pages. 354 | ] 355 | ); 356 | } 357 | 358 | /** 359 | * This checks for sorting input and sorts the data in our array accordingly. 360 | * 361 | * In a real-world situation involving a database, you would probably want 362 | * to handle sorting by passing the 'orderby' and 'order' values directly 363 | * to a custom query. The returned data will be pre-sorted, and this array 364 | * sorting technique would be unnecessary. 365 | * 366 | * @param array $a Array of table row data. 367 | * @param array $b Array of table row data. 368 | * 369 | * @return int Sort order, either 1 or -1. 370 | */ 371 | public function usort_reorder( $a, $b ) { 372 | // phpcs:disable WordPress.Security.NonceVerification.Recommended 373 | $orderby = ( ! empty( $_REQUEST['orderby'] ) ) ? sanitize_key( wp_unslash( $_REQUEST['orderby'] ) ) : 'site'; // If no sort, default to site. 374 | $order = ( ! empty( $_REQUEST['order'] ) ) ? sanitize_key( wp_unslash( $_REQUEST['order'] ) ) : 'asc'; // If no order, default to asc. 375 | // phpcs:enable 376 | $result = strcmp( $a[ $orderby ], $b[ $orderby ] ); // Determine sort order. 377 | return ( 'asc' === $order ) ? $result : -$result; // Send final sort direction to usort. 378 | } 379 | 380 | /** 381 | * Render list table. 382 | * 383 | * Explicitly calls prepare_items() and display(). 384 | * 385 | * @return void 386 | */ 387 | public function render_list_table() { 388 | 389 | // Fetch, prepare, sort, and filter our data... 390 | $this->prepare_items(); 391 | echo '
'; 392 | echo '

' . esc_html__( 'Settings', 'git-remote-updater' ) . '

'; 393 | 394 | // Forms are NOT created automatically, so you need to wrap the table in one to use features like bulk actions. 395 | echo ''; 396 | wp_nonce_field( 'process-items', '_wpnonce_list' ); 397 | 398 | // For plugins, we also need to ensure that the form posts back to our current page. 399 | // phpcs:ignore WordPress.Security.NonceVerification.Recommended 400 | $current_page = isset( $_REQUEST['page'] ) ? sanitize_title_with_dashes( wp_unslash( $_REQUEST['page'] ) ) : null; 401 | echo ''; 402 | 403 | // Now we can render the completed list table. 404 | $this->display(); 405 | echo ''; 406 | echo '
'; 407 | } 408 | } 409 | -------------------------------------------------------------------------------- /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 \Closure(string):void */ 46 | private static $includeFile; 47 | 48 | /** @var string|null */ 49 | private $vendorDir; 50 | 51 | // PSR-4 52 | /** 53 | * @var array> 54 | */ 55 | private $prefixLengthsPsr4 = array(); 56 | /** 57 | * @var array> 58 | */ 59 | private $prefixDirsPsr4 = array(); 60 | /** 61 | * @var list 62 | */ 63 | private $fallbackDirsPsr4 = array(); 64 | 65 | // PSR-0 66 | /** 67 | * List of PSR-0 prefixes 68 | * 69 | * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2'))) 70 | * 71 | * @var array>> 72 | */ 73 | private $prefixesPsr0 = array(); 74 | /** 75 | * @var list 76 | */ 77 | private $fallbackDirsPsr0 = array(); 78 | 79 | /** @var bool */ 80 | private $useIncludePath = false; 81 | 82 | /** 83 | * @var array 84 | */ 85 | private $classMap = array(); 86 | 87 | /** @var bool */ 88 | private $classMapAuthoritative = false; 89 | 90 | /** 91 | * @var array 92 | */ 93 | private $missingClasses = array(); 94 | 95 | /** @var string|null */ 96 | private $apcuPrefix; 97 | 98 | /** 99 | * @var array 100 | */ 101 | private static $registeredLoaders = array(); 102 | 103 | /** 104 | * @param string|null $vendorDir 105 | */ 106 | public function __construct($vendorDir = null) 107 | { 108 | $this->vendorDir = $vendorDir; 109 | self::initializeIncludeClosure(); 110 | } 111 | 112 | /** 113 | * @return array> 114 | */ 115 | public function getPrefixes() 116 | { 117 | if (!empty($this->prefixesPsr0)) { 118 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 119 | } 120 | 121 | return array(); 122 | } 123 | 124 | /** 125 | * @return array> 126 | */ 127 | public function getPrefixesPsr4() 128 | { 129 | return $this->prefixDirsPsr4; 130 | } 131 | 132 | /** 133 | * @return list 134 | */ 135 | public function getFallbackDirs() 136 | { 137 | return $this->fallbackDirsPsr0; 138 | } 139 | 140 | /** 141 | * @return list 142 | */ 143 | public function getFallbackDirsPsr4() 144 | { 145 | return $this->fallbackDirsPsr4; 146 | } 147 | 148 | /** 149 | * @return array Array of classname => path 150 | */ 151 | public function getClassMap() 152 | { 153 | return $this->classMap; 154 | } 155 | 156 | /** 157 | * @param array $classMap Class to filename map 158 | * 159 | * @return void 160 | */ 161 | public function addClassMap(array $classMap) 162 | { 163 | if ($this->classMap) { 164 | $this->classMap = array_merge($this->classMap, $classMap); 165 | } else { 166 | $this->classMap = $classMap; 167 | } 168 | } 169 | 170 | /** 171 | * Registers a set of PSR-0 directories for a given prefix, either 172 | * appending or prepending to the ones previously set for this prefix. 173 | * 174 | * @param string $prefix The prefix 175 | * @param list|string $paths The PSR-0 root directories 176 | * @param bool $prepend Whether to prepend the directories 177 | * 178 | * @return void 179 | */ 180 | public function add($prefix, $paths, $prepend = false) 181 | { 182 | $paths = (array) $paths; 183 | if (!$prefix) { 184 | if ($prepend) { 185 | $this->fallbackDirsPsr0 = array_merge( 186 | $paths, 187 | $this->fallbackDirsPsr0 188 | ); 189 | } else { 190 | $this->fallbackDirsPsr0 = array_merge( 191 | $this->fallbackDirsPsr0, 192 | $paths 193 | ); 194 | } 195 | 196 | return; 197 | } 198 | 199 | $first = $prefix[0]; 200 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 201 | $this->prefixesPsr0[$first][$prefix] = $paths; 202 | 203 | return; 204 | } 205 | if ($prepend) { 206 | $this->prefixesPsr0[$first][$prefix] = array_merge( 207 | $paths, 208 | $this->prefixesPsr0[$first][$prefix] 209 | ); 210 | } else { 211 | $this->prefixesPsr0[$first][$prefix] = array_merge( 212 | $this->prefixesPsr0[$first][$prefix], 213 | $paths 214 | ); 215 | } 216 | } 217 | 218 | /** 219 | * Registers a set of PSR-4 directories for a given namespace, either 220 | * appending or prepending to the ones previously set for this namespace. 221 | * 222 | * @param string $prefix The prefix/namespace, with trailing '\\' 223 | * @param list|string $paths The PSR-4 base directories 224 | * @param bool $prepend Whether to prepend the directories 225 | * 226 | * @throws \InvalidArgumentException 227 | * 228 | * @return void 229 | */ 230 | public function addPsr4($prefix, $paths, $prepend = false) 231 | { 232 | $paths = (array) $paths; 233 | if (!$prefix) { 234 | // Register directories for the root namespace. 235 | if ($prepend) { 236 | $this->fallbackDirsPsr4 = array_merge( 237 | $paths, 238 | $this->fallbackDirsPsr4 239 | ); 240 | } else { 241 | $this->fallbackDirsPsr4 = array_merge( 242 | $this->fallbackDirsPsr4, 243 | $paths 244 | ); 245 | } 246 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 247 | // Register directories for a new namespace. 248 | $length = strlen($prefix); 249 | if ('\\' !== $prefix[$length - 1]) { 250 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 251 | } 252 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 253 | $this->prefixDirsPsr4[$prefix] = $paths; 254 | } elseif ($prepend) { 255 | // Prepend directories for an already registered namespace. 256 | $this->prefixDirsPsr4[$prefix] = array_merge( 257 | $paths, 258 | $this->prefixDirsPsr4[$prefix] 259 | ); 260 | } else { 261 | // Append directories for an already registered namespace. 262 | $this->prefixDirsPsr4[$prefix] = array_merge( 263 | $this->prefixDirsPsr4[$prefix], 264 | $paths 265 | ); 266 | } 267 | } 268 | 269 | /** 270 | * Registers a set of PSR-0 directories for a given prefix, 271 | * replacing any others previously set for this prefix. 272 | * 273 | * @param string $prefix The prefix 274 | * @param list|string $paths The PSR-0 base directories 275 | * 276 | * @return void 277 | */ 278 | public function set($prefix, $paths) 279 | { 280 | if (!$prefix) { 281 | $this->fallbackDirsPsr0 = (array) $paths; 282 | } else { 283 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 284 | } 285 | } 286 | 287 | /** 288 | * Registers a set of PSR-4 directories for a given namespace, 289 | * replacing any others previously set for this namespace. 290 | * 291 | * @param string $prefix The prefix/namespace, with trailing '\\' 292 | * @param list|string $paths The PSR-4 base directories 293 | * 294 | * @throws \InvalidArgumentException 295 | * 296 | * @return void 297 | */ 298 | public function setPsr4($prefix, $paths) 299 | { 300 | if (!$prefix) { 301 | $this->fallbackDirsPsr4 = (array) $paths; 302 | } else { 303 | $length = strlen($prefix); 304 | if ('\\' !== $prefix[$length - 1]) { 305 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 306 | } 307 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 308 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 309 | } 310 | } 311 | 312 | /** 313 | * Turns on searching the include path for class files. 314 | * 315 | * @param bool $useIncludePath 316 | * 317 | * @return void 318 | */ 319 | public function setUseIncludePath($useIncludePath) 320 | { 321 | $this->useIncludePath = $useIncludePath; 322 | } 323 | 324 | /** 325 | * Can be used to check if the autoloader uses the include path to check 326 | * for classes. 327 | * 328 | * @return bool 329 | */ 330 | public function getUseIncludePath() 331 | { 332 | return $this->useIncludePath; 333 | } 334 | 335 | /** 336 | * Turns off searching the prefix and fallback directories for classes 337 | * that have not been registered with the class map. 338 | * 339 | * @param bool $classMapAuthoritative 340 | * 341 | * @return void 342 | */ 343 | public function setClassMapAuthoritative($classMapAuthoritative) 344 | { 345 | $this->classMapAuthoritative = $classMapAuthoritative; 346 | } 347 | 348 | /** 349 | * Should class lookup fail if not found in the current class map? 350 | * 351 | * @return bool 352 | */ 353 | public function isClassMapAuthoritative() 354 | { 355 | return $this->classMapAuthoritative; 356 | } 357 | 358 | /** 359 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 360 | * 361 | * @param string|null $apcuPrefix 362 | * 363 | * @return void 364 | */ 365 | public function setApcuPrefix($apcuPrefix) 366 | { 367 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 368 | } 369 | 370 | /** 371 | * The APCu prefix in use, or null if APCu caching is not enabled. 372 | * 373 | * @return string|null 374 | */ 375 | public function getApcuPrefix() 376 | { 377 | return $this->apcuPrefix; 378 | } 379 | 380 | /** 381 | * Registers this instance as an autoloader. 382 | * 383 | * @param bool $prepend Whether to prepend the autoloader or not 384 | * 385 | * @return void 386 | */ 387 | public function register($prepend = false) 388 | { 389 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 390 | 391 | if (null === $this->vendorDir) { 392 | return; 393 | } 394 | 395 | if ($prepend) { 396 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 397 | } else { 398 | unset(self::$registeredLoaders[$this->vendorDir]); 399 | self::$registeredLoaders[$this->vendorDir] = $this; 400 | } 401 | } 402 | 403 | /** 404 | * Unregisters this instance as an autoloader. 405 | * 406 | * @return void 407 | */ 408 | public function unregister() 409 | { 410 | spl_autoload_unregister(array($this, 'loadClass')); 411 | 412 | if (null !== $this->vendorDir) { 413 | unset(self::$registeredLoaders[$this->vendorDir]); 414 | } 415 | } 416 | 417 | /** 418 | * Loads the given class or interface. 419 | * 420 | * @param string $class The name of the class 421 | * @return true|null True if loaded, null otherwise 422 | */ 423 | public function loadClass($class) 424 | { 425 | if ($file = $this->findFile($class)) { 426 | $includeFile = self::$includeFile; 427 | $includeFile($file); 428 | 429 | return true; 430 | } 431 | 432 | return null; 433 | } 434 | 435 | /** 436 | * Finds the path to the file where the class is defined. 437 | * 438 | * @param string $class The name of the class 439 | * 440 | * @return string|false The path if found, false otherwise 441 | */ 442 | public function findFile($class) 443 | { 444 | // class map lookup 445 | if (isset($this->classMap[$class])) { 446 | return $this->classMap[$class]; 447 | } 448 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 449 | return false; 450 | } 451 | if (null !== $this->apcuPrefix) { 452 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 453 | if ($hit) { 454 | return $file; 455 | } 456 | } 457 | 458 | $file = $this->findFileWithExtension($class, '.php'); 459 | 460 | // Search for Hack files if we are running on HHVM 461 | if (false === $file && defined('HHVM_VERSION')) { 462 | $file = $this->findFileWithExtension($class, '.hh'); 463 | } 464 | 465 | if (null !== $this->apcuPrefix) { 466 | apcu_add($this->apcuPrefix.$class, $file); 467 | } 468 | 469 | if (false === $file) { 470 | // Remember that this class does not exist. 471 | $this->missingClasses[$class] = true; 472 | } 473 | 474 | return $file; 475 | } 476 | 477 | /** 478 | * Returns the currently registered loaders keyed by their corresponding vendor directories. 479 | * 480 | * @return array 481 | */ 482 | public static function getRegisteredLoaders() 483 | { 484 | return self::$registeredLoaders; 485 | } 486 | 487 | /** 488 | * @param string $class 489 | * @param string $ext 490 | * @return string|false 491 | */ 492 | private function findFileWithExtension($class, $ext) 493 | { 494 | // PSR-4 lookup 495 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 496 | 497 | $first = $class[0]; 498 | if (isset($this->prefixLengthsPsr4[$first])) { 499 | $subPath = $class; 500 | while (false !== $lastPos = strrpos($subPath, '\\')) { 501 | $subPath = substr($subPath, 0, $lastPos); 502 | $search = $subPath . '\\'; 503 | if (isset($this->prefixDirsPsr4[$search])) { 504 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 505 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 506 | if (file_exists($file = $dir . $pathEnd)) { 507 | return $file; 508 | } 509 | } 510 | } 511 | } 512 | } 513 | 514 | // PSR-4 fallback dirs 515 | foreach ($this->fallbackDirsPsr4 as $dir) { 516 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 517 | return $file; 518 | } 519 | } 520 | 521 | // PSR-0 lookup 522 | if (false !== $pos = strrpos($class, '\\')) { 523 | // namespaced class name 524 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 525 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 526 | } else { 527 | // PEAR-like class name 528 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 529 | } 530 | 531 | if (isset($this->prefixesPsr0[$first])) { 532 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 533 | if (0 === strpos($class, $prefix)) { 534 | foreach ($dirs as $dir) { 535 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 536 | return $file; 537 | } 538 | } 539 | } 540 | } 541 | } 542 | 543 | // PSR-0 fallback dirs 544 | foreach ($this->fallbackDirsPsr0 as $dir) { 545 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 546 | return $file; 547 | } 548 | } 549 | 550 | // PSR-0 include paths. 551 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 552 | return $file; 553 | } 554 | 555 | return false; 556 | } 557 | 558 | /** 559 | * @return void 560 | */ 561 | private static function initializeIncludeClosure() 562 | { 563 | if (self::$includeFile !== null) { 564 | return; 565 | } 566 | 567 | /** 568 | * Scope isolated include. 569 | * 570 | * Prevents access to $this/self from included files. 571 | * 572 | * @param string $file 573 | * @return void 574 | */ 575 | self::$includeFile = \Closure::bind(static function($file) { 576 | include $file; 577 | }, null, null); 578 | } 579 | } 580 | --------------------------------------------------------------------------------