├── vendor ├── composer │ ├── autoload_psr4.php │ ├── autoload_namespaces.php │ ├── autoload_classmap.php │ ├── autoload_static.php │ ├── LICENSE │ ├── autoload_real.php │ ├── installed.json │ └── ClassLoader.php ├── autoload.php ├── collizo4sky │ └── persist-admin-notices-dismissal │ │ ├── composer.json │ │ ├── CHANGES.md │ │ ├── dismiss-notice.js │ │ ├── README.md │ │ └── persist-admin-notices-dismissal.php └── afragen │ └── wp-dependency-installer │ ├── composer.json │ ├── LICENSE │ ├── README.md │ └── wp-dependency-installer.php ├── README.md ├── wp-dependencies.json ├── LICENSE ├── composer.json ├── languages └── group-plugin-installer.pot └── group-plugin-installer.php /vendor/composer/autoload_psr4.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/collizo4sky/persist-admin-notices-dismissal/persist-admin-notices-dismissal.php', 10 | 'WPDI_Plugin_Installer_Skin' => $vendorDir . '/afragen/wp-dependency-installer/wp-dependency-installer.php', 11 | 'WP_Dependency_Installer' => $vendorDir . '/afragen/wp-dependency-installer/wp-dependency-installer.php', 12 | ); 13 | -------------------------------------------------------------------------------- /vendor/collizo4sky/persist-admin-notices-dismissal/CHANGES.md: -------------------------------------------------------------------------------- 1 | #### 1.4.3 2 | * added filter hook `pand_dismiss_notice_js_url` in case you're using this in a theme or a local environment that doesn't quite find the correct URL. 3 | * added filter hook `pand_theme_loader` that returns a boolean for simpler usage of the `pand_dismiss_notice_js_url` hook 4 | 5 | #### 1.4.2 6 | * No changes to `class PAnD` 7 | * updated `.gitignore` and `.gitattributes` 8 | * now use classmap in composer's autoloader, should be more efficient 9 | 10 | #### 1.4.1 11 | * fixed the `forever` setting with options 12 | 13 | #### 1.4.0 14 | * WPCS 1.1.0 linting done 15 | * switched from storing timeout in transients to storing in the options table, this should play much better with object caching 16 | 17 | #### 1.3.x 18 | * uses transients to store timeout 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Group Plugin Installer 2 | 3 | * Plugin Name: Group Plugin Installer 4 | * Plugin URI: https://github.com/afragen/group-plugin-installer 5 | * Description: Allows you to easily add a group of plugins to a WordPress installation. 6 | * Author: Andy Fragen 7 | * License: MIT 8 | * Requires WP: 5.1 9 | * Requires PHP: 5.6 10 | * GitHub Plugin URI: afragen/group-plugin-installer 11 | 12 | ## Description 13 | 14 | Allows you to easily add a group of plugins to a WordPress installation by using [wp-dependency-installer framework](https://github.com/afragen/wp-dependency-installer). 15 | 16 | Also see [WP Dependency Installer Examples](https://github.com/afragen/wp-dependency-installer-examples). 17 | 18 | You can change the plugins that are installed by editing the `wp-dependencies.json` file. Instructions are in the framework link above. 19 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | __DIR__ . '/..' . '/collizo4sky/persist-admin-notices-dismissal/persist-admin-notices-dismissal.php', 11 | 'WPDI_Plugin_Installer_Skin' => __DIR__ . '/..' . '/afragen/wp-dependency-installer/wp-dependency-installer.php', 12 | 'WP_Dependency_Installer' => __DIR__ . '/..' . '/afragen/wp-dependency-installer/wp-dependency-installer.php', 13 | ); 14 | 15 | public static function getInitializer(ClassLoader $loader) 16 | { 17 | return \Closure::bind(function () use ($loader) { 18 | $loader->classMap = ComposerStaticInit17be29b7c9b1db70c041e7035f752e43::$classMap; 19 | 20 | }, null, ClassLoader::class); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /wp-dependencies.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "GitHub Updater", 4 | "host": "github", 5 | "slug": "github-updater/github-updater.php", 6 | "uri": "afragen/github-updater", 7 | "branch": "develop", 8 | "required": true, 9 | "token": null 10 | }, 11 | { 12 | "name": "Query Monitor", 13 | "host": "wordpress", 14 | "slug": "query-monitor/query-monitor.php", 15 | "uri": "https://wordpress.org/plugins/query-monitor/", 16 | "required": true 17 | }, 18 | { 19 | "name": "WP Debugging", 20 | "host": "wordpress", 21 | "slug": "wp-debugging/wp-debugging.php", 22 | "uri": "https://wordpress.org/plugins/wp-debugging/", 23 | "required": true 24 | }, 25 | { 26 | "name": "Local Development", 27 | "host": "wordpress", 28 | "slug": "local-development/local-development.php", 29 | "uri": "https://wordpress.org/plugins/local-development/", 30 | "required": true 31 | } 32 | ] 33 | -------------------------------------------------------------------------------- /vendor/collizo4sky/persist-admin-notices-dismissal/dismiss-notice.js: -------------------------------------------------------------------------------- 1 | (function ($) { 2 | //shorthand for ready event. 3 | $( 4 | function () { 5 | $( 'div[data-dismissible] button.notice-dismiss' ).click( 6 | function (event) { 7 | event.preventDefault(); 8 | var $this = $( this ); 9 | 10 | var attr_value, option_name, dismissible_length, data; 11 | 12 | attr_value = $this.parent().attr( 'data-dismissible' ).split( '-' ); 13 | 14 | // remove the dismissible length from the attribute value and rejoin the array. 15 | dismissible_length = attr_value.pop(); 16 | 17 | option_name = attr_value.join( '-' ); 18 | 19 | data = { 20 | 'action': 'dismiss_admin_notice', 21 | 'option_name': option_name, 22 | 'dismissible_length': dismissible_length, 23 | 'nonce': dismissible_notice.nonce 24 | }; 25 | 26 | // We can also pass the url value separately from ajaxurl for front end AJAX implementations 27 | $.post( ajaxurl, data ); 28 | } 29 | ); 30 | } 31 | ) 32 | 33 | }(jQuery)); 34 | -------------------------------------------------------------------------------- /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/afragen/wp-dependency-installer/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afragen/wp-dependency-installer", 3 | "description": "Library that helps WordPress plugin dependency management.", 4 | "version": "3.0.0", 5 | "type": "library", 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Andy Fragen", 10 | "email": "andy@thefragens.com", 11 | "homepage": "https://thefragens.com", 12 | "role": "Developer" 13 | }, 14 | { 15 | "name": "Matt Gibbs", 16 | "homepage": "https://facetwp.com", 17 | "role": "Developer" 18 | }, 19 | { 20 | "name": "Raruto", 21 | "homepage": "https://raruto.github.io", 22 | "role": "Developer" 23 | } 24 | ], 25 | "prefer-stable": true, 26 | "require": { 27 | "php": ">=5.6", 28 | "collizo4sky/persist-admin-notices-dismissal": "^1" 29 | }, 30 | "support": { 31 | "issues": "https://github.com/afragen/wp-dependency-installer/issues", 32 | "source": "https://github.com/afragen/wp-dependency-installer" 33 | }, 34 | "autoload": { 35 | "classmap": [ 36 | "wp-dependency-installer.php" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /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/afragen/wp-dependency-installer/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 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 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afragen/group-plugin-installer", 3 | "description": "Allows you to easily add a group of plugins to a WordPress installation.", 4 | "type": "wordpress-plugin", 5 | "keywords": [ 6 | "wordpress", 7 | "development", 8 | "plugin" 9 | ], 10 | "license": "MIT", 11 | "authors": [ 12 | { 13 | "name": "Andy Fragen", 14 | "email": "andy@thefragens.com", 15 | "homepage": "https://thefragens.com", 16 | "role": "Developer" 17 | } 18 | ], 19 | "repositories": [ 20 | { 21 | "type": "vcs", 22 | "url": "https://github.com/afragen/group-plugin-installer" 23 | } 24 | ], 25 | "support": { 26 | "issues": "https://github.com/afragen/group-plugin-installer/issues", 27 | "source": "https://github.com/afragen/group-plugin-installer" 28 | }, 29 | "prefer-stable": true, 30 | "require": { 31 | "afragen/wp-dependency-installer": "^3" 32 | }, 33 | "scripts": { 34 | "post-update-cmd": [ 35 | "wp i18n make-pot . languages/group-plugin-installer.pot" 36 | ] 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /languages/group-plugin-installer.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2020 Andy Fragen 2 | # This file is distributed under the same license as the Group Plugin Installer plugin. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: Group Plugin Installer 0.4.0\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/group-plugin-installer\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: 2020-02-28T17:48:15+00:00\n" 13 | "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" 14 | "X-Generator: WP-CLI 2.4.0\n" 15 | "X-Domain: group-plugin-installer\n" 16 | 17 | #. Plugin Name of the plugin 18 | #: group-plugin-installer.php:42 19 | #: group-plugin-installer.php:55 20 | msgid "Group Plugin Installer" 21 | msgstr "" 22 | 23 | #. Plugin URI of the plugin 24 | msgid "https://github.com/afragen/group-plugin-installer" 25 | msgstr "" 26 | 27 | #. Description of the plugin 28 | msgid "Allows you to easily add a group of plugins to a WordPress installation." 29 | msgstr "" 30 | 31 | #. Author of the plugin 32 | msgid "Andy Fragen" 33 | msgstr "" 34 | 35 | #: group-plugin-installer.php:57 36 | msgid "Another theme or plugin is using a previous version of the WP Dependency Installer library, please update this file and try again:" 37 | msgstr "" 38 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 27 | if ($useStaticLoader) { 28 | require_once __DIR__ . '/autoload_static.php'; 29 | 30 | call_user_func(\Composer\Autoload\ComposerStaticInit17be29b7c9b1db70c041e7035f752e43::getInitializer($loader)); 31 | } else { 32 | $map = require __DIR__ . '/autoload_namespaces.php'; 33 | foreach ($map as $namespace => $path) { 34 | $loader->set($namespace, $path); 35 | } 36 | 37 | $map = require __DIR__ . '/autoload_psr4.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->setPsr4($namespace, $path); 40 | } 41 | 42 | $classMap = require __DIR__ . '/autoload_classmap.php'; 43 | if ($classMap) { 44 | $loader->addClassMap($classMap); 45 | } 46 | } 47 | 48 | $loader->register(true); 49 | 50 | return $loader; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /group-plugin-installer.php: -------------------------------------------------------------------------------- 1 | run(); 28 | 29 | add_filter( 30 | 'wp_dependency_timeout', 31 | function( $timeout, $source ) { 32 | $timeout = basename( __DIR__ ) !== $source ? $timeout : 14; 33 | return $timeout; 34 | }, 35 | 10, 36 | 2 37 | ); 38 | 39 | add_filter( 40 | 'wp_dependency_dismiss_label', 41 | function( $label, $source ) { 42 | $label = basename( __DIR__ ) !== $source ? $label : __( 'Group Plugin Installer', 'group-plugin-installer' ); 43 | return $label; 44 | }, 45 | 10, 46 | 2 47 | ); 48 | 49 | // Sanity check for WPDI v3.0.0. 50 | if ( ! method_exists( 'WP_Dependency_Installer', 'json_file_decode' ) ) { 51 | add_action( 52 | 'admin_notices', 53 | function() { 54 | $class = 'notice notice-error is-dismissible'; 55 | $label = __( 'Group Plugin Installer', 'group-plugin-installer' ); 56 | $file = ( new ReflectionClass( 'WP_Dependency_Installer' ) )->getFilename(); 57 | $message = __( 'Another theme or plugin is using a previous version of the WP Dependency Installer library, please update this file and try again:', 'group-plugin-installer' ); 58 | printf( '

