From cf165c6c26eb419aeebf08477cf15ddd63435a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=87=E6=B4=BE=E5=A4=87=E6=A1=88?= <130886204+modiqi@users.noreply.github.com> Date: Fri, 11 Apr 2025 10:40:13 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90=E6=9B=B4=E6=96=B0=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Puc/v5/PucFactory.php | 10 + .../Puc/v5p3/Autoloader.php | 86 + .../Puc/v5p3/DebugBar/Extension.php | 199 +++ .../Puc/v5p3/DebugBar/Panel.php | 178 ++ .../Puc/v5p3/DebugBar/PluginExtension.php | 40 + .../Puc/v5p3/DebugBar/PluginPanel.php | 41 + .../Puc/v5p3/DebugBar/ThemePanel.php | 25 + .../Puc/v5p3/InstalledPackage.php | 105 ++ .../Puc/v5p3/Metadata.php | 162 ++ .../Puc/v5p3/OAuthSignature.php | 102 ++ .../Puc/v5p3/Plugin/Package.php | 188 ++ .../Puc/v5p3/Plugin/PluginInfo.php | 136 ++ .../Puc/v5p3/Plugin/Ui.php | 294 ++++ .../Puc/v5p3/Plugin/Update.php | 116 ++ .../Puc/v5p3/Plugin/UpdateChecker.php | 425 +++++ .../Puc/v5p3/PucFactory.php | 362 ++++ .../Puc/v5p3/Scheduler.php | 278 +++ .../Puc/v5p3/StateStore.php | 209 +++ .../Puc/v5p3/Theme/Package.php | 69 + .../Puc/v5p3/Theme/Update.php | 88 + .../Puc/v5p3/Theme/UpdateChecker.php | 159 ++ lib/plugin-update-checker/Puc/v5p3/Update.php | 38 + .../Puc/v5p3/UpdateChecker.php | 1007 +++++++++++ .../Puc/v5p3/UpgraderStatus.php | 200 +++ lib/plugin-update-checker/Puc/v5p3/Utils.php | 70 + .../Puc/v5p3/Vcs/Api.php | 379 ++++ .../Puc/v5p3/Vcs/BaseChecker.php | 29 + .../Puc/v5p3/Vcs/BitBucketApi.php | 272 +++ .../Puc/v5p3/Vcs/GitHubApi.php | 467 +++++ .../Puc/v5p3/Vcs/GitLabApi.php | 414 +++++ .../Puc/v5p3/Vcs/PluginUpdateChecker.php | 275 +++ .../Puc/v5p3/Vcs/Reference.php | 51 + .../Puc/v5p3/Vcs/ReleaseAssetSupport.php | 83 + .../Puc/v5p3/Vcs/ReleaseFilteringFeature.php | 108 ++ .../Puc/v5p3/Vcs/ThemeUpdateChecker.php | 83 + .../Puc/v5p3/Vcs/VcsCheckerMethods.php | 59 + lib/plugin-update-checker/README.md | 372 ++++ lib/plugin-update-checker/composer.json | 23 + .../css/puc-debug-bar.css | 70 + .../examples/plugin.json | 52 + lib/plugin-update-checker/examples/theme.json | 5 + lib/plugin-update-checker/js/debug-bar.js | 54 + .../languages/plugin-update-checker-ca.mo | Bin 0 -> 1186 bytes .../languages/plugin-update-checker-ca.po | 48 + .../languages/plugin-update-checker-cs_CZ.mo | Bin 0 -> 1077 bytes .../languages/plugin-update-checker-cs_CZ.po | 45 + .../languages/plugin-update-checker-da_DK.mo | Bin 0 -> 1010 bytes .../languages/plugin-update-checker-da_DK.po | 42 + .../languages/plugin-update-checker-de_DE.mo | Bin 0 -> 980 bytes .../languages/plugin-update-checker-de_DE.po | 38 + .../languages/plugin-update-checker-es_AR.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_AR.po | 48 + .../languages/plugin-update-checker-es_CL.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_CL.po | 48 + .../languages/plugin-update-checker-es_CO.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_CO.po | 48 + .../languages/plugin-update-checker-es_CR.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_CR.po | 48 + .../languages/plugin-update-checker-es_DO.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_DO.po | 48 + .../languages/plugin-update-checker-es_ES.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_ES.po | 48 + .../languages/plugin-update-checker-es_GT.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_GT.po | 48 + .../languages/plugin-update-checker-es_HN.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_HN.po | 48 + .../languages/plugin-update-checker-es_MX.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_MX.po | 48 + .../languages/plugin-update-checker-es_PE.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_PE.po | 48 + .../languages/plugin-update-checker-es_PR.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_PR.po | 48 + .../languages/plugin-update-checker-es_UY.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_UY.po | 48 + .../languages/plugin-update-checker-es_VE.mo | Bin 0 -> 1140 bytes .../languages/plugin-update-checker-es_VE.po | 48 + .../languages/plugin-update-checker-fa_IR.mo | Bin 0 -> 1128 bytes .../languages/plugin-update-checker-fa_IR.po | 38 + .../languages/plugin-update-checker-fr_CA.mo | Bin 0 -> 1208 bytes .../languages/plugin-update-checker-fr_CA.po | 48 + .../languages/plugin-update-checker-fr_FR.mo | Bin 0 -> 1066 bytes .../languages/plugin-update-checker-fr_FR.po | 42 + .../languages/plugin-update-checker-hu_HU.mo | Bin 0 -> 982 bytes .../languages/plugin-update-checker-hu_HU.po | 41 + .../languages/plugin-update-checker-it_IT.mo | Bin 0 -> 1135 bytes .../languages/plugin-update-checker-it_IT.po | 48 + .../languages/plugin-update-checker-ja.mo | Bin 0 -> 1454 bytes .../languages/plugin-update-checker-ja.po | 57 + .../languages/plugin-update-checker-nl_BE.mo | Bin 0 -> 1211 bytes .../languages/plugin-update-checker-nl_BE.po | 48 + .../languages/plugin-update-checker-nl_NL.mo | Bin 0 -> 1211 bytes .../languages/plugin-update-checker-nl_NL.po | 48 + .../languages/plugin-update-checker-pt_BR.mo | Bin 0 -> 1014 bytes .../languages/plugin-update-checker-pt_BR.po | 48 + .../languages/plugin-update-checker-ru_RU.mo | Bin 0 -> 1337 bytes .../languages/plugin-update-checker-ru_RU.po | 48 + .../languages/plugin-update-checker-sl_SI.mo | Bin 0 -> 1203 bytes .../languages/plugin-update-checker-sl_SI.po | 48 + .../languages/plugin-update-checker-sv_SE.mo | Bin 0 -> 1006 bytes .../languages/plugin-update-checker-sv_SE.po | 42 + .../languages/plugin-update-checker-tr_TR.mo | Bin 0 -> 1118 bytes .../languages/plugin-update-checker-tr_TR.po | 48 + .../languages/plugin-update-checker-uk_UA.mo | Bin 0 -> 1309 bytes .../languages/plugin-update-checker-uk_UA.po | 48 + .../languages/plugin-update-checker-zh_CN.mo | Bin 0 -> 1174 bytes .../languages/plugin-update-checker-zh_CN.po | 57 + .../languages/plugin-update-checker.pot | 49 + lib/plugin-update-checker/license.txt | 7 + lib/plugin-update-checker/load-v5p3.php | 34 + lib/plugin-update-checker/phpcs.xml | 21 + .../plugin-update-checker.php | 10 + .../vendor/Parsedown.php | 4 + .../vendor/ParsedownModern.php | 1538 +++++++++++++++++ .../vendor/PucReadmeParser.php | 352 ++++ lib/updatepulse-updater/LICENSE | 674 ++++++++ lib/updatepulse-updater/README.md | 46 + .../class-updatepulse-updater.php | 1009 +++++++++++ lib/updatepulse-updater/js/main.js | 310 ++++ lib/updatepulse-updater/js/main.min.js | 1 + .../languages/updatepulse-updater.pot | 123 ++ .../templates/license-form.php | 17 + .../templates/plugin-page-license-row.php | 10 + .../templates/theme-page-license.php | 11 + wpfonts.php | 21 +- 124 files changed, 13125 insertions(+), 1 deletion(-) create mode 100644 lib/plugin-update-checker/Puc/v5/PucFactory.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Autoloader.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/DebugBar/Extension.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/DebugBar/Panel.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/DebugBar/PluginExtension.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/DebugBar/PluginPanel.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/DebugBar/ThemePanel.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/InstalledPackage.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Metadata.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/OAuthSignature.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Plugin/Package.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Plugin/PluginInfo.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Plugin/Ui.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Plugin/Update.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Plugin/UpdateChecker.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/PucFactory.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Scheduler.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/StateStore.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Theme/Package.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Theme/Update.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Theme/UpdateChecker.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Update.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/UpdateChecker.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/UpgraderStatus.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Utils.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/Api.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/BaseChecker.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/BitBucketApi.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/GitHubApi.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/GitLabApi.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/PluginUpdateChecker.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/Reference.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/ReleaseAssetSupport.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/ReleaseFilteringFeature.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/ThemeUpdateChecker.php create mode 100644 lib/plugin-update-checker/Puc/v5p3/Vcs/VcsCheckerMethods.php create mode 100644 lib/plugin-update-checker/README.md create mode 100644 lib/plugin-update-checker/composer.json create mode 100644 lib/plugin-update-checker/css/puc-debug-bar.css create mode 100644 lib/plugin-update-checker/examples/plugin.json create mode 100644 lib/plugin-update-checker/examples/theme.json create mode 100644 lib/plugin-update-checker/js/debug-bar.js create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-ca.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-ca.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-cs_CZ.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-cs_CZ.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-da_DK.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-da_DK.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-de_DE.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-de_DE.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_AR.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_AR.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_CL.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_CL.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_CO.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_CO.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_CR.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_CR.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_DO.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_DO.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_ES.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_ES.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_GT.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_GT.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_HN.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_HN.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_MX.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_MX.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_PE.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_PE.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_PR.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_PR.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_UY.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_UY.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_VE.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-es_VE.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-fa_IR.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-fa_IR.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-fr_CA.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-fr_CA.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-fr_FR.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-fr_FR.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-hu_HU.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-hu_HU.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-it_IT.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-it_IT.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-ja.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-ja.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-nl_BE.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-nl_BE.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-nl_NL.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-nl_NL.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-pt_BR.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-pt_BR.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-ru_RU.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-ru_RU.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-sl_SI.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-sl_SI.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-sv_SE.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-sv_SE.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-tr_TR.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-tr_TR.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-uk_UA.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-uk_UA.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker-zh_CN.po create mode 100644 lib/plugin-update-checker/languages/plugin-update-checker.pot create mode 100644 lib/plugin-update-checker/license.txt create mode 100644 lib/plugin-update-checker/load-v5p3.php create mode 100644 lib/plugin-update-checker/phpcs.xml create mode 100644 lib/plugin-update-checker/plugin-update-checker.php create mode 100644 lib/plugin-update-checker/vendor/Parsedown.php create mode 100644 lib/plugin-update-checker/vendor/ParsedownModern.php create mode 100644 lib/plugin-update-checker/vendor/PucReadmeParser.php create mode 100644 lib/updatepulse-updater/LICENSE create mode 100644 lib/updatepulse-updater/README.md create mode 100644 lib/updatepulse-updater/class-updatepulse-updater.php create mode 100644 lib/updatepulse-updater/js/main.js create mode 100644 lib/updatepulse-updater/js/main.min.js create mode 100644 lib/updatepulse-updater/languages/updatepulse-updater.pot create mode 100644 lib/updatepulse-updater/templates/license-form.php create mode 100644 lib/updatepulse-updater/templates/plugin-page-license-row.php create mode 100644 lib/updatepulse-updater/templates/theme-page-license.php diff --git a/lib/plugin-update-checker/Puc/v5/PucFactory.php b/lib/plugin-update-checker/Puc/v5/PucFactory.php new file mode 100644 index 0000000..a2ec2d2 --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5/PucFactory.php @@ -0,0 +1,10 @@ +rootDir = dirname(__FILE__) . '/'; + + $namespaceWithSlash = __NAMESPACE__ . '\\'; + $this->prefix = $namespaceWithSlash; + + $this->libraryDir = $this->rootDir . '../..'; + if ( !self::isPhar() ) { + $this->libraryDir = realpath($this->libraryDir); + } + $this->libraryDir = $this->libraryDir . '/'; + + //Usually, dependencies like Parsedown are in the global namespace, + //but if someone adds a custom namespace to the entire library, they + //will be in the same namespace as this class. + $isCustomNamespace = ( + substr($namespaceWithSlash, 0, strlen(self::DEFAULT_NS_PREFIX)) !== self::DEFAULT_NS_PREFIX + ); + $libraryPrefix = $isCustomNamespace ? $namespaceWithSlash : ''; + + $this->staticMap = array( + $libraryPrefix . 'PucReadmeParser' => 'vendor/PucReadmeParser.php', + $libraryPrefix . 'Parsedown' => 'vendor/Parsedown.php', + ); + + //Add the generic, major-version-only factory class to the static map. + $versionSeparatorPos = strrpos(__NAMESPACE__, '\\v'); + if ( $versionSeparatorPos !== false ) { + $versionSegment = substr(__NAMESPACE__, $versionSeparatorPos + 1); + $pointPos = strpos($versionSegment, 'p'); + if ( ($pointPos !== false) && ($pointPos > 1) ) { + $majorVersionSegment = substr($versionSegment, 0, $pointPos); + $majorVersionNs = __NAMESPACE__ . '\\' . $majorVersionSegment; + $this->staticMap[$majorVersionNs . '\\PucFactory'] = + 'Puc/' . $majorVersionSegment . '/Factory.php'; + } + } + + spl_autoload_register(array($this, 'autoload')); + } + + /** + * Determine if this file is running as part of a Phar archive. + * + * @return bool + */ + private static function isPhar() { + //Check if the current file path starts with "phar://". + static $pharProtocol = 'phar://'; + return (substr(__FILE__, 0, strlen($pharProtocol)) === $pharProtocol); + } + + public function autoload($className) { + if ( isset($this->staticMap[$className]) && file_exists($this->libraryDir . $this->staticMap[$className]) ) { + include($this->libraryDir . $this->staticMap[$className]); + return; + } + + if ( strpos($className, $this->prefix) === 0 ) { + $path = substr($className, strlen($this->prefix)); + $path = str_replace(array('_', '\\'), '/', $path); + $path = $this->rootDir . $path . '.php'; + + if ( file_exists($path) ) { + include $path; + } + } + } + } + +endif; diff --git a/lib/plugin-update-checker/Puc/v5p3/DebugBar/Extension.php b/lib/plugin-update-checker/Puc/v5p3/DebugBar/Extension.php new file mode 100644 index 0000000..df4a493 --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5p3/DebugBar/Extension.php @@ -0,0 +1,199 @@ +updateChecker = $updateChecker; + if ( isset($panelClass) ) { + $this->panelClass = $panelClass; + } + + if ( (strpos($this->panelClass, '\\') === false) ) { + $this->panelClass = __NAMESPACE__ . '\\' . $this->panelClass; + } + + add_filter('debug_bar_panels', array($this, 'addDebugBarPanel')); + add_action('debug_bar_enqueue_scripts', array($this, 'enqueuePanelDependencies')); + + add_action('wp_ajax_puc_v5_debug_check_now', array($this, 'ajaxCheckNow')); + } + + /** + * Register the PUC Debug Bar panel. + * + * @param array $panels + * @return array + */ + public function addDebugBarPanel($panels) { + if ( $this->updateChecker->userCanInstallUpdates() ) { + $panels[] = new $this->panelClass($this->updateChecker); + } + return $panels; + } + + /** + * Enqueue our Debug Bar scripts and styles. + */ + public function enqueuePanelDependencies() { + wp_enqueue_style( + 'puc-debug-bar-style-v5', + $this->getLibraryUrl("/css/puc-debug-bar.css"), + array('debug-bar'), + '20221008' + ); + + wp_enqueue_script( + 'puc-debug-bar-js-v5', + $this->getLibraryUrl("/js/debug-bar.js"), + array('jquery'), + '20221008' + ); + } + + /** + * Run an update check and output the result. Useful for making sure that + * the update checking process works as expected. + */ + public function ajaxCheckNow() { + //phpcs:ignore WordPress.Security.NonceVerification.Missing -- Nonce is checked in preAjaxRequest(). + if ( !isset($_POST['uid']) || ($_POST['uid'] !== $this->updateChecker->getUniqueName('uid')) ) { + return; + } + $this->preAjaxRequest(); + $update = $this->updateChecker->checkForUpdates(); + if ( $update !== null ) { + echo "An update is available:"; + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r -- For debugging output. + echo '
', esc_html(print_r($update, true)), ''; + } else { + echo 'No updates found.'; + } + + $errors = $this->updateChecker->getLastRequestApiErrors(); + if ( !empty($errors) ) { + printf('
The update checker encountered %d API error%s.
', count($errors), (count($errors) > 1) ? 's' : ''); + + foreach (array_values($errors) as $num => $item) { + $wpError = $item['error']; + /** @var \WP_Error $wpError */ + printf('%s
%s
%s
)%d %s
'; + foreach (wp_remote_retrieve_headers($item['httpResponse']) as $name => $value) { + printf("%s: %s\n", esc_html($name), esc_html($value)); + } + echo '
%s
' . htmlentities(print_r($value, true)) . ''; + } else if ($value === null) { + $value = '
null
';
+ }
+ printf(
+ '', esc_html(print_r($info, true)), ''; + } else { + echo 'Failed to retrieve plugin info from the metadata URL.'; + } + exit; + } + } + +endif; diff --git a/lib/plugin-update-checker/Puc/v5p3/DebugBar/PluginPanel.php b/lib/plugin-update-checker/Puc/v5p3/DebugBar/PluginPanel.php new file mode 100644 index 0000000..d7e418d --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5p3/DebugBar/PluginPanel.php @@ -0,0 +1,41 @@ +row('Plugin file', htmlentities($this->updateChecker->pluginFile)); + parent::displayConfigHeader(); + } + + protected function getMetadataButton() { + $requestInfoButton = ''; + if ( function_exists('get_submit_button') ) { + $requestInfoButton = get_submit_button( + 'Request Info', + 'secondary', + 'puc-request-info-button', + false, + array('id' => $this->updateChecker->getUniqueName('request-info-button')) + ); + } + return $requestInfoButton; + } + + protected function getUpdateFields() { + return array_merge( + parent::getUpdateFields(), + array('homepage', 'upgrade_notice', 'tested',) + ); + } + } + +endif; diff --git a/lib/plugin-update-checker/Puc/v5p3/DebugBar/ThemePanel.php b/lib/plugin-update-checker/Puc/v5p3/DebugBar/ThemePanel.php new file mode 100644 index 0000000..3183ead --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5p3/DebugBar/ThemePanel.php @@ -0,0 +1,25 @@ +row('Theme directory', htmlentities($this->updateChecker->directoryName)); + parent::displayConfigHeader(); + } + + protected function getUpdateFields() { + return array_merge(parent::getUpdateFields(), array('details_url')); + } + } + +endif; diff --git a/lib/plugin-update-checker/Puc/v5p3/InstalledPackage.php b/lib/plugin-update-checker/Puc/v5p3/InstalledPackage.php new file mode 100644 index 0000000..06a5d21 --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5p3/InstalledPackage.php @@ -0,0 +1,105 @@ +updateChecker = $updateChecker; + } + + /** + * Get the currently installed version of the plugin or theme. + * + * @return string|null Version number. + */ + abstract public function getInstalledVersion(); + + /** + * Get the full path of the plugin or theme directory (without a trailing slash). + * + * @return string + */ + abstract public function getAbsoluteDirectoryPath(); + + /** + * Check whether a regular file exists in the package's directory. + * + * @param string $relativeFileName File name relative to the package directory. + * @return bool + */ + public function fileExists($relativeFileName) { + return is_file( + $this->getAbsoluteDirectoryPath() + . DIRECTORY_SEPARATOR + . ltrim($relativeFileName, '/\\') + ); + } + + /* ------------------------------------------------------------------- + * File header parsing + * ------------------------------------------------------------------- + */ + + /** + * Parse plugin or theme metadata from the header comment. + * + * This is basically a simplified version of the get_file_data() function from /wp-includes/functions.php. + * It's intended as a utility for subclasses that detect updates by parsing files in a VCS. + * + * @param string|null $content File contents. + * @return string[] + */ + public function getFileHeader($content) { + $content = (string)$content; + + //WordPress only looks at the first 8 KiB of the file, so we do the same. + $content = substr($content, 0, 8192); + //Normalize line endings. + $content = str_replace("\r", "\n", $content); + + $headers = $this->getHeaderNames(); + $results = array(); + foreach ($headers as $field => $name) { + $success = preg_match('/^[ \t\/*#@]*' . preg_quote($name, '/') . ':(.*)$/mi', $content, $matches); + + if ( ($success === 1) && $matches[1] ) { + $value = $matches[1]; + if ( function_exists('_cleanup_header_comment') ) { + $value = _cleanup_header_comment($value); + } + $results[$field] = $value; + } else { + $results[$field] = ''; + } + } + + return $results; + } + + /** + * @return array Format: ['HeaderKey' => 'Header Name'] + */ + abstract protected function getHeaderNames(); + + /** + * Get the value of a specific plugin or theme header. + * + * @param string $headerName + * @return string Either the value of the header, or an empty string if the header doesn't exist. + */ + abstract public function getHeaderValue($headerName); + + } +endif; diff --git a/lib/plugin-update-checker/Puc/v5p3/Metadata.php b/lib/plugin-update-checker/Puc/v5p3/Metadata.php new file mode 100644 index 0000000..f368f88 --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5p3/Metadata.php @@ -0,0 +1,162 @@ + + */ + protected $extraProperties = array(); + + /** + * Create an instance of this class from a JSON document. + * + * @abstract + * @param string $json + * @return self + */ + public static function fromJson($json) { + throw new LogicException('The ' . __METHOD__ . ' method must be implemented by subclasses'); + } + + /** + * @param string $json + * @param self $target + * @return bool + */ + protected static function createFromJson($json, $target) { + /** @var \StdClass $apiResponse */ + $apiResponse = json_decode($json); + if ( empty($apiResponse) || !is_object($apiResponse) ){ + $errorMessage = "Failed to parse update metadata. Try validating your .json file with https://jsonlint.com/"; + do_action('puc_api_error', new WP_Error('puc-invalid-json', $errorMessage)); + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- For plugin developers. + trigger_error(esc_html($errorMessage), E_USER_NOTICE); + return false; + } + + $valid = $target->validateMetadata($apiResponse); + if ( is_wp_error($valid) ){ + do_action('puc_api_error', $valid); + //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_trigger_error -- For plugin developers. + trigger_error(esc_html($valid->get_error_message()), E_USER_NOTICE); + return false; + } + + foreach(get_object_vars($apiResponse) as $key => $value){ + $target->$key = $value; + } + + return true; + } + + /** + * No validation by default! Subclasses should check that the required fields are present. + * + * @param \StdClass $apiResponse + * @return bool|\WP_Error + */ + protected function validateMetadata($apiResponse) { + return true; + } + + /** + * Create a new instance by copying the necessary fields from another object. + * + * @abstract + * @param \StdClass|self $object The source object. + * @return self The new copy. + */ + public static function fromObject($object) { + throw new LogicException('The ' . __METHOD__ . ' method must be implemented by subclasses'); + } + + /** + * Create an instance of StdClass that can later be converted back to an + * update or info container. Useful for serialization and caching, as it + * avoids the "incomplete object" problem if the cached value is loaded + * before this class. + * + * @return \StdClass + */ + public function toStdClass() { + $object = new stdClass(); + $this->copyFields($this, $object); + return $object; + } + + /** + * Transform the metadata into the format used by WordPress core. + * + * @return object + */ + abstract public function toWpFormat(); + + /** + * Copy known fields from one object to another. + * + * @param \StdClass|self $from + * @param \StdClass|self $to + */ + protected function copyFields($from, $to) { + $fields = $this->getFieldNames(); + + if ( property_exists($from, 'slug') && !empty($from->slug) ) { + //Let plugins add extra fields without having to create subclasses. + $fields = apply_filters($this->getPrefixedFilter('retain_fields') . '-' . $from->slug, $fields); + } + + foreach ($fields as $field) { + if ( property_exists($from, $field) ) { + $to->$field = $from->$field; + } + } + } + + /** + * @return string[] + */ + protected function getFieldNames() { + return array(); + } + + /** + * @param string $tag + * @return string + */ + protected function getPrefixedFilter($tag) { + return 'puc_' . $tag; + } + + public function __set($name, $value) { + $this->extraProperties[$name] = $value; + } + + public function __get($name) { + return isset($this->extraProperties[$name]) ? $this->extraProperties[$name] : null; + } + + public function __isset($name) { + return isset($this->extraProperties[$name]); + } + + public function __unset($name) { + unset($this->extraProperties[$name]); + } + } + +endif; diff --git a/lib/plugin-update-checker/Puc/v5p3/OAuthSignature.php b/lib/plugin-update-checker/Puc/v5p3/OAuthSignature.php new file mode 100644 index 0000000..be9ad9e --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5p3/OAuthSignature.php @@ -0,0 +1,102 @@ +consumerKey = $consumerKey; + $this->consumerSecret = $consumerSecret; + } + + /** + * Sign a URL using OAuth 1.0. + * + * @param string $url The URL to be signed. It may contain query parameters. + * @param string $method HTTP method such as "GET", "POST" and so on. + * @return string The signed URL. + */ + public function sign($url, $method = 'GET') { + $parameters = array(); + + //Parse query parameters. + $query = wp_parse_url($url, PHP_URL_QUERY); + if ( !empty($query) ) { + parse_str($query, $parsedParams); + if ( is_array($parsedParams) ) { + $parameters = $parsedParams; + } + //Remove the query string from the URL. We'll replace it later. + $url = substr($url, 0, strpos($url, '?')); + } + + $parameters = array_merge( + $parameters, + array( + 'oauth_consumer_key' => $this->consumerKey, + 'oauth_nonce' => $this->nonce(), + 'oauth_signature_method' => 'HMAC-SHA1', + 'oauth_timestamp' => time(), + 'oauth_version' => '1.0', + ) + ); + unset($parameters['oauth_signature']); + + //Parameters must be sorted alphabetically before signing. + ksort($parameters); + + //The most complicated part of the request - generating the signature. + //The string to sign contains the HTTP method, the URL path, and all of + //our query parameters. Everything is URL encoded. Then we concatenate + //them with ampersands into a single string to hash. + $encodedVerb = urlencode($method); + $encodedUrl = urlencode($url); + $encodedParams = urlencode(http_build_query($parameters, '', '&')); + + $stringToSign = $encodedVerb . '&' . $encodedUrl . '&' . $encodedParams; + + //Since we only have one OAuth token (the consumer secret) we only have + //to use it as our HMAC key. However, we still have to append an & to it + //as if we were using it with additional tokens. + $secret = urlencode($this->consumerSecret) . '&'; + + //The signature is a hash of the consumer key and the base string. Note + //that we have to get the raw output from hash_hmac and base64 encode + //the binary data result. + $parameters['oauth_signature'] = base64_encode(hash_hmac('sha1', $stringToSign, $secret, true)); + + return ($url . '?' . http_build_query($parameters)); + } + + /** + * Generate a random nonce. + * + * @return string + */ + private function nonce() { + $mt = microtime(); + + $rand = null; + if ( is_callable('random_bytes') ) { + try { + $rand = random_bytes(16); + } catch (\Exception $ex) { + //Fall back to mt_rand (below). + } + } + if ( $rand === null ) { + //phpcs:ignore WordPress.WP.AlternativeFunctions.rand_mt_rand + $rand = function_exists('wp_rand') ? wp_rand() : mt_rand(); + } + + return md5($mt . '_' . $rand); + } + } + +endif; diff --git a/lib/plugin-update-checker/Puc/v5p3/Plugin/Package.php b/lib/plugin-update-checker/Puc/v5p3/Plugin/Package.php new file mode 100644 index 0000000..6b19bb3 --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5p3/Plugin/Package.php @@ -0,0 +1,188 @@ +pluginAbsolutePath = $pluginAbsolutePath; + $this->pluginFile = plugin_basename($this->pluginAbsolutePath); + + parent::__construct($updateChecker); + + //Clear the version number cache when something - anything - is upgraded or WP clears the update cache. + add_filter('upgrader_post_install', array($this, 'clearCachedVersion')); + add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion')); + } + + public function getInstalledVersion() { + if ( isset($this->cachedInstalledVersion) ) { + return $this->cachedInstalledVersion; + } + + $pluginHeader = $this->getPluginHeader(); + if ( isset($pluginHeader['Version']) ) { + $this->cachedInstalledVersion = $pluginHeader['Version']; + return $pluginHeader['Version']; + } else { + //This can happen if the filename points to something that is not a plugin. + $this->updateChecker->triggerError( + sprintf( + "Cannot read the Version header for '%s'. The filename is incorrect or is not a plugin.", + $this->updateChecker->pluginFile + ), + E_USER_WARNING + ); + return null; + } + } + + /** + * Clear the cached plugin version. This method can be set up as a filter (hook) and will + * return the filter argument unmodified. + * + * @param mixed $filterArgument + * @return mixed + */ + public function clearCachedVersion($filterArgument = null) { + $this->cachedInstalledVersion = null; + return $filterArgument; + } + + public function getAbsoluteDirectoryPath() { + return dirname($this->pluginAbsolutePath); + } + + /** + * Get the value of a specific plugin or theme header. + * + * @param string $headerName + * @param string $defaultValue + * @return string Either the value of the header, or $defaultValue if the header doesn't exist or is empty. + */ + public function getHeaderValue($headerName, $defaultValue = '') { + $headers = $this->getPluginHeader(); + if ( isset($headers[$headerName]) && ($headers[$headerName] !== '') ) { + return $headers[$headerName]; + } + return $defaultValue; + } + + protected function getHeaderNames() { + return array( + 'Name' => 'Plugin Name', + 'PluginURI' => 'Plugin URI', + 'Version' => 'Version', + 'Description' => 'Description', + 'Author' => 'Author', + 'AuthorURI' => 'Author URI', + 'TextDomain' => 'Text Domain', + 'DomainPath' => 'Domain Path', + 'Network' => 'Network', + + //The newest WordPress version that this plugin requires or has been tested with. + //We support several different formats for compatibility with other libraries. + 'Tested WP' => 'Tested WP', + 'Requires WP' => 'Requires WP', + 'Tested up to' => 'Tested up to', + 'Requires at least' => 'Requires at least', + ); + } + + /** + * Get the translated plugin title. + * + * @return string + */ + public function getPluginTitle() { + $title = ''; + $header = $this->getPluginHeader(); + if ( $header && !empty($header['Name']) && isset($header['TextDomain']) ) { + $title = translate($header['Name'], $header['TextDomain']); + } + return $title; + } + + /** + * Get plugin's metadata from its file header. + * + * @return array + */ + public function getPluginHeader() { + if ( !is_file($this->pluginAbsolutePath) ) { + //This can happen if the plugin filename is wrong. + $this->updateChecker->triggerError( + sprintf( + "Can't to read the plugin header for '%s'. The file does not exist.", + $this->updateChecker->pluginFile + ), + E_USER_WARNING + ); + return array(); + } + + if ( !function_exists('get_plugin_data') ) { + require_once(ABSPATH . '/wp-admin/includes/plugin.php'); + } + return get_plugin_data($this->pluginAbsolutePath, false, false); + } + + public function removeHooks() { + remove_filter('upgrader_post_install', array($this, 'clearCachedVersion')); + remove_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion')); + } + + /** + * Check if the plugin file is inside the mu-plugins directory. + * + * @return bool + */ + public function isMuPlugin() { + static $cachedResult = null; + + if ( $cachedResult === null ) { + if ( !defined('WPMU_PLUGIN_DIR') || !is_string(WPMU_PLUGIN_DIR) ) { + $cachedResult = false; + return $cachedResult; + } + + //Convert both paths to the canonical form before comparison. + $muPluginDir = realpath(WPMU_PLUGIN_DIR); + $pluginPath = realpath($this->pluginAbsolutePath); + //If realpath() fails, just normalize the syntax instead. + if (($muPluginDir === false) || ($pluginPath === false)) { + $muPluginDir = PucFactory::normalizePath(WPMU_PLUGIN_DIR); + $pluginPath = PucFactory::normalizePath($this->pluginAbsolutePath); + } + + $cachedResult = (strpos($pluginPath, $muPluginDir) === 0); + } + + return $cachedResult; + } + } + +endif; diff --git a/lib/plugin-update-checker/Puc/v5p3/Plugin/PluginInfo.php b/lib/plugin-update-checker/Puc/v5p3/Plugin/PluginInfo.php new file mode 100644 index 0000000..69978a6 --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5p3/Plugin/PluginInfo.php @@ -0,0 +1,136 @@ +sections = (array)$instance->sections; + $instance->icons = (array)$instance->icons; + + return $instance; + } + + /** + * Very, very basic validation. + * + * @param \StdClass $apiResponse + * @return bool|\WP_Error + */ + protected function validateMetadata($apiResponse) { + if ( + !isset($apiResponse->name, $apiResponse->version) + || empty($apiResponse->name) + || empty($apiResponse->version) + ) { + return new \WP_Error( + 'puc-invalid-metadata', + "The plugin metadata file does not contain the required 'name' and/or 'version' keys." + ); + } + return true; + } + + + /** + * Transform plugin info into the format used by the native WordPress.org API + * + * @return object + */ + public function toWpFormat(){ + $info = new \stdClass; + + //The custom update API is built so that many fields have the same name and format + //as those returned by the native WordPress.org API. These can be assigned directly. + $sameFormat = array( + 'name', 'slug', 'version', 'requires', 'tested', 'rating', 'upgrade_notice', + 'num_ratings', 'downloaded', 'active_installs', 'homepage', 'last_updated', + 'requires_php', + ); + foreach($sameFormat as $field){ + if ( isset($this->$field) ) { + $info->$field = $this->$field; + } else { + $info->$field = null; + } + } + + //Other fields need to be renamed and/or transformed. + $info->download_link = $this->download_url; + $info->author = $this->getFormattedAuthor(); + $info->sections = array_merge(array('description' => ''), $this->sections); + + if ( !empty($this->banners) ) { + //WP expects an array with two keys: "high" and "low". Both are optional. + //Docs: https://wordpress.org/plugins/about/faq/#banners + $info->banners = is_object($this->banners) ? get_object_vars($this->banners) : $this->banners; + $info->banners = array_intersect_key($info->banners, array('high' => true, 'low' => true)); + } + + return $info; + } + + protected function getFormattedAuthor() { + if ( !empty($this->author_homepage) ){ + /** @noinspection HtmlUnknownTarget */ + return sprintf('%s', $this->author_homepage, $this->author); + } + return $this->author; + } + } + +endif; diff --git a/lib/plugin-update-checker/Puc/v5p3/Plugin/Ui.php b/lib/plugin-update-checker/Puc/v5p3/Plugin/Ui.php new file mode 100644 index 0000000..d300a5e --- /dev/null +++ b/lib/plugin-update-checker/Puc/v5p3/Plugin/Ui.php @@ -0,0 +1,294 @@ +updateChecker = $updateChecker; + $this->manualCheckErrorTransient = $this->updateChecker->getUniqueName('manual_check_errors'); + + add_action('admin_init', array($this, 'onAdminInit')); + } + + public function onAdminInit() { + if ( $this->updateChecker->userCanInstallUpdates() ) { + $this->handleManualCheck(); + + add_filter('plugin_row_meta', array($this, 'addViewDetailsLink'), 10, 3); + add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2); + add_action('all_admin_notices', array($this, 'displayManualCheckResult')); + } + } + + /** + * Add a "View Details" link to the plugin row in the "Plugins" page. By default, + * the new link will appear before the "Visit plugin site" link (if present). + * + * You can change the link text by using the "puc_view_details_link-$slug" filter. + * Returning an empty string from the filter will disable the link. + * + * You can change the position of the link using the + * "puc_view_details_link_position-$slug" filter. + * Returning 'before' or 'after' will place the link immediately before/after + * the "Visit plugin site" link. + * Returning 'append' places the link after any existing links at the time of the hook. + * Returning 'replace' replaces the "Visit plugin site" link. + * Returning anything else disables the link when there is a "Visit plugin site" link. + * + * If there is no "Visit plugin site" link 'append' is always used! + * + * @param array $pluginMeta Array of meta links. + * @param string $pluginFile + * @param array $pluginData Array of plugin header data. + * @return array + */ + public function addViewDetailsLink($pluginMeta, $pluginFile, $pluginData = array()) { + if ( $this->isMyPluginFile($pluginFile) && !isset($pluginData['slug']) ) { + $linkText = apply_filters($this->updateChecker->getUniqueName('view_details_link'), __('View details')); + if ( !empty($linkText) ) { + $viewDetailsLinkPosition = 'append'; + + //Find the "Visit plugin site" link (if present). + $visitPluginSiteLinkIndex = count($pluginMeta) - 1; + if ( $pluginData['PluginURI'] ) { + $escapedPluginUri = esc_url($pluginData['PluginURI']); + foreach ($pluginMeta as $linkIndex => $existingLink) { + if ( strpos($existingLink, $escapedPluginUri) !== false ) { + $visitPluginSiteLinkIndex = $linkIndex; + $viewDetailsLinkPosition = apply_filters( + $this->updateChecker->getUniqueName('view_details_link_position'), + 'before' + ); + break; + } + } + } + + $viewDetailsLink = sprintf('%s', + esc_url(network_admin_url('plugin-install.php?tab=plugin-information&plugin=' . urlencode($this->updateChecker->slug) . + '&TB_iframe=true&width=600&height=550')), + esc_attr(sprintf(__('More information about %s'), $pluginData['Name'])), + esc_attr($pluginData['Name']), + $linkText + ); + switch ($viewDetailsLinkPosition) { + case 'before': + array_splice($pluginMeta, $visitPluginSiteLinkIndex, 0, $viewDetailsLink); + break; + case 'after': + array_splice($pluginMeta, $visitPluginSiteLinkIndex + 1, 0, $viewDetailsLink); + break; + case 'replace': + $pluginMeta[$visitPluginSiteLinkIndex] = $viewDetailsLink; + break; + case 'append': + default: + $pluginMeta[] = $viewDetailsLink; + break; + } + } + } + return $pluginMeta; + } + + /** + * Add a "Check for updates" link to the plugin row in the "Plugins" page. By default, + * the new link will appear after the "Visit plugin site" link if present, otherwise + * after the "View plugin details" link. + * + * You can change the link text by using the "puc_manual_check_link-$slug" filter. + * Returning an empty string from the filter will disable the link. + * + * @param array $pluginMeta Array of meta links. + * @param string $pluginFile + * @return array + */ + public function addCheckForUpdatesLink($pluginMeta, $pluginFile) { + if ( $this->isMyPluginFile($pluginFile) ) { + $linkUrl = wp_nonce_url( + add_query_arg( + array( + 'puc_check_for_updates' => 1, + 'puc_slug' => $this->updateChecker->slug, + ), + self_admin_url('plugins.php') + ), + 'puc_check_for_updates' + ); + + $linkText = apply_filters( + $this->updateChecker->getUniqueName('manual_check_link'), + __('Check for updates', 'plugin-update-checker') + ); + if ( !empty($linkText) ) { + /** @noinspection HtmlUnknownTarget */ + $pluginMeta[] = sprintf('%s', esc_attr($linkUrl), $linkText); + } + } + return $pluginMeta; + } + + protected function isMyPluginFile($pluginFile) { + return ($pluginFile == $this->updateChecker->pluginFile) + || (!empty($this->updateChecker->muPluginFile) && ($pluginFile == $this->updateChecker->muPluginFile)); + } + + /** + * Check for updates when the user clicks the "Check for updates" link. + * + * @see self::addCheckForUpdatesLink() + * + * @return void + */ + public function handleManualCheck() { + $shouldCheck = + isset($_GET['puc_check_for_updates'], $_GET['puc_slug']) + && $_GET['puc_slug'] == $this->updateChecker->slug + && check_admin_referer('puc_check_for_updates'); + + if ( $shouldCheck ) { + $update = $this->updateChecker->checkForUpdates(); + $status = ($update === null) ? 'no_update' : 'update_available'; + $lastRequestApiErrors = $this->updateChecker->getLastRequestApiErrors(); + + if ( ($update === null) && !empty($lastRequestApiErrors) ) { + //Some errors are not critical. For example, if PUC tries to retrieve the readme.txt + //file from GitHub and gets a 404, that's an API error, but it doesn't prevent updates + //from working. Maybe the plugin simply doesn't have a readme. + //Let's only show important errors. + $foundCriticalErrors = false; + $questionableErrorCodes = array( + 'puc-github-http-error', + 'puc-gitlab-http-error', + 'puc-bitbucket-http-error', + ); + + foreach ($lastRequestApiErrors as $item) { + $wpError = $item['error']; + /** @var \WP_Error $wpError */ + if ( !in_array($wpError->get_error_code(), $questionableErrorCodes) ) { + $foundCriticalErrors = true; + break; + } + } + + if ( $foundCriticalErrors ) { + $status = 'error'; + set_site_transient($this->manualCheckErrorTransient, $lastRequestApiErrors, 60); + } + } + + wp_redirect(add_query_arg( + array( + 'puc_update_check_result' => $status, + 'puc_slug' => $this->updateChecker->slug, + ), + self_admin_url('plugins.php') + )); + exit; + } + } + + /** + * Display the results of a manual update check. + * + * @see self::handleManualCheck() + * + * You can change the result message by using the "puc_manual_check_message-$slug" filter. + */ + public function displayManualCheckResult() { + //phpcs:disable WordPress.Security.NonceVerification.Recommended -- Just displaying a message. + if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->updateChecker->slug) ) { + $status = sanitize_key($_GET['puc_update_check_result']); + $title = $this->updateChecker->getInstalledPackage()->getPluginTitle(); + $noticeClass = 'updated notice-success'; + $details = ''; + + if ( $status == 'no_update' ) { + $message = sprintf(_x('The %s plugin is up to date.', 'the plugin title', 'plugin-update-checker'), $title); + } else if ( $status == 'update_available' ) { + $message = sprintf(_x('A new version of the %s plugin is available.', 'the plugin title', 'plugin-update-checker'), $title); + } else if ( $status === 'error' ) { + $message = sprintf(_x('Could not determine if updates are available for %s.', 'the plugin title', 'plugin-update-checker'), $title); + $noticeClass = 'error notice-error'; + + $details = $this->formatManualCheckErrors(get_site_transient($this->manualCheckErrorTransient)); + delete_site_transient($this->manualCheckErrorTransient); + } else { + $message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), $status); + $noticeClass = 'error notice-error'; + } + + $message = esc_html($message); + + //Plugins can replace the message with their own, including adding HTML. + $message = apply_filters( + $this->updateChecker->getUniqueName('manual_check_message'), + $message, + $status + ); + + printf( + '
%s
%s%2$s
%1$s %2$s
This section will be displayed by default when the user clicks 'View version x.y.z details'.
", + "custom_section": "This is a custom section labeled 'Custom Section'." + }, + + "icons" : { + "1x" : "http://w-shadow.com/files/external-update-example/assets/icon-128x128.png", + "2x" : "http://w-shadow.com/files/external-update-example/assets/icon-256x256.png" + }, + + "banners": { + "low": "http://w-shadow.com/files/external-update-example/assets/banner-772x250.png", + "high": "http://w-shadow.com/files/external-update-example/assets/banner-1544x500.png" + }, + + "translations": [ + { + "language": "fr_FR", + "version": "4.0", + "updated": "2016-04-22 23:22:42", + "package": "http://example.com/updates/translations/french-language-pack.zip" + }, + { + "language": "de_DE", + "version": "5.0", + "updated": "2016-04-22 23:22:42", + "package": "http://example.com/updates/translations/german-language-pack.zip" + } + ], + + "rating": 90, + "num_ratings": 123, + + "downloaded": 1234, + "active_installs": 12345 +} \ No newline at end of file diff --git a/lib/plugin-update-checker/examples/theme.json b/lib/plugin-update-checker/examples/theme.json new file mode 100644 index 0000000..0e08072 --- /dev/null +++ b/lib/plugin-update-checker/examples/theme.json @@ -0,0 +1,5 @@ +{ + "version": "2.0", + "details_url": "http://example.com/version-2.0-details.html", + "download_url": "http://example.com/example-theme-2.0.zip" +} \ No newline at end of file diff --git a/lib/plugin-update-checker/js/debug-bar.js b/lib/plugin-update-checker/js/debug-bar.js new file mode 100644 index 0000000..80f53f1 --- /dev/null +++ b/lib/plugin-update-checker/js/debug-bar.js @@ -0,0 +1,54 @@ +jQuery(function($) { + + function runAjaxAction(button, action) { + button = $(button); + var panel = button.closest('.puc-debug-bar-panel-v5'); + var responseBox = button.closest('td').find('.puc-ajax-response'); + + responseBox.text('Processing...').show(); + $.post( + ajaxurl, + { + action : action, + uid : panel.data('uid'), + _wpnonce: panel.data('nonce') + }, + function(data) { + //The response contains HTML that should already be escaped in server-side code. + //phpcs:ignore WordPressVIPMinimum.JS.HTMLExecutingFunctions.html + responseBox.html(data); + }, + 'html' + ); + } + + $('.puc-debug-bar-panel-v5 input[name="puc-check-now-button"]').on('click', function() { + runAjaxAction(this, 'puc_v5_debug_check_now'); + return false; + }); + + $('.puc-debug-bar-panel-v5 input[name="puc-request-info-button"]').on('click', function() { + runAjaxAction(this, 'puc_v5_debug_request_info'); + return false; + }); + + + // Debug Bar uses the panel class name as part of its link and container IDs. This means we can + // end up with multiple identical IDs if more than one plugin uses the update checker library. + // Fix it by replacing the class name with the plugin slug. + var panels = $('#debug-menu-targets').find('.puc-debug-bar-panel-v5'); + panels.each(function() { + var panel = $(this); + var uid = panel.data('uid'); + var target = panel.closest('.debug-menu-target'); + + //Change the panel wrapper ID. + target.attr('id', 'debug-menu-target-puc-' + uid); + + //Change the menu link ID as well and point it at the new target ID. + $('#debug-bar-menu').find('.puc-debug-menu-link-' + uid) + .closest('.debug-menu-link') + .attr('id', 'debug-menu-link-puc-' + uid) + .attr('href', '#' + target.attr('id')); + }); +}); \ No newline at end of file diff --git a/lib/plugin-update-checker/languages/plugin-update-checker-ca.mo b/lib/plugin-update-checker/languages/plugin-update-checker-ca.mo new file mode 100644 index 0000000000000000000000000000000000000000..59645faba22e5f3b1358ef076a01d5a7fa3aa534 GIT binary patch literal 1186 zcmZ`&%We}f6g5yD3JU~l7MF@jAn=eGUhPB?f>02mg$Po_u5xE?6T{RV*`7X-koW+0 zhy^>uFR+0P5@LzOC$M16M{p*S5}sDhoY=mPbM5P|$7Ws%jDx^&U;rEjo&)uG2OI_7 z0|a~qW`XZO7dWy?Y+5Q5_Zx+>QQSw1WxG&*
zrLezHK%nsfpZ9cg4~#38`f*cCNTU+7K=@3Cu}vkLqX%#+mrMKJtT(ZFrW-cj$W7rV
zv{;=R70#L<*`3?;IGcIb$xR=co*aM{%|dP>3?vVbeK7*2 ')
+ {
+ $markup = $trimmedMarkup;
+ $markup = substr($markup, 3);
+
+ $position = strpos($markup, " %2$shGx51C-cI*DmKm7sfZ|UStu|vFUgWo2?dJ#
zgc^qh{C1>$@u>QfL4lPtBz%E^BAdmA{e{1!XIS;LY0s>hQtXC3j-Uzxi-(oXvt)N~
e(TmOhac^_B%Vwy(g_1+_)VeFXzd5TNhy4cu&FZoM
literal 0
HcmV?d00001
diff --git a/lib/plugin-update-checker/languages/plugin-update-checker-uk_UA.po b/lib/plugin-update-checker/languages/plugin-update-checker-uk_UA.po
new file mode 100644
index 0000000..b84b16e
--- /dev/null
+++ b/lib/plugin-update-checker/languages/plugin-update-checker-uk_UA.po
@@ -0,0 +1,48 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: plugin-update-checker\n"
+"POT-Creation-Date: 2020-08-08 14:36+0300\n"
+"PO-Revision-Date: 2021-12-20 17:55+0200\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2\n"
+"X-Poedit-Basepath: ..\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : 2);\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
+"Last-Translator: \n"
+"Language: uk_UA\n"
+"X-Poedit-SearchPath-0: .\n"
+
+#: Puc/v4p11/Plugin/Ui.php:128
+msgid "Check for updates"
+msgstr "Перевірити оновлення"
+
+#: Puc/v4p11/Plugin/Ui.php:213
+#, php-format
+msgctxt "the plugin title"
+msgid "The %s plugin is up to date."
+msgstr "Плагін %s оновлено."
+
+#: Puc/v4p11/Plugin/Ui.php:215
+#, php-format
+msgctxt "the plugin title"
+msgid "A new version of the %s plugin is available."
+msgstr "Нова версія %s доступна."
+
+#: Puc/v4p11/Plugin/Ui.php:217
+#, php-format
+msgctxt "the plugin title"
+msgid "Could not determine if updates are available for %s."
+msgstr "Не вдалося визначити, чи доступні оновлення для %s."
+
+#: Puc/v4p11/Plugin/Ui.php:223
+#, php-format
+msgid "Unknown update checker status \"%s\""
+msgstr "Невідомий статус перевірки оновлень \"%s\""
+
+#: Puc/v4p11/Vcs/PluginUpdateChecker.php:98
+msgid "There is no changelog available."
+msgstr "Немає доступного журналу змін."
diff --git a/lib/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo b/lib/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo
new file mode 100644
index 0000000000000000000000000000000000000000..86d114472907c99814f248ed3e063f8bd0ce5819
GIT binary patch
literal 1174
zcmZ`&U2hXd6dfQGu!_|3hCt{lC@;Wl*DeoXO%Py0#Ye!DfOx{1*b{qUGh^+{Ccsnp
zsMHjpBDD|{EI|Z8RfXaT5KkZ;`Um(06$QNJjR*b!XT2c?MT~TGcIMuD?zywO|E=q}
z!LZf<8-WV27I+9G>lN@l@ISB~_y7>Fb{S*6U
\n", $text);
+ }
+ else
+ {
+ $text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "
\n", $text);
+ $text = str_replace(" \n", "\n", $text);
+ }
+
+ return $text;
+ }
+
+ #
+ # Handlers
+ #
+
+ protected function element(array $Element)
+ {
+ $markup = '<'.$Element['name'];
+
+ if (isset($Element['attributes']))
+ {
+ foreach ($Element['attributes'] as $name => $value)
+ {
+ if ($value === null)
+ {
+ continue;
+ }
+
+ $markup .= ' '.$name.'="'.$value.'"';
+ }
+ }
+
+ if (isset($Element['text']))
+ {
+ $markup .= '>';
+
+ if (isset($Element['handler']))
+ {
+ $markup .= $this->{$Element['handler']}($Element['text']);
+ }
+ else
+ {
+ $markup .= $Element['text'];
+ }
+
+ $markup .= ''.$Element['name'].'>';
+ }
+ else
+ {
+ $markup .= ' />';
+ }
+
+ return $markup;
+ }
+
+ protected function elements(array $Elements)
+ {
+ $markup = '';
+
+ foreach ($Elements as $Element)
+ {
+ $markup .= "\n" . $this->element($Element);
+ }
+
+ $markup .= "\n";
+
+ return $markup;
+ }
+
+ # ~
+
+ protected function li($lines)
+ {
+ $markup = $this->lines($lines);
+
+ $trimmedMarkup = trim($markup);
+
+ if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '$2
', $_sections[$i+1]);
+ $content = $this->filter_text( $content, true );
+ } else {
+ $content = '';
+ }
+ $sections[str_replace(' ', '_', strtolower($title))] = array('title' => $title, 'content' => $content);
+ }
+
+
+ // Special sections
+ // This is where we nab our special sections, so we can enforce their order and treat them differently, if needed
+ // upgrade_notice is not a section, but parse it like it is for now
+ $final_sections = array();
+ foreach ( array('description', 'installation', 'frequently_asked_questions', 'screenshots', 'changelog', 'change_log', 'upgrade_notice') as $special_section ) {
+ if ( isset($sections[$special_section]) ) {
+ $final_sections[$special_section] = $sections[$special_section]['content'];
+ unset($sections[$special_section]);
+ }
+ }
+ if ( isset($final_sections['change_log']) && empty($final_sections['changelog']) )
+ $final_sections['changelog'] = $final_sections['change_log'];
+
+
+ $final_screenshots = array();
+ if ( isset($final_sections['screenshots']) ) {
+ preg_match_all('|(.*?)
#', $final_sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
+ if ( count($split) >= 2 ) {
+ for ( $i = 0; $i < count( $split ); $i += 2 ) {
+ $upgrade_notice[$this->sanitize_text( $split[$i] )] = substr( $this->sanitize_text( $split[$i + 1] ), 0, 300 );
+ }
+ }
+ unset( $final_sections['upgrade_notice'] );
+ }
+
+ // No description?
+ // No problem... we'll just fall back to the old style of description
+ // We'll even let you use markup this time!
+ $excerpt = false;
+ if ( !isset($final_sections['description']) ) {
+ $final_sections = array_merge(array('description' => $this->filter_text( $_short_description[2], true )), $final_sections);
+ $excerpt = true;
+ }
+
+
+ // dump the non-special sections into $remaining_content
+ // their order will be determined by their original order in the readme.txt
+ $remaining_content = '';
+ foreach ( $sections as $s_name => $s_data ) {
+ $remaining_content .= "\n{$s_data['title']}
\n{$s_data['content']}";
+ }
+ $remaining_content = trim($remaining_content);
+
+
+ // All done!
+ // $r['tags'] and $r['contributors'] are simple arrays
+ // $r['sections'] is an array with named elements
+ $r = array(
+ 'name' => $name,
+ 'tags' => $tags,
+ 'requires_at_least' => $requires_at_least,
+ 'tested_up_to' => $tested_up_to,
+ 'requires_php' => $requires_php,
+ 'stable_tag' => $stable_tag,
+ 'contributors' => $contributors,
+ 'donate_link' => $donate_link,
+ 'short_description' => $short_description,
+ 'screenshots' => $final_screenshots,
+ 'is_excerpt' => $excerpt,
+ 'is_truncated' => $truncated,
+ 'sections' => $final_sections,
+ 'remaining_content' => $remaining_content,
+ 'upgrade_notice' => $upgrade_notice
+ );
+
+ return $r;
+ }
+
+ function chop_string( $string, $chop ) { // chop a "prefix" from a string: Agressive! uses strstr not 0 === strpos
+ if ( $_string = strstr($string, $chop) ) {
+ $_string = substr($_string, strlen($chop));
+ return trim($_string);
+ } else {
+ return trim($string);
+ }
+ }
+
+ function user_sanitize( $text, $strict = false ) { // whitelisted chars
+ if ( function_exists('user_sanitize') ) // bbPress native
+ return user_sanitize( $text, $strict );
+
+ if ( $strict ) {
+ $text = preg_replace('/[^a-z0-9-]/i', '', $text);
+ $text = preg_replace('|-+|', '-', $text);
+ } else {
+ $text = preg_replace('/[^a-z0-9_-]/i', '', $text);
+ }
+ return $text;
+ }
+
+ function sanitize_text( $text ) { // not fancy
+ $text = function_exists('wp_strip_all_tags')
+ ? wp_strip_all_tags($text)
+ //phpcs:ignore WordPressVIPMinimum.Functions.StripTags.StripTagsOneParameter -- Using wp_strip_all_tags() if available
+ : strip_tags($text);
+
+ $text = esc_html($text);
+ $text = trim($text);
+ return $text;
+ }
+
+ function filter_text( $text, $markdown = false ) { // fancy, Markdown
+ $text = trim($text);
+
+ $text = call_user_func( array( __CLASS__, 'code_trick' ), $text, $markdown ); // A better parser than Markdown's for: backticks -> CODE
+
+ if ( $markdown ) { // Parse markdown.
+ if ( !class_exists('Parsedown', false) ) {
+ /** @noinspection PhpIncludeInspection */
+ require_once(dirname(__FILE__) . '/Parsedown' . (version_compare(PHP_VERSION, '5.3.0', '>=') ? '' : 'Legacy') . '.php');
+ }
+ $instance = Parsedown::instance();
+ $text = $instance->text($text);
+ }
+
+ $allowed = array(
+ 'a' => array(
+ 'href' => array(),
+ 'title' => array(),
+ 'rel' => array()),
+ 'blockquote' => array('cite' => array()),
+ 'br' => array(),
+ 'p' => array(),
+ 'code' => array(),
+ 'pre' => array(),
+ 'em' => array(),
+ 'strong' => array(),
+ 'ul' => array(),
+ 'ol' => array(),
+ 'li' => array(),
+ 'h3' => array(),
+ 'h4' => array()
+ );
+
+ $text = balanceTags($text);
+
+ $text = wp_kses( $text, $allowed );
+ $text = trim($text);
+ return $text;
+ }
+
+ function code_trick( $text, $markdown ) { // Don't use bbPress native function - it's incompatible with Markdown
+ // If doing markdown, first take any user formatted code blocks and turn them into backticks so that
+ // markdown will preserve things like underscores in code blocks
+ if ( $markdown )
+ $text = preg_replace_callback("!(
|)!s", array( __CLASS__,'decodeit'), $text);
+
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+ if ( !$markdown ) {
+ // This gets the "inline" code blocks, but can't be used with Markdown.
+ $text = preg_replace_callback("|(`)(.*?)`|", array( __CLASS__, 'encodeit'), $text);
+ // This gets the "block level" code blocks and converts them to PRE CODE
+ $text = preg_replace_callback("!(^|\n)`(.*?)`!s", array( __CLASS__, 'encodeit'), $text);
+ } else {
+ // Markdown can do inline code, we convert bbPress style block level code to Markdown style
+ $text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent'), $text);
+ }
+ return $text;
+ }
+
+ function indent( $matches ) {
+ $text = $matches[3];
+ $text = preg_replace('|^|m', $matches[2] . ' ', $text);
+ return $matches[1] . $text;
+ }
+
+ function encodeit( $matches ) {
+ if ( function_exists('encodeit') ) // bbPress native
+ return encodeit( $matches );
+
+ $text = trim($matches[2]);
+ $text = htmlspecialchars($text, ENT_QUOTES);
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
+ $text = preg_replace("|\n\n\n+|", "\n\n", $text);
+ $text = str_replace('<', '<', $text);
+ $text = str_replace('>', '>', $text);
+ $text = "|
)(.*?)(
$text
";
+ if ( "`" != $matches[1] )
+ $text = "$text
";
+ return $text;
+ }
+
+ function decodeit( $matches ) {
+ if ( function_exists('decodeit') ) // bbPress native
+ return decodeit( $matches );
+
+ $text = $matches[2];
+ $trans_table = array_flip(get_html_translation_table(HTML_ENTITIES));
+ $text = strtr($text, $trans_table);
+ $text = str_replace('
', '', $text);
+ $text = str_replace('&', '&', $text);
+ $text = str_replace(''', "'", $text);
+ if ( '' == $matches[1] )
+ $text = "\n$text\n";
+ return "`$text`";
+ }
+
+} // end class
+
+endif;
diff --git a/lib/updatepulse-updater/LICENSE b/lib/updatepulse-updater/LICENSE
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/lib/updatepulse-updater/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
' . $error_data->message;
+
+ break;
+ case 'unexpected_error':
+ default:
+ $return['message'] = __( 'An unexpected error has occured. Please try again. If the problem persists, please contact support.', 'updatepulse-updater' );
+
+ break;
+ }
+
+ return (object) $return;
+ }
+
+ protected function get_license_form() {
+ $license = $this->get_option( 'licenseKey' );
+ $next_deactivate = $this->get_option( 'licenseNextDeactivate' );
+ $may_deactivate = false;
+ $deactivate_text = '';
+
+ if ( time() < $next_deactivate ) {
+ $deactivate_text = __( 'Deactivation is possible after:', 'updatepulse-updater' );
+ } else {
+ $may_deactivate = true;
+ $deactivate_text = __( 'Deactivate', 'updatepulse-updater' );
+ }
+
+ ob_start();
+
+ $this->get_template(
+ 'license-form.php',
+ array(
+ 'license' => $license,
+ 'package_id' => $this->package_id,
+ 'package_slug' => $this->package_slug,
+ 'show_license' => ( ! empty( $license ) ),
+ 'may_deactivate' => $may_deactivate,
+ 'deactivate_text' => $deactivate_text,
+ 'next_deactivate' => intval( $next_deactivate ),
+ 'date_format' => get_option( 'date_format' ) . ' H:i:s',
+ )
+ );
+
+ return ob_get_clean();
+ }
+
+ protected function set_type() {
+ $theme_directory = self::get_theme_directory_name( $this->package_path );
+
+ if ( self::is_plugin_file( $this->package_path ) ) {
+ $this->type = 'Plugin';
+ } elseif ( null !== $theme_directory ) {
+ $this->type = 'Theme';
+ } else {
+ throw new RuntimeException(
+ sprintf(
+ 'The package updater cannot determine if "%s" is a plugin or a theme. ',
+ esc_html( htmlentities( $this->package_path ) )
+ )
+ );
+ }
+ }
+ }
+}
diff --git a/lib/updatepulse-updater/js/main.js b/lib/updatepulse-updater/js/main.js
new file mode 100644
index 0000000..6e54c52
--- /dev/null
+++ b/lib/updatepulse-updater/js/main.js
@@ -0,0 +1,310 @@
+/* global UPupdater */
+
+let P = function(global) {
+
+ let P = {lang: 'en-GB'};
+
+ // Format tokens and functions
+ let tokens = {
+ // DAY
+ // day of month, pad to 2 digits
+ d: d => pad(d.getDate()),
+ // Day name, first 3 letters
+ D: d => getDayName(d).substr(0,3),
+ // day of month, no padding
+ j: d => d.getDate(),
+ // Full day name
+ l: d => getDayName(d),
+ // ISO weekday number (1 = Monday ... 7 = Sunday)
+ N: d => d.getDay() || 7,
+ // Ordinal suffix for day of the month
+ S: d => getOrdinal(d.getDate()),
+ // Weekday number (0 = Sunday, 6 = Saturday)
+ w: d => d.getDay(),
+ // Day of year, 1 Jan is 0
+ z: d => {
+ let Y = d.getFullYear(),
+ M = d.getMonth(),
+ D = d.getDate();
+ return Math.floor((Date.UTC(Y, M, D) - Date.UTC(Y, 0, 1)) / 8.64e7);
+ },
+ // ISO week number of year
+ W: d => getWeekNumber(d)[1],
+ // Full month name
+ F: d => getMonthName(d),
+ // Month number, padded
+ m: d => pad(d.getMonth() + 1),
+ // 3 letter month name
+ M: d => getMonthName(d).substr(0, 3),
+ // Month number, no pading
+ n: d => d.getMonth() + 1,
+ // Days in month
+ t: d => new Date(d.getFullYear(), d.getMonth() + 1, 0).getDate(),
+ // Return 1 if d is a leap year, otherwise 0
+ L: d => new Date(d.getFullYear(), 1, 29).getDate() == 29? 1 : 0,
+ // ISO week numbering year
+ o: d => getWeekNumber(d)[0],
+ // 4 digit year
+ Y: d => {
+ let year = d.getFullYear();
+ if (year < 0) {
+ year = '-' + ('000' + Math.abs(year)).slice(-4);
+ }
+ return year;
+ },
+ // 2 digit year
+ y: d => {
+ let year = d.getFullYear();
+ if (year >= 0) {
+ return ('0' + year).slice(-2);
+ } else {
+ year = Math.abs(year);
+ return - + ('0' + year).slice(-2);
+ }
+ },
+ // Lowercase am or pm
+ a: d => d.getHours() < 12? 'am' : 'pm',
+ // Uppercase AM or PM
+ A: d => d.getHours() < 12? 'AM' : 'PM',
+ // Swatch internet time
+ B: d => (((+d + 3.6e6) % 8.64e7) / 8.64e4).toFixed(0),
+ // 12 hour hour no padding
+ g: d => (d.getHours() % 12) || 12,
+ // 24 hour hour no padding
+ G: d => d.getHours(),
+ // 12 hour hour padded
+ h: d => pad((d.getHours() % 12) || 12),
+ // 24 hour hour padded
+ H: d => pad(d.getHours()),
+ // Minutes padded
+ i: d => pad(d.getMinutes()),
+ // Seconds padded
+ s: d => pad(d.getSeconds()),
+ // Microseconds padded - always returns 000000
+ u: d => '000000',
+ // Milliseconds
+ v: d => padd(d.getMilliseconds()),
+ // Timezone identifier: UTC, GMT or IANA Tz database identifier - Not supported
+ e: d => void 0,
+ // If in daylight saving: 1 yes, 0 no
+ I: d => d.getTimezoneOffset() == getOffsets(d)[0]? 0 : 1,
+ // Difference to GMT in hours, e.g. +0200
+ O: d => minsToHours(-d.getTimezoneOffset(), false),
+ // Difference to GMT in hours with colon, e.g. +02:00
+ P: d => minsToHours(-d.getTimezoneOffset(), true),
+ // Timezone abbreviation, e.g. AEST. Dodgy but may work…
+ T: d => d.toLocaleString('en',{year:'numeric',timeZoneName:'long'}).replace(/[^A-Z]/g, ''),
+ // Timezone offset in seconds, +ve east
+ Z: d => d.getTimezoneOffset() * -60,
+ // ISO 8601 format - local
+ c: d => P.format(d, 'Y-m-d\\TH:i:sP'),
+ // RFC 2822 formatted date, local timezone
+ r: d => P.format(d, 'D, d M Y H:i:s O'),
+ // Seconds since UNIX epoch (same as ECMAScript epoch)
+ U: d => d.getTime() / 1000 | 0
+ };
+
+ // Helpers
+ // Return day name for date
+ let getDayName = d => d.toLocaleString(P.lang, {weekday:'long'});
+ // Return month name for date
+ let getMonthName = d => d.toLocaleString(P.lang, {month:'long'});
+ // Return [std offest, DST offset]. If no DST, same offset for both
+ let getOffsets = d => {
+ let y = d.getFullYear();
+ let offsets = [0, 2, 5, 9].map(m => new Date(y, m).getTimezoneOffset());
+ return [Math.max(...offsets), Math.min(...offsets)];
+ }
+ // Return ordinal for positive integer
+ let getOrdinal = n => {
+ n = n % 100;
+ let ords = ['th','st','nd','rd'];
+ return (n < 10 || n > 13) ? ords[n%10] || 'th' : 'th';
+ };
+ // Return ISO week number and year
+ let getWeekNumber = d => {
+ let e = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
+ e.setUTCDate(e.getUTCDate() + 4 - (e.getUTCDay()||7));
+ var yearStart = new Date(Date.UTC(e.getUTCFullYear(),0,1));
+ var weekNo = Math.ceil(( ( (e - yearStart) / 86400000) + 1)/7);
+ return [e.getUTCFullYear(), weekNo];
+ };
+ // Return true if o is a Date, otherwise false
+ let isDate = o => Object.prototype.toString.call(o) == '[object Date]';
+ // Convert numeric minutes to +/-HHMM or +/-HH:MM
+ let minsToHours = (mins, colon) => {
+ let sign = mins < 0? '-' : '+';
+ mins = Math.abs(mins);
+ let H = pad(mins / 60 | 0);
+ let M = pad(mins % 60);
+ return sign + H + (colon? ':' : '') + M;
+ };
+ // Pad single digits with a leading zero
+ let pad = n => (n < 10? '0' : '') + n;
+ // Pad single digits with two leading zeros, double digits with one leading zero
+ let padd = n => (n < 10? '00' : n < 100? '0' : '') + n;
+ // To be completed...
+ let parse = s => 'not complete';
+
+ P.parse = parse;
+
+ // Format date using token string s
+ function format(date, s) {
+
+ // Minimal input validation
+ if (!isDate(date) || typeof s != 'string') {
+ return; // undefined
+ }
+
+ return s.split('').reduce((acc, c, i, chars) => {
+ // Add quoted characters to output
+ if (c == '\\') {
+ acc += chars.splice(i+1, 1);
+ // If character matches a token, use it
+ } else if (c in tokens) {
+ acc += tokens[c](date);
+ // Otherwise, just add character to output
+ } else {
+ acc += c;
+ }
+ return acc;
+ }, '');
+ }
+ P.format = format;
+
+ return P;
+}(this);
+
+jQuery(document).ready(function ($) {
+
+ $('.wrap-license').each(function () {
+ var licenseContainer = $(this);
+
+ if (licenseContainer.find('.deactivate-license').is(':disabled')) {
+ var nextDeactivate = licenseContainer.find('.deactivate-license').data('next_deactivate');
+ var dateFormat = licenseContainer.find('.deactivate-license').data('date_format');
+ var date = new Date(nextDeactivate * 1000);
+ console.log(date, nextDeactivate);
+
+ P.lang = { lang: document.documentElement.lang };
+
+ licenseContainer.find('.deactivate-license').val(licenseContainer.find('.deactivate-license').val() + ' ' + P.format(date, dateFormat));
+ }
+ });
+
+ $('body').on('click', '.wrap-license .activate-license', function (e) {
+ e.preventDefault();
+
+ var licenseContainer = $(this).closest('.wrap-license');
+ var packageID = licenseContainer.attr('id').replace('wrap_license_', '');
+ var data = {
+ 'nonce' : licenseContainer.data('nonce'),
+ 'license_key' : licenseContainer.find('.license').val(),
+ 'package_slug' : licenseContainer.data('package_slug'),
+ 'action' : 'upupdater_' + packageID + '_activate_license'
+ };
+
+ $.ajax({
+ url: UPupdater.ajax_url,
+ data: data,
+ type: 'POST',
+ success: function (response) {
+
+ if (response.success) {
+ licenseContainer.find('.current-license').html(licenseContainer.find('.license').val());
+ licenseContainer.find('.current-license-error').addClass('hidden');
+ licenseContainer.find('.license-message').removeClass('hidden');
+ licenseContainer.find('.deactivate-license-container').removeClass('hidden');
+ licenseContainer.find('.activate-license-container').addClass('hidden');
+ $('.license-error-' + licenseContainer.data('package_slug') + '.notice').addClass('hidden');
+
+ if (response.data.may_deactivate) {
+ licenseContainer.find('.deactivate-license').prop('disabled', false);
+ } else {
+ licenseContainer.find('.deactivate-license').prop('disabled', true);
+ }
+
+ licenseContainer.find('.deactivate-license').val(response.data.deactivate_text);
+
+ if (licenseContainer.find('.deactivate-license').is(':disabled')) {
+ var nextDeactivate = response.data.next_deactivate;
+ var dateFormat = response.data.date_format;
+ var date = new Date(nextDeactivate * 1000);
+ console.log(date, nextDeactivate);
+
+ P.lang = { lang: document.documentElement.lang };
+
+ licenseContainer.find('.deactivate-license').val(licenseContainer.find('.deactivate-license').val() + ' ' + P.format(date, dateFormat));
+ }
+
+ $('tr[data-plugin="' + packageID + '"] .column-auto-updates a').removeClass('hidden');
+ $('tr.plugin-update-tr:not(.updatepulse)[data-plugin="' + packageID + '"]').removeClass('hidden');
+ licenseContainer.closest('.theme-info').find('.theme-autoupdate').removeClass('hidden');
+ $('#update-theme').closest('.notice').removeClass('hidden');
+ } else {
+ var errorContainer = licenseContainer.find('.current-license-error');
+
+ errorContainer.html(response.data[0].message + '
');
+ errorContainer.removeClass('hidden');
+ licenseContainer.find('.license-message').removeClass('hidden');
+ }
+
+ if ('' === licenseContainer.find('.current-license').html()) {
+ licenseContainer.find('.current-license-label').addClass('hidden');
+ licenseContainer.find('.current-license').addClass('hidden');
+ } else {
+ licenseContainer.find('.current-license-label').removeClass('hidden');
+ licenseContainer.find('.current-license').removeClass('hidden');
+ }
+ }
+ });
+ });
+
+ $('body').on('click', '.wrap-license .deactivate-license', function (e) {
+ e.preventDefault();
+
+ var licenseContainer = $(this).closest('.wrap-license');
+ var packageID = licenseContainer.attr('id').replace('wrap_license_', '');
+ var data = {
+ 'nonce' : licenseContainer.data('nonce'),
+ 'license_key' : licenseContainer.find('.license').val(),
+ 'package_slug' : licenseContainer.data('package_slug'),
+ 'action' : 'upupdater_' + packageID + '_deactivate_license'
+ };
+
+ $.ajax({
+ url: UPupdater.ajax_url,
+ data: data,
+ type: 'POST',
+ success: function (response) {
+
+ if (response.success) {
+ licenseContainer.find('.current-license').html('');
+ licenseContainer.find('.current-license-error').addClass('hidden');
+ licenseContainer.find('.license-message').addClass('hidden');
+ licenseContainer.find('.deactivate-license-container').addClass('hidden');
+ licenseContainer.find('.activate-license-container').removeClass('hidden');
+
+ $('tr[data-plugin="' + packageID + '"] .column-auto-updates a').addClass('hidden');
+ $('tr.plugin-update-tr:not(.updatepulse)[data-plugin="' + packageID + '"]').addClass('hidden');
+ licenseContainer.closest('.theme-info').find('.theme-autoupdate').addClass('hidden');
+ $('#update-theme').closest('.notice').addClass('hidden');
+ } else {
+ var errorContainer = licenseContainer.find('.current-license-error');
+
+ errorContainer.html(response.data[0].message + '
');
+ errorContainer.removeClass('hidden');
+ licenseContainer.find('.license-message').removeClass('hidden');
+ }
+
+ if ('' === licenseContainer.find('.current-license').html()) {
+ licenseContainer.find('.current-license-label').addClass('hidden');
+ licenseContainer.find('.current-license').addClass('hidden');
+ } else {
+ licenseContainer.find('.current-license-label').removeClass('hidden');
+ licenseContainer.find('.current-license').removeClass('hidden');
+ }
+ }
+ });
+ });
+});
\ No newline at end of file
diff --git a/lib/updatepulse-updater/js/main.min.js b/lib/updatepulse-updater/js/main.min.js
new file mode 100644
index 0000000..c02821d
--- /dev/null
+++ b/lib/updatepulse-updater/js/main.min.js
@@ -0,0 +1 @@
+let P=function(e){function t(e,t){if(r(e)&&"string"==typeof t)return t.split("").reduce((t,a,i,d)=>(t+="\\"==a?d.splice(i+1,1):a in n?n[a](e):a,t),"")}let a={lang:"en-GB"},n={d:e=>u(e.getDate()),D:e=>i(e).substr(0,3),j:e=>e.getDate(),l:e=>i(e),N:e=>e.getDay()||7,S:e=>s(e.getDate()),w:e=>e.getDay(),z:e=>{let t=e.getFullYear(),a=e.getMonth(),n=e.getDate();return Math.floor((Date.UTC(t,a,n)-Date.UTC(t,0,1))/864e5)},W:e=>c(e)[1],F:e=>d(e),m:e=>u(e.getMonth()+1),M:e=>d(e).substr(0,3),n:e=>e.getMonth()+1,t:e=>new Date(e.getFullYear(),e.getMonth()+1,0).getDate(),L:e=>29==new Date(e.getFullYear(),1,29).getDate()?1:0,o:e=>c(e)[0],Y:e=>{let t=e.getFullYear();return t<0&&(t="-"+("000"+Math.abs(t)).slice(-4)),t},y:e=>{let t=e.getFullYear();return t>=0?("0"+t).slice(-2):(t=Math.abs(t),-+("0"+t).slice(-2))},a:e=>e.getHours()<12?"am":"pm",A:e=>e.getHours()<12?"AM":"PM",B:e=>((+e+36e5)%864e5/86400).toFixed(0),g:e=>e.getHours()%12||12,G:e=>e.getHours(),h:e=>u(e.getHours()%12||12),H:e=>u(e.getHours()),i:e=>u(e.getMinutes()),s:e=>u(e.getSeconds()),u:e=>"000000",v:e=>g(e.getMilliseconds()),e:e=>void 0,I:e=>e.getTimezoneOffset()==l(e)[0]?0:1,O:e=>o(-e.getTimezoneOffset(),!1),P:e=>o(-e.getTimezoneOffset(),!0),T:e=>e.toLocaleString("en",{year:"numeric",timeZoneName:"long"}).replace(/[^A-Z]/g,""),Z:e=>-60*e.getTimezoneOffset(),c:e=>a.format(e,"Y-m-d\\TH:i:sP"),r:e=>a.format(e,"D, d M Y H:i:s O"),U:e=>e.getTime()/1e3|0},i=e=>e.toLocaleString(a.lang,{weekday:"long"}),d=e=>e.toLocaleString(a.lang,{month:"long"}),l=e=>{let t=e.getFullYear(),a=[0,2,5,9].map(e=>new Date(t,e).getTimezoneOffset());return[Math.max(...a),Math.min(...a)]},s=e=>{e%=100;let t=["th","st","nd","rd"];return(e<10||e>13)&&t[e%10]||"th"},c=e=>{let t=new Date(Date.UTC(e.getFullYear(),e.getMonth(),e.getDate()));t.setUTCDate(t.getUTCDate()+4-(t.getUTCDay()||7));var a=new Date(Date.UTC(t.getUTCFullYear(),0,1)),n=Math.ceil(((t-a)/864e5+1)/7);return[t.getUTCFullYear(),n]},r=e=>"[object Date]"==Object.prototype.toString.call(e),o=(e,t)=>{let a=e<0?"-":"+";e=Math.abs(e);let n=u(e/60|0),i=u(e%60);return a+n+(t?":":"")+i},u=e=>(e<10?"0":"")+e,g=e=>(e<10?"00":e<100?"0":"")+e,f=e=>"not complete";return a.parse=f,a.format=t,a}();jQuery(document).ready(function(e){e(".wrap-license").each(function(){var t=e(this);if(t.find(".deactivate-license").is(":disabled")){var a=t.find(".deactivate-license").data("next_deactivate"),n=t.find(".deactivate-license").data("date_format"),i=new Date(1e3*a);console.log(i,a),P.lang={lang:document.documentElement.lang},t.find(".deactivate-license").val(t.find(".deactivate-license").val()+" "+P.format(i,n))}}),e("body").on("click",".wrap-license .activate-license",function(t){t.preventDefault();var a=e(this).closest(".wrap-license"),n=a.attr("id").replace("wrap_license_",""),i={nonce:a.data("nonce"),license_key:a.find(".license").val(),package_slug:a.data("package_slug"),action:"upupdater_"+n+"_activate_license"};e.ajax({url:UPupdater.ajax_url,data:i,type:"POST",success:function(t){if(t.success){if(a.find(".current-license").html(a.find(".license").val()),a.find(".current-license-error").addClass("hidden"),a.find(".license-message").removeClass("hidden"),a.find(".deactivate-license-container").removeClass("hidden"),a.find(".activate-license-container").addClass("hidden"),e(".license-error-"+a.data("package_slug")+".notice").addClass("hidden"),t.data.may_deactivate?a.find(".deactivate-license").prop("disabled",!1):a.find(".deactivate-license").prop("disabled",!0),a.find(".deactivate-license").val(t.data.deactivate_text),a.find(".deactivate-license").is(":disabled")){var i=t.data.next_deactivate,d=t.data.date_format,l=new Date(1e3*i);console.log(l,i),P.lang={lang:document.documentElement.lang},a.find(".deactivate-license").val(a.find(".deactivate-license").val()+" "+P.format(l,d))}e('tr[data-plugin="'+n+'"] .column-auto-updates a').removeClass("hidden"),e('tr.plugin-update-tr:not(.updatepulse)[data-plugin="'+n+'"]').removeClass("hidden"),a.closest(".theme-info").find(".theme-autoupdate").removeClass("hidden"),e("#update-theme").closest(".notice").removeClass("hidden")}else{var s=a.find(".current-license-error");s.html(t.data[0].message+"
"),s.removeClass("hidden"),a.find(".license-message").removeClass("hidden")}""===a.find(".current-license").html()?(a.find(".current-license-label").addClass("hidden"),a.find(".current-license").addClass("hidden")):(a.find(".current-license-label").removeClass("hidden"),a.find(".current-license").removeClass("hidden"))}})}),e("body").on("click",".wrap-license .deactivate-license",function(t){t.preventDefault();var a=e(this).closest(".wrap-license"),n=a.attr("id").replace("wrap_license_",""),i={nonce:a.data("nonce"),license_key:a.find(".license").val(),package_slug:a.data("package_slug"),action:"upupdater_"+n+"_deactivate_license"};e.ajax({url:UPupdater.ajax_url,data:i,type:"POST",success:function(t){if(t.success)a.find(".current-license").html(""),a.find(".current-license-error").addClass("hidden"),a.find(".license-message").addClass("hidden"),a.find(".deactivate-license-container").addClass("hidden"),a.find(".activate-license-container").removeClass("hidden"),e('tr[data-plugin="'+n+'"] .column-auto-updates a').addClass("hidden"),e('tr.plugin-update-tr:not(.updatepulse)[data-plugin="'+n+'"]').addClass("hidden"),a.closest(".theme-info").find(".theme-autoupdate").addClass("hidden"),e("#update-theme").closest(".notice").addClass("hidden");else{var i=a.find(".current-license-error");i.html(t.data[0].message+"
"),i.removeClass("hidden"),a.find(".license-message").removeClass("hidden")}""===a.find(".current-license").html()?(a.find(".current-license-label").addClass("hidden"),a.find(".current-license").addClass("hidden")):(a.find(".current-license-label").removeClass("hidden"),a.find(".current-license").removeClass("hidden"))}})})});
\ No newline at end of file
diff --git a/lib/updatepulse-updater/languages/updatepulse-updater.pot b/lib/updatepulse-updater/languages/updatepulse-updater.pot
new file mode 100644
index 0000000..2c14891
--- /dev/null
+++ b/lib/updatepulse-updater/languages/updatepulse-updater.pot
@@ -0,0 +1,123 @@
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: UpdatePulse Updater\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2025-01-06 06:26+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME
+
diff --git a/lib/updatepulse-updater/templates/theme-page-license.php b/lib/updatepulse-updater/templates/theme-page-license.php
new file mode 100644
index 0000000..621fee6
--- /dev/null
+++ b/lib/updatepulse-updater/templates/theme-page-license.php
@@ -0,0 +1,11 @@
+
+
+
+