├── samples ├── edd-sample-theme │ ├── screenshot.png │ ├── README.md │ ├── wp-dependencies.json │ ├── index.php │ ├── style.css │ └── functions.php └── edd-sample-plugin │ ├── README.md │ ├── wp-dependencies.json │ └── edd-sample-plugin.php ├── vendor ├── autoload.php ├── composer │ ├── autoload_namespaces.php │ ├── autoload_classmap.php │ ├── autoload_psr4.php │ ├── installed.php │ ├── platform_check.php │ ├── LICENSE │ ├── autoload_static.php │ ├── autoload_real.php │ ├── installed.json │ ├── InstalledVersions.php │ └── ClassLoader.php └── afragen │ └── translations-updater │ ├── CHANGES.md │ ├── LICENSE │ ├── composer.json │ ├── src │ └── Translations_Updater │ │ ├── Init.php │ │ ├── Language_Pack.php │ │ ├── Base.php │ │ ├── API.php │ │ └── Language_Pack_API.php │ └── README.md ├── edd-sl-updater.php ├── LICENSE ├── composer.json ├── src ├── Bootstrap.php ├── Init.php ├── Theme_Updater.php ├── License_Form.php ├── Plugin_Updater.php ├── Theme_Updater_Admin.php ├── Plugin_Updater_Admin.php ├── Settings.php ├── Updater_Common.php ├── License_Actions.php └── API_Common.php ├── CHANGES.md ├── languages └── edd-sl-updater.pot ├── readme.txt └── README.md /samples/edd-sample-theme/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/afragen/edd-sl-updater/develop/samples/edd-sample-theme/screenshot.png -------------------------------------------------------------------------------- /samples/edd-sample-plugin/README.md: -------------------------------------------------------------------------------- 1 | To correctly utilize this sample. You must run `composer install afragen/wp-dependency-installer` from the sample's directory. 2 | -------------------------------------------------------------------------------- /samples/edd-sample-theme/README.md: -------------------------------------------------------------------------------- 1 | To correctly utilize this sample. You must run `composer install afragen/wp-dependency-installer` from the sample's directory. 2 | -------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | ); 11 | -------------------------------------------------------------------------------- /vendor/composer/autoload_psr4.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/afragen/translations-updater/src/Translations_Updater'), 10 | 'EDD\\Software_Licensing\\Updater\\' => array($baseDir . '/src'), 11 | ); 12 | -------------------------------------------------------------------------------- /vendor/afragen/translations-updater/CHANGES.md: -------------------------------------------------------------------------------- 1 | #### [unreleased] 2 | * make work with self-hosted installs of git hosts 3 | * update `Init::can_update()` for parity with GitHub Updater 4 | * update for possible universal EDD SL Updater plugin 5 | * switch to `site_transient_update_{plugins|themes}` filter 6 | * convert to composer dependency from [EDD Translations Updater](https://github.com/afragen/edd-translations-updater) and make more generic for any WordPress plugin or theme 7 | * support EDD Software Licensing `post_edd_sl_{plugin|theme}_updater_setup` action hooks 8 | * update for Bitbucket API 2.0 9 | * initial commit 10 | -------------------------------------------------------------------------------- /samples/edd-sample-theme/index.php: -------------------------------------------------------------------------------- 1 | 12 | > 13 | 14 | 15 | EDD Sample Theme 16 | 17 | 18 | 19 | 20 | > 21 | 22 | 23 |
24 |

EDD Sample Theme

25 |

Easy licensing for your WordPress Themes

26 |
27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /samples/edd-sample-theme/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Theme Name: EDD Sample Theme 3 | Theme URI: http://pippinsplugins.com 4 | Description: An example theme to show how EDD licensing works 5 | Author: Pippin Williamson, Devin Price 6 | Author URI: http://pippinsplugins.com 7 | Version: 1.0 8 | License: GNU General Public License v2.0 or later 9 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 10 | */ 11 | 12 | body { 13 | margin:0; 14 | background:#eee; 15 | } 16 | 17 | body, input, textarea { 18 | color: #373737; 19 | font: 15px "Helvetica Neue", Helvetica, Arial, sans-serif; 20 | font-weight: 300; 21 | line-height: 1.625; 22 | } 23 | 24 | p { 25 | margin:0 0 10px 0; 26 | } 27 | 28 | h1, h2, h3 { 29 | margin:0 0 15px 0; 30 | } 31 | 32 | article { 33 | background:#000; 34 | color:#fff; 35 | margin:14% 0 0 6%; 36 | padding:2%; 37 | } 38 | -------------------------------------------------------------------------------- /vendor/composer/installed.php: -------------------------------------------------------------------------------- 1 | 3 | array ( 4 | 'pretty_version' => 'dev-develop', 5 | 'version' => 'dev-develop', 6 | 'aliases' => 7 | array ( 8 | ), 9 | 'reference' => '3ce34e7b89473dda7c4fce4533a6761e3b37e058', 10 | 'name' => 'afragen/edd-sl-updater', 11 | ), 12 | 'versions' => 13 | array ( 14 | 'afragen/edd-sl-updater' => 15 | array ( 16 | 'pretty_version' => 'dev-develop', 17 | 'version' => 'dev-develop', 18 | 'aliases' => 19 | array ( 20 | ), 21 | 'reference' => '3ce34e7b89473dda7c4fce4533a6761e3b37e058', 22 | ), 23 | 'afragen/translations-updater' => 24 | array ( 25 | 'pretty_version' => 'dev-master', 26 | 'version' => 'dev-master', 27 | 'aliases' => 28 | array ( 29 | ), 30 | 'reference' => '93cd3ad14f59e435f0cfa915c6971126095a29f1', 31 | ), 32 | ), 33 | ); 34 | -------------------------------------------------------------------------------- /edd-sl-updater.php: -------------------------------------------------------------------------------- 1 | run(); 34 | -------------------------------------------------------------------------------- /vendor/composer/platform_check.php: -------------------------------------------------------------------------------- 1 | = 50600)) { 8 | $issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.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/afragen/translations-updater/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 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/translations-updater/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afragen/translations-updater", 3 | "description": "This framework provides automatic decoupled languate pack updates from a public repository for your WordPress plugin or theme.", 4 | "type": "library", 5 | "license": "MIT", 6 | "keywords": [ 7 | "wordpress", 8 | "plugin", 9 | "theme", 10 | "updater", 11 | "language pack", 12 | "translations" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Andy Fragen", 17 | "email": "andy@thefragens.com", 18 | "homepage": "https://thefragens.com", 19 | "role": "Developer" 20 | } 21 | ], 22 | "support": { 23 | "issues": "https://github.com/afragen/translations-updater/issues", 24 | "source": "https://github.com/afragen/translations-updater" 25 | }, 26 | "repositories": [ 27 | { 28 | "type": "vcs", 29 | "url": "https://github.com/afragen/translations-updater" 30 | } 31 | ], 32 | "prefer-stable": true, 33 | "require": { 34 | "php": ">=5.4" 35 | }, 36 | "autoload": { 37 | "psr-4": { 38 | "Fragen\\Translations_Updater\\": "src/Translations_Updater/" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "afragen/edd-sl-updater", 3 | "description": "A universal updater for EDD Software Licensing.", 4 | "type": "wordpress-plugin", 5 | "keywords": [ 6 | "easydigitaldownloads", 7 | "software licensing", 8 | "updater" 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 | "name": "Easy Digital Downloads", 20 | "homepage": "https://easydigitaldownloads.com", 21 | "role": "Developer" 22 | } 23 | ], 24 | "repositories": [ 25 | { 26 | "type": "vcs", 27 | "url": "https://github.com/afragen/edd-sl-updater" 28 | } 29 | ], 30 | "support": { 31 | "issues": "https://github.com/afragen/edd-sl-updater/issues", 32 | "source": "https://github.com/afragen/edd-sl-updater" 33 | }, 34 | "prefer-stable": true, 35 | "require": { 36 | "php": ">=5.6", 37 | "afragen/translations-updater": "dev-master" 38 | }, 39 | "autoload": { 40 | "psr-4": { 41 | "EDD\\Software_Licensing\\Updater\\": "src/" 42 | } 43 | }, 44 | "scripts": { 45 | "post-update-cmd": [ 46 | "wp i18n make-pot . languages/edd-sl-updater.pot" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /samples/edd-sample-plugin/edd-sample-plugin.php: -------------------------------------------------------------------------------- 1 | run(); 13 | 14 | /** 15 | * Load updater. 16 | * Must be in main plugin file. 17 | * 18 | * @return void 19 | */ 20 | function edd_test_plugin_updater() { 21 | $config = [ 22 | 'type' => 'plugin', // Declare the type. 23 | 'file' => __FILE__, 24 | 'api_url' => 'http://eddstore.test', // Site where EDD SL store is located. 25 | 'item_name' => 'EDD Test Plugin', // Name of plugin. 26 | 'item_id' => 11, // ID of the product. 27 | 'version' => '1.0', // Current version number. 28 | 'author' => 'Andy Fragen', // Author of this plugin. 29 | 'beta' => false, 30 | 'license' => '', // Optional, if plugin handles license actions you can set license here. 31 | ]; 32 | if ( class_exists( 'EDD\\Software_Licensing\\Updater\\Bootstrap' ) ) { 33 | ( new EDD\Software_Licensing\Updater\Init() )->run( $config ); 34 | } 35 | } 36 | add_action( 'plugins_loaded', 'edd_test_plugin_updater' ); 37 | -------------------------------------------------------------------------------- /src/Bootstrap.php: -------------------------------------------------------------------------------- 1 | file = $file; 35 | } 36 | 37 | /** 38 | * Let's get started. 39 | * 40 | * @return void 41 | */ 42 | public function run() { 43 | add_action( 44 | 'init', 45 | function () { 46 | load_plugin_textdomain( 'edd-sl-updater' ); 47 | } 48 | ); 49 | 50 | // Initiate decoupled language pack updating. 51 | ( new \Fragen\Translations_Updater\Init( __NAMESPACE__ ) )->edd_run(); 52 | 53 | $updater_config = [ 54 | 'type' => 'plugin', 55 | 'file' => $this->file, 56 | 'api_url' => 'http://easydigitaldownloads.com', 57 | 'item_name' => 'EDD SL Updater', 58 | 'item_id' => 123, 59 | 'version' => '1.0', 60 | 'author' => 'Easy Digital Downloads', 61 | 'beta' => false, 62 | ]; 63 | // ( new Init() )->run( $updater_config ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'Fragen\\Translations_Updater\\' => 28, 13 | ), 14 | 'E' => 15 | array ( 16 | 'EDD\\Software_Licensing\\Updater\\' => 31, 17 | ), 18 | ); 19 | 20 | public static $prefixDirsPsr4 = array ( 21 | 'Fragen\\Translations_Updater\\' => 22 | array ( 23 | 0 => __DIR__ . '/..' . '/afragen/translations-updater/src/Translations_Updater', 24 | ), 25 | 'EDD\\Software_Licensing\\Updater\\' => 26 | array ( 27 | 0 => __DIR__ . '/../..' . '/src', 28 | ), 29 | ); 30 | 31 | public static $classMap = array ( 32 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 33 | ); 34 | 35 | public static function getInitializer(ClassLoader $loader) 36 | { 37 | return \Closure::bind(function () use ($loader) { 38 | $loader->prefixLengthsPsr4 = ComposerStaticInit047415cf42b6d7dd064038c532564bfd::$prefixLengthsPsr4; 39 | $loader->prefixDirsPsr4 = ComposerStaticInit047415cf42b6d7dd064038c532564bfd::$prefixDirsPsr4; 40 | $loader->classMap = ComposerStaticInit047415cf42b6d7dd064038c532564bfd::$classMap; 41 | 42 | }, null, ClassLoader::class); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /samples/edd-sample-theme/functions.php: -------------------------------------------------------------------------------- 1 | run(); 17 | 18 | // Required filter to ensure persist-admin-notices-dismissal properly loads JS. 19 | add_filter( 'pand_theme_loader', '__return_true' ); 20 | 21 | /** 22 | * Test theme updater instantiate. 23 | * 24 | * @return void 25 | */ 26 | function edd_test_theme_updater() { 27 | $config = [ 28 | 'type' => 'theme', // Declare the type. 29 | 'api_url' => 'http://eddstore.test', // Site where EDD is hosted. 30 | 'item_name' => 'EDD Test Theme', // Name of theme. 31 | 'item_id' => 27, // Item ID from Downloads page. 32 | 'slug' => 'edd-test-theme', // Theme slug. 33 | 'version' => '1.0', // The current version of this theme. 34 | 'author' => 'Andy Fragen', // The author of this theme. 35 | 'download_id' => '', // Optional, used for generating a license renewal link. 36 | 'renew_url' => '', // Optional, allows for a custom license renewal link. 37 | 'beta' => false, // Optional, set to true to opt into beta versions. 38 | 'license' => '', // Optional, if theme handles license actions you can set license here. 39 | ]; 40 | if ( class_exists( 'EDD\\Software_Licensing\\Updater\\Bootstrap' ) ) { 41 | ( new EDD\Software_Licensing\Updater\Init() )->run( $config ); 42 | } 43 | } 44 | add_action( 'after_setup_theme', 'edd_test_theme_updater' ); 45 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | #### [unreleased] 2 | * initial commit 3 | * updated samples 4 | * consolidated strings 5 | * added `afragen/translations-updater` for decoupled language pack updating 6 | * added initialization and integration for `afragen/translations-updater` 7 | * updated samples to include test for class to prevent fatal when updating Updater 8 | * added `wp-dependency.json`, `composer require afragen/wp-dependency-installer`, and install code for samples to automatically install EDD SL Updater 9 | * created `class Init` with universal instantiator function, just add configuration array 10 | * created filter hook `edd_sl_license_form_table` to easily customize the license form 11 | * switch to `site_transient_update_{plugins|themes}` filter 12 | * created Settings page for licenses instead of appending to menus 13 | * added ability to set updater only using `Init->updater( $config )` 14 | * update examples for `afragen/wp-dependency-installer:^3` 15 | * sanitize, escape & ignore 16 | * remove some unnecessary function specific `$cache_key` references, [#1607](https://github.com/easydigitaldownloads/EDD-Software-Licensing/issues/1607) 17 | * add `update-available` to update transient so auto update link displays, WP 5.5 18 | * add plugin and theme data to `$transient->no_update` so auto update link displays 19 | * remove calling custom update meta row 20 | * plugin and theme updating via WP 5.5 auto-updating works, needed to bypass caps check during wp-cron 🤔 21 | * major refactor to combine common functions into `class Updater_Common` and `class API_Common` 22 | * move license check to `Updater_Common::update_transient()` 23 | * add `environment` param per [#1670](https://github.com/easydigitaldownloads/EDD-Software-Licensing/pull/1670/) 24 | * add an initiation for just using the license settings page. 25 | -------------------------------------------------------------------------------- /src/Init.php: -------------------------------------------------------------------------------- 1 | load_hooks(); 33 | if ( in_array( 'plugin', $config, true ) ) { 34 | ( new Plugin_Updater_Admin( $config ) )->load_hooks(); 35 | } 36 | if ( in_array( 'theme', $config, true ) ) { 37 | ( new Theme_Updater_Admin( $config ) )->load_hooks(); 38 | } 39 | } 40 | 41 | /** 42 | * Universal settings loader. 43 | * 44 | * Load the correct settings from a single function. 45 | * 46 | * @param array $config Configuration data for plugin or theme. 47 | * 48 | * @return void 49 | */ 50 | public function no_updater( $config ) { 51 | ( new Settings() )->load_hooks(); 52 | if ( in_array( 'plugin', $config, true ) ) { 53 | ( new Plugin_Updater_Admin( $config ) )->load_settings(); 54 | } 55 | if ( in_array( 'theme', $config, true ) ) { 56 | ( new Theme_Updater_Admin( $config ) )->load_settings(); 57 | } 58 | } 59 | 60 | /** 61 | * Universal updater. 62 | * 63 | * Load the correct updater from a single function. 64 | * 65 | * @param array $config Configuration data for plugin or theme. 66 | * 67 | * @return void 68 | */ 69 | public function updater( $config ) { 70 | if ( in_array( 'plugin', $config, true ) ) { 71 | ( new Plugin_Updater_Admin( $config ) )->updater(); 72 | } 73 | if ( in_array( 'theme', $config, true ) ) { 74 | ( new Theme_Updater_Admin( $config ) )->updater(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | = 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); 32 | if ($useStaticLoader) { 33 | require __DIR__ . '/autoload_static.php'; 34 | 35 | call_user_func(\Composer\Autoload\ComposerStaticInit047415cf42b6d7dd064038c532564bfd::getInitializer($loader)); 36 | } else { 37 | $map = require __DIR__ . '/autoload_namespaces.php'; 38 | foreach ($map as $namespace => $path) { 39 | $loader->set($namespace, $path); 40 | } 41 | 42 | $map = require __DIR__ . '/autoload_psr4.php'; 43 | foreach ($map as $namespace => $path) { 44 | $loader->setPsr4($namespace, $path); 45 | } 46 | 47 | $classMap = require __DIR__ . '/autoload_classmap.php'; 48 | if ($classMap) { 49 | $loader->addClassMap($classMap); 50 | } 51 | } 52 | 53 | $loader->register(true); 54 | 55 | return $loader; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Theme_Updater.php: -------------------------------------------------------------------------------- 1 | 'http://easydigitaldownloads.com', 41 | 'slug' => get_stylesheet(), 42 | 'item_name' => '', 43 | 'license' => '', 44 | 'version' => '', 45 | 'author' => '', 46 | 'beta' => false, 47 | ]; 48 | 49 | $args = wp_parse_args( $args, $defaults ); 50 | 51 | $this->api_url = $args['api_url']; 52 | $this->api_data = $args; 53 | $this->license = $args['license']; 54 | $this->item_name = $args['item_name']; 55 | $this->version = $args['version']; 56 | $this->slug = sanitize_key( $args['slug'] ); 57 | $this->author = $args['author']; 58 | $this->beta = $args['beta']; 59 | $this->cache_key = $args['cache_key']; 60 | $this->response_key = $this->slug . '-' . $this->beta . '-update-response'; 61 | $this->strings = $strings; 62 | } 63 | 64 | /** 65 | * Load hooks. 66 | * 67 | * @return void 68 | */ 69 | public function load_hooks() { 70 | add_filter( 'site_transient_update_themes', [ $this, 'update_transient' ] ); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "name": "afragen/translations-updater", 5 | "version": "dev-master", 6 | "version_normalized": "dev-master", 7 | "source": { 8 | "type": "git", 9 | "url": "https://github.com/afragen/translations-updater.git", 10 | "reference": "93cd3ad14f59e435f0cfa915c6971126095a29f1" 11 | }, 12 | "dist": { 13 | "type": "zip", 14 | "url": "https://api.github.com/repos/afragen/translations-updater/zipball/93cd3ad14f59e435f0cfa915c6971126095a29f1", 15 | "reference": "93cd3ad14f59e435f0cfa915c6971126095a29f1", 16 | "shasum": "" 17 | }, 18 | "require": { 19 | "php": ">=5.4" 20 | }, 21 | "time": "2020-06-24T02:11:27+00:00", 22 | "type": "library", 23 | "installation-source": "dist", 24 | "autoload": { 25 | "psr-4": { 26 | "Fragen\\Translations_Updater\\": "src/Translations_Updater/" 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 | "description": "This framework provides automatic decoupled languate pack updates from a public repository for your WordPress plugin or theme.", 42 | "keywords": [ 43 | "language pack", 44 | "plugin", 45 | "theme", 46 | "translations", 47 | "updater", 48 | "wordpress" 49 | ], 50 | "support": { 51 | "issues": "https://github.com/afragen/translations-updater/issues", 52 | "source": "https://github.com/afragen/translations-updater" 53 | }, 54 | "install-path": "../afragen/translations-updater" 55 | } 56 | ], 57 | "dev": true, 58 | "dev-package-names": [] 59 | } 60 | -------------------------------------------------------------------------------- /src/License_Form.php: -------------------------------------------------------------------------------- 1 |   ' : ' '; 38 | $dashicon = 'theme' === $addon['type'] ? '  ' : $dashicon; 39 | 40 | echo ''; ?> 41 | 42 | 43 | 44 | 45 | 46 | 47 |