[%2$s] %3$s

%4$s
', esc_attr( $class ), esc_html( $label ), esc_html( $message ), esc_html( $file ) ); 59 | }, 60 | 1 61 | ); 62 | return false; // Exit early. 63 | } 64 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "afragen/wp-dependency-installer", 4 | "version": "3.0.0", 5 | "version_normalized": "3.0.0.0", 6 | "source": { 7 | "type": "git", 8 | "url": "https://github.com/afragen/wp-dependency-installer.git", 9 | "reference": "70c6131c9a7a475d42de370b12fbe1d5c8bb5aba" 10 | }, 11 | "dist": { 12 | "type": "zip", 13 | "url": "https://api.github.com/repos/afragen/wp-dependency-installer/zipball/70c6131c9a7a475d42de370b12fbe1d5c8bb5aba", 14 | "reference": "70c6131c9a7a475d42de370b12fbe1d5c8bb5aba", 15 | "shasum": "" 16 | }, 17 | "require": { 18 | "collizo4sky/persist-admin-notices-dismissal": "^1", 19 | "php": ">=5.6" 20 | }, 21 | "time": "2020-02-28T17:28:54+00:00", 22 | "type": "library", 23 | "installation-source": "dist", 24 | "autoload": { 25 | "classmap": [ 26 | "wp-dependency-installer.php" 27 | ] 28 | }, 29 | "notification-url": "https://packagist.org/downloads/", 30 | "license": [ 31 | "MIT" 32 | ], 33 | "authors": [ 34 | { 35 | "name": "Andy Fragen", 36 | "email": "andy@thefragens.com", 37 | "homepage": "https://thefragens.com", 38 | "role": "Developer" 39 | }, 40 | { 41 | "name": "Matt Gibbs", 42 | "homepage": "https://facetwp.com", 43 | "role": "Developer" 44 | }, 45 | { 46 | "name": "Raruto", 47 | "homepage": "https://raruto.github.io", 48 | "role": "Developer" 49 | } 50 | ], 51 | "description": "Library that helps WordPress plugin dependency management." 52 | }, 53 | { 54 | "name": "collizo4sky/persist-admin-notices-dismissal", 55 | "version": "1.4.3", 56 | "version_normalized": "1.4.3.0", 57 | "source": { 58 | "type": "git", 59 | "url": "https://github.com/collizo4sky/persist-admin-notices-dismissal.git", 60 | "reference": "2d7d8bb3cba631ad227c92296a4b675d7cbc71d7" 61 | }, 62 | "dist": { 63 | "type": "zip", 64 | "url": "https://api.github.com/repos/collizo4sky/persist-admin-notices-dismissal/zipball/2d7d8bb3cba631ad227c92296a4b675d7cbc71d7", 65 | "reference": "2d7d8bb3cba631ad227c92296a4b675d7cbc71d7", 66 | "shasum": "" 67 | }, 68 | "time": "2019-03-12T05:19:51+00:00", 69 | "type": "library", 70 | "installation-source": "dist", 71 | "autoload": { 72 | "classmap": [ 73 | "persist-admin-notices-dismissal.php" 74 | ] 75 | }, 76 | "notification-url": "https://packagist.org/downloads/", 77 | "authors": [ 78 | { 79 | "name": "Collins Agbonghama", 80 | "email": "me@w3guy.com" 81 | } 82 | ], 83 | "description": "Simple library to persist dismissal of admin notices across pages in WordPress dashboard." 84 | } 85 | ] 86 | -------------------------------------------------------------------------------- /vendor/afragen/wp-dependency-installer/README.md: -------------------------------------------------------------------------------- 1 | # WP Dependency Installer 2 | * Contributors: [Andy Fragen](https://github.com/afragen), [Matt Gibbs](https://github.com/mgibbs189), [Raruto](https://github.com/Raruto), [contributors](https://github.com/afragen/wp-dependency-installer/graphs/contributors) 3 | * Tags: plugin, dependency, install 4 | * Requires at least: 5.1 5 | * Requires PHP: 5.6 6 | * Stable tag: master 7 | * Donate link: 8 | * License: MIT 9 | 10 | This is a drop in class for developers to optionally or automatically install plugin dependencies for their own plugins or themes. It can install a plugin from wp.org, GitHub, Bitbucket, GitLab, Gitea, or a direct URL. 11 | 12 | [Comprehensive information regarding WP Dependency Installer is available on the wiki.](https://github.com/afragen/wp-dependency-installer/wiki) 13 | 14 | See also: [example plugin](https://github.com/afragen/wp-dependency-installer-examples). 15 | 16 | ## Description 17 | 18 | You can use **composer** to install this package within your WordPress plugin / theme. 19 | 20 | 1. Within your plugin or theme root folder, run the following command: 21 | 22 | ```shell 23 | composer require afragen/wp-dependency-installer 24 | ``` 25 | 26 | 2. Then create a sample [**`wp-dependencies.json`**](https://github.com/afragen/wp-dependency-installer/wiki/Configuration#json-config-file-format) file 27 | 28 | ```js 29 | [ 30 | { 31 | "name": "GitHub Updater", 32 | "host": "github", 33 | "slug": "github-updater/github-updater.php", 34 | "uri": "afragen/github-updater", 35 | "branch": "develop", 36 | "required": true, 37 | "token": null 38 | }, 39 | { 40 | "name": "Query Monitor", 41 | "host": "wordpress", 42 | "slug": "query-monitor/query-monitor.php", 43 | "uri": "https://wordpress.org/plugins/query-monitor/", 44 | "optional": true 45 | }, 46 | { 47 | "name": "Local Development", 48 | "host": "WordPress", 49 | "slug": "local-development/local-development.php", 50 | "uri": "https://wordpress.org/plugins/local-development/", 51 | "required": true 52 | } 53 | ] 54 | ``` 55 | 56 | You will then need to update `wp-dependencies.json` to suit your requirements. 57 | 58 | 3. Finally add the following lines to your plugin or theme's `functions.php` file: 59 | 60 | ```php 61 | require_once __DIR__ . '/vendor/autoload.php'; 62 | WP_Dependency_Installer::instance( __DIR__ )->run(); 63 | 64 | // Needed in theme's functions.php file. 65 | add_filter( 'pand_theme_loader', '__return_true' ); 66 | ``` 67 | 68 | 4. (optional) Take a look at some of built in [Hooks](https://github.com/afragen/wp-dependency-installer/wiki/Actions-and-Hooks) and [Functions](https://github.com/afragen/wp-dependency-installer/wiki/Helper-Functions) to further customize your plugin look and behaviour: 69 | 70 | ```php 71 | /** 72 | * Display your plugin or theme name in dismissable notices. 73 | */ 74 | add_filter( 75 | 'wp_dependency_dismiss_label', 76 | function( $label, $source ) { 77 | $label = basename( __DIR__ ) !== $source ? $label : __( 'Group Plugin Installer', 'group-plugin-installer' ); 78 | return $label; 79 | }, 10, 2 80 | ); 81 | ``` 82 | 83 | 5. Sanity Check 84 | 85 | ```php 86 | // Sanity check for WPDI v3.0.0. 87 | if ( ! method_exists( 'WP_Dependency_Installer', 'json_file_decode' ) ) { 88 | add_action( 89 | 'admin_notices', 90 | function() { 91 | $class = 'notice notice-error is-dismissible'; 92 | $label = __( 'Your Plugin Name', 'your-plugin' ); 93 | $file = ( new ReflectionClass( 'WP_Dependency_Installer' ) )->getFilename(); 94 | $message = __( 'Another theme or plugin is using a previous version of the WP Dependency Installer library, please update this file and try again:', 'group-plugin-installer' ); 95 | printf( '

[%2$s] %3$s

%4$s
', esc_attr( $class ), esc_html( $label ), esc_html( $message ), esc_html( $file ) ); 96 | }, 97 | 1 98 | ); 99 | return false; // Exit early. 100 | } 101 | ``` 102 | 103 | That's it, happy blogging! 104 | 105 | ## Development 106 | 107 | PRs are welcome against the `develop` branch. 108 | -------------------------------------------------------------------------------- /vendor/collizo4sky/persist-admin-notices-dismissal/README.md: -------------------------------------------------------------------------------- 1 | # Persist Admin notice Dismissals 2 | [![Latest Stable Version](https://poser.pugx.org/collizo4sky/persist-admin-notices-dismissal/v/stable)](https://packagist.org/packages/collizo4sky/persist-admin-notices-dismissal) 3 | [![Total Downloads](https://poser.pugx.org/collizo4sky/persist-admin-notices-dismissal/downloads)](https://packagist.org/packages/collizo4sky/persist-admin-notices-dismissal) 4 | 5 | Simple framework library that persists the dismissal of admin notices across pages in WordPress dashboard. 6 | 7 | ## Installation 8 | 9 | Run `composer require collizo4sky/persist-admin-notices-dismissal` 10 | 11 | Alternatively, clone or download this repo into the `vendor/` folder in your plugin, and include/require the `persist-admin-notices-dismissal.php` file like so 12 | 13 | ```php 14 | require __DIR__ . '/vendor/persist-admin-notices-dismissal/persist-admin-notices-dismissal.php'; 15 | add_action( 'admin_init', array( 'PAnD', 'init' ) ); 16 | ``` 17 | 18 | or let Composer's autoloader do the work. 19 | 20 | ## How to Use 21 | Firstly, install and activate this library within a plugin. 22 | 23 | Say you have the following markup as your admin notice, 24 | 25 | 26 | ```php 27 | function sample_admin_notice__success() { 28 | ?> 29 |
30 |

31 |
32 | 47 |
48 |

49 |
50 | 84 |
85 |

86 |
87 | 96 |
97 |

98 |
99 | 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | * 21 | * @package Persist Admin notices Dismissal 22 | * @author Collins Agbonghama 23 | * @author Andy Fragen 24 | * @license http://www.gnu.org/licenses GNU General Public License 25 | * @version 1.4.3 26 | */ 27 | 28 | /** 29 | * Exit if called directly. 30 | */ 31 | if ( ! defined( 'ABSPATH' ) ) { 32 | die; 33 | } 34 | 35 | if ( ! class_exists( 'PAnD' ) ) { 36 | 37 | /** 38 | * Class PAnD 39 | */ 40 | class PAnD { 41 | 42 | /** 43 | * Init hooks. 44 | */ 45 | public static function init() { 46 | add_action( 'admin_enqueue_scripts', array( __CLASS__, 'load_script' ) ); 47 | add_action( 'wp_ajax_dismiss_admin_notice', array( __CLASS__, 'dismiss_admin_notice' ) ); 48 | 49 | /** 50 | * Filter to activate another filter providing a simpler use case. 51 | * 52 | * @since 1.4.3 53 | * 54 | * @param bool 55 | */ 56 | if ( apply_filters( 'pand_theme_loader', false ) ) { 57 | add_filter( 58 | 'pand_dismiss_notice_js_url', 59 | function( $js_url, $composer_path ) { 60 | return get_stylesheet_directory_uri() . $composer_path; 61 | }, 62 | 10, 63 | 2 64 | ); 65 | } 66 | } 67 | 68 | /** 69 | * Enqueue javascript and variables. 70 | */ 71 | public static function load_script() { 72 | 73 | if ( is_customize_preview() ) { 74 | return; 75 | } 76 | 77 | $js_url = plugins_url( 'dismiss-notice.js', __FILE__ ); 78 | $composer_path = '/vendor/collizo4sky/persist-admin-notices-dismissal/dismiss-notice.js'; 79 | 80 | /** 81 | * Filter dismiss-notice.js URL. 82 | * 83 | * @since 1.4.3 84 | * 85 | * @param string $js_url URL to the Javascript file. 86 | * @param string $composer_path Relative path of Javascript file from composer install. 87 | */ 88 | $js_url = apply_filters( 'pand_dismiss_notice_js_url', $js_url, $composer_path ); 89 | wp_enqueue_script( 90 | 'dismissible-notices', 91 | $js_url, 92 | array( 'jquery', 'common' ), 93 | false, 94 | true 95 | ); 96 | 97 | wp_localize_script( 98 | 'dismissible-notices', 99 | 'dismissible_notice', 100 | array( 101 | 'nonce' => wp_create_nonce( 'dismissible-notice' ), 102 | ) 103 | ); 104 | } 105 | 106 | /** 107 | * Handles Ajax request to persist notices dismissal. 108 | * Uses check_ajax_referer to verify nonce. 109 | */ 110 | public static function dismiss_admin_notice() { 111 | $option_name = sanitize_text_field( $_POST['option_name'] ); 112 | $dismissible_length = sanitize_text_field( $_POST['dismissible_length'] ); 113 | 114 | if ( 'forever' != $dismissible_length ) { 115 | // If $dismissible_length is not an integer default to 1 116 | $dismissible_length = ( 0 == absint( $dismissible_length ) ) ? 1 : $dismissible_length; 117 | $dismissible_length = strtotime( absint( $dismissible_length ) . ' days' ); 118 | } 119 | 120 | check_ajax_referer( 'dismissible-notice', 'nonce' ); 121 | self::set_admin_notice_cache( $option_name, $dismissible_length ); 122 | wp_die(); 123 | } 124 | 125 | /** 126 | * Is admin notice active? 127 | * 128 | * @param string $arg data-dismissible content of notice. 129 | * 130 | * @return bool 131 | */ 132 | public static function is_admin_notice_active( $arg ) { 133 | $array = explode( '-', $arg ); 134 | $length = array_pop( $array ); 135 | $option_name = implode( '-', $array ); 136 | $db_record = self::get_admin_notice_cache( $option_name ); 137 | 138 | if ( 'forever' == $db_record ) { 139 | return false; 140 | } elseif ( absint( $db_record ) >= time() ) { 141 | return false; 142 | } else { 143 | return true; 144 | } 145 | } 146 | 147 | /** 148 | * Returns admin notice cached timeout. 149 | * 150 | * @access public 151 | * 152 | * @param string|bool $id admin notice name or false. 153 | * 154 | * @return array|bool The timeout. False if expired. 155 | */ 156 | public static function get_admin_notice_cache( $id = false ) { 157 | if ( ! $id ) { 158 | return false; 159 | } 160 | $cache_key = 'pand-' . md5( $id ); 161 | $timeout = get_site_option( $cache_key ); 162 | $timeout = 'forever' === $timeout ? time() + 60 : $timeout; 163 | 164 | if ( empty( $timeout ) || time() > $timeout ) { 165 | return false; 166 | } 167 | 168 | return $timeout; 169 | } 170 | 171 | /** 172 | * Sets admin notice timeout in site option. 173 | * 174 | * @access public 175 | * 176 | * @param string $id Data Identifier. 177 | * @param string|bool $timeout Timeout for admin notice. 178 | * 179 | * @return bool 180 | */ 181 | public static function set_admin_notice_cache( $id, $timeout ) { 182 | $cache_key = 'pand-' . md5( $id ); 183 | update_site_option( $cache_key, $timeout ); 184 | 185 | return true; 186 | } 187 | 188 | } 189 | 190 | } 191 | -------------------------------------------------------------------------------- /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 http://www.php-fig.org/psr/psr-0/ 41 | * @see http://www.php-fig.org/psr/psr-4/ 42 | */ 43 | class ClassLoader 44 | { 45 | // PSR-4 46 | private $prefixLengthsPsr4 = array(); 47 | private $prefixDirsPsr4 = array(); 48 | private $fallbackDirsPsr4 = array(); 49 | 50 | // PSR-0 51 | private $prefixesPsr0 = array(); 52 | private $fallbackDirsPsr0 = array(); 53 | 54 | private $useIncludePath = false; 55 | private $classMap = array(); 56 | private $classMapAuthoritative = false; 57 | private $missingClasses = array(); 58 | private $apcuPrefix; 59 | 60 | public function getPrefixes() 61 | { 62 | if (!empty($this->prefixesPsr0)) { 63 | return call_user_func_array('array_merge', $this->prefixesPsr0); 64 | } 65 | 66 | return array(); 67 | } 68 | 69 | public function getPrefixesPsr4() 70 | { 71 | return $this->prefixDirsPsr4; 72 | } 73 | 74 | public function getFallbackDirs() 75 | { 76 | return $this->fallbackDirsPsr0; 77 | } 78 | 79 | public function getFallbackDirsPsr4() 80 | { 81 | return $this->fallbackDirsPsr4; 82 | } 83 | 84 | public function getClassMap() 85 | { 86 | return $this->classMap; 87 | } 88 | 89 | /** 90 | * @param array $classMap Class to filename map 91 | */ 92 | public function addClassMap(array $classMap) 93 | { 94 | if ($this->classMap) { 95 | $this->classMap = array_merge($this->classMap, $classMap); 96 | } else { 97 | $this->classMap = $classMap; 98 | } 99 | } 100 | 101 | /** 102 | * Registers a set of PSR-0 directories for a given prefix, either 103 | * appending or prepending to the ones previously set for this prefix. 104 | * 105 | * @param string $prefix The prefix 106 | * @param array|string $paths The PSR-0 root directories 107 | * @param bool $prepend Whether to prepend the directories 108 | */ 109 | public function add($prefix, $paths, $prepend = false) 110 | { 111 | if (!$prefix) { 112 | if ($prepend) { 113 | $this->fallbackDirsPsr0 = array_merge( 114 | (array) $paths, 115 | $this->fallbackDirsPsr0 116 | ); 117 | } else { 118 | $this->fallbackDirsPsr0 = array_merge( 119 | $this->fallbackDirsPsr0, 120 | (array) $paths 121 | ); 122 | } 123 | 124 | return; 125 | } 126 | 127 | $first = $prefix[0]; 128 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 129 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 130 | 131 | return; 132 | } 133 | if ($prepend) { 134 | $this->prefixesPsr0[$first][$prefix] = array_merge( 135 | (array) $paths, 136 | $this->prefixesPsr0[$first][$prefix] 137 | ); 138 | } else { 139 | $this->prefixesPsr0[$first][$prefix] = array_merge( 140 | $this->prefixesPsr0[$first][$prefix], 141 | (array) $paths 142 | ); 143 | } 144 | } 145 | 146 | /** 147 | * Registers a set of PSR-4 directories for a given namespace, either 148 | * appending or prepending to the ones previously set for this namespace. 149 | * 150 | * @param string $prefix The prefix/namespace, with trailing '\\' 151 | * @param array|string $paths The PSR-4 base directories 152 | * @param bool $prepend Whether to prepend the directories 153 | * 154 | * @throws \InvalidArgumentException 155 | */ 156 | public function addPsr4($prefix, $paths, $prepend = false) 157 | { 158 | if (!$prefix) { 159 | // Register directories for the root namespace. 160 | if ($prepend) { 161 | $this->fallbackDirsPsr4 = array_merge( 162 | (array) $paths, 163 | $this->fallbackDirsPsr4 164 | ); 165 | } else { 166 | $this->fallbackDirsPsr4 = array_merge( 167 | $this->fallbackDirsPsr4, 168 | (array) $paths 169 | ); 170 | } 171 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 172 | // Register directories for a new namespace. 173 | $length = strlen($prefix); 174 | if ('\\' !== $prefix[$length - 1]) { 175 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 176 | } 177 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 178 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 179 | } elseif ($prepend) { 180 | // Prepend directories for an already registered namespace. 181 | $this->prefixDirsPsr4[$prefix] = array_merge( 182 | (array) $paths, 183 | $this->prefixDirsPsr4[$prefix] 184 | ); 185 | } else { 186 | // Append directories for an already registered namespace. 187 | $this->prefixDirsPsr4[$prefix] = array_merge( 188 | $this->prefixDirsPsr4[$prefix], 189 | (array) $paths 190 | ); 191 | } 192 | } 193 | 194 | /** 195 | * Registers a set of PSR-0 directories for a given prefix, 196 | * replacing any others previously set for this prefix. 197 | * 198 | * @param string $prefix The prefix 199 | * @param array|string $paths The PSR-0 base directories 200 | */ 201 | public function set($prefix, $paths) 202 | { 203 | if (!$prefix) { 204 | $this->fallbackDirsPsr0 = (array) $paths; 205 | } else { 206 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 207 | } 208 | } 209 | 210 | /** 211 | * Registers a set of PSR-4 directories for a given namespace, 212 | * replacing any others previously set for this namespace. 213 | * 214 | * @param string $prefix The prefix/namespace, with trailing '\\' 215 | * @param array|string $paths The PSR-4 base directories 216 | * 217 | * @throws \InvalidArgumentException 218 | */ 219 | public function setPsr4($prefix, $paths) 220 | { 221 | if (!$prefix) { 222 | $this->fallbackDirsPsr4 = (array) $paths; 223 | } else { 224 | $length = strlen($prefix); 225 | if ('\\' !== $prefix[$length - 1]) { 226 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 227 | } 228 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 229 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 230 | } 231 | } 232 | 233 | /** 234 | * Turns on searching the include path for class files. 235 | * 236 | * @param bool $useIncludePath 237 | */ 238 | public function setUseIncludePath($useIncludePath) 239 | { 240 | $this->useIncludePath = $useIncludePath; 241 | } 242 | 243 | /** 244 | * Can be used to check if the autoloader uses the include path to check 245 | * for classes. 246 | * 247 | * @return bool 248 | */ 249 | public function getUseIncludePath() 250 | { 251 | return $this->useIncludePath; 252 | } 253 | 254 | /** 255 | * Turns off searching the prefix and fallback directories for classes 256 | * that have not been registered with the class map. 257 | * 258 | * @param bool $classMapAuthoritative 259 | */ 260 | public function setClassMapAuthoritative($classMapAuthoritative) 261 | { 262 | $this->classMapAuthoritative = $classMapAuthoritative; 263 | } 264 | 265 | /** 266 | * Should class lookup fail if not found in the current class map? 267 | * 268 | * @return bool 269 | */ 270 | public function isClassMapAuthoritative() 271 | { 272 | return $this->classMapAuthoritative; 273 | } 274 | 275 | /** 276 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 277 | * 278 | * @param string|null $apcuPrefix 279 | */ 280 | public function setApcuPrefix($apcuPrefix) 281 | { 282 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 283 | } 284 | 285 | /** 286 | * The APCu prefix in use, or null if APCu caching is not enabled. 287 | * 288 | * @return string|null 289 | */ 290 | public function getApcuPrefix() 291 | { 292 | return $this->apcuPrefix; 293 | } 294 | 295 | /** 296 | * Registers this instance as an autoloader. 297 | * 298 | * @param bool $prepend Whether to prepend the autoloader or not 299 | */ 300 | public function register($prepend = false) 301 | { 302 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 303 | } 304 | 305 | /** 306 | * Unregisters this instance as an autoloader. 307 | */ 308 | public function unregister() 309 | { 310 | spl_autoload_unregister(array($this, 'loadClass')); 311 | } 312 | 313 | /** 314 | * Loads the given class or interface. 315 | * 316 | * @param string $class The name of the class 317 | * @return bool|null True if loaded, null otherwise 318 | */ 319 | public function loadClass($class) 320 | { 321 | if ($file = $this->findFile($class)) { 322 | includeFile($file); 323 | 324 | return true; 325 | } 326 | } 327 | 328 | /** 329 | * Finds the path to the file where the class is defined. 330 | * 331 | * @param string $class The name of the class 332 | * 333 | * @return string|false The path if found, false otherwise 334 | */ 335 | public function findFile($class) 336 | { 337 | // class map lookup 338 | if (isset($this->classMap[$class])) { 339 | return $this->classMap[$class]; 340 | } 341 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 342 | return false; 343 | } 344 | if (null !== $this->apcuPrefix) { 345 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 346 | if ($hit) { 347 | return $file; 348 | } 349 | } 350 | 351 | $file = $this->findFileWithExtension($class, '.php'); 352 | 353 | // Search for Hack files if we are running on HHVM 354 | if (false === $file && defined('HHVM_VERSION')) { 355 | $file = $this->findFileWithExtension($class, '.hh'); 356 | } 357 | 358 | if (null !== $this->apcuPrefix) { 359 | apcu_add($this->apcuPrefix.$class, $file); 360 | } 361 | 362 | if (false === $file) { 363 | // Remember that this class does not exist. 364 | $this->missingClasses[$class] = true; 365 | } 366 | 367 | return $file; 368 | } 369 | 370 | private function findFileWithExtension($class, $ext) 371 | { 372 | // PSR-4 lookup 373 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 374 | 375 | $first = $class[0]; 376 | if (isset($this->prefixLengthsPsr4[$first])) { 377 | $subPath = $class; 378 | while (false !== $lastPos = strrpos($subPath, '\\')) { 379 | $subPath = substr($subPath, 0, $lastPos); 380 | $search = $subPath . '\\'; 381 | if (isset($this->prefixDirsPsr4[$search])) { 382 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 383 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 384 | if (file_exists($file = $dir . $pathEnd)) { 385 | return $file; 386 | } 387 | } 388 | } 389 | } 390 | } 391 | 392 | // PSR-4 fallback dirs 393 | foreach ($this->fallbackDirsPsr4 as $dir) { 394 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 395 | return $file; 396 | } 397 | } 398 | 399 | // PSR-0 lookup 400 | if (false !== $pos = strrpos($class, '\\')) { 401 | // namespaced class name 402 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 403 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 404 | } else { 405 | // PEAR-like class name 406 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 407 | } 408 | 409 | if (isset($this->prefixesPsr0[$first])) { 410 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 411 | if (0 === strpos($class, $prefix)) { 412 | foreach ($dirs as $dir) { 413 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 414 | return $file; 415 | } 416 | } 417 | } 418 | } 419 | } 420 | 421 | // PSR-0 fallback dirs 422 | foreach ($this->fallbackDirsPsr0 as $dir) { 423 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 424 | return $file; 425 | } 426 | } 427 | 428 | // PSR-0 include paths. 429 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 430 | return $file; 431 | } 432 | 433 | return false; 434 | } 435 | } 436 | 437 | /** 438 | * Scope isolated include. 439 | * 440 | * Prevents access to $this/self from included files. 441 | */ 442 | function includeFile($file) 443 | { 444 | include $file; 445 | } 446 | -------------------------------------------------------------------------------- /vendor/afragen/wp-dependency-installer/wp-dependency-installer.php: -------------------------------------------------------------------------------- 1 | config = []; 83 | $this->notices = []; 84 | } 85 | 86 | /** 87 | * Load hooks. 88 | * 89 | * @return void 90 | */ 91 | public function load_hooks() { 92 | add_action( 'admin_init', [ $this, 'admin_init' ] ); 93 | add_action( 'admin_footer', [ $this, 'admin_footer' ] ); 94 | add_action( 'admin_notices', [ $this, 'admin_notices' ] ); 95 | add_action( 'network_admin_notices', [ $this, 'admin_notices' ] ); 96 | add_action( 'wp_ajax_dependency_installer', [ $this, 'ajax_router' ] ); 97 | add_filter( 'http_request_args', [ $this, 'add_basic_auth_headers' ], 15, 2 ); 98 | 99 | // Initialize Persist admin Notices Dismissal dependency. 100 | add_action( 'admin_init', [ 'PAnD', 'init' ] ); 101 | } 102 | 103 | /** 104 | * Let's get going. 105 | * First load data from wp-dependencies.json if present. 106 | * Then load hooks needed to run. 107 | * 108 | * @param string $caller Path to plugin or theme calling the framework. 109 | * 110 | * @return self 111 | */ 112 | public function run( $caller = false ) { 113 | $caller = ! $caller ? self::$caller : $caller; 114 | $config = $this->json_file_decode( $caller . '/wp-dependencies.json' ); 115 | if ( ! empty( $config ) ) { 116 | $this->register( $config, $caller ); 117 | } 118 | if ( ! empty( $this->config ) ) { 119 | $this->load_hooks(); 120 | } 121 | 122 | return $this; 123 | } 124 | 125 | /** 126 | * Decode JSON config data from a file. 127 | * 128 | * @param string $json_path File path to JSON config file. 129 | * 130 | * @return bool|array $config 131 | */ 132 | public function json_file_decode( $json_path ) { 133 | $config = []; 134 | if ( file_exists( $json_path ) ) { 135 | $config = file_get_contents( $json_path ); 136 | $config = json_decode( $config, true ); 137 | } 138 | 139 | return $config; 140 | } 141 | 142 | /** 143 | * Register dependencies (supports multiple instances). 144 | * 145 | * @param array $config JSON config as array. 146 | * @param string $caller Path to plugin or theme calling the framework. 147 | * 148 | * @return self 149 | */ 150 | public function register( $config, $caller = false ) { 151 | $source = ! self::$source ? basename( $caller ) : self::$source; 152 | foreach ( $config as $dependency ) { 153 | // Save a reference of current dependent plugin. 154 | $dependency['source'] = $source; 155 | $dependency['sources'][] = $source; 156 | $slug = $dependency['slug']; 157 | // Keep a reference of all dependent plugins. 158 | if ( isset( $this->config[ $slug ] ) ) { 159 | $dependency['sources'] = array_merge( $this->config[ $slug ]['sources'], $dependency['sources'] ); 160 | } 161 | // Update config. 162 | if ( ! isset( $this->config[ $slug ] ) || $this->is_required( $dependency ) ) { 163 | $this->config[ $slug ] = $dependency; 164 | } 165 | } 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Process the registered dependencies. 172 | */ 173 | private function apply_config() { 174 | foreach ( $this->config as $dependency ) { 175 | $download_link = null; 176 | $base = null; 177 | $uri = $dependency['uri']; 178 | $slug = $dependency['slug']; 179 | $uri_args = parse_url( $uri ); // phpcs:ignore WordPress.WP.AlternativeFunctions.parse_url_parse_url 180 | $port = isset( $uri_args['port'] ) ? $uri_args['port'] : null; 181 | $api = isset( $uri_args['host'] ) ? $uri_args['host'] : null; 182 | $api = ! $port ? $api : "$api:$port"; 183 | $scheme = isset( $uri_args['scheme'] ) ? $uri_args['scheme'] : null; 184 | $scheme = null !== $scheme ? $scheme . '://' : 'https://'; 185 | $path = isset( $uri_args['path'] ) ? $uri_args['path'] : null; 186 | $owner_repo = str_replace( '.git', '', trim( $path, '/' ) ); 187 | 188 | switch ( $dependency['host'] ) { 189 | case 'github': 190 | $base = null === $api || 'github.com' === $api ? 'api.github.com' : $api; 191 | $download_link = "{$scheme}{$base}/repos/{$owner_repo}/zipball/{$dependency['branch']}"; 192 | break; 193 | case 'bitbucket': 194 | $base = null === $api || 'bitbucket.org' === $api ? 'bitbucket.org' : $api; 195 | $download_link = "{$scheme}{$base}/{$owner_repo}/get/{$dependency['branch']}.zip"; 196 | break; 197 | case 'gitlab': 198 | $base = null === $api || 'gitlab.com' === $api ? 'gitlab.com' : $api; 199 | $project_id = rawurlencode( $owner_repo ); 200 | $download_link = "{$scheme}{$base}/api/v4/projects/{$project_id}/repository/archive.zip"; 201 | $download_link = add_query_arg( 'sha', $dependency['branch'], $download_link ); 202 | break; 203 | case 'gitea': 204 | $download_link = "{$scheme}{$api}/api/v1/repos/{$owner_repo}/archive/{$dependency['branch']}.zip"; 205 | break; 206 | case 'wordpress': // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled 207 | $download_link = $this->get_dot_org_latest_download( basename( $owner_repo ) ); 208 | break; 209 | case 'direct': 210 | $download_link = filter_var( $uri, FILTER_VALIDATE_URL ); 211 | break; 212 | } 213 | 214 | /** 215 | * Allow filtering of download link for dependency configuration. 216 | * 217 | * @since 1.4.11 218 | * 219 | * @param string $download_link Download link. 220 | * @param array $dependency Dependency configuration. 221 | */ 222 | $dependency['download_link'] = apply_filters( 'wp_dependency_download_link', $download_link, $dependency ); 223 | 224 | /** 225 | * Allow filtering of individual dependency config. 226 | * 227 | * @since 3.0.0 228 | * 229 | * @param array $dependency Dependency configuration. 230 | */ 231 | $this->config[ $slug ] = apply_filters( 'wp_dependency_config', $dependency ); 232 | } 233 | } 234 | 235 | /** 236 | * Get lastest download link from WordPress API. 237 | * 238 | * @param string $slug Plugin slug. 239 | * @return string $download_link 240 | */ 241 | private function get_dot_org_latest_download( $slug ) { 242 | $download_link = get_site_transient( 'wpdi-' . md5( $slug ) ); 243 | 244 | if ( ! $download_link ) { 245 | $url = 'https://api.wordpress.org/plugins/info/1.1/'; 246 | $url = add_query_arg( 247 | [ 248 | 'action' => 'plugin_information', 249 | rawurlencode( 'request[slug]' ) => $slug, 250 | ], 251 | $url 252 | ); 253 | $response = wp_remote_get( $url ); 254 | $response = json_decode( wp_remote_retrieve_body( $response ) ); 255 | $download_link = empty( $response ) 256 | ? "https://downloads.wordpress.org/plugin/{$slug}.zip" 257 | : $response->download_link; 258 | 259 | set_site_transient( 'wpdi-' . md5( $slug ), $download_link, DAY_IN_SECONDS ); 260 | } 261 | 262 | return $download_link; 263 | } 264 | 265 | /** 266 | * Determine if dependency is active or installed. 267 | */ 268 | public function admin_init() { 269 | // Get the gears turning. 270 | $this->apply_config(); 271 | 272 | // Generate admin notices. 273 | foreach ( $this->config as $slug => $dependency ) { 274 | $is_required = $this->is_required( $dependency ); 275 | 276 | if ( $is_required ) { 277 | $this->modify_plugin_row( $slug ); 278 | } 279 | 280 | if ( $this->is_active( $slug ) ) { 281 | // Do nothing. 282 | } elseif ( $this->is_installed( $slug ) ) { 283 | if ( $is_required ) { 284 | $this->notices[] = $this->activate( $slug ); 285 | } else { 286 | $this->notices[] = $this->activate_notice( $slug ); 287 | } 288 | } else { 289 | if ( $is_required ) { 290 | $this->notices[] = $this->install( $slug ); 291 | } else { 292 | $this->notices[] = $this->install_notice( $slug ); 293 | } 294 | } 295 | 296 | /** 297 | * Allow filtering of admin notices. 298 | * 299 | * @since 3.0.0 300 | * 301 | * @param array $notices admin notices. 302 | * @param string $slug plugin slug. 303 | */ 304 | $this->notices = apply_filters( 'wp_dependency_notices', $this->notices, $slug ); 305 | } 306 | } 307 | 308 | /** 309 | * Register jQuery AJAX. 310 | */ 311 | public function admin_footer() { 312 | ?> 313 | 339 | $method( $slug ); 352 | echo $response['message']; 353 | } 354 | wp_die(); 355 | } 356 | 357 | /** 358 | * Check if a dependency is currently required. 359 | * 360 | * @param string|array $plugin Plugin dependency slug or config. 361 | * 362 | * @return boolean True if required. Default: False 363 | */ 364 | public function is_required( &$plugin ) { 365 | if ( empty( $this->config ) ) { 366 | return false; 367 | } 368 | if ( is_string( $plugin ) && isset( $this->config[ $plugin ] ) ) { 369 | $dependency = &$this->config[ $plugin ]; 370 | } else { 371 | $dependency = &$plugin; 372 | } 373 | if ( isset( $dependency['required'] ) ) { 374 | return true === $dependency['required'] || 'true' === $dependency['required']; 375 | } 376 | if ( isset( $dependency['optional'] ) ) { 377 | return false === $dependency['optional'] || 'false' === $dependency['optional']; 378 | } 379 | 380 | return false; 381 | } 382 | 383 | /** 384 | * Is dependency installed? 385 | * 386 | * @param string $slug Plugin slug. 387 | * 388 | * @return boolean 389 | */ 390 | public function is_installed( $slug ) { 391 | $plugins = get_plugins(); 392 | 393 | return isset( $plugins[ $slug ] ); 394 | } 395 | 396 | /** 397 | * Is dependency active? 398 | * 399 | * @param string $slug Plugin slug. 400 | * 401 | * @return boolean 402 | */ 403 | public function is_active( $slug ) { 404 | return is_plugin_active( $slug ); 405 | } 406 | 407 | /** 408 | * Install and activate dependency. 409 | * 410 | * @param string $slug Plugin slug. 411 | * 412 | * @return bool|array false or Message. 413 | */ 414 | public function install( $slug ) { 415 | if ( $this->is_installed( $slug ) || ! current_user_can( 'update_plugins' ) ) { 416 | return false; 417 | } 418 | 419 | $this->current_slug = $slug; 420 | add_filter( 'upgrader_source_selection', [ $this, 'upgrader_source_selection' ], 10, 2 ); 421 | 422 | $skin = new WPDI_Plugin_Installer_Skin( 423 | [ 424 | 'type' => 'plugin', 425 | 'nonce' => wp_nonce_url( $this->config[ $slug ]['download_link'] ), 426 | ] 427 | ); 428 | $upgrader = new Plugin_Upgrader( $skin ); 429 | $result = $upgrader->install( $this->config[ $slug ]['download_link'] ); 430 | 431 | if ( is_wp_error( $result ) ) { 432 | return [ 433 | 'status' => 'error', 434 | 'message' => $result->get_error_message(), 435 | ]; 436 | } 437 | 438 | if ( null === $result ) { 439 | return [ 440 | 'status' => 'error', 441 | 'message' => esc_html__( 'Plugin download failed' ), 442 | ]; 443 | } 444 | 445 | wp_cache_flush(); 446 | if ( $this->is_required( $slug ) ) { 447 | $this->activate( $slug ); 448 | 449 | return [ 450 | 'status' => 'updated', 451 | 'slug' => $slug, 452 | /* translators: %s: Plugin name */ 453 | 'message' => sprintf( esc_html__( '%s has been installed and activated.' ), $this->config[ $slug ]['name'] ), 454 | 'source' => $this->config[ $slug ]['source'], 455 | ]; 456 | } 457 | 458 | if ( true !== $result && 'error' === $result['status'] ) { 459 | return $result; 460 | } 461 | 462 | return [ 463 | 'status' => 'updated', 464 | /* translators: %s: Plugin name */ 465 | 'message' => sprintf( esc_html__( '%s has been installed.' ), $this->config[ $slug ]['name'] ), 466 | 'source' => $this->config[ $slug ]['source'], 467 | ]; 468 | } 469 | 470 | /** 471 | * Get install plugin notice. 472 | * 473 | * @param string $slug Plugin slug. 474 | * 475 | * @return array Admin notice. 476 | */ 477 | public function install_notice( $slug ) { 478 | $dependency = $this->config[ $slug ]; 479 | 480 | return [ 481 | 'action' => 'install', 482 | 'slug' => $slug, 483 | /* translators: %s: Plugin name */ 484 | 'message' => sprintf( esc_html__( 'The %s plugin is recommended.' ), $dependency['name'] ), 485 | 'source' => $dependency['source'], 486 | ]; 487 | } 488 | 489 | /** 490 | * Activate dependency. 491 | * 492 | * @param string $slug Plugin slug. 493 | * 494 | * @return array Message. 495 | */ 496 | public function activate( $slug ) { 497 | // network activate only if on network admin pages. 498 | $result = is_network_admin() ? activate_plugin( $slug, null, true ) : activate_plugin( $slug ); 499 | 500 | if ( is_wp_error( $result ) ) { 501 | return [ 502 | 'status' => 'error', 503 | 'message' => $result->get_error_message(), 504 | ]; 505 | } 506 | 507 | return [ 508 | 'status' => 'updated', 509 | /* translators: %s: Plugin name */ 510 | 'message' => sprintf( esc_html__( '%s has been activated.' ), $this->config[ $slug ]['name'] ), 511 | 'source' => $this->config[ $slug ]['source'], 512 | ]; 513 | } 514 | 515 | /** 516 | * Get activate plugin notice. 517 | * 518 | * @param string $slug Plugin slug. 519 | * 520 | * @return array Admin notice. 521 | */ 522 | public function activate_notice( $slug ) { 523 | $dependency = $this->config[ $slug ]; 524 | 525 | return [ 526 | 'action' => 'activate', 527 | 'slug' => $slug, 528 | /* translators: %s: Plugin name */ 529 | 'message' => sprintf( esc_html__( 'Please activate the %s plugin.' ), $dependency['name'] ), 530 | 'source' => $dependency['source'], 531 | ]; 532 | } 533 | 534 | /** 535 | * Dismiss admin notice for a week. 536 | * 537 | * @return array Empty Message. 538 | */ 539 | public function dismiss() { 540 | return [ 541 | 'status' => 'updated', 542 | 'message' => '', 543 | ]; 544 | } 545 | 546 | /** 547 | * Correctly rename dependency for activation. 548 | * 549 | * @param string $source Path fo $source. 550 | * @param string $remote_source Path of $remote_source. 551 | * 552 | * @return string $new_source 553 | */ 554 | public function upgrader_source_selection( $source, $remote_source ) { 555 | $new_source = trailingslashit( $remote_source ) . dirname( $this->current_slug ); 556 | $this->move( $source, $new_source ); 557 | 558 | return trailingslashit( $new_source ); 559 | } 560 | 561 | /** 562 | * Rename or recursive file copy and delete. 563 | * 564 | * This is more versatile than `$wp_filesystem->move()`. 565 | * It moves/renames directories as well as files. 566 | * Fix for https://github.com/afragen/github-updater/issues/826, 567 | * strange failure of `rename()`. 568 | * 569 | * @param string $source File path of source. 570 | * @param string $destination File path of destination. 571 | * 572 | * @return bool|void 573 | */ 574 | private function move( $source, $destination ) { 575 | if ( @rename( $source, $destination ) ) { 576 | return true; 577 | } 578 | $dir = opendir( $source ); 579 | mkdir( $destination ); 580 | $source = untrailingslashit( $source ); 581 | // phpcs:ignore WordPress.CodeAnalysis.AssignmentInCondition.FoundInWhileCondition 582 | while ( false !== ( $file = readdir( $dir ) ) ) { 583 | if ( ( '.' !== $file ) && ( '..' !== $file ) && "$source/$file" !== $destination ) { 584 | if ( is_dir( "$source/$file" ) ) { 585 | $this->move( "$source/$file", "$destination/$file" ); 586 | } else { 587 | copy( "$source/$file", "$destination/$file" ); 588 | unlink( "$source/$file" ); 589 | } 590 | } 591 | } 592 | @rmdir( $source ); 593 | closedir( $dir ); 594 | } 595 | 596 | /** 597 | * Display admin notices / action links. 598 | * 599 | * @return bool/string false or Admin notice. 600 | */ 601 | public function admin_notices() { 602 | if ( ! current_user_can( 'update_plugins' ) ) { 603 | return false; 604 | } 605 | foreach ( $this->notices as $notice ) { 606 | $status = isset( $notice['status'] ) ? $notice['status'] : 'updated'; 607 | $source = isset( $notice['source'] ) ? $notice['source'] : __( 'Dependency' ); 608 | $class = esc_attr( $status ) . ' notice is-dismissible dependency-installer'; 609 | $label = esc_html( $this->get_dismiss_label( $source ) ); 610 | $message = ''; 611 | $action = ''; 612 | $dismissible = ''; 613 | 614 | if ( isset( $notice['message'] ) ) { 615 | $message = esc_html( $notice['message'] ); 616 | } 617 | 618 | if ( isset( $notice['action'] ) ) { 619 | $action = sprintf( 620 | ' %3$s Now » ', 621 | esc_attr( $notice['action'] ), 622 | esc_attr( $notice['slug'] ), 623 | esc_html( ucfirst( $notice['action'] ) ) 624 | ); 625 | } 626 | if ( isset( $notice['slug'] ) ) { 627 | /** 628 | * Filters the dismissal timeout. 629 | * 630 | * @since 1.4.1 631 | * 632 | * @param string|int '7' Default dismissal in days. 633 | * @param string $notice['source'] Plugin slug of calling plugin. 634 | * @return string|int Dismissal timeout in days. 635 | */ 636 | $timeout = apply_filters( 'wp_dependency_timeout', '7', $source ); 637 | $dependency = dirname( $notice['slug'] ); 638 | $dismissible = empty( $timeout ) ? '' : sprintf( 'dependency-installer-%1$s-%2$s', esc_attr( $dependency ), esc_attr( $timeout ) ); 639 | } 640 | if ( class_exists( '\PAnD' ) && \PAnD::is_admin_notice_active( $dismissible ) ) { 641 | printf( '

[%3$s] %4$s%5$s

', $class, $dismissible, $label, $message, $action ); 642 | } 643 | } 644 | } 645 | 646 | /** 647 | * Make modifications to plugin row. 648 | * 649 | * @param string $plugin_file Plugin file. 650 | */ 651 | private function modify_plugin_row( $plugin_file ) { 652 | add_filter( 'network_admin_plugin_action_links_' . $plugin_file, [ $this, 'unset_action_links' ], 10, 2 ); 653 | add_filter( 'plugin_action_links_' . $plugin_file, [ $this, 'unset_action_links' ], 10, 2 ); 654 | add_action( 'after_plugin_row_' . $plugin_file, [ $this, 'modify_plugin_row_elements' ] ); 655 | } 656 | 657 | /** 658 | * Unset plugin action links so required plugins can't be removed or deactivated. 659 | * 660 | * @param array $actions Action links. 661 | * @param string $plugin_file Plugin file. 662 | * 663 | * @return mixed 664 | */ 665 | public function unset_action_links( $actions, $plugin_file ) { 666 | /** 667 | * Allow to remove required plugin action links. 668 | * 669 | * @since 3.0.0 670 | * 671 | * @param bool $unset remove default action links. 672 | */ 673 | if ( apply_filters( 'wp_dependency_unset_action_links', true ) ) { 674 | if ( isset( $actions['delete'] ) ) { 675 | unset( $actions['delete'] ); 676 | } 677 | 678 | if ( isset( $actions['deactivate'] ) ) { 679 | unset( $actions['deactivate'] ); 680 | } 681 | } 682 | 683 | /** 684 | * Allow to display of requied plugin label. 685 | * 686 | * @since 3.0.0 687 | * 688 | * @param bool $display show required plugin label. 689 | */ 690 | if ( apply_filters( 'wp_dependency_required_label', true ) ) { 691 | /* translators: %s: opening and closing span tags */ 692 | $actions = array_merge( [ 'required-plugin' => sprintf( esc_html__( '%1$sRequired Plugin%2$s' ), '', '' ) ], $actions ); 693 | } 694 | 695 | return $actions; 696 | } 697 | 698 | /** 699 | * Modify the plugin row elements. 700 | * 701 | * @param string $plugin_file Plugin file. 702 | * 703 | * @return void 704 | */ 705 | public function modify_plugin_row_elements( $plugin_file ) { 706 | print ''; 720 | } 721 | 722 | /** 723 | * Get formatted string of dependent plugins. 724 | * 725 | * @param string $plugin_file Plugin file. 726 | * 727 | * @return string $dependents 728 | */ 729 | private function get_dependency_sources( $plugin_file ) { 730 | // Remove empty values from $sources. 731 | $sources = array_filter( $this->config[ $plugin_file ]['sources'] ); 732 | $sources = array_map( [ $this, 'get_dismiss_label' ], $sources ); 733 | $sources = implode( ', ', $sources ); 734 | 735 | return $sources; 736 | } 737 | 738 | /** 739 | * Get formatted source string for text usage. 740 | * 741 | * @param string $source plugin source. 742 | * 743 | * @return string friendly plugin name. 744 | */ 745 | private function get_dismiss_label( $source ) { 746 | $label = str_replace( '-', ' ', $source ); 747 | $label = ucwords( $label ); 748 | $label = str_ireplace( 'wp ', 'WP ', $label ); 749 | 750 | /** 751 | * Filters the dismissal notice label 752 | * 753 | * @since 3.0.0 754 | * 755 | * @param string $label Default dismissal notice string. 756 | * @param string $source Plugin slug of calling plugin. 757 | * @return string Dismissal notice string. 758 | */ 759 | return apply_filters( 'wp_dependency_dismiss_label', $label, $source ); 760 | } 761 | 762 | /** 763 | * Get the configuration. 764 | * 765 | * @since 1.4.11 766 | * 767 | * @param string $slug Plugin slug. 768 | * @param string $key Dependency key. 769 | * 770 | * @return mixed|array The configuration. 771 | */ 772 | public function get_config( $slug = '', $key = '' ) { 773 | if ( empty( $slug ) && empty( $key ) ) { 774 | return $this->config; 775 | } elseif ( empty( $key ) ) { 776 | return isset( $this->config[ $slug ] ) ? $this->config[ $slug ] : null; 777 | } else { 778 | return isset( $this->config[ $slug ][ $key ] ) ? $this->config[ $slug ][ $key ] : null; 779 | } 780 | } 781 | 782 | /** 783 | * Add Basic Auth headers for authentication. 784 | * 785 | * @param array $args HTTP header args. 786 | * @param string $url URL. 787 | * 788 | * @return array $args 789 | */ 790 | public function add_basic_auth_headers( $args, $url ) { 791 | if ( null === $this->current_slug ) { 792 | return $args; 793 | } 794 | $package = $this->config[ $this->current_slug ]; 795 | $host = $package['host']; 796 | $token = empty( $package['token'] ) ? false : $package['token']; 797 | 798 | if ( $token && $url === $package['download_link'] ) { 799 | if ( 'bitbucket' === $host ) { 800 | // Bitbucket token must be in the form of 'username:password'. 801 | // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode 802 | $args['headers']['Authorization'] = 'Basic ' . base64_encode( $token ); 803 | } 804 | if ( 'github' === $host || 'gitea' === $host ) { 805 | $args['headers']['Authorization'] = 'token ' . $token; 806 | } 807 | if ( 'gitlab' === $host ) { 808 | $args['headers']['Authorization'] = 'Bearer ' . $token; 809 | } 810 | } 811 | 812 | // dot org should not have auth header. 813 | // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled 814 | if ( 'wordpress' === $host ) { 815 | unset( $args['headers']['Authorization'] ); 816 | } 817 | remove_filter( 'http_request_args', [ $this, 'add_basic_auth_headers' ] ); 818 | 819 | return $args; 820 | } 821 | } 822 | 823 | require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; 824 | 825 | /** 826 | * Class WPDI_Plugin_Installer_Skin 827 | */ 828 | class WPDI_Plugin_Installer_Skin extends Plugin_Installer_Skin { 829 | public function header() { 830 | } 831 | 832 | public function footer() { 833 | } 834 | 835 | public function error( $errors ) { 836 | } 837 | 838 | public function feedback( $string, ...$args ) { 839 | } 840 | } 841 | } 842 | --------------------------------------------------------------------------------