diff --git a/includes/plugin-update-checker/Puc/v5/PucFactory.php b/includes/plugin-update-checker/Puc/v5/PucFactory.php index 0bc62ce..920248d 100644 --- a/includes/plugin-update-checker/Puc/v5/PucFactory.php +++ b/includes/plugin-update-checker/Puc/v5/PucFactory.php @@ -4,7 +4,7 @@ namespace YahnisElsts\PluginUpdateChecker\v5; if ( !class_exists(PucFactory::class, false) ): - class PucFactory extends \YahnisElsts\PluginUpdateChecker\v5p4\PucFactory { + class PucFactory extends \YahnisElsts\PluginUpdateChecker\v5p7\PucFactory { } endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/Autoloader.php b/includes/plugin-update-checker/Puc/v5p7/Autoloader.php similarity index 98% rename from includes/plugin-update-checker/Puc/v5p4/Autoloader.php rename to includes/plugin-update-checker/Puc/v5p7/Autoloader.php index 5a98133..9f036c1 100644 --- a/includes/plugin-update-checker/Puc/v5p4/Autoloader.php +++ b/includes/plugin-update-checker/Puc/v5p7/Autoloader.php @@ -1,6 +1,6 @@ Configuration'; echo ''; $this->displayConfigHeader(); - $this->row('Slug', htmlentities($this->updateChecker->slug)); - $this->row('DB option', htmlentities($this->updateChecker->optionName)); + $this->row('Slug', esc_html($this->updateChecker->slug)); + $this->row('DB option', esc_html($this->updateChecker->optionName)); $requestInfoButton = $this->getMetadataButton(); - $this->row('Metadata URL', htmlentities($this->updateChecker->metadataUrl) . ' ' . $requestInfoButton . $this->responseBox); + $this->row('Metadata URL', esc_html($this->updateChecker->metadataUrl) . ' ' . $requestInfoButton . $this->responseBox); $scheduler = $this->updateChecker->scheduler; if ( $scheduler->checkPeriod > 0 ) { @@ -86,14 +86,22 @@ if ( !class_exists(Panel::class, false) && class_exists('Debug_Bar_Panel', false echo '

Status

'; echo '
'; $state = $this->updateChecker->getUpdateState(); - $checkNowButton = ''; + $checkButtonId = $this->updateChecker->getUniqueName('check-now-button'); if ( function_exists('get_submit_button') ) { $checkNowButton = get_submit_button( 'Check Now', 'secondary', 'puc-check-now-button', false, - array('id' => $this->updateChecker->getUniqueName('check-now-button')) + array('id' => $checkButtonId) + ); + } else { + //get_submit_button() is not available in the frontend. Make a button directly. + //It won't look the same without admin styles, but it should still work. + $checkNowButton = sprintf( + '', + esc_attr($checkButtonId), + esc_attr('Check Now') ); } @@ -107,10 +115,10 @@ if ( !class_exists(Panel::class, false) && class_exists('Debug_Bar_Panel', false $this->row('Next automatic check', $this->formatTimeWithDelta($nextCheck)); if ( $state->getCheckedVersion() !== '' ) { - $this->row('Checked version', htmlentities($state->getCheckedVersion())); + $this->row('Checked version', esc_html($state->getCheckedVersion())); $this->row('Cached update', $state->getUpdate()); } - $this->row('Update checker class', htmlentities(get_class($this->updateChecker))); + $this->row('Update checker class', esc_html(get_class($this->updateChecker))); echo '
'; } @@ -124,7 +132,7 @@ if ( !class_exists(Panel::class, false) && class_exists('Debug_Bar_Panel', false if ( property_exists($update, $field) ) { $this->row( ucwords(str_replace('_', ' ', $field)), - isset($update->$field) ? htmlentities($update->$field) : null + isset($update->$field) ? esc_html($update->$field) : null ); } } @@ -162,7 +170,7 @@ if ( !class_exists(Panel::class, false) && class_exists('Debug_Bar_Panel', false if ( is_object($value) || is_array($value) ) { //This is specifically for debugging, so print_r() is fine. //phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_print_r - $value = '
' . htmlentities(print_r($value, true)) . '
'; + $value = '
' . esc_html(print_r($value, true)) . '
'; } else if ($value === null) { $value = 'null'; } diff --git a/includes/plugin-update-checker/Puc/v5p4/DebugBar/PluginExtension.php b/includes/plugin-update-checker/Puc/v5p7/DebugBar/PluginExtension.php similarity index 90% rename from includes/plugin-update-checker/Puc/v5p4/DebugBar/PluginExtension.php rename to includes/plugin-update-checker/Puc/v5p7/DebugBar/PluginExtension.php index b30f3ee..17770d0 100644 --- a/includes/plugin-update-checker/Puc/v5p4/DebugBar/PluginExtension.php +++ b/includes/plugin-update-checker/Puc/v5p7/DebugBar/PluginExtension.php @@ -1,8 +1,8 @@ row('Plugin file', htmlentities($this->updateChecker->pluginFile)); + $this->row('Plugin file', esc_html($this->updateChecker->pluginFile)); parent::displayConfigHeader(); } protected function getMetadataButton() { - $requestInfoButton = ''; + $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' => $this->updateChecker->getUniqueName('request-info-button')) + array('id' => $buttonId) + ); + } else { + $requestInfoButton = sprintf( + '', + esc_attr($buttonId), + esc_attr('Request Info') ); } return $requestInfoButton; diff --git a/includes/plugin-update-checker/Puc/v5p4/DebugBar/ThemePanel.php b/includes/plugin-update-checker/Puc/v5p7/DebugBar/ThemePanel.php similarity index 64% rename from includes/plugin-update-checker/Puc/v5p4/DebugBar/ThemePanel.php rename to includes/plugin-update-checker/Puc/v5p7/DebugBar/ThemePanel.php index 7b9d99a..dd43d25 100644 --- a/includes/plugin-update-checker/Puc/v5p4/DebugBar/ThemePanel.php +++ b/includes/plugin-update-checker/Puc/v5p7/DebugBar/ThemePanel.php @@ -1,8 +1,8 @@ row('Theme directory', htmlentities($this->updateChecker->directoryName)); + $this->row('Theme directory', esc_html($this->updateChecker->directoryName)); parent::displayConfigHeader(); } diff --git a/includes/plugin-update-checker/Puc/v5p4/InstalledPackage.php b/includes/plugin-update-checker/Puc/v5p7/InstalledPackage.php similarity index 98% rename from includes/plugin-update-checker/Puc/v5p4/InstalledPackage.php rename to includes/plugin-update-checker/Puc/v5p7/InstalledPackage.php index 341e7a3..894e316 100644 --- a/includes/plugin-update-checker/Puc/v5p4/InstalledPackage.php +++ b/includes/plugin-update-checker/Puc/v5p7/InstalledPackage.php @@ -1,5 +1,5 @@ $status, 'puc_slug' => $this->updateChecker->slug, diff --git a/includes/plugin-update-checker/Puc/v5p4/Plugin/Update.php b/includes/plugin-update-checker/Puc/v5p7/Plugin/Update.php similarity index 91% rename from includes/plugin-update-checker/Puc/v5p4/Plugin/Update.php rename to includes/plugin-update-checker/Puc/v5p7/Plugin/Update.php index 0fb3137..280bf68 100644 --- a/includes/plugin-update-checker/Puc/v5p4/Plugin/Update.php +++ b/includes/plugin-update-checker/Puc/v5p7/Plugin/Update.php @@ -1,7 +1,7 @@ tested = $this->tested; $update->requires_php = $this->requires_php; $update->plugin = $this->filename; + $update->autoupdate = $this->autoupdate; if ( !empty($this->upgrade_notice) ) { $update->upgrade_notice = $this->upgrade_notice; @@ -102,7 +104,7 @@ if ( !class_exists(Update::class, false) ): $update->icons = $icons; //It appears that the 'default' icon isn't used anywhere in WordPress 4.9, - //but lets set it just in case a future release needs it. + //but let's set it just in case a future release needs it. if ( !isset($update->icons['default']) ) { $update->icons['default'] = current($update->icons); } diff --git a/includes/plugin-update-checker/Puc/v5p4/Plugin/UpdateChecker.php b/includes/plugin-update-checker/Puc/v5p7/Plugin/UpdateChecker.php similarity index 97% rename from includes/plugin-update-checker/Puc/v5p4/Plugin/UpdateChecker.php rename to includes/plugin-update-checker/Puc/v5p7/Plugin/UpdateChecker.php index 6d1aae8..20cf839 100644 --- a/includes/plugin-update-checker/Puc/v5p4/Plugin/UpdateChecker.php +++ b/includes/plugin-update-checker/Puc/v5p7/Plugin/UpdateChecker.php @@ -1,10 +1,10 @@ lazyLoad(); $this->update = $update; return $this; diff --git a/includes/plugin-update-checker/Puc/v5p4/Theme/Package.php b/includes/plugin-update-checker/Puc/v5p7/Theme/Package.php similarity index 93% rename from includes/plugin-update-checker/Puc/v5p4/Theme/Package.php rename to includes/plugin-update-checker/Puc/v5p7/Theme/Package.php index 0b20702..90528bb 100644 --- a/includes/plugin-update-checker/Puc/v5p4/Theme/Package.php +++ b/includes/plugin-update-checker/Puc/v5p7/Theme/Package.php @@ -1,7 +1,7 @@ maybeInitDebugBar(); @@ -192,6 +196,7 @@ if ( !class_exists(UpdateChecker::class, false) ): remove_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10); remove_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10); + remove_filter('http_request_args', array($this, 'excludeEntityFromWordPressAPI')); remove_action('plugins_loaded', array($this, 'maybeInitDebugBar')); remove_action('init', array($this, 'loadTextDomain')); @@ -266,6 +271,76 @@ if ( !class_exists(UpdateChecker::class, false) ): */ abstract protected function createScheduler($checkPeriod); + /** + * Remove information about this plugin or theme from the requests that WordPress core sends + * to api.wordpress.org when checking for updates. + * + * @param array $args + * @param string $url + * @return array + */ + public function excludeEntityFromWordPressAPI($args, $url) { + //Is this an api.wordpress.org update check request? + $parsedUrl = wp_parse_url($url); + if ( !isset($parsedUrl['host']) || (strtolower($parsedUrl['host']) !== 'api.wordpress.org') ) { + return $args; + } + + $typePluralised = $this->componentType . 's'; + $expectedPathPrefix = '/' . $typePluralised . '/update-check/1.'; //e.g. "/plugins/update-check/1.1/" + if ( !isset($parsedUrl['path']) || !Utils::startsWith($parsedUrl['path'], $expectedPathPrefix) ) { + return $args; + } + + //Plugins and themes can disable this feature by using the filter below. + if ( !apply_filters( + $this->getUniqueName('remove_from_default_update_checks'), + true, $this, $args, $url + ) ) { + return $args; + } + + if ( empty($args['body'][$typePluralised]) ) { + return $args; + } + + $reportingItems = json_decode($args['body'][$typePluralised], true); + if ( $reportingItems === null ) { + return $args; + } + + //The list of installed items uses different key formats for plugins and themes. + //Luckily, we can reuse the getUpdateListKey() method here. + $updateListKey = $this->getUpdateListKey(); + if ( isset($reportingItems[$typePluralised][$updateListKey]) ) { + unset($reportingItems[$typePluralised][$updateListKey]); + } + + if ( !empty($reportingItems['active']) ) { + if ( is_array($reportingItems['active']) ) { + foreach ($reportingItems['active'] as $index => $relativePath) { + if ( $relativePath === $updateListKey ) { + unset($reportingItems['active'][$index]); + } + } + //Re-index the array. + $reportingItems['active'] = array_values($reportingItems['active']); + } else if ( $reportingItems['active'] === $updateListKey ) { + //For themes, the "active" field is a string that contains the theme's directory name. + //Pretend that the default theme is active so that we don't reveal the actual theme. + if ( defined('WP_DEFAULT_THEME') ) { + $reportingItems['active'] = WP_DEFAULT_THEME; + } + + //Unfortunately, it doesn't seem to be documented if we can safely remove the "active" + //key. So when we don't know the default theme, we'll just leave it as is. + } + } + + $args['body'][$typePluralised] = wp_json_encode($reportingItems); + return $args; + } + /** * Check for updates. The results are stored in the DB option specified in $optionName. * @@ -384,7 +459,7 @@ if ( !class_exists(UpdateChecker::class, false) ): * * @param Metadata|null $update */ - protected function fixSupportedWordpressVersion(Metadata $update = null) { + protected function fixSupportedWordpressVersion($update = null) { if ( !isset($update->tested) || !preg_match('/^\d++\.\d++$/', $update->tested) ) { return; } @@ -557,7 +632,15 @@ if ( !class_exists(UpdateChecker::class, false) ): if ( !empty($update) ) { //Let plugins filter the update info before it's passed on to WordPress. $update = apply_filters($this->getUniqueName('pre_inject_update'), $update); - $updates = $this->addUpdateToList($updates, $update->toWpFormat()); + //Convert the update into the format used by WordPress core. + $wpUpdate = $update->toWpFormat(); + //Disable the "autoupdate" flag unless explicitly allowed. This is a safety precaution; + //untrusted or compromised update sources could otherwise set this flag to true even + //if the plugin/theme developer didn't intend to allow automatic update installation. + if ( isset($wpUpdate->autoupdate) && !$this->isAutoupdateFieldAllowed($update) ) { + $wpUpdate->autoupdate = false; + } + $updates = $this->addUpdateToList($updates, $wpUpdate); } else { //Clean up any stale update info. $updates = $this->removeUpdateFromList($updates); @@ -655,6 +738,38 @@ if ( !class_exists(UpdateChecker::class, false) ): return true; } + /** + * @var bool + */ + protected $autoupdateFieldAllowed = false; + + /** + * Allow the "autoupdate" field in incoming plugin updates to be set to `true`. + * + * By default, the update checker will parse the field (so it will be available in the update + * object), but will set it to `false` before passing the update to WordPress. + * + * @return $this + */ + public function allowAutoupdateField() { + $this->autoupdateFieldAllowed = true; + return $this; + } + + /** + * Is the "autoupdate" field for injected updates allowed to be set to true? + * + * @param object $incomingUpdate + * @return bool + */ + protected function isAutoupdateFieldAllowed($incomingUpdate) { + return apply_filters( + $this->getUniqueName('autoupdate_field_allowed'), + $this->autoupdateFieldAllowed, + $incomingUpdate + ); + } + /* ------------------------------------------------------------------- * JSON-based update API * ------------------------------------------------------------------- @@ -698,7 +813,7 @@ if ( !class_exists(UpdateChecker::class, false) ): $result = wp_remote_get($url, $options); $result = apply_filters($this->getUniqueName('request_metadata_http_result'), $result, $url, $options); - + //Try to parse the response $status = $this->validateApiResponse($result); $metadata = null; @@ -924,25 +1039,62 @@ if ( !class_exists(UpdateChecker::class, false) ): return $source; } + //Fix the remote source structure if necessary. + //The update archive should contain a single directory that contains the rest of plugin/theme files. + //Otherwise, WordPress will try to copy the entire working directory ($source == $remoteSource). + //We can't rename $remoteSource because that would break WordPress code that cleans up temporary files + //after update. + if ( $this->isBadDirectoryStructure($remoteSource) ) { + //Create a new directory using the plugin slug. + $newDirectory = trailingslashit($remoteSource) . $this->slug . '/'; + + if ( !$wp_filesystem->is_dir($newDirectory) ) { + $wp_filesystem->mkdir($newDirectory); + + //Move all files to the newly created directory. + $sourceFiles = $wp_filesystem->dirlist($remoteSource); + if ( is_array($sourceFiles) ) { + $sourceFiles = array_keys($sourceFiles); + $allMoved = true; + foreach ($sourceFiles as $filename) { + //Skip our newly created folder. + if ( $filename === $this->slug ) { + continue; + } + + $previousSource = trailingslashit($remoteSource) . $filename; + $newSource = trailingslashit($newDirectory) . $filename; + + if ( !$wp_filesystem->move($previousSource, $newSource, true) ) { + $allMoved = false; + break; + } + } + + if ( $allMoved ) { + //Rename source. + $source = $newDirectory; + } else { + //Delete our newly created folder including all files in it. + $wp_filesystem->rmdir($newDirectory, true); + + //And return a relevant error. + return new WP_Error( + 'puc-incorrect-directory-structure', + sprintf( + 'The directory structure of the update was incorrect. All files should be inside ' . + 'a directory named %s, not at the root of the ZIP archive. Plugin Update Checker tried to fix the directory structure, but failed.', + esc_html($this->slug) + ) + ); + } + } + } + } + //Rename the source to match the existing directory. $correctedSource = trailingslashit($remoteSource) . $this->directoryName . '/'; if ( $source !== $correctedSource ) { - //The update archive should contain a single directory that contains the rest of plugin/theme files. - //Otherwise, WordPress will try to copy the entire working directory ($source == $remoteSource). - //We can't rename $remoteSource because that would break WordPress code that cleans up temporary files - //after update. - if ( $this->isBadDirectoryStructure($remoteSource) ) { - return new WP_Error( - 'puc-incorrect-directory-structure', - sprintf( - 'The directory structure of the update is incorrect. All files should be inside ' . - 'a directory named %s, not at the root of the ZIP archive.', - htmlentities($this->slug) - ) - ); - } - - /** @var \WP_Upgrader_Skin $upgrader ->skin */ $upgrader->skin->feedback(sprintf( 'Renaming %s to %s…', '' . basename($source) . '', diff --git a/includes/plugin-update-checker/Puc/v5p4/UpgraderStatus.php b/includes/plugin-update-checker/Puc/v5p7/UpgraderStatus.php similarity index 99% rename from includes/plugin-update-checker/Puc/v5p4/UpgraderStatus.php rename to includes/plugin-update-checker/Puc/v5p7/UpgraderStatus.php index e8340fd..9cc364c 100644 --- a/includes/plugin-update-checker/Puc/v5p4/UpgraderStatus.php +++ b/includes/plugin-update-checker/Puc/v5p7/UpgraderStatus.php @@ -1,5 +1,5 @@ wp_doing_cron() ? 10 : 3]; + if ( $this->isAuthenticationEnabled() && !empty($this->authorizationHeader) ) { + $options['headers'] = ['Authorization' => $this->authorizationHeader]; + } + + if ( !empty($this->httpFilterName) ) { + $options = apply_filters($this->httpFilterName, $options); + } + + return $options; + } + /** * Set authentication credentials. * @@ -332,7 +358,130 @@ if ( !class_exists(Api::class, false) ): } public function isAuthenticationEnabled() { - return !empty($this->credentials); + return !empty($this->credentials) || !empty($this->authorizationHeader); + } + + /** + * Get the value of the "Authorization" header for API requests, if any. + * + * @return string + */ + protected function getAuthorizationHeader() { + return $this->authorizationHeader ?: ''; + } + + /** + * Enable basic access authentication with the specified username and password. + * + * @param string $username + * @param string $password + * @param string|null $downloadUrlPrefix Optionally, add the same Authorization header to update + * downloads where the download URL starts with this prefix. + */ + protected function enableBasicAuth($username, $password, $downloadUrlPrefix = null) { + $this->authorizationHeader = 'Basic ' . base64_encode($username . ':' . $password); + + $this->downloadUrlPrefixRequiringAuth = $downloadUrlPrefix; + if ( !empty($this->downloadUrlPrefixRequiringAuth) ) { + $this->enableDownloadRequestFilter(); + } + } + + /** + * @var bool Whether the hook that registers download request filter(s) has already been added. + */ + private $preDownloadHookAdded = false; + + /** + * Enable the hooks that let you filter HTTP requests for update downloads. + */ + protected function enableDownloadRequestFilter() { + if ( $this->preDownloadHookAdded ) { + return; + } + $this->preDownloadHookAdded = true; + + //Optimization: Instead of filtering all HTTP requests, let's do it only when + //WordPress is about to download an update. So this is a two-step process; + //the actual request arg filter is added in the following hook. + add_filter('upgrader_pre_download', [$this, 'addDownloadRequestFilters']); //WP 3.7+ + } + + /** + * @var bool + */ + private $downloadFiltersAdded = false; + + /** + * @internal + * @param bool $result Pass-through value for the "upgrader_pre_download" filter. Ignored by this callback. + * @return bool + */ + public function addDownloadRequestFilters($result) { + if ( !$this->downloadFiltersAdded ) { + $this->downloadFiltersAdded = true; + + //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args -- The callback doesn't change the timeout. + add_filter('http_request_args', [$this, 'filterUpdateDownloadRequestArgs'], 10, 2); + + $authorizationHeader = $this->getAuthorizationHeader(); + if ( $this->isAuthenticationEnabled() && !empty($authorizationHeader) ) { + add_action('requests-requests.before_redirect', [$this, 'removeAuthHeaderFromRedirects'], 10, 2); + } + } + return $result; + } + + /** + * Filter request arguments/options for update downloads. + * + * Note that this callback can potentially be called for *any* update download. You still + * need to verify that the URL is one of yours. + * + * @internal + * @param array $requestArgs + * @param string $url + * @return array + */ + public function filterUpdateDownloadRequestArgs($requestArgs, $url = '') { + //Add an authorization header to our downloads if needed. + $authHeader = $this->getAuthorizationHeader(); + if ( + $this->isAuthenticationEnabled() + && !empty($authHeader) + && !empty($this->downloadUrlPrefixRequiringAuth) + && ((strpos($url, $this->downloadUrlPrefixRequiringAuth)) === 0) + ) { + $requestArgs['headers']['Authorization'] = $authHeader; + } + return $requestArgs; + } + + /** + * At least in older WP versions, when following a redirect, the Requests library will + * automatically forward the Authorization header to other hosts. We don't want that + * because it breaks AWS downloads and can leak authorization information. + * + * @param string $location + * @param array $headers + * @internal + */ + public function removeAuthHeaderFromRedirects(&$location, &$headers) { + if ( + //If there's no download URL prefix configured, we would not have added an auth header, + //and there's also no way to check if this URL needs auth or not. + empty($this->downloadUrlPrefixRequiringAuth) + //If this request goes to our download URL, we can leave the header. + || ((strpos($location, $this->downloadUrlPrefixRequiringAuth)) === 0) + ) { + return; + } + + //Remove the header. + $authorizationHeader = $this->getAuthorizationHeader(); + if ( isset($headers['Authorization']) && ($headers['Authorization'] === $authorizationHeader) ) { + unset($headers['Authorization']); + } } /** diff --git a/includes/plugin-update-checker/Puc/v5p4/Vcs/BaseChecker.php b/includes/plugin-update-checker/Puc/v5p7/Vcs/BaseChecker.php similarity index 90% rename from includes/plugin-update-checker/Puc/v5p4/Vcs/BaseChecker.php rename to includes/plugin-update-checker/Puc/v5p7/Vcs/BaseChecker.php index 78e0dde..c416370 100644 --- a/includes/plugin-update-checker/Puc/v5p4/Vcs/BaseChecker.php +++ b/includes/plugin-update-checker/Puc/v5p7/Vcs/BaseChecker.php @@ -1,5 +1,5 @@ getDownloadBaseUrl() . $ref . '.zip'; + } + + /** + * Get the base URL for ZIP downloads from our repo. Includes the trailing slash. + * + * @return string + */ + protected function getDownloadBaseUrl() { return sprintf( - 'https://bitbucket.org/%s/%s/get/%s.zip', + 'https://bitbucket.org/%s/%s/get/', $this->username, - $this->repository, - $ref + $this->repository ); } @@ -206,15 +208,7 @@ if ( !class_exists(BitBucketApi::class, false) ): )); $baseUrl = $url; - if ( $this->oauth ) { - $url = $this->oauth->sign($url,'GET'); - } - - $options = array('timeout' => wp_doing_cron() ? 10 : 3); - if ( !empty($this->httpFilterName) ) { - $options = apply_filters($this->httpFilterName, $options); - } - $response = wp_remote_get($url, $options); + $response = wp_remote_get($url, $this->getApiRequestHttpOptions()); if ( is_wp_error($response) ) { do_action('puc_api_error', $response, null, $url, $this->slug); return $response; @@ -248,25 +242,18 @@ if ( !class_exists(BitBucketApi::class, false) ): public function setAuthentication($credentials) { parent::setAuthentication($credentials); - if ( !empty($credentials) && !empty($credentials['consumer_key']) ) { - $this->oauth = new OAuthSignature( - $credentials['consumer_key'], - $credentials['consumer_secret'] + if ( + is_array($credentials) + && !empty($credentials['username']) + && !empty($credentials['api_token']) + ) { + $this->enableBasicAuth( + $credentials['username'], + $credentials['api_token'], + $this->getDownloadBaseUrl() ); - } else { - $this->oauth = null; } } - - public function signDownloadUrl($url) { - //Add authentication data to download URLs. Since OAuth signatures incorporate - //timestamps, we have to do this immediately before inserting the update. Otherwise, - //authentication could fail due to a stale timestamp. - if ( $this->oauth ) { - $url = $this->oauth->sign($url); - } - return $url; - } } endif; diff --git a/includes/plugin-update-checker/Puc/v5p4/Vcs/GitHubApi.php b/includes/plugin-update-checker/Puc/v5p7/Vcs/GitHubApi.php similarity index 78% rename from includes/plugin-update-checker/Puc/v5p4/Vcs/GitHubApi.php rename to includes/plugin-update-checker/Puc/v5p7/Vcs/GitHubApi.php index 610d932..782bfc2 100644 --- a/includes/plugin-update-checker/Puc/v5p4/Vcs/GitHubApi.php +++ b/includes/plugin-update-checker/Puc/v5p7/Vcs/GitHubApi.php @@ -1,6 +1,6 @@ [^/]+?)/(?P[^/#?&]+?)/?$@', $path, $matches) ) { @@ -248,15 +243,7 @@ if ( !class_exists(GitHubApi::class, false) ): $baseUrl = $url; $url = $this->buildApiUrl($url, $queryParams); - $options = array('timeout' => wp_doing_cron() ? 10 : 3); - if ( $this->isAuthenticationEnabled() ) { - $options['headers'] = array('Authorization' => $this->getAuthorizationHeader()); - } - - if ( !empty($this->httpFilterName) ) { - $options = apply_filters($this->httpFilterName, $options); - } - $response = wp_remote_get($url, $options); + $response = wp_remote_get($url, $this->getApiRequestHttpOptions()); if ( is_wp_error($response) ) { do_action('puc_api_error', $response, null, $url, $this->slug); return $response; @@ -348,11 +335,17 @@ if ( !class_exists(GitHubApi::class, false) ): public function setAuthentication($credentials) { parent::setAuthentication($credentials); + //This property is no longer used internally, but is kept for backwards compatibility. $this->accessToken = is_string($credentials) ? $credentials : null; - //Optimization: Instead of filtering all HTTP requests, let's do it only when - //WordPress is about to download an update. - add_filter('upgrader_pre_download', array($this, 'addHttpRequestFilter'), 10, 1); //WP 3.7+ + if ( is_string($credentials) && !empty($credentials) ) { + $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', []); + $this->enableBasicAuth($this->userName, $credentials, $repoApiBaseUrl); + } + + //Assets sometimes need an Accept header, so we add a request filter even if basic + //authentication isn't enabled. + $this->enableDownloadRequestFilter(); } protected function getUpdateDetectionStrategies($configBranch) { @@ -393,74 +386,15 @@ if ( !class_exists(GitHubApi::class, false) ): return null; } - /** - * @param bool $result - * @return bool - * @internal - */ - public function addHttpRequestFilter($result) { - if ( !$this->downloadFilterAdded && $this->isAuthenticationEnabled() ) { - //phpcs:ignore WordPressVIPMinimum.Hooks.RestrictedHooks.http_request_args -- The callback doesn't change the timeout. - add_filter('http_request_args', array($this, 'setUpdateDownloadHeaders'), 10, 2); - add_action('requests-requests.before_redirect', array($this, 'removeAuthHeaderFromRedirects'), 10, 4); - $this->downloadFilterAdded = true; - } - return $result; - } - - /** - * Set the HTTP headers that are necessary to download updates from private repositories. - * - * See GitHub docs: - * - * @link https://developer.github.com/v3/repos/releases/#get-a-single-release-asset - * @link https://developer.github.com/v3/auth/#basic-authentication - * - * @internal - * @param array $requestArgs - * @param string $url - * @return array - */ - public function setUpdateDownloadHeaders($requestArgs, $url = '') { + public function filterUpdateDownloadRequestArgs($requestArgs, $url = '') { + //Release assets need an "Accept" header. See GitHub Docs: + //https://developer.github.com/v3/repos/releases/#get-a-single-release-asset //Is WordPress trying to download one of our release assets? if ( $this->releaseAssetsEnabled && (strpos($url, $this->getAssetApiBaseUrl()) !== false) ) { $requestArgs['headers']['Accept'] = 'application/octet-stream'; } - //Use Basic authentication, but only if the download is from our repository. - $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); - if ( $this->isAuthenticationEnabled() && (strpos($url, $repoApiBaseUrl)) === 0 ) { - $requestArgs['headers']['Authorization'] = $this->getAuthorizationHeader(); - } - return $requestArgs; - } - /** - * When following a redirect, the Requests library will automatically forward - * the authorization header to other hosts. We don't want that because it breaks - * AWS downloads and can leak authorization information. - * - * @param string $location - * @param array $headers - * @internal - */ - public function removeAuthHeaderFromRedirects(&$location, &$headers) { - $repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array()); - if ( strpos($location, $repoApiBaseUrl) === 0 ) { - return; //This request is going to GitHub, so it's fine. - } - //Remove the header. - if ( isset($headers['Authorization']) ) { - unset($headers['Authorization']); - } - } - - /** - * Generate the value of the "Authorization" header. - * - * @return string - */ - protected function getAuthorizationHeader() { - return 'Basic ' . base64_encode($this->userName . ':' . $this->accessToken); + return parent::filterUpdateDownloadRequestArgs($requestArgs, $url); } } diff --git a/includes/plugin-update-checker/Puc/v5p4/Vcs/GitLabApi.php b/includes/plugin-update-checker/Puc/v5p7/Vcs/GitLabApi.php similarity index 97% rename from includes/plugin-update-checker/Puc/v5p4/Vcs/GitLabApi.php rename to includes/plugin-update-checker/Puc/v5p7/Vcs/GitLabApi.php index 2cbd6eb..8b5dcc5 100644 --- a/includes/plugin-update-checker/Puc/v5p4/Vcs/GitLabApi.php +++ b/includes/plugin-update-checker/Puc/v5p7/Vcs/GitLabApi.php @@ -1,6 +1,6 @@ buildApiUrl($url, $queryParams); - $options = array('timeout' => wp_doing_cron() ? 10 : 3); - if ( !empty($this->httpFilterName) ) { - $options = apply_filters($this->httpFilterName, $options); - } - - $response = wp_remote_get($url, $options); + $response = wp_remote_get($url, $this->getApiRequestHttpOptions()); if ( is_wp_error($response) ) { do_action('puc_api_error', $response, null, $url, $this->slug); return $response; diff --git a/includes/plugin-update-checker/Puc/v5p4/Vcs/PluginUpdateChecker.php b/includes/plugin-update-checker/Puc/v5p7/Vcs/PluginUpdateChecker.php similarity index 98% rename from includes/plugin-update-checker/Puc/v5p4/Vcs/PluginUpdateChecker.php rename to includes/plugin-update-checker/Puc/v5p7/Vcs/PluginUpdateChecker.php index f00097f..3c4f6b7 100644 --- a/includes/plugin-update-checker/Puc/v5p4/Vcs/PluginUpdateChecker.php +++ b/includes/plugin-update-checker/Puc/v5p7/Vcs/PluginUpdateChecker.php @@ -1,8 +1,8 @@ getVcsApi()->enableReleaseAssets(); ``` + By default, PUC will simply use the first available asset. You can pass a regular expression to `enableReleaseAssets()` to filter assets by name. For example: + ```php + $myUpdateChecker->getVcsApi()->enableReleaseAssets('/\.zip($|[?&#])/i'); + ``` + - **Tags** To release version 1.2.3, create a new Git tag named `v1.2.3` or `1.2.3`. That's it. @@ -188,14 +193,12 @@ The library will pull update details from the following parts of a release/tag/b 'unique-plugin-or-theme-slug' ); - //Optional: If you're using a private repository, create an OAuth consumer - //and set the authentication credentials like this: - //Note: For now you need to check "This is a private consumer" when - //creating the consumer to work around #134: - // https://github.com/YahnisElsts/plugin-update-checker/issues/134 + //Optional: If you're using a private repository, create an API token + //with the "read:repository:bitbucket" scope and set the authentication + //credentials like this: $myUpdateChecker->setAuthentication(array( - 'consumer_key' => '...', - 'consumer_secret' => '...', + 'username' => 'example@example.com', //Your BitBucket email address. + 'api_token' => '...', )); //Optional: Set the branch that contains the stable release. @@ -252,8 +255,8 @@ BitBucket doesn't have an equivalent to GitHub's releases, so the process is sli Alternatively, if you're using a self-hosted GitLab instance, initialize the update checker like this: ```php - use YahnisElsts\PluginUpdateChecker\v5p4\Vcs\PluginUpdateChecker; - use YahnisElsts\PluginUpdateChecker\v5p4\Vcs\GitLabApi; + use YahnisElsts\PluginUpdateChecker\v5p7\Vcs\PluginUpdateChecker; + use YahnisElsts\PluginUpdateChecker\v5p7\Vcs\GitLabApi; $myUpdateChecker = new PluginUpdateChecker( new GitLabApi('https://myserver.com/user-name/repo-name/'), @@ -264,8 +267,8 @@ BitBucket doesn't have an equivalent to GitHub's releases, so the process is sli ``` If you're using a self-hosted GitLab instance and [subgroups or nested groups](https://docs.gitlab.com/ce/user/group/subgroups/index.html), you have to tell the update checker which parts of the URL are subgroups: ```php - use YahnisElsts\PluginUpdateChecker\v5p4\Vcs\PluginUpdateChecker; - use YahnisElsts\PluginUpdateChecker\v5p4\Vcs\GitLabApi; + use YahnisElsts\PluginUpdateChecker\v5p7\Vcs\PluginUpdateChecker; + use YahnisElsts\PluginUpdateChecker\v5p7\Vcs\GitLabApi; $myUpdateChecker = new PluginUpdateChecker( new GitLabApi( @@ -347,14 +350,14 @@ Other classes have also been renamed, usually by simply removing the `Puc_vXpY_` | Old class name | New class name | |-------------------------------------|----------------------------------------------------------------| | `Puc_v4_Factory` | `YahnisElsts\PluginUpdateChecker\v5\PucFactory` | -| `Puc_v4p13_Factory` | `YahnisElsts\PluginUpdateChecker\v5p4\PucFactory` | -| `Puc_v4p13_Plugin_UpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p4\Plugin\UpdateChecker` | -| `Puc_v4p13_Theme_UpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p4\Theme\UpdateChecker` | -| `Puc_v4p13_Vcs_PluginUpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p4\Vcs\PluginUpdateChecker` | -| `Puc_v4p13_Vcs_ThemeUpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p4\Vcs\ThemeUpdateChecker` | -| `Puc_v4p13_Vcs_GitHubApi` | `YahnisElsts\PluginUpdateChecker\v5p4\Vcs\GitHubApi` | -| `Puc_v4p13_Vcs_GitLabApi` | `YahnisElsts\PluginUpdateChecker\v5p4\Vcs\GitLabApi` | -| `Puc_v4p13_Vcs_BitBucketApi` | `YahnisElsts\PluginUpdateChecker\v5p4\Vcs\BitBucketApi` | +| `Puc_v4p13_Factory` | `YahnisElsts\PluginUpdateChecker\v5p7\PucFactory` | +| `Puc_v4p13_Plugin_UpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p7\Plugin\UpdateChecker` | +| `Puc_v4p13_Theme_UpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p7\Theme\UpdateChecker` | +| `Puc_v4p13_Vcs_PluginUpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p7\Vcs\PluginUpdateChecker` | +| `Puc_v4p13_Vcs_ThemeUpdateChecker` | `YahnisElsts\PluginUpdateChecker\v5p7\Vcs\ThemeUpdateChecker` | +| `Puc_v4p13_Vcs_GitHubApi` | `YahnisElsts\PluginUpdateChecker\v5p7\Vcs\GitHubApi` | +| `Puc_v4p13_Vcs_GitLabApi` | `YahnisElsts\PluginUpdateChecker\v5p7\Vcs\GitLabApi` | +| `Puc_v4p13_Vcs_BitBucketApi` | `YahnisElsts\PluginUpdateChecker\v5p7\Vcs\BitBucketApi` | License Management ------------------ diff --git a/includes/plugin-update-checker/composer.json b/includes/plugin-update-checker/composer.json index f7af7eb..85b1f35 100644 --- a/includes/plugin-update-checker/composer.json +++ b/includes/plugin-update-checker/composer.json @@ -18,6 +18,6 @@ "ext-json": "*" }, "autoload": { - "files": ["load-v5p4.php"] + "files": ["load-v5p7.php"] } } diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-el.mo b/includes/plugin-update-checker/languages/plugin-update-checker-el.mo new file mode 100644 index 0000000..08306de Binary files /dev/null and b/includes/plugin-update-checker/languages/plugin-update-checker-el.mo differ diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-el.po b/includes/plugin-update-checker/languages/plugin-update-checker-el.po new file mode 100644 index 0000000..6b1ba21 --- /dev/null +++ b/includes/plugin-update-checker/languages/plugin-update-checker-el.po @@ -0,0 +1,52 @@ +msgid "" +msgstr "" +"Project-Id-Version: plugin-update-checker\n" +"POT-Creation-Date: 2022-07-29 15:34+0300\n" +"PO-Revision-Date: 2024-05-09 22:22+0000\n" +"Last-Translator: theogk\n" +"Language-Team: Ελληνικά\n" +"Language: el\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: Loco https://localise.biz/\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" +"Report-Msgid-Bugs-To: \n" +"X-Loco-Version: 2.6.9; wp-6.5.3" + +#: Puc/v5p4/Plugin/Ui.php:128 +msgid "Check for updates" +msgstr "Έλεγχος για ενημερώσεις" + +#: Puc/v5p4/Plugin/Ui.php:214 +#, php-format +msgctxt "the plugin title" +msgid "The %s plugin is up to date." +msgstr "Το πρόσθετο %s είναι ενημερωμένο." + +#: Puc/v5p4/Plugin/Ui.php:216 +#, php-format +msgctxt "the plugin title" +msgid "A new version of the %s plugin is available." +msgstr "Μία νέα έκδοση είναι διαθέσιμη για το πρόσθετο %s." + +#: Puc/v5p4/Plugin/Ui.php:218 +#, php-format +msgctxt "the plugin title" +msgid "Could not determine if updates are available for %s." +msgstr "" +"Δεν ήταν εφικτό να εκτελεστεί ο έλεγχος για νέες ενημερώσεις για το πρόσθετο " +"%s." + +#: Puc/v5p4/Plugin/Ui.php:224 +#, php-format +msgid "Unknown update checker status \"%s\"" +msgstr "Άγνωστο πρόβλημα του ενημερωτή προσθέτων \"%s\"" + +#: Puc/v5p4/Vcs/PluginUpdateChecker.php:100 +msgid "There is no changelog available." +msgstr "Δεν υπάρχει διαθέσιμο αρχείο αλλαγών." diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.mo b/includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.mo index a68f100..4a9e948 100644 Binary files a/includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.mo and b/includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.mo differ diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.po b/includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.po index 20b6938..4c6ef4d 100644 --- a/includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.po +++ b/includes/plugin-update-checker/languages/plugin-update-checker-fa_IR.po @@ -1,38 +1,50 @@ msgid "" msgstr "" "Project-Id-Version: plugin-update-checker\n" -"POT-Creation-Date: 2016-02-17 14:21+0100\n" -"PO-Revision-Date: 2016-10-28 14:30+0330\n" -"Last-Translator: studio RVOLA \n" -"Language-Team: Pro Style \n" +"POT-Creation-Date: 2025-06-12 23:40+0100\n" +"PO-Revision-Date: 2025-06-12 23:49+0100\n" +"Last-Translator: Pro Style \n" +"Language-Team: Alex Javadi \n" "Language: fa_IR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Generator: Poedit 1.8.8\n" -"X-Poedit-Basepath: ..\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\n" "X-Poedit-SearchPath-0: .\n" -#: github-checker.php:120 -msgid "There is no changelog available." -msgstr "شرحی برای تغییرات یافت نشد" +#: Puc/v5p6/Plugin/Ui.php:56 +msgid "View details" +msgstr "مشاهده جزئیات" -#: plugin-update-checker.php:637 +#: Puc/v5p6/Plugin/Ui.php:79 +#, php-format +msgid "More information about %s" +msgstr "اطلاعات بیشتر درباره %s" + +# It had some “potential” grammar issues and also didn’t sound native. +# P.S. I know the current translation is literally “Checking for new updates”, however, I thought it might sound more natural and known to others. +#: Puc/v5p6/Plugin/Ui.php:130 msgid "Check for updates" -msgstr "بررسی برای بروزرسانی " +msgstr "بررسی بروزرسانی جدید" -#: plugin-update-checker.php:681 -msgid "This plugin is up to date." -msgstr "شما از آخرین نسخه استفاده میکنید . به‌روز باشید" - -#: plugin-update-checker.php:683 -msgid "A new version of this plugin is available." -msgstr "نسخه جدیدی برای افزونه ارائه شده است ." - -#: plugin-update-checker.php:685 +# The word “ناشناخته” is seems to be translated directly from the word (Un-known), rather than checking for the context. +# I think “نامشخص” (unknown) might be a suitable version in this scenario. +#: Puc/v5p6/Plugin/Ui.php:227 #, php-format msgid "Unknown update checker status \"%s\"" -msgstr "وضعیت ناشناخته برای بروزرسانی \"%s\"" +msgstr "وضعیت نامشخص برای بروزرسانی \"%s\"" + +# The previous translation was okay, however, it didn’t sound native to me. +#: Puc/v5p6/Vcs/PluginUpdateChecker.php:113 +msgid "There is no changelog available." +msgstr "آخرین تغییراتی یافت نشد." + +#~ msgid "This plugin is up to date." +#~ msgstr "شما از آخرین نسخه استفاده میکنید . به‌روز باشید" + +#~ msgid "A new version of this plugin is available." +#~ msgstr "نسخه جدیدی برای افزونه ارائه شده است ." diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-sr_RS.mo b/includes/plugin-update-checker/languages/plugin-update-checker-sr_RS.mo new file mode 100644 index 0000000..af67f9e Binary files /dev/null and b/includes/plugin-update-checker/languages/plugin-update-checker-sr_RS.mo differ diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-sr_RS.po b/includes/plugin-update-checker/languages/plugin-update-checker-sr_RS.po new file mode 100644 index 0000000..144a2d9 --- /dev/null +++ b/includes/plugin-update-checker/languages/plugin-update-checker-sr_RS.po @@ -0,0 +1,57 @@ +msgid "" +msgstr "" +"Project-Id-Version: plugin-update-checker\n" +"POT-Creation-Date: 2024-12-09 11:45+0100\n" +"PO-Revision-Date: 2024-12-09 12:20+0100\n" +"Last-Translator: Aleksandar Urošević \n" +"Language-Team: \n" +"Language: sr_RS\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 2.4.3\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/v5p5/Plugin/Ui.php:56 +msgid "View details" +msgstr "Види детаље" + +#: Puc/v5p5/Plugin/Ui.php:79 +#, php-format +msgid "More information about %s" +msgstr "Више информација о %s" + +#: Puc/v5p5/Plugin/Ui.php:130 +msgid "Check for updates" +msgstr "Провера ажурирања" + +#: Puc/v5p5/Plugin/Ui.php:217 +#, php-format +msgctxt "the plugin title" +msgid "The %s plugin is up to date." +msgstr "Додатак %s је у најновијем издању." + +#: Puc/v5p5/Plugin/Ui.php:219 +#, php-format +msgctxt "the plugin title" +msgid "A new version of the %s plugin is available." +msgstr "Доступно је ново издање за %s." + +#: Puc/v5p5/Plugin/Ui.php:221 +#, php-format +msgctxt "the plugin title" +msgid "Could not determine if updates are available for %s." +msgstr "Није могуће утврдити да ли су доступне исправке за %s." + +#: Puc/v5p5/Plugin/Ui.php:227 +#, php-format +msgid "Unknown update checker status \"%s\"" +msgstr "Непознат статус провере ажурирања \"%s\"" + +#: Puc/v5p5/Vcs/PluginUpdateChecker.php:113 +msgid "There is no changelog available." +msgstr "Белешке о изменама нису доступне." diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo b/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo index 86d1144..4e8ce4a 100644 Binary files a/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo and b/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.mo differ diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.po b/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.po index d4f7056..d78ba5e 100644 --- a/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.po +++ b/includes/plugin-update-checker/languages/plugin-update-checker-zh_CN.po @@ -1,57 +1,57 @@ msgid "" msgstr "" "Project-Id-Version: plugin-update-checker\n" -"POT-Creation-Date: 2022-01-29 12:09+0800\n" -"PO-Revision-Date: 2022-01-29 12:10+0800\n" +"POT-Creation-Date: 2025-11-21 10:40+0800\n" +"PO-Revision-Date: 2025-11-21 10:40+0800\n" "Last-Translator: Seaton Jiang \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" -"X-Generator: Poedit 2.4.3\n" -"X-Poedit-Basepath: ..\n" "Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 3.8\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/v4p11/Plugin/Ui.php:54 +#: Puc/v5p6/Plugin/Ui.php:56 msgid "View details" msgstr "查看详情" -#: Puc/v4p11/Plugin/Ui.php:77 +#: Puc/v5p6/Plugin/Ui.php:79 #, php-format msgid "More information about %s" msgstr "%s 的更多信息" -#: Puc/v4p11/Plugin/Ui.php:128 +#: Puc/v5p6/Plugin/Ui.php:130 msgid "Check for updates" msgstr "检查更新" -#: Puc/v4p11/Plugin/Ui.php:214 +#: Puc/v5p6/Plugin/Ui.php:217 #, php-format msgctxt "the plugin title" msgid "The %s plugin is up to date." msgstr "%s 目前是最新版本。" -#: Puc/v4p11/Plugin/Ui.php:216 +#: Puc/v5p6/Plugin/Ui.php:219 #, php-format msgctxt "the plugin title" msgid "A new version of the %s plugin is available." msgstr "%s 当前有可用的更新。" -#: Puc/v4p11/Plugin/Ui.php:218 +#: Puc/v5p6/Plugin/Ui.php:221 #, php-format msgctxt "the plugin title" msgid "Could not determine if updates are available for %s." msgstr "%s 无法确定是否有可用的更新。" -#: Puc/v4p11/Plugin/Ui.php:224 +#: Puc/v5p6/Plugin/Ui.php:227 #, php-format msgid "Unknown update checker status \"%s\"" msgstr "未知的更新检查状态:%s" -#: Puc/v4p11/Vcs/PluginUpdateChecker.php:100 +#: Puc/v5p6/Vcs/PluginUpdateChecker.php:113 msgid "There is no changelog available." msgstr "没有可用的更新日志。" diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-zh_TW.mo b/includes/plugin-update-checker/languages/plugin-update-checker-zh_TW.mo new file mode 100644 index 0000000..a7d2511 Binary files /dev/null and b/includes/plugin-update-checker/languages/plugin-update-checker-zh_TW.mo differ diff --git a/includes/plugin-update-checker/languages/plugin-update-checker-zh_TW.po b/includes/plugin-update-checker/languages/plugin-update-checker-zh_TW.po new file mode 100644 index 0000000..e3ab5ee --- /dev/null +++ b/includes/plugin-update-checker/languages/plugin-update-checker-zh_TW.po @@ -0,0 +1,66 @@ +# Blank Plugin POT Template +# Copyright 2025 ... +# This file is distributed under the GNU General Public License v3 or later. +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Plugin Update Checker\n" +"Report-Msgid-Bugs-To: Alex Lion \n" +"POT-Creation-Date: 2025-09-19 14:05-0700\n" +"PO-Revision-Date: \n" +"Last-Translator: Alex Lion \n" +"Language-Team: Alex Lion \n" +"Language: zh_TW\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-Poedit-WPHeader: plugin-update-checker.php\n" +"X-Textdomain-Support: yesX-Generator: Poedit 1.6.4\n" +"X-Poedit-SourceCharset: UTF-8\n" +"X-Poedit-KeywordsList: " +"__;_e;esc_html_e;esc_html_x:1,2c;esc_html__;esc_attr_e;esc_attr_x:1,2c;esc_attr__;_ex:1,2c;_nx:4c,1,2;_nx_noop:4c,1,2;_x:1,2c;_n:1,2;_n_noop:1,2;__ngettext:1,2;__ngettext_noop:1,2;_c,_nc:4c,1,2\n" +"X-Poedit-Basepath: ..\n" +"X-Poedit-Bookmarks: \n" +"X-Generator: Poedit 3.7\n" +"X-Poedit-SearchPath-0: .\n" + +#: Puc/v5p6/Plugin/Ui.php:56 +msgid "View details" +msgstr "檢視詳細資料" + +#: Puc/v5p6/Plugin/Ui.php:79 +#, php-format +msgid "More information about %s" +msgstr "進一步了解 %s 的相關資訊" + +#: 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 "%s 外掛已為最新版本。" + +#: Puc/v5p6/Plugin/Ui.php:219 +#, php-format +msgctxt "the plugin title" +msgid "A new version of the %s plugin is available." +msgstr "%s 外掛已有新版本可供更新。" + +#: Puc/v5p6/Plugin/Ui.php:221 +#, php-format +msgctxt "the plugin title" +msgid "Could not determine if updates are available for %s." +msgstr "無法確定 %s 是否有可用的更新。" + +#: Puc/v5p6/Plugin/Ui.php:227 +#, php-format +msgid "Unknown update checker status \"%s\"" +msgstr "未知的更新檢查程式狀態: %s" + +#: Puc/v5p6/Vcs/PluginUpdateChecker.php:113 +msgid "There is no changelog available." +msgstr "目前沒有可供檢閱的變更記錄。" diff --git a/includes/plugin-update-checker/languages/plugin-update-checker.pot b/includes/plugin-update-checker/languages/plugin-update-checker.pot index 5b6319c..abe04a4 100644 --- a/includes/plugin-update-checker/languages/plugin-update-checker.pot +++ b/includes/plugin-update-checker/languages/plugin-update-checker.pot @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: plugin-update-checker\n" -"POT-Creation-Date: 2022-07-29 15:34+0300\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" @@ -11,39 +11,39 @@ msgstr "" "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.1.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/v5p4/Plugin/Ui.php:128 +#: Puc/v5p7/Plugin/Ui.php:130 msgid "Check for updates" msgstr "" -#: Puc/v5p4/Plugin/Ui.php:214 +#: Puc/v5p7/Plugin/Ui.php:217 #, php-format msgctxt "the plugin title" msgid "The %s plugin is up to date." msgstr "" -#: Puc/v5p4/Plugin/Ui.php:216 +#: Puc/v5p7/Plugin/Ui.php:219 #, php-format msgctxt "the plugin title" msgid "A new version of the %s plugin is available." msgstr "" -#: Puc/v5p4/Plugin/Ui.php:218 +#: Puc/v5p7/Plugin/Ui.php:221 #, php-format msgctxt "the plugin title" msgid "Could not determine if updates are available for %s." msgstr "" -#: Puc/v5p4/Plugin/Ui.php:224 +#: Puc/v5p7/Plugin/Ui.php:227 #, php-format msgid "Unknown update checker status \"%s\"" msgstr "" -#: Puc/v5p4/Vcs/PluginUpdateChecker.php:100 +#: Puc/v5p7/Vcs/PluginUpdateChecker.php:113 msgid "There is no changelog available." msgstr "" diff --git a/includes/plugin-update-checker/load-v5p4.php b/includes/plugin-update-checker/load-v5p7.php similarity index 80% rename from includes/plugin-update-checker/load-v5p4.php rename to includes/plugin-update-checker/load-v5p7.php index 2cd9580..be2b1b6 100644 --- a/includes/plugin-update-checker/load-v5p4.php +++ b/includes/plugin-update-checker/load-v5p7.php @@ -1,14 +1,14 @@ $pucVersionedClass ) { - MajorFactory::addVersion($pucGeneralClass, $pucVersionedClass, '5.4'); + MajorFactory::addVersion($pucGeneralClass, $pucVersionedClass, '5.7'); //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.4'); + MinorFactory::addVersion($pucGeneralClass, $pucVersionedClass, '5.7'); } diff --git a/includes/plugin-update-checker/plugin-update-checker.php b/includes/plugin-update-checker/plugin-update-checker.php index da4cb1e..e5b3753 100644 --- a/includes/plugin-update-checker/plugin-update-checker.php +++ b/includes/plugin-update-checker/plugin-update-checker.php @@ -1,10 +1,10 @@ DefinitionData = array(); - - # standardize line breaks - $text = str_replace(array("\r\n", "\r"), "\n", $text); - - # remove surrounding line breaks - $text = trim($text, "\n"); - - # split text into lines - $lines = explode("\n", $text); - - # iterate through lines to identify blocks - $markup = $this->lines($lines); - - # trim line breaks - $markup = trim($markup, "\n"); - - return $markup; - } - - # - # Setters - # - - function setBreaksEnabled($breaksEnabled) - { - $this->breaksEnabled = $breaksEnabled; - - return $this; - } - - protected $breaksEnabled; - - function setMarkupEscaped($markupEscaped) - { - $this->markupEscaped = $markupEscaped; - - return $this; - } - - protected $markupEscaped; - - function setUrlsLinked($urlsLinked) - { - $this->urlsLinked = $urlsLinked; - - return $this; - } - - protected $urlsLinked = true; - - # - # Lines - # - - protected $BlockTypes = array( - '#' => array('Header'), - '*' => array('Rule', 'List'), - '+' => array('List'), - '-' => array('SetextHeader', 'Table', 'Rule', 'List'), - '0' => array('List'), - '1' => array('List'), - '2' => array('List'), - '3' => array('List'), - '4' => array('List'), - '5' => array('List'), - '6' => array('List'), - '7' => array('List'), - '8' => array('List'), - '9' => array('List'), - ':' => array('Table'), - '<' => array('Comment', 'Markup'), - '=' => array('SetextHeader'), - '>' => array('Quote'), - '[' => array('Reference'), - '_' => array('Rule'), - '`' => array('FencedCode'), - '|' => array('Table'), - '~' => array('FencedCode'), - ); - - # ~ - - protected $unmarkedBlockTypes = array( - 'Code', - ); - - # - # Blocks - # - - protected function lines(array $lines) - { - $CurrentBlock = null; - - foreach ($lines as $line) - { - if (chop($line) === '') - { - if (isset($CurrentBlock)) - { - $CurrentBlock['interrupted'] = true; - } - - continue; - } - - if (strpos($line, "\t") !== false) - { - $parts = explode("\t", $line); - - $line = $parts[0]; - - unset($parts[0]); - - foreach ($parts as $part) - { - $shortage = 4 - mb_strlen($line, 'utf-8') % 4; - - $line .= str_repeat(' ', $shortage); - $line .= $part; - } - } - - $indent = 0; - - while (isset($line[$indent]) and $line[$indent] === ' ') - { - $indent ++; - } - - $text = $indent > 0 ? substr($line, $indent) : $line; - - # ~ - - $Line = array('body' => $line, 'indent' => $indent, 'text' => $text); - - # ~ - - if (isset($CurrentBlock['continuable'])) - { - $Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock); - - if (isset($Block)) - { - $CurrentBlock = $Block; - - continue; - } - else - { - if ($this->isBlockCompletable($CurrentBlock['type'])) - { - $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); - } - } - } - - # ~ - - $marker = $text[0]; - - # ~ - - $blockTypes = $this->unmarkedBlockTypes; - - if (isset($this->BlockTypes[$marker])) - { - foreach ($this->BlockTypes[$marker] as $blockType) - { - $blockTypes []= $blockType; - } - } - - # - # ~ - - foreach ($blockTypes as $blockType) - { - $Block = $this->{'block'.$blockType}($Line, $CurrentBlock); - - if (isset($Block)) - { - $Block['type'] = $blockType; - - if ( ! isset($Block['identified'])) - { - $Blocks []= $CurrentBlock; - - $Block['identified'] = true; - } - - if ($this->isBlockContinuable($blockType)) - { - $Block['continuable'] = true; - } - - $CurrentBlock = $Block; - - continue 2; - } - } - - # ~ - - if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted'])) - { - $CurrentBlock['element']['text'] .= "\n".$text; - } - else - { - $Blocks []= $CurrentBlock; - - $CurrentBlock = $this->paragraph($Line); - - $CurrentBlock['identified'] = true; - } - } - - # ~ - - if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) - { - $CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock); - } - - # ~ - - $Blocks []= $CurrentBlock; - - unset($Blocks[0]); - - # ~ - - $markup = ''; - - foreach ($Blocks as $Block) - { - if (isset($Block['hidden'])) - { - continue; - } - - $markup .= "\n"; - $markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']); - } - - $markup .= "\n"; - - # ~ - - return $markup; - } - - protected function isBlockContinuable($Type) - { - return method_exists($this, 'block'.$Type.'Continue'); - } - - protected function isBlockCompletable($Type) - { - return method_exists($this, 'block'.$Type.'Complete'); - } - - # - # Code - - protected function blockCode($Line, $Block = null) - { - if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted'])) - { - return; - } - - if ($Line['indent'] >= 4) - { - $text = substr($Line['body'], 4); - - $Block = array( - 'element' => array( - 'name' => 'pre', - 'handler' => 'element', - 'text' => array( - 'name' => 'code', - 'text' => $text, - ), - ), - ); - - return $Block; - } - } - - protected function blockCodeContinue($Line, $Block) - { - if ($Line['indent'] >= 4) - { - if (isset($Block['interrupted'])) - { - $Block['element']['text']['text'] .= "\n"; - - unset($Block['interrupted']); - } - - $Block['element']['text']['text'] .= "\n"; - - $text = substr($Line['body'], 4); - - $Block['element']['text']['text'] .= $text; - - return $Block; - } - } - - protected function blockCodeComplete($Block) - { - $text = $Block['element']['text']['text']; - - $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); - - $Block['element']['text']['text'] = $text; - - return $Block; - } - - # - # Comment - - protected function blockComment($Line) - { - if ($this->markupEscaped) - { - return; - } - - if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!') - { - $Block = array( - 'markup' => $Line['body'], - ); - - if (preg_match('/-->$/', $Line['text'])) - { - $Block['closed'] = true; - } - - return $Block; - } - } - - protected function blockCommentContinue($Line, array $Block) - { - if (isset($Block['closed'])) - { - return; - } - - $Block['markup'] .= "\n" . $Line['body']; - - if (preg_match('/-->$/', $Line['text'])) - { - $Block['closed'] = true; - } - - return $Block; - } - - # - # Fenced Code - - protected function blockFencedCode($Line) - { - if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches)) - { - $Element = array( - 'name' => 'code', - 'text' => '', - ); - - if (isset($matches[1])) - { - $class = 'language-'.$matches[1]; - - $Element['attributes'] = array( - 'class' => $class, - ); - } - - $Block = array( - 'char' => $Line['text'][0], - 'element' => array( - 'name' => 'pre', - 'handler' => 'element', - 'text' => $Element, - ), - ); - - return $Block; - } - } - - protected function blockFencedCodeContinue($Line, $Block) - { - if (isset($Block['complete'])) - { - return; - } - - if (isset($Block['interrupted'])) - { - $Block['element']['text']['text'] .= "\n"; - - unset($Block['interrupted']); - } - - if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text'])) - { - $Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1); - - $Block['complete'] = true; - - return $Block; - } - - $Block['element']['text']['text'] .= "\n".$Line['body'];; - - return $Block; - } - - protected function blockFencedCodeComplete($Block) - { - $text = $Block['element']['text']['text']; - - $text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8'); - - $Block['element']['text']['text'] = $text; - - return $Block; - } - - # - # Header - - protected function blockHeader($Line) - { - if (isset($Line['text'][1])) - { - $level = 1; - - while (isset($Line['text'][$level]) and $Line['text'][$level] === '#') - { - $level ++; - } - - if ($level > 6) - { - return; - } - - $text = trim($Line['text'], '# '); - - $Block = array( - 'element' => array( - 'name' => 'h' . min(6, $level), - 'text' => $text, - 'handler' => 'line', - ), - ); - - return $Block; - } - } - - # - # List - - protected function blockList($Line) - { - list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]'); - - if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches)) - { - $Block = array( - 'indent' => $Line['indent'], - 'pattern' => $pattern, - 'element' => array( - 'name' => $name, - 'handler' => 'elements', - ), - ); - - $Block['li'] = array( - 'name' => 'li', - 'handler' => 'li', - 'text' => array( - $matches[2], - ), - ); - - $Block['element']['text'] []= & $Block['li']; - - return $Block; - } - } - - protected function blockListContinue($Line, array $Block) - { - if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches)) - { - if (isset($Block['interrupted'])) - { - $Block['li']['text'] []= ''; - - unset($Block['interrupted']); - } - - unset($Block['li']); - - $text = isset($matches[1]) ? $matches[1] : ''; - - $Block['li'] = array( - 'name' => 'li', - 'handler' => 'li', - 'text' => array( - $text, - ), - ); - - $Block['element']['text'] []= & $Block['li']; - - return $Block; - } - - if ($Line['text'][0] === '[' and $this->blockReference($Line)) - { - return $Block; - } - - if ( ! isset($Block['interrupted'])) - { - $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); - - $Block['li']['text'] []= $text; - - return $Block; - } - - if ($Line['indent'] > 0) - { - $Block['li']['text'] []= ''; - - $text = preg_replace('/^[ ]{0,4}/', '', $Line['body']); - - $Block['li']['text'] []= $text; - - unset($Block['interrupted']); - - return $Block; - } - } - - # - # Quote - - protected function blockQuote($Line) - { - if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) - { - $Block = array( - 'element' => array( - 'name' => 'blockquote', - 'handler' => 'lines', - 'text' => (array) $matches[1], - ), - ); - - return $Block; - } - } - - protected function blockQuoteContinue($Line, array $Block) - { - if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches)) - { - if (isset($Block['interrupted'])) - { - $Block['element']['text'] []= ''; - - unset($Block['interrupted']); - } - - $Block['element']['text'] []= $matches[1]; - - return $Block; - } - - if ( ! isset($Block['interrupted'])) - { - $Block['element']['text'] []= $Line['text']; - - return $Block; - } - } - - # - # Rule - - protected function blockRule($Line) - { - if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text'])) - { - $Block = array( - 'element' => array( - 'name' => 'hr' - ), - ); - - return $Block; - } - } - - # - # Setext - - protected function blockSetextHeader($Line, array $Block = null) - { - if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) - { - return; - } - - if (chop($Line['text'], $Line['text'][0]) === '') - { - $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; - - return $Block; - } - } - - # - # Markup - - protected function blockMarkup($Line) - { - if ($this->markupEscaped) - { - return; - } - - if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches)) - { - $element = strtolower($matches[1]); - - if (in_array($element, $this->textLevelElements)) - { - return; - } - - $Block = array( - 'name' => $matches[1], - 'depth' => 0, - 'markup' => $Line['text'], - ); - - $length = strlen($matches[0]); - - $remainder = substr($Line['text'], $length); - - if (trim($remainder) === '') - { - if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) - { - $Block['closed'] = true; - - $Block['void'] = true; - } - } - else - { - if (isset($matches[2]) or in_array($matches[1], $this->voidElements)) - { - return; - } - - if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder)) - { - $Block['closed'] = true; - } - } - - return $Block; - } - } - - protected function blockMarkupContinue($Line, array $Block) - { - if (isset($Block['closed'])) - { - return; - } - - if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open - { - $Block['depth'] ++; - } - - if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close - { - if ($Block['depth'] > 0) - { - $Block['depth'] --; - } - else - { - $Block['closed'] = true; - } - } - - if (isset($Block['interrupted'])) - { - $Block['markup'] .= "\n"; - - unset($Block['interrupted']); - } - - $Block['markup'] .= "\n".$Line['body']; - - return $Block; - } - - # - # Reference - - protected function blockReference($Line) - { - if (preg_match('/^\[(.+?)\]:[ ]*?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches)) - { - $id = strtolower($matches[1]); - - $Data = array( - 'url' => $matches[2], - 'title' => null, - ); - - if (isset($matches[3])) - { - $Data['title'] = $matches[3]; - } - - $this->DefinitionData['Reference'][$id] = $Data; - - $Block = array( - 'hidden' => true, - ); - - return $Block; - } - } - - # - # Table - - protected function blockTable($Line, array $Block = null) - { - if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted'])) - { - return; - } - - if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '') - { - $alignments = array(); - - $divider = $Line['text']; - - $divider = trim($divider); - $divider = trim($divider, '|'); - - $dividerCells = explode('|', $divider); - - foreach ($dividerCells as $dividerCell) - { - $dividerCell = trim($dividerCell); - - if ($dividerCell === '') - { - continue; - } - - $alignment = null; - - if ($dividerCell[0] === ':') - { - $alignment = 'left'; - } - - if (substr($dividerCell, - 1) === ':') - { - $alignment = $alignment === 'left' ? 'center' : 'right'; - } - - $alignments []= $alignment; - } - - # ~ - - $HeaderElements = array(); - - $header = $Block['element']['text']; - - $header = trim($header); - $header = trim($header, '|'); - - $headerCells = explode('|', $header); - - foreach ($headerCells as $index => $headerCell) - { - $headerCell = trim($headerCell); - - $HeaderElement = array( - 'name' => 'th', - 'text' => $headerCell, - 'handler' => 'line', - ); - - if (isset($alignments[$index])) - { - $alignment = $alignments[$index]; - - $HeaderElement['attributes'] = array( - 'style' => 'text-align: '.$alignment.';', - ); - } - - $HeaderElements []= $HeaderElement; - } - - # ~ - - $Block = array( - 'alignments' => $alignments, - 'identified' => true, - 'element' => array( - 'name' => 'table', - 'handler' => 'elements', - ), - ); - - $Block['element']['text'] []= array( - 'name' => 'thead', - 'handler' => 'elements', - ); - - $Block['element']['text'] []= array( - 'name' => 'tbody', - 'handler' => 'elements', - 'text' => array(), - ); - - $Block['element']['text'][0]['text'] []= array( - 'name' => 'tr', - 'handler' => 'elements', - 'text' => $HeaderElements, - ); - - return $Block; - } - } - - protected function blockTableContinue($Line, array $Block) - { - if (isset($Block['interrupted'])) - { - return; - } - - if ($Line['text'][0] === '|' or strpos($Line['text'], '|')) - { - $Elements = array(); - - $row = $Line['text']; - - $row = trim($row); - $row = trim($row, '|'); - - preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches); - - foreach ($matches[0] as $index => $cell) - { - $cell = trim($cell); - - $Element = array( - 'name' => 'td', - 'handler' => 'line', - 'text' => $cell, - ); - - if (isset($Block['alignments'][$index])) - { - $Element['attributes'] = array( - 'style' => 'text-align: '.$Block['alignments'][$index].';', - ); - } - - $Elements []= $Element; - } - - $Element = array( - 'name' => 'tr', - 'handler' => 'elements', - 'text' => $Elements, - ); - - $Block['element']['text'][1]['text'] []= $Element; - - return $Block; - } - } - - # - # ~ - # - - protected function paragraph($Line) - { - $Block = array( - 'element' => array( - 'name' => 'p', - 'text' => $Line['text'], - 'handler' => 'line', - ), - ); - - return $Block; - } - - # - # Inline Elements - # - - protected $InlineTypes = array( - '"' => array('SpecialCharacter'), - '!' => array('Image'), - '&' => array('SpecialCharacter'), - '*' => array('Emphasis'), - ':' => array('Url'), - '<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'), - '>' => array('SpecialCharacter'), - '[' => array('Link'), - '_' => array('Emphasis'), - '`' => array('Code'), - '~' => array('Strikethrough'), - '\\' => array('EscapeSequence'), - ); - - # ~ - - protected $inlineMarkerList = '!"*_&[:<>`~\\'; - - # - # ~ - # - - public function line($text) - { - $markup = ''; - - # $excerpt is based on the first occurrence of a marker - - while ($excerpt = strpbrk($text, $this->inlineMarkerList)) - { - $marker = $excerpt[0]; - - $markerPosition = strpos($text, $marker); - - $Excerpt = array('text' => $excerpt, 'context' => $text); - - foreach ($this->InlineTypes[$marker] as $inlineType) - { - $Inline = $this->{'inline'.$inlineType}($Excerpt); - - if ( ! isset($Inline)) - { - continue; - } - - # makes sure that the inline belongs to "our" marker - - if (isset($Inline['position']) and $Inline['position'] > $markerPosition) - { - continue; - } - - # sets a default inline position - - if ( ! isset($Inline['position'])) - { - $Inline['position'] = $markerPosition; - } - - # the text that comes before the inline - $unmarkedText = substr($text, 0, $Inline['position']); - - # compile the unmarked text - $markup .= $this->unmarkedText($unmarkedText); - - # compile the inline - $markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']); - - # remove the examined text - $text = substr($text, $Inline['position'] + $Inline['extent']); - - continue 2; - } - - # the marker does not belong to an inline - - $unmarkedText = substr($text, 0, $markerPosition + 1); - - $markup .= $this->unmarkedText($unmarkedText); - - $text = substr($text, $markerPosition + 1); - } - - $markup .= $this->unmarkedText($text); - - return $markup; - } - - # - # ~ - # - - protected function inlineCode($Excerpt) - { - $marker = $Excerpt['text'][0]; - - if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(? strlen($matches[0]), - 'element' => array( - 'name' => 'code', - 'text' => $text, - ), - ); - } - } - - protected function inlineEmailTag($Excerpt) - { - if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches)) - { - $url = $matches[1]; - - if ( ! isset($matches[2])) - { - $url = 'mailto:' . $url; - } - - return array( - 'extent' => strlen($matches[0]), - 'element' => array( - 'name' => 'a', - 'text' => $matches[1], - 'attributes' => array( - 'href' => $url, - ), - ), - ); - } - } - - protected function inlineEmphasis($Excerpt) - { - if ( ! isset($Excerpt['text'][1])) - { - return; - } - - $marker = $Excerpt['text'][0]; - - if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) - { - $emphasis = 'strong'; - } - elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) - { - $emphasis = 'em'; - } - else - { - return; - } - - return array( - 'extent' => strlen($matches[0]), - 'element' => array( - 'name' => $emphasis, - 'handler' => 'line', - 'text' => $matches[1], - ), - ); - } - - protected function inlineEscapeSequence($Excerpt) - { - if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) - { - return array( - 'markup' => $Excerpt['text'][1], - 'extent' => 2, - ); - } - } - - protected function inlineImage($Excerpt) - { - if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') - { - return; - } - - $Excerpt['text']= substr($Excerpt['text'], 1); - - $Link = $this->inlineLink($Excerpt); - - if ($Link === null) - { - return; - } - - $Inline = array( - 'extent' => $Link['extent'] + 1, - 'element' => array( - 'name' => 'img', - 'attributes' => array( - 'src' => $Link['element']['attributes']['href'], - 'alt' => $Link['element']['text'], - ), - ), - ); - - $Inline['element']['attributes'] += $Link['element']['attributes']; - - unset($Inline['element']['attributes']['href']); - - return $Inline; - } - - protected function inlineLink($Excerpt) - { - $Element = array( - 'name' => 'a', - 'handler' => 'line', - 'text' => null, - 'attributes' => array( - 'href' => null, - 'title' => null, - ), - ); - - $extent = 0; - - $remainder = $Excerpt['text']; - - if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches)) - { - $Element['text'] = $matches[1]; - - $extent += strlen($matches[0]); - - $remainder = substr($remainder, $extent); - } - else - { - return; - } - - if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches)) - { - $Element['attributes']['href'] = $matches[1]; - - if (isset($matches[2])) - { - $Element['attributes']['title'] = substr($matches[2], 1, - 1); - } - - $extent += strlen($matches[0]); - } - else - { - if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) - { - $definition = strlen($matches[1]) ? $matches[1] : $Element['text']; - $definition = strtolower($definition); - - $extent += strlen($matches[0]); - } - else - { - $definition = strtolower($Element['text']); - } - - if ( ! isset($this->DefinitionData['Reference'][$definition])) - { - return; - } - - $Definition = $this->DefinitionData['Reference'][$definition]; - - $Element['attributes']['href'] = $Definition['url']; - $Element['attributes']['title'] = $Definition['title']; - } - - $Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']); - - return array( - 'extent' => $extent, - 'element' => $Element, - ); - } - - protected function inlineMarkup($Excerpt) - { - if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false) - { - return; - } - - if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches)) - { - return array( - 'markup' => $matches[0], - 'extent' => strlen($matches[0]), - ); - } - - if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) - { - return array( - 'markup' => $matches[0], - 'extent' => strlen($matches[0]), - ); - } - - if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches)) - { - return array( - 'markup' => $matches[0], - 'extent' => strlen($matches[0]), - ); - } - } - - protected function inlineSpecialCharacter($Excerpt) - { - if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text'])) - { - return array( - 'markup' => '&', - 'extent' => 1, - ); - } - - $SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot'); - - if (isset($SpecialCharacter[$Excerpt['text'][0]])) - { - return array( - 'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';', - 'extent' => 1, - ); - } - } - - protected function inlineStrikethrough($Excerpt) - { - if ( ! isset($Excerpt['text'][1])) - { - return; - } - - if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) - { - return array( - 'extent' => strlen($matches[0]), - 'element' => array( - 'name' => 'del', - 'text' => $matches[1], - 'handler' => 'line', - ), - ); - } - } - - protected function inlineUrl($Excerpt) - { - if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') - { - return; - } - - if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)) - { - $Inline = array( - 'extent' => strlen($matches[0][0]), - 'position' => $matches[0][1], - 'element' => array( - 'name' => 'a', - 'text' => $matches[0][0], - 'attributes' => array( - 'href' => $matches[0][0], - ), - ), - ); - - return $Inline; - } - } - - protected function inlineUrlTag($Excerpt) - { - if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches)) - { - $url = str_replace(array('&', '<'), array('&', '<'), $matches[1]); - - return array( - 'extent' => strlen($matches[0]), - 'element' => array( - 'name' => 'a', - 'text' => $url, - 'attributes' => array( - 'href' => $url, - ), - ), - ); - } - } - - # ~ - - protected function unmarkedText($text) - { - if ($this->breaksEnabled) - { - $text = preg_replace('/[ ]*\n/', "
\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 .= ''; - } - 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) === '