48 | 49 |

50 | 51 | '; 54 | wp_nonce_field( $slug . '_nonce', $slug . '_nonce' ); 55 | if ( 'valid' === $status ) { 56 | ?> 57 | 58 | 61 | 62 | '; 65 | } 66 | echo ''; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /vendor/afragen/translations-updater/src/Translations_Updater/Init.php: -------------------------------------------------------------------------------- 1 | caller = $caller; 42 | } 43 | 44 | /** 45 | * Test for proper user capabilities. 46 | * 47 | * @return bool 48 | */ 49 | private function can_update() { 50 | // WP-CLI access has full capabilities. 51 | if ( static::is_wp_cli() ) { 52 | return true; 53 | } 54 | 55 | $can_user_update = current_user_can( 'update_plugins' ) && current_user_can( 'update_themes' ); 56 | 57 | return $can_user_update; 58 | } 59 | 60 | /** 61 | * Start the processing. 62 | * 63 | * @param mixed $config [ 'git' => '{github|bitbucket|gitlab|gitea}', 64 | * 'type' => '{plugin|theme}', 65 | * 'slug' => 'my-repo-slug', 66 | * 'version => '1.0', 67 | * 'languages' => 'https://github.com//my-translations', 68 | * ]. 69 | * @return void|bool 70 | */ 71 | public function run( $config ) { 72 | if ( ! isset( $config['git'], $config['type'], $config['slug'], $config['version'], $config['languages'] ) ) { 73 | return false; 74 | } 75 | if ( $this->can_update() ) { 76 | $config = $this->sanitize( $config ); 77 | $this->get_remote_repo_data( $config ); 78 | } 79 | } 80 | 81 | /** 82 | * Load relevant action hooks for EDD Software Licensing. 83 | */ 84 | public function edd_run() { 85 | add_action( 'post_edd_sl_plugin_updater_setup', [ $this, 'parse_edd_config' ], 15, 1 ); 86 | add_action( 'post_edd_sl_theme_updater_setup', [ $this, 'parse_edd_config' ], 15, 1 ); 87 | } 88 | 89 | /** 90 | * Parse passed config from EDD SL. 91 | * 92 | * @param array $config EDD SL config array. 93 | * 94 | * @return void 95 | */ 96 | public function parse_edd_config( $config ) { 97 | $edd_sl_updater = 'EDD\Software_Licensing\Updater'; 98 | if ( $edd_sl_updater !== $this->caller ) { 99 | if ( 'post_edd_sl_plugin_updater_setup' === current_filter() ) { 100 | $slug = array_keys( $config )[0]; 101 | $config = array_values( $config )[0]; 102 | $config['type'] = 'plugin'; 103 | $config['slug'] = $slug; 104 | } 105 | if ( 'post_edd_sl_theme_updater_setup' === current_filter() ) { 106 | $config['type'] = 'theme'; 107 | $config['slug'] = $config['theme_slug']; 108 | } 109 | } 110 | $this->run( $config ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /vendor/afragen/translations-updater/src/Translations_Updater/Language_Pack.php: -------------------------------------------------------------------------------- 1 | languages ) { 43 | return; 44 | } 45 | 46 | $this->repo = $config; 47 | $this->repo_api = $api; 48 | } 49 | 50 | /** 51 | * Do the Language Pack integration. 52 | */ 53 | public function run() { 54 | $headers = $this->parse_header_uri( $this->repo->languages ); 55 | $repo = $this->repo_api->get_language_pack( $headers ); 56 | $this->config[ $repo->slug ] = $repo; 57 | 58 | add_filter( 'site_transient_update_plugins', [ $this, 'update_site_transient' ] ); 59 | add_filter( 'site_transient_update_themes', [ $this, 'update_site_transient' ] ); 60 | } 61 | 62 | /** 63 | * Add language translations to update_plugins or update_themes transients. 64 | * 65 | * @param \stdClass $transient Update transient. 66 | * 67 | * @return mixed 68 | */ 69 | public function update_site_transient( $transient ) { 70 | $locales = get_available_languages(); 71 | $locales = ! empty( $locales ) ? $locales : [ get_locale() ]; 72 | $repos = []; 73 | 74 | if ( ! isset( $transient->translations ) ) { 75 | return $transient; 76 | } 77 | 78 | if ( 'site_transient_update_plugins' === current_filter() ) { 79 | $translations = wp_get_installed_translations( 'plugins' ); 80 | } 81 | if ( 'site_transient_update_themes' === current_filter() ) { 82 | $translations = wp_get_installed_translations( 'themes' ); 83 | } 84 | 85 | $repos = array_filter( 86 | $this->config, 87 | function ( $e ) { 88 | return isset( $e->language_packs ); 89 | } 90 | ); 91 | 92 | foreach ( $repos as $repo ) { 93 | foreach ( $locales as $locale ) { 94 | $lang_pack_mod = isset( $repo->language_packs->$locale ) 95 | ? strtotime( $repo->language_packs->$locale->updated ) 96 | : 0; 97 | $translation_mod = isset( $translations[ $repo->slug ][ $locale ] ) 98 | ? strtotime( $translations[ $repo->slug ][ $locale ]['PO-Revision-Date'] ) 99 | : 0; 100 | if ( $lang_pack_mod > $translation_mod ) { 101 | $transient->translations[] = (array) $repo->language_packs->$locale; 102 | } 103 | } 104 | } 105 | 106 | $transient->translations = array_unique( $transient->translations, SORT_REGULAR ); 107 | 108 | return $transient; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Plugin_Updater.php: -------------------------------------------------------------------------------- 1 | 'http://easydigitaldownloads.com', 49 | 'slug' => '', 50 | 'item_name' => '', 51 | 'license' => '', 52 | 'version' => '', 53 | 'author' => '', 54 | 'beta' => false, 55 | ]; 56 | 57 | $args = wp_parse_args( $args, $defaults ); 58 | 59 | $this->license = $args['license']; 60 | $this->api_url = $args['api_url']; 61 | $this->api_data = $args; 62 | $this->file = $args['file']; 63 | $this->slug = $args['slug']; 64 | $this->item_name = $args['item_name']; 65 | $this->author = $args['author']; 66 | $this->version = $args['version']; 67 | $this->wp_override = $args['wp_override']; 68 | $this->beta = $args['beta']; 69 | $this->cache_key = $args['cache_key']; 70 | $this->strings = $strings; 71 | $edd_plugin_data[ $this->slug ] = $this->api_data; 72 | } 73 | 74 | /** 75 | * Set up WordPress filters to hook into WP's update process. 76 | * 77 | * @return void 78 | */ 79 | public function load_hooks() { 80 | add_filter( 'site_transient_update_plugins', [ $this, 'update_transient' ], 15, 1 ); 81 | add_filter( 'plugins_api', [ $this, 'plugins_api' ], 99, 3 ); 82 | } 83 | 84 | /** 85 | * Updates information on the "View version x.x details" and "View details" page with custom data. 86 | * 87 | * @param mixed $data Default false. 88 | * @param string $action The type of information being requested from the Plugin Installation API. 89 | * @param object $args Plugin API arguments. 90 | * @return object $data 91 | */ 92 | public function plugins_api( $data, $action = '', $args = null ) { 93 | if ( 'plugin_information' !== $action ) { 94 | return $data; 95 | } 96 | 97 | if ( ! isset( $args->slug ) || ( $this->slug !== $args->slug ) ) { 98 | return $data; 99 | } 100 | 101 | $data = $this->get_repo_api_data( 'plugin' ); 102 | 103 | return $data; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /vendor/afragen/translations-updater/src/Translations_Updater/Base.php: -------------------------------------------------------------------------------- 1 | languages ) ) { 47 | return false; 48 | } 49 | 50 | $this->config[ $config->slug ] = $config; 51 | $language_pack = new Language_Pack( $config, new Language_Pack_API( $config ) ); 52 | $language_pack->run(); 53 | 54 | return true; 55 | } 56 | 57 | /** 58 | * Parse URI param returning array of parts. 59 | * 60 | * @param string $repo_header Repository URI. 61 | * 62 | * @return array $header 63 | */ 64 | protected function parse_header_uri( $repo_header ) { 65 | $header_parts = parse_url( $repo_header ); 66 | $header_path = pathinfo( $header_parts['path'] ); 67 | $header['original'] = $repo_header; 68 | $header['scheme'] = isset( $header_parts['scheme'] ) ? $header_parts['scheme'] : null; 69 | $header['host'] = isset( $header_parts['host'] ) ? $header_parts['host'] : null; 70 | $header['type'] = explode( '.', $header['host'] )[0] . '_' . $this->repo->type; 71 | $header['owner'] = trim( $header_path['dirname'], '/' ); 72 | $header['repo'] = $header_path['filename']; 73 | $header['owner_repo'] = implode( '/', [ $header['owner'], $header['repo'] ] ); 74 | $header['base_uri'] = str_replace( $header_parts['path'], '', $repo_header ); 75 | $header['uri'] = isset( $header['scheme'] ) ? trim( $repo_header, '/' ) : null; 76 | 77 | $header = $this->sanitize( $header ); 78 | 79 | return $header; 80 | } 81 | 82 | /** 83 | * Sanitize each setting field as needed. 84 | * 85 | * @param array $input Contains all settings fields as array keys. 86 | * 87 | * @return array 88 | */ 89 | public function sanitize( $input ) { 90 | $new_input = []; 91 | foreach ( array_keys( (array) $input ) as $id ) { 92 | $new_input[ sanitize_file_name( $id ) ] = sanitize_text_field( $input[ $id ] ); 93 | } 94 | 95 | return $new_input; 96 | } 97 | 98 | /** 99 | * Delete options from database. 100 | * 101 | * @return void 102 | */ 103 | public function delete_cached_data() { 104 | global $wpdb; 105 | 106 | $table = is_multisite() ? $wpdb->base_prefix . 'sitemeta' : $wpdb->base_prefix . 'options'; 107 | $column = is_multisite() ? 'meta_key' : 'option_name'; 108 | $delete_string = 'DELETE FROM ' . $table . ' WHERE ' . $column . ' LIKE %s LIMIT 1000'; 109 | 110 | $wpdb->query( $wpdb->prepare( $delete_string, [ '%tu-%' ] ) ); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Theme_Updater_Admin.php: -------------------------------------------------------------------------------- 1 | 'http://easydigitaldownloads.com', 48 | 'slug' => get_template(), 49 | 'item_name' => '', 50 | 'item_id' => '', 51 | 'download_id' => '', 52 | 'license' => '', 53 | 'version' => '', 54 | 'author' => '', 55 | 'renew_url' => '', 56 | 'beta' => false, 57 | ] 58 | ); 59 | 60 | // Set config arguments. 61 | $this->api_url = $config['api_url']; 62 | $this->item_name = $config['item_name']; 63 | $this->item_id = $config['item_id']; 64 | $this->download_id = $config['download_id']; 65 | $this->slug = sanitize_key( $config['slug'] ); 66 | $this->license = ! empty( $config['license'] ) ? $config['license'] : trim( get_site_option( $this->slug . '_license_key' ) ); 67 | $this->api_data = $config; 68 | $this->version = $config['version']; 69 | $this->author = $config['author']; 70 | $this->renew_url = $config['renew_url']; 71 | $this->beta = $config['beta']; 72 | $this->cache_key = 'edd_sl_' . md5( json_encode( $this->slug . $this->api_data['license'] . $this->beta ) ); 73 | 74 | // Populate version fallback. 75 | if ( empty( $config['version'] ) ) { 76 | $theme = wp_get_theme( $this->slug ); 77 | $this->version = $theme->get( 'Version' ); 78 | } 79 | 80 | $this->strings = $this->get_strings(); 81 | $this->data = $config; 82 | 83 | /** 84 | * Fires after the theme $config is setup. 85 | * 86 | * @since 1.0.0 87 | * 88 | * @param array $config Array of EDD SL theme data. 89 | */ 90 | do_action( 'post_edd_sl_theme_updater_setup', $config ); 91 | } 92 | 93 | /** 94 | * Load all our hooks. 95 | * 96 | * @return void 97 | */ 98 | public function load_hooks() { 99 | add_action( 'init', [ $this, 'updater' ] ); 100 | $this->load_settings(); 101 | } 102 | 103 | /** 104 | * Load hooks for licence settings. 105 | * 106 | * @return void 107 | */ 108 | public function load_settings() { 109 | add_action( 'admin_init', [ $this, 'register_option' ] ); 110 | add_action( 'admin_init', [ $this, 'license_action' ] ); 111 | add_action( 'admin_notices', [ $this, 'show_error' ] ); 112 | add_action( 'admin_init', [ $this, 'update_settings' ] ); 113 | add_filter( 'http_request_args', [ $this, 'disable_wporg_request' ], 5, 2 ); 114 | add_filter( 'edd_sl_updater_add_admin_page', [ $this, 'license_page' ] ); 115 | } 116 | 117 | /** 118 | * Creates the updater class. 119 | */ 120 | public function updater() { 121 | // Kludge to override capability check when doing cron. 122 | $doing_cron = defined( 'DOING_CRON' ) && DOING_CRON; 123 | if ( ! current_user_can( 'manage_options' ) && ! $doing_cron ) { 124 | return; 125 | } 126 | 127 | ( new Theme_Updater( 128 | [ 129 | 'api_url' => $this->api_url, 130 | 'api_data' => $this->api_data, 131 | 'version' => $this->version, 132 | 'license' => $this->license, 133 | 'item_name' => $this->item_name, 134 | 'item_id' => $this->item_id, 135 | 'author' => $this->author, 136 | 'beta' => $this->beta, 137 | 'cache_key' => $this->cache_key, 138 | ], 139 | $this->strings 140 | ) )->load_hooks(); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /vendor/afragen/translations-updater/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Translations Updater 3 | 4 | * Contributors: [Andy Fragen](https://github.com/afragen) 5 | * Tags: plugins, themes, edd software licensing, language pack, updater 6 | * Requires at least: 4.6 7 | * Requires PHP: 5.4 8 | * Donate link: 9 | * License: MIT 10 | * License URI: 11 | 12 | ## Description 13 | 14 | This framework allows for decoupled language pack updates for your WordPress plugins or themes that are hosted on public repositories in GitHub, Bitbucket, GitLab, or Gitea. 15 | 16 | The URI should point to a repository that contains the translations files. Refer to [GitHub Updater Translations](https://github.com/afragen/github-updater-translations) as an example. It is created using the [Language Pack Maker](https://github.com/afragen/language-pack-maker). The repo **must** be a public repo. 17 | 18 | ## Usage 19 | 20 | Install via Composer: `composer require afragen/translations-updater:dev-master` 21 | 22 | **Prior to release use the following command** 23 | `composer require afragen/translations-updater:dev-` currently `dev-master` 24 | 25 | Add `require_once __DIR__ . '/vendor/autoload.php';` to the main plugin file or theme's functions.php file. 26 | 27 | A configuration array with the following format is needed. All array elements are required. 28 | 29 | ```php 30 | add_action( 'admin_init', function() { 31 | $config = [ 32 | 'git' => '(github|bitbucket|gitlab|gitea)', 33 | 'type' => '(plugin|theme)', 34 | 'slug' => 'my-repo-slug', 35 | 'version' => 'my-repo-version', // Current version of plugin|theme. 36 | 'languages' => 'https://my-path-to/language-packs', 37 | ]; 38 | 39 | ( new \Fragen\Translations_Updater\Init() )->run( $config ); 40 | } ); 41 | ``` 42 | 43 | If you wish to delete the data stored in the options table associated with this framework you will need to issue the following command. 44 | 45 | ```php 46 | ( new \Fragen\Translations_Updater\Init() )->delete_cached_data(); 47 | ``` 48 | 49 | ## EDD Software Licensing Usage 50 | 51 | If using this framework with EDD Software Licensing you will need to update to the latest versions of the updaters in the EDD Software Licensing sample code to ensure the appropriate action hooks are present. 52 | 53 | You will need to add two key/value pairs to your setup array similar to the following, 54 | ```php 55 | 'git' => 'github', 56 | 'languages' => 'https://github.com//my-language-pack', 57 | ``` 58 | 59 | You will need to include the following command to your bootstrap file to activate the updater. 60 | 61 | ```php 62 | ( new \Fragen\Translations_Updater\Init( __NAMESPACE__ ) )->edd_run(); 63 | ``` 64 | 65 | ### Plugins 66 | 67 | You must add two additional key/value pairs to the setup array in your `EDD_SL_Plugin_Updater` setup. The array will be similar to the following from the `edd-sample-plugin.php` file. 68 | 69 | ```php 70 | $edd_updater = new EDD_SL_Plugin_Updater( EDD_SAMPLE_STORE_URL, __FILE__, array( 71 | 'version' => '1.0', // current version number 72 | 'license' => $license_key, // license key (used get_option above to retrieve from DB) 73 | 'item_name' => EDD_SAMPLE_ITEM_NAME, // name of this plugin 74 | 'author' => 'Pippin Williamson', // author of this plugin 75 | 'beta' => false, 76 | 'git' => 'bitbucket', 77 | 'languages' => 'https://bitbucket.org/afragen/test-language-pack', 78 | ) 79 | ``` 80 | 81 | ### Themes 82 | 83 | You must add two additional key/value pairs to the setup array in your `EDD_Theme_Updater_Admin` setup. The array will be similar to the following from the `edd-sample-theme/updater/theme-updater.php` file. 84 | 85 | ```php 86 | $updater = new EDD_Theme_Updater_Admin( 87 | 88 | // Config settings 89 | $config = array( 90 | 'remote_api_url' => 'https://easydigitaldownloads.com', // Site where EDD is hosted 91 | 'item_name' => 'Theme Name', // Name of theme 92 | 'theme_slug' => 'theme-slug', // Theme slug 93 | 'version' => '1.0.0', // The current version of this theme 94 | 'author' => 'Easy Digital Downloads', // The author of this theme 95 | 'download_id' => '', // Optional, used for generating a license renewal link 96 | 'renew_url' => '', // Optional, allows for a custom license renewal link 97 | 'beta' => false, // Optional, set to true to opt into beta versions 98 | 'git' => 'github', 99 | 'languages' => 'https://github.com//my-language-pack', 100 | ), 101 | ... 102 | ``` 103 | -------------------------------------------------------------------------------- /vendor/afragen/translations-updater/src/Translations_Updater/API.php: -------------------------------------------------------------------------------- 1 | repo->type; 48 | 49 | switch ( $this->repo->git ) { 50 | case 'github': 51 | $arr['git'] = 'github'; 52 | $arr['base_uri'] = 'https://api.github.com'; 53 | $arr['base_download'] = 'https://github.com'; 54 | break; 55 | case 'bitbucket': 56 | $arr['git'] = 'bitbucket'; 57 | $arr['base_uri'] = 'https://bitbucket.org/api'; 58 | $arr['base_download'] = 'https://bitbucket.org'; 59 | break; 60 | case 'gitlab': 61 | $arr['git'] = 'gitlab'; 62 | $arr['base_uri'] = 'https://gitlab.com/api/v4'; 63 | $arr['base_download'] = 'https://gitlab.com'; 64 | break; 65 | case 'gitea': 66 | $arr['git'] = 'gitea'; 67 | // TODO: make sure this works. 68 | $arr['base_uri'] = $this->repo->languages . '/api/v1'; 69 | $arr['base_download'] = $this->repo->languages; 70 | break; 71 | } 72 | 73 | return $arr; 74 | } 75 | 76 | /** 77 | * Call the API and return a json decoded body. 78 | * 79 | * @param string $url Repository URL. 80 | * 81 | * @return boolean|\stdClass 82 | */ 83 | protected function api( $url ) { 84 | $response = wp_remote_get( $this->get_api_url( $url ) ); 85 | 86 | if ( is_wp_error( $response ) ) { 87 | return false; 88 | } 89 | 90 | return json_decode( wp_remote_retrieve_body( $response ) ); 91 | } 92 | 93 | /** 94 | * Return API url. 95 | * 96 | * @access protected 97 | * 98 | * @param string $endpoint API endpoint. 99 | * 100 | * @return string $endpoint 101 | */ 102 | protected function get_api_url( $endpoint ) { 103 | $type = $this->return_repo_type(); 104 | 105 | switch ( $type['git'] ) { 106 | case 'github': 107 | case 'bitbucket': 108 | case 'gitea': 109 | break; 110 | case 'gitlab': 111 | $endpoint = add_query_arg( 'ref', 'master', $endpoint ); 112 | break; 113 | default: 114 | } 115 | 116 | return $type['base_uri'] . $endpoint; 117 | } 118 | 119 | /** 120 | * Validate wp_remote_get response. 121 | * 122 | * @param \stdClass $response API response. 123 | * 124 | * @return bool true if invalid 125 | */ 126 | protected function validate_response( $response ) { 127 | return empty( $response ) || isset( $response->message ); 128 | } 129 | 130 | /** 131 | * Returns repo cached data. 132 | * 133 | * @param string|bool $repo Repo name or false. 134 | * 135 | * @return array|bool false for expired cache 136 | */ 137 | protected function get_repo_cache( $repo = false ) { 138 | if ( ! $repo ) { 139 | $repo = isset( $this->type->slug ) ? $this->type->slug : 'tu'; 140 | } 141 | $cache_key = 'tu-' . md5( $repo ); 142 | $cache = get_site_option( $cache_key ); 143 | 144 | if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) { 145 | return false; 146 | } 147 | 148 | return $cache; 149 | } 150 | 151 | /** 152 | * Sets repo data for cache in site option. 153 | * 154 | * @param string $id Data Identifier. 155 | * @param mixed $response Data to be stored. 156 | * @param string|bool $repo Repo name or false. 157 | * 158 | * @return bool 159 | */ 160 | protected function set_repo_cache( $id, $response, $repo = false ) { 161 | if ( ! $repo ) { 162 | $repo = isset( $this->type->slug ) ? $this->type->slug : 'tu'; 163 | } 164 | $cache_key = 'tu-' . md5( $repo ); 165 | $timeout = '+' . self::$hours . ' hours'; 166 | 167 | $this->response['timeout'] = strtotime( $timeout ); 168 | $this->response[ $id ] = $response; 169 | 170 | update_site_option( $cache_key, $this->response ); 171 | 172 | return true; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /languages/edd-sl-updater.pot: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2021 Andy Fragen 2 | # This file is distributed under the same license as the EDD Software Licensing Updater plugin. 3 | msgid "" 4 | msgstr "" 5 | "Project-Id-Version: EDD Software Licensing Updater 0.11.16\n" 6 | "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/edd-sl-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: 2021-04-08T23:52:49+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: edd-sl-updater\n" 16 | 17 | #. Plugin Name of the plugin 18 | msgid "EDD Software Licensing Updater" 19 | msgstr "" 20 | 21 | #. Plugin URI of the plugin 22 | msgid "https://github.com/afragen/edd-sl-updater" 23 | msgstr "" 24 | 25 | #. Description of the plugin 26 | msgid "A universal updater for EDD Software Licensing products." 27 | msgstr "" 28 | 29 | #. Author of the plugin 30 | msgid "Andy Fragen" 31 | msgstr "" 32 | 33 | #: src/API_Common.php:45 34 | msgid "WP_Error" 35 | msgstr "" 36 | 37 | #: src/API_Common.php:53 38 | msgid "HTTP Error Code" 39 | msgstr "" 40 | 41 | #. translators: %s: item name 42 | #: src/License_Actions.php:111 43 | #: src/License_Actions.php:163 44 | msgid "activate_license-%s" 45 | msgstr "" 46 | 47 | #: src/License_Actions.php:178 48 | msgid "Theme License" 49 | msgstr "" 50 | 51 | #: src/License_Actions.php:179 52 | msgid "Plugin License" 53 | msgstr "" 54 | 55 | #: src/License_Actions.php:180 56 | msgid "Enter your license key." 57 | msgstr "" 58 | 59 | #: src/License_Actions.php:181 60 | #: src/Settings.php:78 61 | msgid "License Key" 62 | msgstr "" 63 | 64 | #: src/License_Actions.php:182 65 | #: src/Settings.php:80 66 | msgid "License Action" 67 | msgstr "" 68 | 69 | #: src/License_Actions.php:183 70 | msgid "Deactivate License" 71 | msgstr "" 72 | 73 | #: src/License_Actions.php:184 74 | msgid "Activate License" 75 | msgstr "" 76 | 77 | #: src/License_Actions.php:185 78 | #: src/License_Actions.php:205 79 | msgid "License status is unknown." 80 | msgstr "" 81 | 82 | #: src/License_Actions.php:186 83 | msgid "Invalid License." 84 | msgstr "" 85 | 86 | #. translators: %s: item name 87 | #: src/License_Actions.php:188 88 | msgid "This appears to be an invalid license key for %s." 89 | msgstr "" 90 | 91 | #: src/License_Actions.php:189 92 | msgid "Renew?" 93 | msgstr "" 94 | 95 | #: src/License_Actions.php:190 96 | msgid "unlimited" 97 | msgstr "" 98 | 99 | #: src/License_Actions.php:191 100 | msgid "License key is active." 101 | msgstr "" 102 | 103 | #. translators: %s: expiration date 104 | #: src/License_Actions.php:193 105 | msgid "Expires %s." 106 | msgstr "" 107 | 108 | #: src/License_Actions.php:194 109 | msgid "Lifetime License." 110 | msgstr "" 111 | 112 | #. translators: %1: number of sites activated, %2: total number of sites activated 113 | #: src/License_Actions.php:196 114 | msgid "You have %1$s / %2$s sites activated." 115 | msgstr "" 116 | 117 | #. translators: %s: expiration date 118 | #: src/License_Actions.php:198 119 | msgid "License key expired on %s." 120 | msgstr "" 121 | 122 | #: src/License_Actions.php:199 123 | msgid "License key has expired." 124 | msgstr "" 125 | 126 | #: src/License_Actions.php:200 127 | msgid "License keys do not match." 128 | msgstr "" 129 | 130 | #: src/License_Actions.php:201 131 | msgid "License is inactive." 132 | msgstr "" 133 | 134 | #: src/License_Actions.php:202 135 | msgid "License key is disabled." 136 | msgstr "" 137 | 138 | #: src/License_Actions.php:203 139 | msgid "Your license is not active for this URL." 140 | msgstr "" 141 | 142 | #: src/License_Actions.php:204 143 | msgid "Site is inactive." 144 | msgstr "" 145 | 146 | #: src/License_Actions.php:206 147 | msgid "Your license key has reached its activation limit." 148 | msgstr "" 149 | 150 | #: src/License_Actions.php:207 151 | msgid "Updating this theme will lose any customizations you have made. 'Cancel' to stop, 'OK' to update." 152 | msgstr "" 153 | 154 | #. translators: %1: Name, %2: new version, %3: URL, %4: link title, %5: URL, %6: opening tag 155 | #: src/License_Actions.php:209 156 | msgid "%1$s %2$s is available. Check out what's new or update now." 157 | msgstr "" 158 | 159 | #: src/License_Actions.php:210 160 | msgid "An error occurred, please try again." 161 | msgstr "" 162 | 163 | #: src/Settings.php:55 164 | #: src/Settings.php:70 165 | msgid "EDD SL Licenses" 166 | msgstr "" 167 | 168 | #: src/Settings.php:56 169 | msgctxt "Menu item" 170 | msgid "EDD SL Licenses" 171 | msgstr "" 172 | -------------------------------------------------------------------------------- /vendor/afragen/translations-updater/src/Translations_Updater/Language_Pack_API.php: -------------------------------------------------------------------------------- 1 | repo = $config; 35 | $this->response = $this->get_repo_cache( $config->slug ); 36 | } 37 | 38 | /** 39 | * Get/process Language Packs. 40 | * 41 | * @param array $headers Array of headers of Language Pack. 42 | * 43 | * @return bool When invalid response. 44 | */ 45 | public function get_language_pack( $headers ) { 46 | $response = ! empty( $this->response['languages'] ) ? $this->response['languages'] : false; 47 | 48 | if ( ! $response ) { 49 | $response = $this->get_language_pack_json( $this->repo->git, $headers ); 50 | 51 | if ( $response ) { 52 | foreach ( $response as $locale ) { 53 | $package = $this->process_language_pack_package( $this->repo->git, $locale, $headers ); 54 | 55 | $response->{$locale->language}->package = $package; 56 | $response->{$locale->language}->type = $this->repo->type; 57 | $response->{$locale->language}->version = $this->repo->version; 58 | } 59 | $this->set_repo_cache( 'languages', $response, $this->repo->slug ); 60 | } else { 61 | return false; 62 | } 63 | } 64 | $this->repo->language_packs = $response; 65 | 66 | return $this->repo; 67 | } 68 | 69 | /** 70 | * Get language-pack.json from appropriate host. 71 | * 72 | * @param string $git ( github|bitbucket|gitlab|gitea ). 73 | * @param array $headers Repository headers. 74 | * 75 | * @return array|bool|mixed|object $response API response object. 76 | */ 77 | private function get_language_pack_json( $git, $headers ) { 78 | switch ( $git ) { 79 | case 'github': 80 | $response = $this->api( '/repos/' . $headers['owner'] . '/' . $headers['repo'] . '/contents/language-pack.json' ); 81 | $response = isset( $response->content ) 82 | ? json_decode( base64_decode( $response->content ) ) 83 | : null; 84 | break; 85 | case 'bitbucket': 86 | $response = $this->api( '/2.0/repositories/' . $headers['owner'] . '/' . $headers['repo'] . '/src/master/language-pack.json' ); 87 | break; 88 | case 'gitlab': 89 | $id = rawurlencode( $headers['owner'] . '/' . $headers['repo'] ); 90 | $response = $this->api( '/projects/' . $id . '/repository/files/language-pack.json' ); 91 | $response = isset( $response->content ) 92 | ? json_decode( base64_decode( $response->content ) ) 93 | : null; 94 | break; 95 | case 'gitea': 96 | $response = $this->api( '/repos/' . $headers['owner'] . '/' . $headers['repo'] . '/raw/master/language-pack.json' ); 97 | $response = isset( $response->content ) 98 | ? json_decode( base64_decode( $response->content ) ) 99 | : null; 100 | break; 101 | } 102 | 103 | if ( $this->validate_response( $response ) ) { 104 | return false; 105 | } 106 | 107 | return $response; 108 | } 109 | 110 | /** 111 | * Process $package for update transient. 112 | * 113 | * @param string $git ( github|bitbucket|gitlab|gitea ). 114 | * @param string $locale Site locale. 115 | * @param array $headers Repository headers. 116 | * 117 | * @return array|null|string 118 | */ 119 | private function process_language_pack_package( $git, $locale, $headers ) { 120 | $package = null; 121 | switch ( $git ) { 122 | case 'github': 123 | $package = [ $headers['uri'], 'blob/master' ]; 124 | $package = implode( '/', $package ) . $locale->package; 125 | $package = add_query_arg( [ 'raw' => 'true' ], $package ); 126 | break; 127 | case 'bitbucket': 128 | $package = [ $headers['uri'], 'raw/master' ]; 129 | $package = implode( '/', $package ) . $locale->package; 130 | break; 131 | case 'gitlab': 132 | $package = [ $headers['uri'], 'raw/master' ]; 133 | $package = implode( '/', $package ) . $locale->package; 134 | break; 135 | case 'gitea': 136 | // TODO: make sure this works. 137 | $package = [ $headers['uri'], 'raw/master' ]; 138 | $package = implode( '/', $package ) . $local->package; 139 | break; 140 | } 141 | 142 | return $package; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Plugin_Updater_Admin.php: -------------------------------------------------------------------------------- 1 | '', 50 | 'api_url' => 'http://easydigitaldownloads.com', 51 | 'item_name' => '', 52 | 'item_id' => '', 53 | 'download_id' => '', 54 | 'version' => '', 55 | 'license' => '', 56 | 'author' => '', 57 | 'renew_url' => '', 58 | 'beta' => false, 59 | ] 60 | ); 61 | 62 | // Set config arguments. 63 | $this->api_url = $config['api_url']; 64 | $this->item_name = $config['item_name']; 65 | $this->item_id = $config['item_id']; 66 | $this->download_id = $config['item_id']; 67 | $this->file = plugin_basename( $config['file'] ); 68 | $this->slug = dirname( $this->file ); 69 | $this->version = $config['version']; 70 | $this->author = $config['author']; 71 | $this->renew_url = $config['renew_url']; 72 | $this->beta = $config['beta']; 73 | $this->license = ! empty( $config['license'] ) ? $config['license'] : trim( get_site_option( $this->slug . '_license_key' ) ); 74 | $this->api_data = $config; 75 | $this->version = $config['version']; 76 | $this->wp_override = isset( $config['wp_override'] ) ? (bool) $config['wp_override'] : false; 77 | $this->beta = ! empty( $this->api_data['beta'] ) ? true : false; 78 | $this->cache_key = 'edd_sl_' . md5( json_encode( $this->slug . $this->api_data['license'] . $this->beta ) ); 79 | 80 | $edd_plugin_data[ $this->slug ] = $this->api_data; 81 | 82 | // Populate version fallback. 83 | if ( empty( $config['version'] ) ) { 84 | $plugin = get_file_data( $config['file'], 'plugin' ); 85 | $this->version = $plugin['Version']; 86 | } 87 | 88 | $config['slug'] = $this->slug; 89 | $config['file'] = $this->file; 90 | $this->strings = $this->get_strings(); 91 | $this->data = $config; 92 | 93 | /** 94 | * Fires after the $config is setup. 95 | * 96 | * @since 1.0.0 97 | * 98 | * @param array $config Array of EDD SL plugin data. 99 | */ 100 | do_action( 'post_edd_sl_plugin_updater_setup', $config ); 101 | } 102 | 103 | /** 104 | * Load all our hooks. 105 | * 106 | * @return void 107 | */ 108 | public function load_hooks() { 109 | add_action( 'init', [ $this, 'updater' ] ); 110 | $this->load_settings(); 111 | } 112 | 113 | /** 114 | * Load hooks for licence settings. 115 | * 116 | * @return void 117 | */ 118 | public function load_settings() { 119 | add_action( 'admin_init', [ $this, 'register_option' ] ); 120 | add_action( 'admin_init', [ $this, 'license_action' ] ); 121 | add_action( 'admin_notices', [ $this, 'show_error' ] ); 122 | add_action( 'admin_init', [ $this, 'update_settings' ] ); 123 | add_filter( 'edd_sl_updater_add_admin_page', [ $this, 'license_page' ] ); 124 | } 125 | 126 | /** 127 | * Creates the updater class. 128 | * 129 | * @return void 130 | */ 131 | public function updater() { 132 | // Kludge to override capability check when doing cron. 133 | $doing_cron = defined( 'DOING_CRON' ) && DOING_CRON; 134 | if ( ! current_user_can( 'manage_options' ) && ! $doing_cron ) { 135 | return; 136 | } 137 | 138 | ( new Plugin_Updater( 139 | [ 140 | 'api_url' => $this->api_url, 141 | 'api_data' => $this->api_data, 142 | 'name' => $this->item_name, 143 | 'file' => $this->file, 144 | 'item_name' => $this->item_name, 145 | 'item_id' => $this->item_id, 146 | 'slug' => $this->slug, 147 | 'version' => $this->version, 148 | 'license' => $this->license, 149 | 'author' => $this->author, 150 | 'wp_override' => $this->wp_override, 151 | 'beta' => $this->beta, 152 | 'cache_key' => $this->cache_key, 153 | ], 154 | $this->strings 155 | ) )->load_hooks(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/Settings.php: -------------------------------------------------------------------------------- 1 | slug . '-license', 36 | $this->slug . '_license_key', 37 | [] 38 | ); 39 | } 40 | 41 | /** 42 | * Add options page. 43 | */ 44 | public function add_plugin_menu() { 45 | global $_registered_pages; 46 | if ( isset( $_registered_pages['settings_page_edd-sl-updater'] ) ) { 47 | return; 48 | } 49 | 50 | $parent = 'options-general.php'; 51 | $capability = 'manage_options'; 52 | 53 | add_submenu_page( 54 | $parent, 55 | esc_html__( 'EDD SL Licenses', 'edd-sl-updater' ), 56 | esc_html_x( 'EDD SL Licenses', 'Menu item', 'edd-sl-updater' ), 57 | $capability, 58 | 'edd-sl-updater', 59 | [ $this, 'create_admin_page' ] 60 | ); 61 | } 62 | 63 | /** 64 | * Options page callback. 65 | */ 66 | public function create_admin_page() { 67 | ?> 68 |
69 |

70 | 71 |

72 |
73 | 74 | 75 | 76 | 77 | 79 | 81 | 82 | 83 |
78 | 80 |
'; 92 | submit_button(); 93 | echo ''; 94 | } 95 | 96 | /** 97 | * Update settings. 98 | * 99 | * @return void|bool 100 | */ 101 | public function update_settings() { 102 | $_post = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing 103 | if ( ! isset( $_post['_wp_http_referer'] ) ) { 104 | return false; 105 | } 106 | $query = parse_url( esc_url_raw( wp_unslash( $_post['_wp_http_referer'] ) ), PHP_URL_QUERY ); 107 | parse_str( $query, $arr ); 108 | 109 | if ( isset( $_post['option_page'] ) && 110 | 'edd-sl-updater' === $arr['page'] 111 | ) { 112 | foreach ( array_keys( $_post ) as $key ) { 113 | if ( false !== strpos( $key, '_deactivate' ) ) { 114 | return; 115 | } 116 | if ( false !== strpos( $key, '_activate' ) ) { 117 | return; 118 | } 119 | } 120 | 121 | foreach ( $_post as $option => $value ) { 122 | if ( false !== strpos( $option, '_license_key' ) ) { 123 | $slug = str_replace( '_license_key', '', $option ); 124 | $value = $this->sanitize_license( $slug, $value ); 125 | update_site_option( sanitize_key( $option ), sanitize_text_field( $value ) ); 126 | delete_transient( $slug . '_license_message' ); 127 | } 128 | } 129 | $this->redirect(); 130 | } 131 | } 132 | 133 | /** 134 | * Sanitize license. 135 | * 136 | * @param string $slug Slug. 137 | * @param string $new License. 138 | * 139 | * @return string $new 140 | */ 141 | public function sanitize_license( $slug, $new ) { 142 | $old = get_site_option( $slug . '_license_key' ); 143 | if ( $old && $old !== $new ) { 144 | delete_site_option( $this->slug . '_license_key_status' ); 145 | delete_transient( $this->slug . '_license_message' ); 146 | } 147 | 148 | return $new; 149 | } 150 | 151 | /** 152 | * Outputs the markup used on the plugin license page. 153 | */ 154 | public function license_page() { 155 | $license = $this->license; 156 | $status = get_site_option( $this->slug . '_license_key_status', false ); 157 | 158 | // Checks license status to display under license key. 159 | if ( ! $license ) { 160 | $message = $this->strings['enter-key']; 161 | } else { 162 | // delete_transient( $this->slug . '_license_message' ); 163 | if ( ! get_transient( $this->slug . '_license_message', false ) ) { 164 | set_transient( $this->slug . '_license_message', $this->check_license( $this->slug ), DAY_IN_SECONDS ); 165 | } 166 | $message = get_transient( $this->slug . '_license_message' ); 167 | } 168 | settings_fields( $this->data['slug'] . '_license' ); 169 | $form_table_row = ( new License_Form() )->row( $this->data, $license, $status, $message, $this->strings ); 170 | /** 171 | * Filter to echo a customized license form table. 172 | * 173 | * @since 1.0.0 174 | * 175 | * @param string $form_table Table HTML for a license page setting. 176 | * @param string $plugin_data EDD SL Add-on data. 177 | * @param string $license EDD SL license. 178 | * @param string $status License status. 179 | * @param string $message License message. 180 | * @param array $strings Messaging strings. 181 | */ 182 | echo esc_html( apply_filters( 'edd_sl_license_form_table', $form_table_row, $this->data, $license, $status, $message, $this->strings ) ); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | # EDD Sofware Licensing Updater 2 | 3 | Author URI: https://easydigitaldownloads.com 4 | Plugin URI: https://easydigitaldownloads.com 5 | Contributors: easydigitaldownloads, afragen 6 | Donate link: https://easydigitaldownloads.com/donate/ 7 | Tags: easydigitaldownloads, updater 8 | Requires at least: 5.2 9 | Requires PHP: 5.6 10 | Tested up to: 5.5 11 | Stable Tag: x.x 12 | License: MIT 13 | 14 | ## Description 15 | 16 | A universal updater for EDD Software Licensing products. 17 | 18 | This plugin would be installed by all users of EDD Software Licensing Addons so they would be able to see updates from the store. This allows EDD Addon developers to simplify the code required to access the update system from EDD Software Licensing. 19 | 20 | PRs welcome at [EDD SL Updater](https://github.com/afragen/edd-sl-updater). 21 | 22 | ## Installation for EDD SL Add-ons 23 | 24 | The following code examples show how to instantiate and install the EDD SL Updater plugin for EDD SL Add-on plugins and themes. 25 | 26 | In the samples, the `wp-dependency.json` file **must** be included with all EDD SL Add-ons. To add the appropriate elements to your `composer.json` run the following command from your plugin/theme folder. If you have done this previously please ensure you are using the _latest_ version of `wp-dependency-installer` by adjusting your `composer.json`, if needed. 27 | 28 | composer require afragen/wp-dependency-installer 29 | 30 | The code installed via composer will automatically install and activate the EDD SL Updater plugin as a required dependency for any EDD SL Add-on. 31 | 32 | Additionally, you may need then need to run `composer update` prior to plugin distribution. 33 | 34 | The EDD SL Updater now uses a universal instantiation function that will correctly instantiate your plugin or theme based upon the configuration array passed to the function. 35 | 36 | ### Plugin Updater Example 37 | 38 | The following is an example of how to instantiate the settings/updater from a plugin. It is run from the main plugin file. This will provide a settings page for the plugin too. 39 | 40 | // Automatically install EDD SL Updater. 41 | require_once __DIR__ . '/vendor/autoload.php'; 42 | \WP_Dependency_Installer::instance( __DIR__ )->run(); 43 | 44 | // Loads the updater classes 45 | function prefix_plugin_updater() { 46 | $config = [ 47 | 'type' => 'plugin', // Declare the type. 48 | 'file' => __FILE__, 49 | 'api_url' => 'http://eddstore.test', // Site where EDD SL store is located. 50 | 'item_name' => 'EDD Test Plugin', // Name of plugin. 51 | 'item_id' => 11, // ID of the product. 52 | 'version' => '1.0', // Current version number. 53 | 'author' => 'Andy Fragen', // Author of this plugin. 54 | 'beta' => false, 55 | ]; 56 | if ( class_exists( 'EDD\\Software_Licensing\\Updater\\Bootstrap' ) ) { 57 | ( new EDD\Software_Licensing\Updater\Init() )->run( $config ); 58 | } 59 | } 60 | add_action( 'plugins_loaded', 'prefix_plugin_updater' ); 61 | 62 | 63 | ### Theme Updater Example 64 | 65 | The following is an example of how to instantiate the settings/updater from a theme. It is run from the theme's `functions.php` file. This will provide a settings page for your theme too. 66 | 67 | // Automatically install EDD SL Updater. 68 | require_once __DIR__ . '/vendor/autoload.php'; 69 | \WP_Dependency_Installer::instance( __DIR__ )->run(); 70 | 71 | // Loads the updater classes 72 | function prefix_theme_updater() { 73 | $config = [ 74 | 'type' => 'theme', // Declare the type. 75 | 'api_url' => 'http://eddstore.test', // Site where EDD SL store is located. 76 | 'item_name' => 'EDD Test Theme', // Name of theme. 77 | 'item_id' => 27, // ID of the product. 78 | 'slug' => 'edd-test-theme', // Theme slug. 79 | 'version' => '1.0', // Current version of this theme. 80 | 'author' => 'Andy Fragen', // Author of this theme 81 | 'download_id' => '', // Optional, used for generating a license renewal link. 82 | 'renew_url' => '', // Optional, allows for a custom license renewal link. 83 | 'beta' => false, // Optional, set to true to opt into beta versions. 84 | ]; 85 | if ( class_exists( 'EDD\\Software_Licensing\\Updater\\Bootstrap' ) ) { 86 | ( new EDD\Software_Licensing\Updater\Init() )->run( $config ); 87 | } 88 | } 89 | add_action( 'after_setup_theme', 'prefix_theme_updater' ); 90 | 91 | ### Updater Only 92 | 93 | If your plugin or theme creates and manages it's own settings you can simply activate only the updater by changing the init code line to the following. 94 | 95 | ( new EDD\Software_Licensing\Updater\Init() )->updater( $config ); 96 | 97 | You must save/get your plugin/theme license in an option with the format `get_option( $slug . '_license_key' );` **or** you can set a key/value pair in the config array to return the license by setting the optional `license` key in the config array. Something like the following. 98 | 99 | 'license' => get_option( 'my-license-key '), 100 | 101 | ### Decoupled Translation Packs 102 | 103 | This added framework allows for decoupled language pack updates. For more complete instructions please refer to [translations-updater framework](https://github.com/afragen/translations-updater). 104 | 105 | The URI should point to a repository that contains the translations files. Refer to [GitHub Updater Translations](https://github.com/afragen/github-updater-translations) as an example. It is created using the [Language Pack Maker](https://github.com/afragen/language-pack-maker). The repo **must** be a public repo. 106 | 107 | You will need to add two key/value pairs to your EDD SL Add-on config array similar to the following, 108 | 109 | 'git' => 'github', 110 | 'languages' => 'https://github.com//my-language-pack', 111 | 112 | ## Changelog 113 | [Changelog](./CHANGES.md) 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EDD Sofware Licensing Updater 2 | 3 | * Author URI: https://easydigitaldownloads.com 4 | * Plugin URI: https://easydigitaldownloads.com 5 | * Contributors: easydigitaldownloads, afragen 6 | * Donate link: https://easydigitaldownloads.com/donate/ 7 | * Tags: easydigitaldownloads, updater 8 | * Requires at least: 5.2 9 | * Requires PHP: 5.6 10 | * Stable Tag: master 11 | * License: MIT 12 | 13 | ## Description 14 | 15 | A universal updater for EDD Software Licensing products. 16 | 17 | This plugin would be installed by all users of EDD Software Licensing Addons so they would be able to see updates from the store. This allows EDD Addon developers to simplify the code required to access the update system from EDD Software Licensing. 18 | 19 | PRs welcome at [EDD SL Updater](https://github.com/afragen/edd-sl-updater). 20 | 21 | ## Installation for EDD SL Add-ons 22 | 23 | The following code examples show how to instantiate and install the EDD SL Updater plugin for EDD SL Add-on plugins and themes. 24 | 25 | In the samples, the `wp-dependency.json` file **must** be included with all EDD SL Add-ons. To add the appropriate elements to your `composer.json` run the following command from your plugin/theme folder. If you have done this previously please ensure you are using the _latest_ version of `wp-dependency-installer` by adjusting your `composer.json`, if needed. 26 | 27 | `composer require afragen/wp-dependency-installer` 28 | 29 | The code installed via composer will automatically install and activate the EDD SL Updater plugin as a required dependency for any EDD SL Add-on. 30 | 31 | Additionally, you may need then need to run `composer update` prior to plugin distribution. 32 | 33 | The EDD SL Updater now uses a universal instantiation function that will correctly instantiate your plugin or theme based upon the configuration array passed to the function. 34 | 35 | ### Plugin Updater Example 36 | 37 | The following is an example of how to instantiate the settings/updater from a plugin. It is run from the main plugin file. This will provide a settings page for the plugin too. 38 | 39 | ```php 40 | // Automatically install EDD SL Updater. 41 | require_once __DIR__ . '/vendor/autoload.php'; 42 | \WP_Dependency_Installer::instance( __DIR__ )->run(); 43 | 44 | // Loads the updater classes 45 | function prefix_plugin_updater() { 46 | $config = [ 47 | 'type' => 'plugin', // Declare the type. 48 | 'file' => __FILE__, 49 | 'api_url' => 'http://eddstore.test', // Site where EDD SL store is located. 50 | 'item_name' => 'EDD Test Plugin', // Name of plugin. 51 | 'item_id' => 11, // ID of the product. 52 | 'version' => '1.0', // Current version number. 53 | 'author' => 'Andy Fragen', // Author of this plugin. 54 | 'beta' => false, 55 | 'license' => '', // Optional, if plugin handles license actions you can set license here. 56 | ]; 57 | if ( class_exists( 'EDD\\Software_Licensing\\Updater\\Bootstrap' ) ) { 58 | ( new EDD\Software_Licensing\Updater\Init() )->run( $config ); 59 | } 60 | } 61 | add_action( 'plugins_loaded', 'prefix_plugin_updater' ); 62 | ``` 63 | 64 | ### Theme Updater Example 65 | 66 | The following is an example of how to instantiate the settings/updater from a theme. It is run from the theme's `functions.php` file. This will provide a settings page for your theme too. 67 | 68 | ```php 69 | // Automatically install EDD SL Updater. 70 | require_once __DIR__ . '/vendor/autoload.php'; 71 | \WP_Dependency_Installer::instance()->run( __DIR__ ); 72 | 73 | // Loads the updater classes 74 | function prefix_theme_updater() { 75 | $config = [ 76 | 'type' => 'theme', // Declare the type. 77 | 'api_url' => 'http://eddstore.test', // Site where EDD SL store is located. 78 | 'item_name' => 'EDD Test Theme', // Name of theme. 79 | 'item_id' => 27, // ID of the product. 80 | 'slug' => 'edd-test-theme', // Theme slug. 81 | 'version' => '1.0', // Current version of this theme. 82 | 'author' => 'Andy Fragen', // Author of this theme 83 | 'download_id' => '', // Optional, used for generating a license renewal link. 84 | 'renew_url' => '', // Optional, allows for a custom license renewal link. 85 | 'beta' => false, // Optional, set to true to opt into beta versions. 86 | 'license' => '', // Optional, if theme handles license actions you can set license here. 87 | ]; 88 | if ( class_exists( 'EDD\\Software_Licensing\\Updater\\Bootstrap' ) ) { 89 | ( new EDD\Software_Licensing\Updater\Init() )->run( $config ); 90 | } 91 | } 92 | add_action( 'after_setup_theme', 'prefix_theme_updater' ); 93 | ``` 94 | 95 | ### Updater Only 96 | 97 | If your plugin or theme creates and manages it's own settings you can simply activate only the updater by changing the init code line to the following. 98 | 99 | ```php 100 | ( new EDD\Software_Licensing\Updater\Init() )->updater( $config ); 101 | ``` 102 | 103 | You must save/get your plugin/theme license in an option with the format `get_option( $slug . '_license_key' );` **or** you can set a key/value pair in the config array to return the license by setting the optional `license` key in the config array. Something like the following. 104 | 105 | ```php 106 | 'license' => get_option( 'my-license-key '), 107 | ``` 108 | 109 | ### Decoupled Translation Packs 110 | 111 | This added framework allows for decoupled language pack updates. For more complete instructions please refer to [translations-updater framework](https://github.com/afragen/translations-updater). 112 | 113 | The URI should point to a repository that contains the translations files. Refer to [GitHub Updater Translations](https://github.com/afragen/github-updater-translations) as an example. It is created using the [Language Pack Maker](https://github.com/afragen/language-pack-maker). The repo **must** be a public repo. 114 | 115 | You will need to add two key/value pairs to your EDD SL Add-on config array similar to the following, 116 | 117 | ```php 118 | 'git' => 'github', 119 | 'languages' => 'https://github.com//my-language-pack', 120 | ``` 121 | 122 | ## Changelog 123 | [Changelog](./CHANGES.md) 124 | -------------------------------------------------------------------------------- /src/Updater_Common.php: -------------------------------------------------------------------------------- 1 | sections ) && is_object( $data->sections ) ) { 32 | $data->sections = $this->convert_object_to_array( $data->sections ); 33 | } 34 | if ( isset( $data->contributors ) && is_object( $data->contributors ) ) { 35 | $data->contributors = $this->convert_object_to_array( $data->contributors ); 36 | } 37 | if ( isset( $data->banners ) && is_object( $data->banners ) ) { 38 | $data->banners = $this->convert_object_to_array( $data->banners ); 39 | } 40 | if ( isset( $data->icons ) && is_object( $data->icons ) ) { 41 | $data->icons = $this->convert_object_to_array( $data->icons ); 42 | } 43 | 44 | return $data; 45 | } 46 | 47 | /** 48 | * Convert some objects to arrays when injecting data into the update API. 49 | * 50 | * Some data like sections, banners, and icons are expected to be an associative array, 51 | * however due to the JSON decoding, they are objects. This method allows us to pass 52 | * in the object and return an associative array. 53 | * 54 | * @since 3.6.5 55 | * 56 | * @param stdClass $data Data to be converted. 57 | * 58 | * @return array 59 | */ 60 | private function convert_object_to_array( $data ) { 61 | $new_data = []; 62 | foreach ( $data as $key => $value ) { 63 | $new_data[ $key ] = is_object( $value ) ? $this->convert_object_to_array( $value ) : $value; 64 | } 65 | 66 | return $new_data; 67 | } 68 | 69 | /** 70 | * Get cached version info. 71 | * 72 | * @param string $cache_key Cache key. 73 | * 74 | * @return string $cache['value'] 75 | */ 76 | protected function get_cached_version_info( $cache_key = '' ) { 77 | if ( empty( $cache_key ) ) { 78 | $cache_key = $this->cache_key; 79 | } 80 | 81 | $cache = get_site_option( $cache_key ); 82 | 83 | if ( empty( $cache['timeout'] ) || time() > $cache['timeout'] ) { 84 | return false; // Cache is expired. 85 | } 86 | 87 | if ( empty( $cache['value'] ) ) { 88 | return false; // Ensure there is some useful data. 89 | } 90 | 91 | // We need to turn the icons into an array, thanks to WP Core forcing these into an object at some point. 92 | $cache['value'] = json_decode( $cache['value'] ); 93 | if ( ! empty( $cache['value']->icons ) ) { 94 | $cache['value']->icons = (array) $cache['value']->icons; 95 | } 96 | 97 | return $cache['value']; 98 | } 99 | 100 | /** 101 | * Set version info cache. 102 | * 103 | * @param string $value Cache value. 104 | * @param string $cache_key Cache key. 105 | * 106 | * @return bool|void 107 | */ 108 | protected function set_version_info_cache( $value = '', $cache_key = '' ) { 109 | if ( empty( $value ) ) { 110 | return false; 111 | } 112 | if ( empty( $cache_key ) ) { 113 | $cache_key = $this->cache_key; 114 | } 115 | 116 | $data = [ 117 | 'timeout' => strtotime( '+12 hours', time() ), 118 | 'value' => json_encode( $value ), 119 | ]; 120 | 121 | update_site_option( $cache_key, $data ); 122 | } 123 | 124 | /** 125 | * Get repo API data from store. 126 | * Save to cache. 127 | * 128 | * @param string $type (plugin|theme). 129 | * 130 | * @return \stdClass 131 | */ 132 | protected function get_repo_api_data( $type ) { 133 | $file = 'plugin' === $type ? $this->file : $this->slug; 134 | $version_info = $this->get_cached_version_info(); 135 | 136 | if ( ! $version_info ) { 137 | $version_info = $this->api_request( 138 | [ 139 | 'edd_action' => 'get_version', 140 | 'license' => $this->license, 141 | 'name' => $this->item_name, 142 | 'slug' => $this->slug, 143 | 'version' => $this->version, 144 | 'author' => $this->author, 145 | 'beta' => $this->beta, 146 | ] 147 | ); 148 | } 149 | 150 | if ( ! \is_object( $version_info ) ) { 151 | return false; 152 | } 153 | 154 | $version_info = $this->convert_sections_to_array( $version_info ); 155 | $this->set_version_info_cache( $version_info ); 156 | 157 | // Make sure the plugin|theme property is set. 158 | $version_info->{$type} = $file; 159 | 160 | // Add for auto update link, WP 5.5. 161 | $version_info->{'update-available'} = true; 162 | 163 | return $version_info; 164 | } 165 | 166 | /** 167 | * Check for Updates at the defined API endpoint and modify the update transient. 168 | * 169 | * // TODO: figure out what to do with $this->wp_override. 170 | * 171 | * @param array $transient Update transient. 172 | * @return array Modified update transienet with custom data. 173 | */ 174 | public function update_transient( $transient ) { 175 | // needed to fix PHP 7.4 warning. 176 | if ( ! \is_object( $transient ) ) { 177 | $transient = new \stdClass(); 178 | } 179 | 180 | $type = $this instanceof Plugin_Updater ? 'plugin' : null; 181 | $type = $this instanceof Theme_Updater ? 'theme' : $type; 182 | $file = 'plugin' === $type ? $this->file : $this->slug; 183 | $current = $this->get_repo_api_data( $type ); 184 | 185 | if ( ! $current ) { 186 | return $transient; 187 | } 188 | 189 | // If there is no valid license key status, don't allow updates. 190 | if ( version_compare( $this->version, $current->new_version, '<' ) 191 | && 'valid' === get_site_option( $this->slug . '_license_key_status', false ) 192 | ) { 193 | $transient->response[ $file ] = 'plugin' === $type ? $current : (array) $current; 194 | } else { 195 | $transient->no_update[ $file ] = 'plugin' === $type ? $current : (array) $current; 196 | } 197 | 198 | return $transient; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /vendor/composer/InstalledVersions.php: -------------------------------------------------------------------------------- 1 | 27 | array ( 28 | 'pretty_version' => 'dev-develop', 29 | 'version' => 'dev-develop', 30 | 'aliases' => 31 | array ( 32 | ), 33 | 'reference' => '3ce34e7b89473dda7c4fce4533a6761e3b37e058', 34 | 'name' => 'afragen/edd-sl-updater', 35 | ), 36 | 'versions' => 37 | array ( 38 | 'afragen/edd-sl-updater' => 39 | array ( 40 | 'pretty_version' => 'dev-develop', 41 | 'version' => 'dev-develop', 42 | 'aliases' => 43 | array ( 44 | ), 45 | 'reference' => '3ce34e7b89473dda7c4fce4533a6761e3b37e058', 46 | ), 47 | 'afragen/translations-updater' => 48 | array ( 49 | 'pretty_version' => 'dev-master', 50 | 'version' => 'dev-master', 51 | 'aliases' => 52 | array ( 53 | ), 54 | 'reference' => '93cd3ad14f59e435f0cfa915c6971126095a29f1', 55 | ), 56 | ), 57 | ); 58 | private static $canGetVendors; 59 | private static $installedByVendor = array(); 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | public static function getInstalledPackages() 68 | { 69 | $packages = array(); 70 | foreach (self::getInstalled() as $installed) { 71 | $packages[] = array_keys($installed['versions']); 72 | } 73 | 74 | 75 | if (1 === \count($packages)) { 76 | return $packages[0]; 77 | } 78 | 79 | return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); 80 | } 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | public static function isInstalled($packageName) 91 | { 92 | foreach (self::getInstalled() as $installed) { 93 | if (isset($installed['versions'][$packageName])) { 94 | return true; 95 | } 96 | } 97 | 98 | return false; 99 | } 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | public static function satisfies(VersionParser $parser, $packageName, $constraint) 115 | { 116 | $constraint = $parser->parseConstraints($constraint); 117 | $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); 118 | 119 | return $provided->matches($constraint); 120 | } 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | public static function getVersionRanges($packageName) 132 | { 133 | foreach (self::getInstalled() as $installed) { 134 | if (!isset($installed['versions'][$packageName])) { 135 | continue; 136 | } 137 | 138 | $ranges = array(); 139 | if (isset($installed['versions'][$packageName]['pretty_version'])) { 140 | $ranges[] = $installed['versions'][$packageName]['pretty_version']; 141 | } 142 | if (array_key_exists('aliases', $installed['versions'][$packageName])) { 143 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); 144 | } 145 | if (array_key_exists('replaced', $installed['versions'][$packageName])) { 146 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); 147 | } 148 | if (array_key_exists('provided', $installed['versions'][$packageName])) { 149 | $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); 150 | } 151 | 152 | return implode(' || ', $ranges); 153 | } 154 | 155 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 156 | } 157 | 158 | 159 | 160 | 161 | 162 | public static function getVersion($packageName) 163 | { 164 | foreach (self::getInstalled() as $installed) { 165 | if (!isset($installed['versions'][$packageName])) { 166 | continue; 167 | } 168 | 169 | if (!isset($installed['versions'][$packageName]['version'])) { 170 | return null; 171 | } 172 | 173 | return $installed['versions'][$packageName]['version']; 174 | } 175 | 176 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 177 | } 178 | 179 | 180 | 181 | 182 | 183 | public static function getPrettyVersion($packageName) 184 | { 185 | foreach (self::getInstalled() as $installed) { 186 | if (!isset($installed['versions'][$packageName])) { 187 | continue; 188 | } 189 | 190 | if (!isset($installed['versions'][$packageName]['pretty_version'])) { 191 | return null; 192 | } 193 | 194 | return $installed['versions'][$packageName]['pretty_version']; 195 | } 196 | 197 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 198 | } 199 | 200 | 201 | 202 | 203 | 204 | public static function getReference($packageName) 205 | { 206 | foreach (self::getInstalled() as $installed) { 207 | if (!isset($installed['versions'][$packageName])) { 208 | continue; 209 | } 210 | 211 | if (!isset($installed['versions'][$packageName]['reference'])) { 212 | return null; 213 | } 214 | 215 | return $installed['versions'][$packageName]['reference']; 216 | } 217 | 218 | throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); 219 | } 220 | 221 | 222 | 223 | 224 | 225 | public static function getRootPackage() 226 | { 227 | $installed = self::getInstalled(); 228 | 229 | return $installed[0]['root']; 230 | } 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | public static function getRawData() 239 | { 240 | return self::$installed; 241 | } 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | public static function reload($data) 262 | { 263 | self::$installed = $data; 264 | self::$installedByVendor = array(); 265 | } 266 | 267 | 268 | 269 | 270 | private static function getInstalled() 271 | { 272 | if (null === self::$canGetVendors) { 273 | self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); 274 | } 275 | 276 | $installed = array(); 277 | 278 | if (self::$canGetVendors) { 279 | foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { 280 | if (isset(self::$installedByVendor[$vendorDir])) { 281 | $installed[] = self::$installedByVendor[$vendorDir]; 282 | } elseif (is_file($vendorDir.'/composer/installed.php')) { 283 | $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; 284 | } 285 | } 286 | } 287 | 288 | $installed[] = self::$installed; 289 | 290 | return $installed; 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /src/License_Actions.php: -------------------------------------------------------------------------------- 1 | slug . '_license_activate' ] ) ) { 30 | if ( check_admin_referer( $this->slug . '_nonce', $this->slug . '_nonce' ) ) { 31 | $this->activate_license(); 32 | } 33 | } 34 | 35 | if ( isset( $_POST[ $this->slug . '_license_deactivate' ] ) ) { 36 | if ( check_admin_referer( $this->slug . '_nonce', $this->slug . '_nonce' ) ) { 37 | $this->deactivate_license(); 38 | } 39 | } 40 | } 41 | 42 | /** 43 | * Activate the license. 44 | * 45 | * Listen for our activate button to be clicked. 46 | * Exit early if we didn't click the Activate button. 47 | * 48 | * @since 1.0.0 49 | */ 50 | public function activate_license() { 51 | if ( ! isset( $_POST[ $this->slug . '_license_activate' ] ) ) { 52 | return; 53 | } 54 | // run a quick security check. 55 | if ( ! check_admin_referer( $this->slug . '_nonce', $this->slug . '_nonce' ) ) { 56 | return; 57 | } 58 | 59 | // Data to send in our API request. 60 | $api_params = [ 61 | 'edd_action' => 'activate_license', 62 | 'license' => $this->license, 63 | 'item_name' => rawurlencode( $this->item_name ), // the name of our product in EDD. 64 | 'item_id' => $this->item_id, 65 | 'url' => home_url(), 66 | 'environment' => function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : false, 67 | ]; 68 | 69 | add_filter( 'edd_sl_api_request_verify_ssl', '__return_false' ); 70 | $license_data = $this->get_api_response( $this->api_url, $api_params ); 71 | 72 | if ( ! $license_data->success && isset( $license_data->error ) ) { 73 | switch ( $license_data->error ) { 74 | case 'expired': 75 | $message = sprintf( 76 | $this->strings['license-key-expired-%s'], 77 | date_i18n( get_site_option( 'date_format' ), strtotime( $license_data->expires, time() ) ) 78 | ); 79 | break; 80 | case 'disabled': 81 | case 'revoked': 82 | $message = $this->strings['license-key-is-disabled']; 83 | break; 84 | case 'missing': 85 | $message = $this->strings['status-invalid']; 86 | break; 87 | case 'invalid': 88 | case 'site_inactive': 89 | $this->strings['license-inactive-url']; 90 | break; 91 | case 'item_name_mismatch': 92 | $message = sprintf( $this->strings['item-name-mismatch-%s'], $this->item_name ); 93 | break; 94 | case 'no_activations_left': 95 | $message = $this->strings['license-activation-limit']; 96 | break; 97 | default: 98 | $message = $this->strings['error']; 99 | break; 100 | } 101 | } 102 | if ( isset( $license_data, $license_data->license ) ) { 103 | update_site_option( $this->slug . '_license_key_status', $license_data->license ); 104 | delete_transient( $this->slug . '_license_message' ); 105 | } 106 | if ( ! empty( $message ) ) { 107 | $error_data['slug'] = $this->slug; 108 | $error_data['success'] = false; 109 | $error_data['error_code'] = 110 | /* translators: %s: item name */ 111 | sprintf( esc_attr__( 'activate_license-%s', 'edd-sl-updater' ), $this->data['item_name'] ); 112 | $error_data['error_message'] = esc_html( $message ); 113 | } else { 114 | $error_data = null; 115 | } 116 | $this->redirect( $error_data ); 117 | } 118 | 119 | /** 120 | * Deactivate the license. 121 | * 122 | * Listen for our deactivate button to be clicked. 123 | * Exit early if we didn't click the deactivate button. 124 | * 125 | * @since 1.0.0 126 | */ 127 | public function deactivate_license() { 128 | if ( ! isset( $_POST[ $this->slug . '_license_deactivate' ] ) ) { 129 | return; 130 | } 131 | // Run a quick security check. 132 | if ( ! check_admin_referer( $this->slug . '_nonce', $this->slug . '_nonce' ) ) { 133 | return; 134 | } 135 | 136 | // data to send in our API request. 137 | $api_params = [ 138 | 'edd_action' => 'deactivate_license', 139 | 'license' => $this->license, 140 | 'item_name' => rawurlencode( $this->item_name ), // the name of our product in EDD. 141 | 'item_id' => $this->item_id, 142 | 'url' => home_url(), 143 | 'environment' => function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : false, 144 | ]; 145 | 146 | // Call the custom API. 147 | add_filter( 'edd_sl_api_request_verify_ssl', '__return_false' ); 148 | $license_data = $this->get_api_response( $this->api_url, $api_params ); 149 | 150 | // $license_data->license will be either "deactivated" or "failed". 151 | if ( $license_data->success && property_exists( $license_data, 'error' ) ) { 152 | $message = $this->strings['error']; 153 | } 154 | if ( ! isset( $license_data->license ) || 'deactivated' === $license_data->license ) { 155 | delete_site_option( $this->slug . '_license_key_status' ); 156 | delete_transient( $this->slug . '_license_message' ); 157 | } 158 | if ( ! empty( $message ) ) { 159 | $error_data['slug'] = $this->slug; 160 | $error_data['success'] = false; 161 | $error_data['error_code'] = 162 | /* translators: %s: item name */ 163 | sprintf( esc_attr__( 'activate_license-%s', 'edd-sl-updater' ), $this->data['item_name'] ); 164 | $error_data['error_message'] = esc_html( $message ); 165 | } else { 166 | $error_data = null; 167 | } 168 | $this->redirect( $error_data ); 169 | } 170 | 171 | /** 172 | * Get default strings. 173 | * 174 | * @return array $default_strings 175 | */ 176 | public function get_strings() { 177 | $default_strings = [ 178 | 'theme-license' => __( 'Theme License', 'edd-sl-updater' ), 179 | 'plugin-license' => __( 'Plugin License', 'edd-sl-updater' ), 180 | 'enter-key' => __( 'Enter your license key.', 'edd-sl-updater' ), 181 | 'license-key' => __( 'License Key', 'edd-sl-updater' ), 182 | 'license-action' => __( 'License Action', 'edd-sl-updater' ), 183 | 'deactivate-license' => __( 'Deactivate License', 'edd-sl-updater' ), 184 | 'activate-license' => __( 'Activate License', 'edd-sl-updater' ), 185 | 'status-unknown' => __( 'License status is unknown.', 'edd-sl-updater' ), 186 | 'status-invalid' => __( 'Invalid License.', 'edd-sl-updater' ), 187 | /* translators: %s: item name */ 188 | 'item-name-mismatch-%s' => __( 'This appears to be an invalid license key for %s.', 'edd-sl-updater' ), 189 | 'renew' => __( 'Renew?', 'edd-sl-updater' ), 190 | 'unlimited' => __( 'unlimited', 'edd-sl-updater' ), 191 | 'license-key-is-active' => __( 'License key is active.', 'edd-sl-updater' ), 192 | /* translators: %s: expiration date */ 193 | 'expires%s' => __( 'Expires %s.', 'edd-sl-updater' ), 194 | 'expires-never' => __( 'Lifetime License.', 'edd-sl-updater' ), 195 | /* translators: %1: number of sites activated, %2: total number of sites activated */ 196 | '%1$s/%2$-sites' => __( 'You have %1$s / %2$s sites activated.', 'edd-sl-updater' ), 197 | /* translators: %s: expiration date */ 198 | 'license-key-expired-%s' => __( 'License key expired on %s.', 'edd-sl-updater' ), 199 | 'license-key-expired' => __( 'License key has expired.', 'edd-sl-updater' ), 200 | 'license-keys-do-not-match' => __( 'License keys do not match.', 'edd-sl-updater' ), 201 | 'license-is-inactive' => __( 'License is inactive.', 'edd-sl-updater' ), 202 | 'license-key-is-disabled' => __( 'License key is disabled.', 'edd-sl-updater' ), 203 | 'license-inactive-url' => __( 'Your license is not active for this URL.', 'edd-sl-updater' ), 204 | 'site-is-inactive' => __( 'Site is inactive.', 'edd-sl-updater' ), 205 | 'license-status-unknown' => __( 'License status is unknown.', 'edd-sl-updater' ), 206 | 'license-activation-limit' => __( 'Your license key has reached its activation limit.', 'edd-sl-updater' ), 207 | 'update-notice' => __( "Updating this theme will lose any customizations you have made. 'Cancel' to stop, 'OK' to update.", 'edd-sl-updater' ), 208 | /* translators: %1: Name, %2: new version, %3: URL, %4: link title, %5: URL, %6: opening tag */ 209 | 'update-available' => __( '%1$s %2$s is available. Check out what\'s new or update now.', 'edd-sl-updater' ), 210 | 'error' => __( 'An error occurred, please try again.', 'edd-sl-updater' ), 211 | ]; 212 | 213 | /** 214 | * Filter the default strings. 215 | * 216 | * @since 1.0.0 217 | * 218 | * @param array $default_strings Array of default strings for theme updater. 219 | */ 220 | return apply_filters( 'edd_sl_updater_strings', $default_strings ); 221 | } 222 | 223 | /** 224 | * Create admin notice for errors. 225 | * 226 | * @return void 227 | */ 228 | public function show_error() { 229 | $error_data = false; 230 | if ( $this instanceof Plugin_Updater_Admin ) { 231 | $error_data = get_transient( 'sl_plugin_activation' ); 232 | } 233 | if ( $this instanceof Theme_Updater_Admin ) { 234 | $error_data = get_transient( 'sl_theme_activation' ); 235 | } 236 | if ( ! $error_data || $this->slug !== $error_data['slug'] ) { 237 | return; 238 | } 239 | echo '

'; 240 | printf( 241 | /* translators: %1: error code, %2: error message */ 242 | esc_html__( 'EDD SL - %1$s: %2$s' ), 243 | esc_attr( $error_data['error_code'] ), 244 | esc_html( $error_data['error_message'] ) 245 | ); 246 | echo '

'; 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /src/API_Common.php: -------------------------------------------------------------------------------- 1 | 15, 35 | 'sslverify' => $verify_ssl, 36 | 'body' => $api_params, 37 | ] 38 | ); 39 | 40 | $code = (int) wp_remote_retrieve_response_code( $response ); 41 | 42 | if ( is_wp_error( $response ) ) { 43 | $error_data['slug'] = $this->slug; 44 | $error_data['success'] = false; 45 | $error_data['error_code'] = __( 'WP_Error', 'edd-sl-updater' ); 46 | $error_data['error_message'] = $response->get_error_message(); 47 | $this->redirect( $error_data ); 48 | } 49 | 50 | if ( 200 !== $code ) { 51 | $error_data['slug'] = $this->slug; 52 | $error_data['success'] = false; 53 | $error_data['error_code'] = __( 'HTTP Error Code', 'edd-sl-updater' ); 54 | $error_data['error_message'] = $code; 55 | $this->redirect( $error_data ); 56 | } 57 | 58 | $response = json_decode( wp_remote_retrieve_body( $response ) ); 59 | 60 | if ( empty( $response ) ) { 61 | $response = new \stdClass(); 62 | $response->success = false; 63 | } 64 | 65 | $response->success = isset( $response->success ) ? $response->success : true; 66 | 67 | return $response; 68 | } 69 | 70 | /** 71 | * Checks if license is valid and gets expire date. 72 | * 73 | * @since 1.0.0 74 | * 75 | * @param string $slug Plugin/theme slug. 76 | * @return string $message License status message. 77 | */ 78 | public function check_license( $slug ) { 79 | $license = trim( get_site_option( $slug . '_license_key' ) ); 80 | 81 | $api_params = [ 82 | 'edd_action' => 'check_license', 83 | 'license' => $license, 84 | 'item_name' => rawurlencode( $this->item_name ), 85 | 'item_id' => $this->item_id, 86 | 'url' => home_url(), 87 | 'environment' => function_exists( 'wp_get_environment_type' ) ? wp_get_environment_type() : false, 88 | ]; 89 | 90 | $license_data = $this->get_api_response( $this->api_url, $api_params ); 91 | 92 | // If response doesn't include license data, return. 93 | if ( ! isset( $license_data->license ) ) { 94 | $message = $this->strings['license-status-unknown']; 95 | 96 | return $message; 97 | } 98 | 99 | // We need to update the license status at the same time the message isupdated. 100 | if ( $license_data && isset( $license_data->license ) ) { 101 | update_site_option( $slug . '_license_key_status', $license_data->license ); 102 | } 103 | 104 | // Get expire date. 105 | $expires = false; 106 | if ( isset( $license_data->expires ) && 'lifetime' !== $license_data->expires ) { 107 | $expires = date_i18n( get_site_option( 'date_format' ), strtotime( $license_data->expires, time() ) ); 108 | $renew_link = '' . esc_attr( $this->strings['renew'] ) . ''; 109 | } elseif ( isset( $license_data->expires ) && 'lifetime' === $license_data->expires ) { 110 | $expires = 'lifetime'; 111 | } 112 | 113 | // Get site counts. 114 | $site_count = property_exists( $license_data, 'site_count' ) ? $license_data->site_count : null; 115 | $license_limit = property_exists( $license_data, 'license_limit' ) ? $license_data->license_limit : null; 116 | 117 | // If unlimited. 118 | if ( 0 === $license_limit ) { 119 | $license_limit = $this->strings['unlimited']; 120 | } 121 | 122 | switch ( $license_data->license ) { 123 | case 'valid': 124 | $message = $this->strings['license-key-is-active'] . ' '; 125 | if ( isset( $expires ) ) { 126 | $message = 'lifetime' === $expires ? $message .= $this->strings['expires-never'] : $message .= sprintf( $this->strings['expires%s'], $expires ) . ' '; 127 | } 128 | if ( $site_count && $license_limit ) { 129 | $message .= sprintf( $this->strings['%1$s/%2$-sites'], $site_count, $license_limit ); 130 | } 131 | break; 132 | case 'expired': 133 | $message = $expires ? sprintf( $this->strings['license-key-expired-%s'], $expires ) : $this->strings['license-key-expired']; 134 | $message .= $renew_link ? ' ' . $renew_link : null; 135 | break; 136 | case 'invalid': 137 | $message = $this->strings['license-keys-do-not-match']; 138 | break; 139 | case 'inactive': 140 | $message = $this->strings['license-is-inactive']; 141 | break; 142 | case 'disabled': 143 | $message = $this->strings['license-key-is-disabled']; 144 | break; 145 | case 'site_inactive': 146 | $message = $this->strings['site-is-inactive']; 147 | break; 148 | default: 149 | $message = $this->strings['license-status-unknown']; 150 | } 151 | 152 | return sanitize_text_field( $message ); 153 | } 154 | 155 | /** 156 | * Constructs a renewal link. 157 | * 158 | * @since 1.0.0 159 | */ 160 | public function get_renewal_link() { 161 | // If a renewal link was passed in the config, use that. 162 | if ( ! empty( $this->renew_url ) ) { 163 | return $this->renew_url; 164 | } 165 | 166 | // If download_id was passed in the config, a renewal link can be constructed. 167 | $license_key = trim( get_site_option( $this->slug . '_license_key', false ) ); 168 | if ( ! empty( $this->download_id ) && $license_key ) { 169 | $url = esc_url( $this->api_url ); 170 | $url .= '/checkout/?edd_license_key=' . $license_key . '&download_id=' . $this->download_id; 171 | 172 | return $url; 173 | } 174 | 175 | // Otherwise return the api_url. 176 | return $this->api_url; 177 | } 178 | 179 | /** 180 | * Redirect to where we came from. 181 | * 182 | * @param array $error_data Data for error notice. 183 | * Default is null. 184 | * 185 | * @return void 186 | */ 187 | public function redirect( $error_data = null ) { 188 | $redirect_url = wp_nonce_url( admin_url( 'options-general.php' ) ); 189 | $location = add_query_arg( 190 | [ 'page' => 'edd-sl-updater' ], 191 | $redirect_url 192 | ); 193 | 194 | // Save error message data to very short transient. 195 | if ( ! $error_data['success'] ) { 196 | if ( $this instanceof Plugin_Updater_Admin ) { 197 | set_transient( 'sl_plugin_activation', $error_data, 10 ); 198 | } 199 | if ( $this instanceof Theme_Updater_Admin ) { 200 | set_transient( 'sl_theme_activation', $error_data, 10 ); 201 | } 202 | } 203 | 204 | wp_safe_redirect( $location ); 205 | exit(); 206 | } 207 | 208 | /** 209 | * Disable SSL verification in order to prevent download update failures. 210 | * 211 | * @param array $args Array of HTTP args. 212 | * @param string $url URL. 213 | * @return object $array 214 | */ 215 | public function http_request_args( $args, $url ) { 216 | if ( false !== strpos( $url, 'https://' ) && strpos( $url, 'edd_action=package_download' ) ) { 217 | $args['sslverify'] = $this->verify_ssl(); 218 | } 219 | 220 | return $args; 221 | } 222 | 223 | /** 224 | * Returns if the SSL of the store should be verified. 225 | * 226 | * @since 1.6.13 227 | * @return bool 228 | */ 229 | protected function verify_ssl() { 230 | return (bool) apply_filters( 'edd_sl_api_request_verify_ssl', true, $this ); 231 | } 232 | 233 | /** 234 | * Calls the API and, if successful, returns the object delivered by the API. 235 | * 236 | * @param array $data Parameters for the API action. 237 | * @return false|object 238 | */ 239 | protected function api_request( $data ) { 240 | global $edd_plugin_url_available; 241 | 242 | // Do a quick status check on this domain if we haven't already checked it. 243 | $store_hash = md5( $this->api_url ); 244 | if ( ! is_array( $edd_plugin_url_available ) || ! isset( $edd_plugin_url_available[ $store_hash ] ) ) { 245 | $test_url_parts = parse_url( $this->api_url ); 246 | 247 | $scheme = ! empty( $test_url_parts['scheme'] ) ? $test_url_parts['scheme'] : 'http'; 248 | $host = ! empty( $test_url_parts['host'] ) ? $test_url_parts['host'] : ''; 249 | $port = ! empty( $test_url_parts['port'] ) ? ':' . $test_url_parts['port'] : ''; 250 | 251 | if ( empty( $host ) ) { 252 | $edd_plugin_url_available[ $store_hash ] = false; 253 | } else { 254 | $test_url = $scheme . '://' . $host . $port; 255 | $response = wp_remote_get( 256 | $test_url, 257 | [ 258 | 'timeout' => $this->health_check_timeout, 259 | 'sslverify' => $this->verify_ssl(), 260 | ] 261 | ); 262 | $edd_plugin_url_available[ $store_hash ] = is_wp_error( $response ) ? false : true; 263 | } 264 | } 265 | 266 | if ( false === $edd_plugin_url_available[ $store_hash ] ) { 267 | return false; 268 | } 269 | 270 | $data = array_merge( $this->api_data, $data ); 271 | 272 | if ( $this->slug !== $data['slug'] ) { 273 | return false; 274 | } 275 | 276 | /** 277 | * Plugins are not able to update from the store. 278 | * This would cause the store to go into maintence mode as the plugin 279 | * tries to update, likely resulting in a non-responsive site. 280 | * 281 | * @link https://github.com/easydigitaldownloads/easy-digital-downloads/issues/7168 282 | */ 283 | if ( trailingslashit( home_url() ) === $this->api_url ) { 284 | return false; // Don't allow a plugin to ping itself. 285 | } 286 | 287 | $api_params = [ 288 | 'edd_action' => 'get_version', 289 | 'license' => ! empty( $data['license'] ) ? $data['license'] : '', 290 | 'item_name' => isset( $data['name'] ) ? $data['name'] : false, 291 | 'item_id' => isset( $data['item_id'] ) ? $data['item_id'] : false, 292 | 'version' => isset( $data['version'] ) ? $data['version'] : false, 293 | 'slug' => $data['slug'], 294 | 'author' => $data['author'], 295 | 'url' => home_url(), 296 | 'beta' => ! empty( $data['beta'] ), 297 | ]; 298 | 299 | $request = $this->get_api_response( $this->api_url, $api_params ); 300 | 301 | if ( $request && isset( $request->sections ) ) { 302 | $request->sections = maybe_unserialize( $request->sections ); 303 | } else { 304 | $request = false; 305 | } 306 | 307 | if ( $request && isset( $request->banners ) ) { 308 | $request->banners = maybe_unserialize( $request->banners ); 309 | } 310 | 311 | if ( $request && isset( $request->icons ) ) { 312 | $request->icons = maybe_unserialize( $request->icons ); 313 | } 314 | 315 | if ( ! empty( $request->sections ) ) { 316 | foreach ( $request->sections as $key => $section ) { 317 | $request->$key = (array) $section; 318 | } 319 | } 320 | 321 | return $request; 322 | } 323 | 324 | /** 325 | * Disable requests to wp.org repository for this theme. 326 | * 327 | * @since 1.0.0 328 | * 329 | * TODO: Is this necessary, if yes should we implement for plugins too? 330 | * 331 | * @param array $r An array of HTTP request arguments. 332 | * @param string $url The request URL. 333 | * 334 | * @return array 335 | */ 336 | public function disable_wporg_request( $r, $url ) { 337 | // If it's not a theme update request, bail. 338 | if ( 0 !== strpos( $url, 'https://api.wordpress.org/themes/update-check/1.1/' ) ) { 339 | return $r; 340 | } 341 | 342 | // Decode the JSON response. 343 | $themes = json_decode( $r['body']['themes'] ); 344 | 345 | // Remove the active parent and child themes from the check. 346 | $parent = get_option( 'template' ); 347 | $child = get_option( 'stylesheet' ); 348 | unset( $themes->themes->$parent, $themes->themes->$child ); 349 | 350 | // Encode the updated JSON response. 351 | $r['body']['themes'] = wp_json_encode( $themes ); 352 | 353 | return $r; 354 | } 355 | } 356 | -------------------------------------------------------------------------------- /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 | private $vendorDir; 46 | 47 | // PSR-4 48 | private $prefixLengthsPsr4 = array(); 49 | private $prefixDirsPsr4 = array(); 50 | private $fallbackDirsPsr4 = array(); 51 | 52 | // PSR-0 53 | private $prefixesPsr0 = array(); 54 | private $fallbackDirsPsr0 = array(); 55 | 56 | private $useIncludePath = false; 57 | private $classMap = array(); 58 | private $classMapAuthoritative = false; 59 | private $missingClasses = array(); 60 | private $apcuPrefix; 61 | 62 | private static $registeredLoaders = array(); 63 | 64 | public function __construct($vendorDir = null) 65 | { 66 | $this->vendorDir = $vendorDir; 67 | } 68 | 69 | public function getPrefixes() 70 | { 71 | if (!empty($this->prefixesPsr0)) { 72 | return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); 73 | } 74 | 75 | return array(); 76 | } 77 | 78 | public function getPrefixesPsr4() 79 | { 80 | return $this->prefixDirsPsr4; 81 | } 82 | 83 | public function getFallbackDirs() 84 | { 85 | return $this->fallbackDirsPsr0; 86 | } 87 | 88 | public function getFallbackDirsPsr4() 89 | { 90 | return $this->fallbackDirsPsr4; 91 | } 92 | 93 | public function getClassMap() 94 | { 95 | return $this->classMap; 96 | } 97 | 98 | /** 99 | * @param array $classMap Class to filename map 100 | */ 101 | public function addClassMap(array $classMap) 102 | { 103 | if ($this->classMap) { 104 | $this->classMap = array_merge($this->classMap, $classMap); 105 | } else { 106 | $this->classMap = $classMap; 107 | } 108 | } 109 | 110 | /** 111 | * Registers a set of PSR-0 directories for a given prefix, either 112 | * appending or prepending to the ones previously set for this prefix. 113 | * 114 | * @param string $prefix The prefix 115 | * @param array|string $paths The PSR-0 root directories 116 | * @param bool $prepend Whether to prepend the directories 117 | */ 118 | public function add($prefix, $paths, $prepend = false) 119 | { 120 | if (!$prefix) { 121 | if ($prepend) { 122 | $this->fallbackDirsPsr0 = array_merge( 123 | (array) $paths, 124 | $this->fallbackDirsPsr0 125 | ); 126 | } else { 127 | $this->fallbackDirsPsr0 = array_merge( 128 | $this->fallbackDirsPsr0, 129 | (array) $paths 130 | ); 131 | } 132 | 133 | return; 134 | } 135 | 136 | $first = $prefix[0]; 137 | if (!isset($this->prefixesPsr0[$first][$prefix])) { 138 | $this->prefixesPsr0[$first][$prefix] = (array) $paths; 139 | 140 | return; 141 | } 142 | if ($prepend) { 143 | $this->prefixesPsr0[$first][$prefix] = array_merge( 144 | (array) $paths, 145 | $this->prefixesPsr0[$first][$prefix] 146 | ); 147 | } else { 148 | $this->prefixesPsr0[$first][$prefix] = array_merge( 149 | $this->prefixesPsr0[$first][$prefix], 150 | (array) $paths 151 | ); 152 | } 153 | } 154 | 155 | /** 156 | * Registers a set of PSR-4 directories for a given namespace, either 157 | * appending or prepending to the ones previously set for this namespace. 158 | * 159 | * @param string $prefix The prefix/namespace, with trailing '\\' 160 | * @param array|string $paths The PSR-4 base directories 161 | * @param bool $prepend Whether to prepend the directories 162 | * 163 | * @throws \InvalidArgumentException 164 | */ 165 | public function addPsr4($prefix, $paths, $prepend = false) 166 | { 167 | if (!$prefix) { 168 | // Register directories for the root namespace. 169 | if ($prepend) { 170 | $this->fallbackDirsPsr4 = array_merge( 171 | (array) $paths, 172 | $this->fallbackDirsPsr4 173 | ); 174 | } else { 175 | $this->fallbackDirsPsr4 = array_merge( 176 | $this->fallbackDirsPsr4, 177 | (array) $paths 178 | ); 179 | } 180 | } elseif (!isset($this->prefixDirsPsr4[$prefix])) { 181 | // Register directories for a new namespace. 182 | $length = strlen($prefix); 183 | if ('\\' !== $prefix[$length - 1]) { 184 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 185 | } 186 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 187 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 188 | } elseif ($prepend) { 189 | // Prepend directories for an already registered namespace. 190 | $this->prefixDirsPsr4[$prefix] = array_merge( 191 | (array) $paths, 192 | $this->prefixDirsPsr4[$prefix] 193 | ); 194 | } else { 195 | // Append directories for an already registered namespace. 196 | $this->prefixDirsPsr4[$prefix] = array_merge( 197 | $this->prefixDirsPsr4[$prefix], 198 | (array) $paths 199 | ); 200 | } 201 | } 202 | 203 | /** 204 | * Registers a set of PSR-0 directories for a given prefix, 205 | * replacing any others previously set for this prefix. 206 | * 207 | * @param string $prefix The prefix 208 | * @param array|string $paths The PSR-0 base directories 209 | */ 210 | public function set($prefix, $paths) 211 | { 212 | if (!$prefix) { 213 | $this->fallbackDirsPsr0 = (array) $paths; 214 | } else { 215 | $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; 216 | } 217 | } 218 | 219 | /** 220 | * Registers a set of PSR-4 directories for a given namespace, 221 | * replacing any others previously set for this namespace. 222 | * 223 | * @param string $prefix The prefix/namespace, with trailing '\\' 224 | * @param array|string $paths The PSR-4 base directories 225 | * 226 | * @throws \InvalidArgumentException 227 | */ 228 | public function setPsr4($prefix, $paths) 229 | { 230 | if (!$prefix) { 231 | $this->fallbackDirsPsr4 = (array) $paths; 232 | } else { 233 | $length = strlen($prefix); 234 | if ('\\' !== $prefix[$length - 1]) { 235 | throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); 236 | } 237 | $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; 238 | $this->prefixDirsPsr4[$prefix] = (array) $paths; 239 | } 240 | } 241 | 242 | /** 243 | * Turns on searching the include path for class files. 244 | * 245 | * @param bool $useIncludePath 246 | */ 247 | public function setUseIncludePath($useIncludePath) 248 | { 249 | $this->useIncludePath = $useIncludePath; 250 | } 251 | 252 | /** 253 | * Can be used to check if the autoloader uses the include path to check 254 | * for classes. 255 | * 256 | * @return bool 257 | */ 258 | public function getUseIncludePath() 259 | { 260 | return $this->useIncludePath; 261 | } 262 | 263 | /** 264 | * Turns off searching the prefix and fallback directories for classes 265 | * that have not been registered with the class map. 266 | * 267 | * @param bool $classMapAuthoritative 268 | */ 269 | public function setClassMapAuthoritative($classMapAuthoritative) 270 | { 271 | $this->classMapAuthoritative = $classMapAuthoritative; 272 | } 273 | 274 | /** 275 | * Should class lookup fail if not found in the current class map? 276 | * 277 | * @return bool 278 | */ 279 | public function isClassMapAuthoritative() 280 | { 281 | return $this->classMapAuthoritative; 282 | } 283 | 284 | /** 285 | * APCu prefix to use to cache found/not-found classes, if the extension is enabled. 286 | * 287 | * @param string|null $apcuPrefix 288 | */ 289 | public function setApcuPrefix($apcuPrefix) 290 | { 291 | $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; 292 | } 293 | 294 | /** 295 | * The APCu prefix in use, or null if APCu caching is not enabled. 296 | * 297 | * @return string|null 298 | */ 299 | public function getApcuPrefix() 300 | { 301 | return $this->apcuPrefix; 302 | } 303 | 304 | /** 305 | * Registers this instance as an autoloader. 306 | * 307 | * @param bool $prepend Whether to prepend the autoloader or not 308 | */ 309 | public function register($prepend = false) 310 | { 311 | spl_autoload_register(array($this, 'loadClass'), true, $prepend); 312 | 313 | if (null === $this->vendorDir) { 314 | return; 315 | } 316 | 317 | if ($prepend) { 318 | self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; 319 | } else { 320 | unset(self::$registeredLoaders[$this->vendorDir]); 321 | self::$registeredLoaders[$this->vendorDir] = $this; 322 | } 323 | } 324 | 325 | /** 326 | * Unregisters this instance as an autoloader. 327 | */ 328 | public function unregister() 329 | { 330 | spl_autoload_unregister(array($this, 'loadClass')); 331 | 332 | if (null !== $this->vendorDir) { 333 | unset(self::$registeredLoaders[$this->vendorDir]); 334 | } 335 | } 336 | 337 | /** 338 | * Loads the given class or interface. 339 | * 340 | * @param string $class The name of the class 341 | * @return bool|null True if loaded, null otherwise 342 | */ 343 | public function loadClass($class) 344 | { 345 | if ($file = $this->findFile($class)) { 346 | includeFile($file); 347 | 348 | return true; 349 | } 350 | } 351 | 352 | /** 353 | * Finds the path to the file where the class is defined. 354 | * 355 | * @param string $class The name of the class 356 | * 357 | * @return string|false The path if found, false otherwise 358 | */ 359 | public function findFile($class) 360 | { 361 | // class map lookup 362 | if (isset($this->classMap[$class])) { 363 | return $this->classMap[$class]; 364 | } 365 | if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { 366 | return false; 367 | } 368 | if (null !== $this->apcuPrefix) { 369 | $file = apcu_fetch($this->apcuPrefix.$class, $hit); 370 | if ($hit) { 371 | return $file; 372 | } 373 | } 374 | 375 | $file = $this->findFileWithExtension($class, '.php'); 376 | 377 | // Search for Hack files if we are running on HHVM 378 | if (false === $file && defined('HHVM_VERSION')) { 379 | $file = $this->findFileWithExtension($class, '.hh'); 380 | } 381 | 382 | if (null !== $this->apcuPrefix) { 383 | apcu_add($this->apcuPrefix.$class, $file); 384 | } 385 | 386 | if (false === $file) { 387 | // Remember that this class does not exist. 388 | $this->missingClasses[$class] = true; 389 | } 390 | 391 | return $file; 392 | } 393 | 394 | /** 395 | * Returns the currently registered loaders indexed by their corresponding vendor directories. 396 | * 397 | * @return self[] 398 | */ 399 | public static function getRegisteredLoaders() 400 | { 401 | return self::$registeredLoaders; 402 | } 403 | 404 | private function findFileWithExtension($class, $ext) 405 | { 406 | // PSR-4 lookup 407 | $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; 408 | 409 | $first = $class[0]; 410 | if (isset($this->prefixLengthsPsr4[$first])) { 411 | $subPath = $class; 412 | while (false !== $lastPos = strrpos($subPath, '\\')) { 413 | $subPath = substr($subPath, 0, $lastPos); 414 | $search = $subPath . '\\'; 415 | if (isset($this->prefixDirsPsr4[$search])) { 416 | $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); 417 | foreach ($this->prefixDirsPsr4[$search] as $dir) { 418 | if (file_exists($file = $dir . $pathEnd)) { 419 | return $file; 420 | } 421 | } 422 | } 423 | } 424 | } 425 | 426 | // PSR-4 fallback dirs 427 | foreach ($this->fallbackDirsPsr4 as $dir) { 428 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { 429 | return $file; 430 | } 431 | } 432 | 433 | // PSR-0 lookup 434 | if (false !== $pos = strrpos($class, '\\')) { 435 | // namespaced class name 436 | $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) 437 | . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); 438 | } else { 439 | // PEAR-like class name 440 | $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; 441 | } 442 | 443 | if (isset($this->prefixesPsr0[$first])) { 444 | foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { 445 | if (0 === strpos($class, $prefix)) { 446 | foreach ($dirs as $dir) { 447 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 448 | return $file; 449 | } 450 | } 451 | } 452 | } 453 | } 454 | 455 | // PSR-0 fallback dirs 456 | foreach ($this->fallbackDirsPsr0 as $dir) { 457 | if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { 458 | return $file; 459 | } 460 | } 461 | 462 | // PSR-0 include paths. 463 | if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { 464 | return $file; 465 | } 466 | 467 | return false; 468 | } 469 | } 470 | 471 | /** 472 | * Scope isolated include. 473 | * 474 | * Prevents access to $this/self from included files. 475 | */ 476 | function includeFile($file) 477 | { 478 | include $file; 479 | } 480 | --------------------------------------------------------------------------------