From a10255d8278f50fd82842e978e59e83bd5864ddd Mon Sep 17 00:00:00 2001
From: Monika
', 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
' . esc_html(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/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/DebugBar/PluginPanel.php b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/DebugBar/PluginPanel.php new file mode 100644 index 000000000..04db3d937 --- /dev/null +++ b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/DebugBar/PluginPanel.php @@ -0,0 +1,47 @@ +row('Plugin file', esc_html($this->updateChecker->pluginFile)); + parent::displayConfigHeader(); + } + + protected function getMetadataButton() { + $buttonId = $this->updateChecker->getUniqueName('request-info-button'); + if ( function_exists('get_submit_button') ) { + $requestInfoButton = get_submit_button( + 'Request Info', + 'secondary', + 'puc-request-info-button', + false, + array('id' => $buttonId) + ); + } else { + $requestInfoButton = sprintf( + '', + esc_attr($buttonId), + esc_attr('Request Info') + ); + } + return $requestInfoButton; + } + + protected function getUpdateFields() { + return array_merge( + parent::getUpdateFields(), + array('homepage', 'upgrade_notice', 'tested',) + ); + } + } + +endif; diff --git a/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/DebugBar/ThemePanel.php b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/DebugBar/ThemePanel.php new file mode 100644 index 000000000..836961170 --- /dev/null +++ b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/DebugBar/ThemePanel.php @@ -0,0 +1,25 @@ +row('Theme directory', esc_html($this->updateChecker->directoryName)); + parent::displayConfigHeader(); + } + + protected function getUpdateFields() { + return array_merge(parent::getUpdateFields(), array('details_url')); + } + } + +endif; diff --git a/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/InstalledPackage.php b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/InstalledPackage.php new file mode 100644 index 000000000..a9c842181 --- /dev/null +++ b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/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/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/Metadata.php b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/Metadata.php new file mode 100644 index 000000000..6b1b36e82 --- /dev/null +++ b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/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/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/OAuthSignature.php b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/OAuthSignature.php new file mode 100644 index 000000000..f1ded9443 --- /dev/null +++ b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/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/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/Plugin/Package.php b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/Plugin/Package.php new file mode 100644 index 000000000..e99eb5395 --- /dev/null +++ b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/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/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/Plugin/PluginInfo.php b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/Plugin/PluginInfo.php new file mode 100644 index 000000000..e05a264d0 --- /dev/null +++ b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/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/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/Plugin/Ui.php b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/Plugin/Ui.php new file mode 100644 index 000000000..12bfd43a5 --- /dev/null +++ b/platforms/wordpress/gantry5/plugin-update-checker/Puc/v5p6/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_safe_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
?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 %c$XV7hGx51C-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/platforms/wordpress/gantry5/plugin-update-checker/languages/plugin-update-checker-uk_UA.po b/platforms/wordpress/gantry5/plugin-update-checker/languages/plugin-update-checker-uk_UA.po
new file mode 100644
index 000000000..b84b16ea5
--- /dev/null
+++ b/platforms/wordpress/gantry5/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/platforms/wordpress/gantry5/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo b/platforms/wordpress/gantry5/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo
new file mode 100644
index 0000000000000000000000000000000000000000..5236777ef4caecf0cc9162cdcb1f2789f00093ad
GIT binary patch
literal 1174
zcmZ`&-)j_C7`?So>(YSwCKmZf$xH1WCuxLb)`+@|#cFibD1CCd*}d7FWODB^_fDcd
z#cH8xDJ`@%Auedph=^n*7N0CW_z(0iklJ)-`{slHfM<4MHYIrBaQ1#b&Ue0<*_XS!
zZZe!5z$ZW%=ms7E$@v@D1-t_G0B-;Sx<6p72kd}92A>5Ff-ixGz;!SO{{vnDc5X@Q
zUVO;dZp8lr?*p%b3B08%&3_6`;x70z@EP!4@KrEm*hc_b+6N)F71$1JYe&*OFsgI%
zRauF&$D=D+?\n"
+"Language-Team: \n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 3.4.4\n"
+"X-Poedit-Basepath: ..\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
+"X-Poedit-SearchPath-0: .\n"
+
+#: Puc/v5p4/Plugin/Ui.php:56
+msgid "View details"
+msgstr "查看详情"
+
+#: Puc/v5p4/Plugin/Ui.php:79
+#, php-format
+msgid "More information about %s"
+msgstr "%s 的更多信息"
+
+#: Puc/v5p4/Plugin/Ui.php:130
+msgid "Check for updates"
+msgstr "检查更新"
+
+#: Puc/v5p4/Plugin/Ui.php:217
+#, php-format
+msgctxt "the plugin title"
+msgid "The %s plugin is up to date."
+msgstr "%s 目前是最新版本。"
+
+#: Puc/v5p4/Plugin/Ui.php:219
+#, php-format
+msgctxt "the plugin title"
+msgid "A new version of the %s plugin is available."
+msgstr "%s 当前有可用的更新。"
+
+#: Puc/v5p4/Plugin/Ui.php:221
+#, php-format
+msgctxt "the plugin title"
+msgid "Could not determine if updates are available for %s."
+msgstr "%s 无法确定是否有可用的更新。"
+
+#: Puc/v5p4/Plugin/Ui.php:227
+#, php-format
+msgid "Unknown update checker status \"%s\""
+msgstr "未知的更新检查状态:%s"
+
+#: Puc/v5p4/Vcs/PluginUpdateChecker.php:113
+msgid "There is no changelog available."
+msgstr "没有可用的更新日志。"
diff --git a/platforms/wordpress/gantry5/plugin-update-checker/languages/plugin-update-checker.pot b/platforms/wordpress/gantry5/plugin-update-checker/languages/plugin-update-checker.pot
new file mode 100644
index 000000000..ec3b5f328
--- /dev/null
+++ b/platforms/wordpress/gantry5/plugin-update-checker/languages/plugin-update-checker.pot
@@ -0,0 +1,49 @@
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: plugin-update-checker\n"
+"POT-Creation-Date: 2025-05-20 15:27+0300\n"
+"PO-Revision-Date: 2016-01-10 20:59+0100\n"
+"Last-Translator: \n"
+"Language-Team: \n"
+"Language: en_US\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.6\n"
+"X-Poedit-Basepath: ..\n"
+"X-Poedit-SourceCharset: UTF-8\n"
+"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
+"X-Poedit-SearchPath-0: .\n"
+
+#: Puc/v5p6/Plugin/Ui.php:130
+msgid "Check for updates"
+msgstr ""
+
+#: Puc/v5p6/Plugin/Ui.php:217
+#, php-format
+msgctxt "the plugin title"
+msgid "The %s plugin is up to date."
+msgstr ""
+
+#: Puc/v5p6/Plugin/Ui.php:219
+#, php-format
+msgctxt "the plugin title"
+msgid "A new version of the %s plugin is available."
+msgstr ""
+
+#: Puc/v5p6/Plugin/Ui.php:221
+#, php-format
+msgctxt "the plugin title"
+msgid "Could not determine if updates are available for %s."
+msgstr ""
+
+#: Puc/v5p6/Plugin/Ui.php:227
+#, php-format
+msgid "Unknown update checker status \"%s\""
+msgstr ""
+
+#: Puc/v5p6/Vcs/PluginUpdateChecker.php:113
+msgid "There is no changelog available."
+msgstr ""
diff --git a/platforms/wordpress/gantry5/plugin-update-checker/license.txt b/platforms/wordpress/gantry5/plugin-update-checker/license.txt
new file mode 100644
index 000000000..7fff53635
--- /dev/null
+++ b/platforms/wordpress/gantry5/plugin-update-checker/license.txt
@@ -0,0 +1,7 @@
+Copyright (c) 2023 Jānis Elsts
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/platforms/wordpress/gantry5/plugin-update-checker/load-v5p6.php b/platforms/wordpress/gantry5/plugin-update-checker/load-v5p6.php
new file mode 100644
index 000000000..e1de48e71
--- /dev/null
+++ b/platforms/wordpress/gantry5/plugin-update-checker/load-v5p6.php
@@ -0,0 +1,34 @@
+ Plugin\UpdateChecker::class,
+ 'Theme\\UpdateChecker' => Theme\UpdateChecker::class,
+
+ 'Vcs\\PluginUpdateChecker' => Vcs\PluginUpdateChecker::class,
+ 'Vcs\\ThemeUpdateChecker' => Vcs\ThemeUpdateChecker::class,
+
+ 'GitHubApi' => Vcs\GitHubApi::class,
+ 'BitBucketApi' => Vcs\BitBucketApi::class,
+ 'GitLabApi' => Vcs\GitLabApi::class,
+ )
+ as $pucGeneralClass => $pucVersionedClass
+) {
+ MajorFactory::addVersion($pucGeneralClass, $pucVersionedClass, '5.6');
+ //Also add it to the minor-version factory in case the major-version factory
+ //was already defined by another, older version of the update checker.
+ MinorFactory::addVersion($pucGeneralClass, $pucVersionedClass, '5.6');
+}
+
diff --git a/platforms/wordpress/gantry5/plugin-update-checker/plugin-update-checker.php b/platforms/wordpress/gantry5/plugin-update-checker/plugin-update-checker.php
new file mode 100644
index 000000000..234a55432
--- /dev/null
+++ b/platforms/wordpress/gantry5/plugin-update-checker/plugin-update-checker.php
@@ -0,0 +1,10 @@
+