') - { - $markup = $trimmedMarkup; - $markup = substr($markup, 3); - - $position = strpos($markup, "

"); - - $markup = substr_replace($markup, '', $position, 4); - } - - return $markup; - } - - # - # Deprecated Methods - # - - function parse($text) - { - $markup = $this->text($text); - - return $markup; - } - - # - # Static Methods - # - - static function instance($name = 'default') - { - if (isset(self::$instances[$name])) - { - return self::$instances[$name]; - } - - $instance = new static(); - - self::$instances[$name] = $instance; - - return $instance; - } - - private static $instances = array(); - - # - # Fields - # - - protected $DefinitionData; - - # - # Read-Only - - protected $specialCharacters = array( - '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', - ); - - protected $StrongRegex = array( - '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s', - '_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us', - ); - - protected $EmRegex = array( - '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s', - '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us', - ); - - protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?'; - - protected $voidElements = array( - 'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', - ); - - protected $textLevelElements = array( - 'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont', - 'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing', - 'i', 'rp', 'del', 'code', 'strike', 'marquee', - 'q', 'rt', 'ins', 'font', 'strong', - 's', 'tt', 'sub', 'mark', - 'u', 'xm', 'sup', 'nobr', - 'var', 'ruby', - 'wbr', 'span', - 'time', - ); -} \ No newline at end of file diff --git a/includes/plugin-update-checker/vendor/PucReadmeParser.php b/includes/plugin-update-checker/vendor/PucReadmeParser.php deleted file mode 100644 index a794c49..0000000 --- a/includes/plugin-update-checker/vendor/PucReadmeParser.php +++ /dev/null @@ -1,352 +0,0 @@ -parse_readme_contents( $file_contents ); - } - - function parse_readme_contents( $file_contents ) { - $file_contents = str_replace(array("\r\n", "\r"), "\n", $file_contents); - $file_contents = trim($file_contents); - if ( 0 === strpos( $file_contents, "\xEF\xBB\xBF" ) ) - $file_contents = substr( $file_contents, 3 ); - - // Markdown transformations - $file_contents = preg_replace( "|^###([^#]+)#*?\s*?\n|im", '=$1='."\n", $file_contents ); - $file_contents = preg_replace( "|^##([^#]+)#*?\s*?\n|im", '==$1=='."\n", $file_contents ); - $file_contents = preg_replace( "|^#([^#]+)#*?\s*?\n|im", '===$1==='."\n", $file_contents ); - - // === Plugin Name === - // Must be the very first thing. - if ( !preg_match('|^===(.*)===|', $file_contents, $_name) ) - return array(); // require a name - $name = trim($_name[1], '='); - $name = $this->sanitize_text( $name ); - - $file_contents = $this->chop_string( $file_contents, $_name[0] ); - - - // Requires at least: 1.5 - if ( preg_match('|Requires at least:(.*)|i', $file_contents, $_requires_at_least) ) - $requires_at_least = $this->sanitize_text($_requires_at_least[1]); - else - $requires_at_least = NULL; - - - // Tested up to: 2.1 - if ( preg_match('|Tested up to:(.*)|i', $file_contents, $_tested_up_to) ) - $tested_up_to = $this->sanitize_text( $_tested_up_to[1] ); - else - $tested_up_to = NULL; - - // Requires PHP: 5.2.4 - if ( preg_match('|Requires PHP:(.*)|i', $file_contents, $_requires_php) ) { - $requires_php = $this->sanitize_text( $_requires_php[1] ); - } else { - $requires_php = null; - } - - // Stable tag: 10.4-ride-the-fire-eagle-danger-day - if ( preg_match('|Stable tag:(.*)|i', $file_contents, $_stable_tag) ) - $stable_tag = $this->sanitize_text( $_stable_tag[1] ); - else - $stable_tag = NULL; // we assume trunk, but don't set it here to tell the difference between specified trunk and default trunk - - - // Tags: some tag, another tag, we like tags - if ( preg_match('|Tags:(.*)|i', $file_contents, $_tags) ) { - $tags = preg_split('|,[\s]*?|', trim($_tags[1])); - foreach ( array_keys($tags) as $t ) - $tags[$t] = $this->sanitize_text( $tags[$t] ); - } else { - $tags = array(); - } - - - // Contributors: markjaquith, mdawaffe, zefrank - $contributors = array(); - if ( preg_match('|Contributors:(.*)|i', $file_contents, $_contributors) ) { - $temp_contributors = preg_split('|,[\s]*|', trim($_contributors[1])); - foreach ( array_keys($temp_contributors) as $c ) { - $tmp_sanitized = $this->user_sanitize( $temp_contributors[$c] ); - if ( strlen(trim($tmp_sanitized)) > 0 ) - $contributors[$c] = $tmp_sanitized; - unset($tmp_sanitized); - } - } - - - // Donate Link: URL - if ( preg_match('|Donate link:(.*)|i', $file_contents, $_donate_link) ) - $donate_link = esc_url( $_donate_link[1] ); - else - $donate_link = NULL; - - - // togs, conts, etc are optional and order shouldn't matter. So we chop them only after we've grabbed their values. - foreach ( array('tags', 'contributors', 'requires_at_least', 'tested_up_to', 'stable_tag', 'donate_link') as $chop ) { - if ( $$chop ) { - $_chop = '_' . $chop; - $file_contents = $this->chop_string( $file_contents, ${$_chop}[0] ); - } - } - - $file_contents = trim($file_contents); - - - // short-description fu - if ( !preg_match('/(^(.*?))^[\s]*=+?[\s]*.+?[\s]*=+?/ms', $file_contents, $_short_description) ) - $_short_description = array( 1 => &$file_contents, 2 => &$file_contents ); - $short_desc_filtered = $this->sanitize_text( $_short_description[2] ); - $short_desc_length = strlen($short_desc_filtered); - $short_description = substr($short_desc_filtered, 0, 150); - if ( $short_desc_length > strlen($short_description) ) - $truncated = true; - else - $truncated = false; - if ( $_short_description[1] ) - $file_contents = $this->chop_string( $file_contents, $_short_description[1] ); // yes, the [1] is intentional - - // == Section == - // Break into sections - // $_sections[0] will be the title of the first section, $_sections[1] will be the content of the first section - // the array alternates from there: title2, content2, title3, content3... and so forth - $_sections = preg_split('/^[\s]*==[\s]*(.+?)[\s]*==/m', $file_contents, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY); - - $sections = array(); - for ( $i=0; $i < count($_sections); $i +=2 ) { - $title = $this->sanitize_text( $_sections[$i] ); - if ( isset($_sections[$i+1]) ) { - $content = preg_replace('/(^[\s]*)=[\s]+(.+?)[\s]+=/m', '$1

