123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- <?php
- namespace YahnisElsts\PluginUpdateChecker\v5p1\Plugin;
- if ( !class_exists('Ui', false) ):
- /**
- * Additional UI elements for plugins.
- */
- class Ui {
- private $updateChecker;
- private $manualCheckErrorTransient = '';
- /**
- * @param UpdateChecker $updateChecker
- */
- public function __construct($updateChecker) {
- $this->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('<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
- 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('<a href="%s">%s</a>', esc_attr($linkUrl), $linkText);
- }
- }
- return $pluginMeta;
- }
- protected function isMyPluginFile($pluginFile) {
- return ($pluginFile == $this->updateChecker->pluginFile)
- || (!empty($this->updateChecker->muPluginFile) && ($pluginFile == $this->updateChecker->muPluginFile));
- }
- /**
- * Check for updates when the user clicks the "Check for updates" link.
- *
- * @see self::addCheckForUpdatesLink()
- *
- * @return void
- */
- public function handleManualCheck() {
- $shouldCheck =
- isset($_GET['puc_check_for_updates'], $_GET['puc_slug'])
- && $_GET['puc_slug'] == $this->updateChecker->slug
- && check_admin_referer('puc_check_for_updates');
- if ( $shouldCheck ) {
- $update = $this->updateChecker->checkForUpdates();
- $status = ($update === null) ? 'no_update' : 'update_available';
- $lastRequestApiErrors = $this->updateChecker->getLastRequestApiErrors();
- if ( ($update === null) && !empty($lastRequestApiErrors) ) {
- //Some errors are not critical. For example, if PUC tries to retrieve the readme.txt
- //file from GitHub and gets a 404, that's an API error, but it doesn't prevent updates
- //from working. Maybe the plugin simply doesn't have a readme.
- //Let's only show important errors.
- $foundCriticalErrors = false;
- $questionableErrorCodes = array(
- 'puc-github-http-error',
- 'puc-gitlab-http-error',
- 'puc-bitbucket-http-error',
- );
- foreach ($lastRequestApiErrors as $item) {
- $wpError = $item['error'];
- /** @var \WP_Error $wpError */
- if ( !in_array($wpError->get_error_code(), $questionableErrorCodes) ) {
- $foundCriticalErrors = true;
- break;
- }
- }
- if ( $foundCriticalErrors ) {
- $status = 'error';
- set_site_transient($this->manualCheckErrorTransient, $lastRequestApiErrors, 60);
- }
- }
- wp_redirect(add_query_arg(
- array(
- 'puc_update_check_result' => $status,
- 'puc_slug' => $this->updateChecker->slug,
- ),
- self_admin_url('plugins.php')
- ));
- exit;
- }
- }
- /**
- * Display the results of a manual update check.
- *
- * @see self::handleManualCheck()
- *
- * You can change the result message by using the "puc_manual_check_message-$slug" filter.
- */
- public function displayManualCheckResult() {
- //phpcs:disable WordPress.Security.NonceVerification.Recommended -- Just displaying a message.
- if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->updateChecker->slug) ) {
- $status = sanitize_key($_GET['puc_update_check_result']);
- $title = $this->updateChecker->getInstalledPackage()->getPluginTitle();
- $noticeClass = 'updated notice-success';
- $details = '';
- if ( $status == 'no_update' ) {
- $message = sprintf(_x('The %s plugin is up to date.', 'the plugin title', 'plugin-update-checker'), $title);
- } else if ( $status == 'update_available' ) {
- $message = sprintf(_x('A new version of the %s plugin is available.', 'the plugin title', 'plugin-update-checker'), $title);
- } else if ( $status === 'error' ) {
- $message = sprintf(_x('Could not determine if updates are available for %s.', 'the plugin title', 'plugin-update-checker'), $title);
- $noticeClass = 'error notice-error';
- $details = $this->formatManualCheckErrors(get_site_transient($this->manualCheckErrorTransient));
- delete_site_transient($this->manualCheckErrorTransient);
- } else {
- $message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), $status);
- $noticeClass = 'error notice-error';
- }
- $message = esc_html($message);
- //Plugins can replace the message with their own, including adding HTML.
- $message = apply_filters(
- $this->updateChecker->getUniqueName('manual_check_message'),
- $message,
- $status
- );
- printf(
- '<div class="notice %s is-dismissible"><p>%s</p>%s</div>',
- esc_attr($noticeClass),
- //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Was escaped above, and plugins can add HTML.
- $message,
- //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Contains HTML. Content should already be escaped.
- $details
- );
- }
- //phpcs:enable
- }
- /**
- * Format the list of errors that were thrown during an update check.
- *
- * @param array $errors
- * @return string
- */
- protected function formatManualCheckErrors($errors) {
- if ( empty($errors) ) {
- return '';
- }
- $output = '';
- $showAsList = count($errors) > 1;
- if ( $showAsList ) {
- $output .= '<ol>';
- $formatString = '<li>%1$s <code>%2$s</code></li>';
- } else {
- $formatString = '<p>%1$s <code>%2$s</code></p>';
- }
- foreach ($errors as $item) {
- $wpError = $item['error'];
- /** @var \WP_Error $wpError */
- $output .= sprintf(
- $formatString,
- esc_html($wpError->get_error_message()),
- esc_html($wpError->get_error_code())
- );
- }
- if ( $showAsList ) {
- $output .= '</ol>';
- }
- return $output;
- }
- public function removeHooks() {
- remove_action('admin_init', array($this, 'onAdminInit'));
- remove_filter('plugin_row_meta', array($this, 'addViewDetailsLink'), 10);
- remove_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10);
- remove_action('all_admin_notices', array($this, 'displayManualCheckResult'));
- }
- }
- endif;
|