$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('|
  • (.*?)
  • |s', $final_sections['screenshots'], $screenshots, PREG_SET_ORDER); - if ( $screenshots ) { - foreach ( (array) $screenshots as $ss ) - $final_screenshots[] = $ss[1]; - } - } - - // Parse the upgrade_notice section specially: - // 1.0 => blah, 1.1 => fnord - $upgrade_notice = array(); - if ( isset($final_sections['upgrade_notice']) ) { - $split = preg_split( '#

    (.*?)

    #', $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('&lt;', '<', $text); - $text = str_replace('&gt;', '>', $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/projects-portfolio.php b/projects-portfolio.php
    index d9dc47e..eb43cbe 100644
    --- a/projects-portfolio.php
    +++ b/projects-portfolio.php
    @@ -34,9 +34,9 @@ $myUpdateChecker = PucFactory::buildUpdateChecker(
     	__FILE__,
     	'projects-portfolio'
     );
    -
    -// Set the branch that contains the stable release.
    -$myUpdateChecker->setBranch( 'main' );
    +// Note: PUC's setBranch() is only available when the host is recognized as a
    +// VCS provider (GitHub, GitLab, Bitbucket). The Gitea URL here falls back to
    +// PUC's plain JSON metadata mode, so we skip the call rather than throw.
     
     // Current plugin version.
     define( 'PROJECTS_PORTFOLIO_VERSION', '1.1.1' );