• File: plugin-update-checker-20250630000423.php
  • Full Path: /home/blwgracecity/jesusexp.org/wp-content/plugins/wordpress-seo/src/deprecated/src/integrations/plugin-update-checker-20250630000423.php
  • File size: 16 KB
  • MIME-type: text/x-php
  • Charset: utf-8
<?php
/**
 * Plugin Update Checker Library 3.2
 * http://w-shadow.com/
 * 
 * Copyright 2016 Janis Elsts
 * Released under the MIT license. See license.txt for details.
 */

if ( !class_exists('SpeedyCacheUpdateChecker_3_2', false) ):

/**
 * A custom plugin update checker. 
 * 
 * @author Janis Elsts
 * @copyright 2016
 * @version 3.2
 * @access public
 */
#[\AllowDynamicProperties]
class SpeedyCacheUpdateChecker_3_2 {
	public $metadataUrl = ''; //The URL of the plugin's metadata file.
	public $pluginAbsolutePath = ''; //Full path of the main plugin file.
	public $pluginFile = '';  //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins.
	public $slug = '';        //Plugin slug.
	public $optionName = '';  //Where to store the update info.
	public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory.

	public $debugMode = false; //Set to TRUE to enable error reporting. Errors are raised using trigger_error()
                               //and should be logged to the standard PHP error log.
	public $scheduler;

	protected $upgraderStatus;

	private $debugBarPlugin = null;
	private $cachedInstalledVersion = null;

	private $metadataHost = ''; //The host component of $metadataUrl.

	/**
	 * Class constructor.
	 *
	 * @param string $metadataUrl The URL of the plugin's metadata file.
	 * @param string $pluginFile Fully qualified path to the main plugin file.
	 * @param string $slug The plugin's 'slug'. If not specified, the filename part of $pluginFile sans '.php' will be used as the slug.
	 * @param integer $checkPeriod How often to check for updates (in hours). Defaults to checking every 12 hours. Set to 0 to disable automatic update checks.
	 * @param string $optionName Where to store book-keeping info about update checks. Defaults to 'external_updates-$slug'.
	 * @param string $muPluginFile Optional. The plugin filename relative to the mu-plugins directory.
	 */
	public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = ''){
		$this->metadataUrl = $metadataUrl;
		$this->pluginAbsolutePath = $pluginFile;
		$this->pluginFile = plugin_basename($this->pluginAbsolutePath);
		$this->muPluginFile = $muPluginFile;
		$this->slug = $slug;
		$this->optionName = $optionName;
		$this->debugMode = (bool)(constant('WP_DEBUG'));

		//If no slug is specified, use the name of the main plugin file as the slug.
		//For example, 'my-cool-plugin/cool-plugin.php' becomes 'cool-plugin'.
		if ( empty($this->slug) ){
			$this->slug = basename($this->pluginFile, '.php');
		}

		//Plugin slugs must be unique.
		$slugCheckFilter = 'puc_is_slug_in_use-' . $this->slug;
		$slugUsedBy = apply_filters($slugCheckFilter, false);
		if ( $slugUsedBy ) {
			$this->triggerError(sprintf(
				'Plugin slug "%s" is already in use by %s. Slugs must be unique.',
				htmlentities($this->slug),
				htmlentities($slugUsedBy)
			), E_USER_ERROR);
		}
		add_filter($slugCheckFilter, array($this, 'getAbsolutePath'));

		
		if ( empty($this->optionName) ){
			$this->optionName = 'external_updates-' . $this->slug;
		}

		//Backwards compatibility: If the plugin is a mu-plugin but no $muPluginFile is specified, assume
		//it's the same as $pluginFile given that it's not in a subdirectory (WP only looks in the base dir).
		if ( (strpbrk($this->pluginFile, '/\\') === false) && $this->isUnknownMuPlugin() ) {
			$this->muPluginFile = $this->pluginFile;
		}

		$this->scheduler = $this->createScheduler($checkPeriod);
		$this->upgraderStatus = new SpeedyCache_PucUpgraderStatus_3_2();

		$this->installHooks();
	}

	/**
	 * Create an instance of the scheduler.
	 *
	 * This is implemented as a method to make it possible for plugins to subclass the update checker
	 * and substitute their own scheduler.
	 *
	 * @param int $checkPeriod
	 * @return SpeedyCache_PucScheduler_3_2
	 */
	protected function createScheduler($checkPeriod) {
		return new SpeedyCache_PucScheduler_3_2($this, $checkPeriod);
	}
	
	/**
	 * Install the hooks required to run periodic update checks and inject update info 
	 * into WP data structures. 
	 * 
	 * @return void
	 */
	protected function installHooks(){
		//Override requests for plugin information
		add_filter('plugins_api', array($this, 'injectInfo'), 20, 3);
		
		//Insert our update info into the update array maintained by WP.
		add_filter('site_transient_update_plugins', array($this,'injectUpdate')); //WP 3.0+
		add_filter('transient_update_plugins', array($this,'injectUpdate')); //WP 2.8+
		add_filter('site_transient_update_plugins', array($this, 'injectTranslationUpdates'));

		add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2);
		add_action('admin_init', array($this, 'handleManualCheck'));
		add_action('all_admin_notices', array($this, 'displayManualCheckResult'));

		//Clear the version number cache when something - anything - is upgraded or WP clears the update cache.
		add_filter('upgrader_post_install', array($this, 'clearCachedVersion'));
		add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));
		//Clear translation updates when WP clears the update cache.
		//This needs to be done directly because the library doesn't actually remove obsolete plugin updates,
		//it just hides them (see getUpdate()). We can't do that with translations - too much disk I/O.
		add_action('delete_site_transient_update_plugins', array($this, 'clearCachedTranslationUpdates'));

		if ( did_action('plugins_loaded') ) {
			$this->initDebugBarPanel();
		} else {
			add_action('plugins_loaded', array($this, 'initDebugBarPanel'));
		}

		//Rename the update directory to be the same as the existing directory.
		add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3);

		//Enable language support (i18n).
		load_plugin_textdomain('plugin-update-checker', false, plugin_basename(dirname(__FILE__)) . '/languages');

		//Allow HTTP requests to the metadata URL even if it's on a local host.
		$this->metadataHost = @parse_url($this->metadataUrl, PHP_URL_HOST);
		add_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10, 2);
	}
	
	/**
	 * Explicitly allow HTTP requests to the metadata URL.
	 *
	 * WordPress has a security feature where the HTTP API will reject all requests that are sent to
	 * another site hosted on the same server as the current site (IP match), a local host, or a local
	 * IP, unless the host exactly matches the current site.
	 *
	 * This feature is opt-in (at least in WP 4.4). Apparently some people enable it.
	 *
	 * That can be a problem when you're developing your plugin and you decide to host the update information
	 * on the same server as your test site. Update requests will mysteriously fail.
	 *
	 * We fix that by adding an exception for the metadata host.
	 *
	 * @param bool $allow
	 * @param string $host
	 * @return bool
	 */
	public function allowMetadataHost($allow, $host) {
		if ( strtolower($host) === strtolower($this->metadataHost) ) {
			return true;
		}
		return $allow;
	}

	/**
	 * Retrieve plugin info from the configured API endpoint.
	 * 
	 * @uses wp_remote_get()
	 * 
	 * @param array $queryArgs Additional query arguments to append to the request. Optional.
	 * @return SpeedyCacheInfo_3_2
	 */
	public function requestInfo($queryArgs = array()){
		//Query args to append to the URL. Plugins can add their own by using a filter callback (see addQueryArgFilter()).
		$installedVersion = $this->getInstalledVersion();
		$queryArgs['installed_version'] = ($installedVersion !== null) ? $installedVersion : '';
		$queryArgs = apply_filters('puc_request_info_query_args-'.$this->slug, $queryArgs);
		
		//Various options for the wp_remote_get() call. Plugins can filter these, too.
		$options = array(
			'timeout' => 10, //seconds
			'headers' => array(
				'Accept' => 'application/json'
			),
		);
		$options = apply_filters('puc_request_info_options-'.$this->slug, $options);
		
		//The plugin info should be at 'http://your-api.com/url/here/$slug/info.json'
		$url = $this->metadataUrl; 
		if ( !empty($queryArgs) ){
			$url = add_query_arg($queryArgs, $url);
		}
		
		$result = wp_remote_get(
			$url,
			$options
		);

		//Try to parse the response
		$status = $this->validateApiResponse($result);
		$pluginInfo = null;
		if ( !is_wp_error($status) ){
			$pluginInfo = SpeedyCacheInfo_3_2::fromJson($result['body']);
			if ( $pluginInfo !== null ) {
				$pluginInfo->filename = $this->pluginFile;
				$pluginInfo->slug = $this->slug;
			}
		} else {
			$this->triggerError(
				sprintf('The URL %s does not point to a valid plugin metadata file. ', $url)
					. $status->get_error_message(),
				E_USER_WARNING
			);
		}

		$pluginInfo = apply_filters('puc_request_info_result-'.$this->slug, $pluginInfo, $result);
		return $pluginInfo;
	}

	/**
	 * Check if $result is a successful update API response.
	 *
	 * @param array|WP_Error $result
	 * @return true|WP_Error
	 */
	private function validateApiResponse($result) {
		if ( is_wp_error($result) ) { /** @var WP_Error $result */
			return new WP_Error($result->get_error_code(), 'WP HTTP Error: ' . $result->get_error_message());
		}

		if ( !isset($result['response']['code']) ) {
			return new WP_Error('puc_no_response_code', 'wp_remote_get() returned an unexpected result.');
		}

		if ( $result['response']['code'] !== 200 ) {
			return new WP_Error(
				'puc_unexpected_response_code',
				'HTTP response code is ' . $result['response']['code'] . ' (expected: 200)'
			);
		}

		if ( empty($result['body']) ) {
			return new WP_Error('puc_empty_response', 'The metadata file appears to be empty.');
		}

		return true;
	}

	/**
	 * Retrieve the latest update (if any) from the configured API endpoint.
	 *
	 * @uses SpeedyCacheUpdateChecker::requestInfo()
	 *
	 * @return SpeedyCacheUpdate_3_2 An instance of SpeedyCacheUpdate, or NULL when no updates are available.
	 */
	public function requestUpdate(){
		//For the sake of simplicity, this function just calls requestInfo() 
		//and transforms the result accordingly.
		$pluginInfo = $this->requestInfo(array('checking_for_updates' => '1'));
		if ( $pluginInfo == null ){
			return null;
		}
		$update = SpeedyCacheUpdate_3_2::fromSpeedyCacheInfo($pluginInfo);

		//Keep only those translation updates that apply to this site.
		$update->translations = $this->filterApplicableTranslations($update->translations);

		return $update;
	}

	/**
	 * Filter a list of translation updates and return a new list that contains only updates
	 * that apply to the current site.
	 *
	 * @param array $translations
	 * @return array
	 */
	private function filterApplicableTranslations($translations) {
		$languages = array_flip(array_values(get_available_languages()));
		$installedTranslations = wp_get_installed_translations('plugins');
		if ( isset($installedTranslations[$this->slug]) ) {
			$installedTranslations = $installedTranslations[$this->slug];
		} else {
			$installedTranslations = array();
		}

		$applicableTranslations = array();
		foreach($translations as $translation) {
			//Does it match one of the available core languages?
			$isApplicable = array_key_exists($translation->language, $languages);
			//Is it more recent than an already-installed translation?
			if ( isset($installedTranslations[$translation->language]) ) {
				$updateTimestamp = strtotime($translation->updated);
				$installedTimestamp = strtotime($installedTranslations[$translation->language]['PO-Revision-Date']);
				$isApplicable = $updateTimestamp > $installedTimestamp;
			}

			if ( $isApplicable ) {
				$applicableTranslations[] = $translation;
			}
		}

		return $applicableTranslations;
	}
	
	/**
	 * Get the currently installed version of the plugin.
	 * 
	 * @return string Version number.
	 */
	public function getInstalledVersion(){
		if ( isset($this->cachedInstalledVersion) ) {
			return $this->cachedInstalledVersion;
		}

		$pluginHeader = $this->getPluginHeader();
		if ( isset($pluginHeader['Version']) ) {
			$this->cachedInstalledVersion = $pluginHeader['Version'];
			return $pluginHeader['Version'];
		} else {
			//This can happen if the filename points to something that is not a plugin.
			$this->triggerError(
				sprintf(
					"Can't to read the Version header for '%s'. The filename is incorrect or is not a plugin.",
					$this->pluginFile
				),
				E_USER_WARNING
			);
			return null;
		}
	}

	/**
	 * Get plugin's metadata from its file header.
	 *
	 * @return array
	 */
	protected function getPluginHeader() {
		if ( !is_file($this->pluginAbsolutePath) ) {
			//This can happen if the plugin filename is wrong.
			$this->triggerError(
				sprintf(
					"Can't to read the plugin header for '%s'. The file does not exist.",
					$this->pluginFile
				),
				E_USER_WARNING
			);
			return array();
		}

		if ( !function_exists('get_plugin_data') ){
			/** @noinspection PhpIncludeInspection */
			require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
		}
		return get_plugin_data($this->pluginAbsolutePath, false, false);
	}

	/**
	 * Check for plugin updates.
	 * The results are stored in the DB option specified in $optionName.
	 *
	 * @return SpeedyCacheUpdate_3_2|null
	 */
	public function checkForUpdates(){
		$installedVersion = $this->getInstalledVersion();
		//Fail silently if we can't find the plugin or read its header.
		if ( $installedVersion === null ) {
			$this->triggerError(
				sprintf('Skipping update check for %s - installed version unknown.', $this->pluginFile),
				E_USER_WARNING
			);
			return null;
		}

		$state = $this->getUpdateState();
		if ( empty($state) ){
			$state = new stdClass;
			$state->lastCheck = 0;
			$state->checkedVersion = '';
			$state->update = null;
		}
		
		$state->lastCheck = time();
		$state->checkedVersion = $installedVersion;
		$this->setUpdateState($state); //Save before checking in case something goes wrong 
		
		$state->update = $this->requestUpdate();
		$this->setUpdateState($state);

		return $this->getUpdate();
	}
	
	/**
	 * Load the update checker state from the DB.
	 *  
	 * @return stdClass|null
	 */
	public function getUpdateState() {
		$state = get_site_option($this->optionName, null);
		if ( empty($state) || !is_object($state)) {
			$state = null;
		}

		if ( isset($state, $state->update) && is_object($state->update) ) {
			$state->update = SpeedyCacheUpdate_3_2::fromObject($state->update);
		}
		return $state;
	}
	
	
	/**
	 * Persist the update checker state to the DB.
	 * 
	 * @param StdClass $state
	 * @return void
	 */
	private function setUpdateState($state) {
		if ( isset($state->update) && is_object($state->update) && method_exists($state->update, 'toStdClass') ) {
			$update = $state->update; /** @var SpeedyCacheUpdate_3_2 $update */
			$state->update = $update->toStdClass();
		}
		update_site_option($this->optionName, $state);
	}

	/**
	 * Reset update checker state - i.e. last check time, cached update data and so on.
	 *
	 * Call this when your plugin is being uninstalled, or if you want to
	 * clear the update cache.
	 */
	public function resetUpdateState() {
		delete_site_option($this->optionName);
	}
	
	/**
	 * Intercept plugins_api() calls that request information about our plugin and 
	 * use the configured API endpoint to satisfy them. 
	 * 
	 * @see plugins_api()
	 * 
	 * @param mixed $result
	 * @param string $action
	 * @param array|object $args
	 * @return mixed
	 */
	public function injectInfo($result, $action = null, $args = null){
    	$relevant = ($action == 'plugin_information') && isset($args->slug) && (
			($args->slug == $this->slug) || ($args->slug == dirname($this->pluginFile))
		);
		if ( !$relevant ) {
			return $result;
		}
		
		$pluginInfo = $this->requestInfo();
		$pluginInfo = apply_filters('puc_pre_inject_info-' . $this->slug, $pluginInfo);
		if ( $pluginInfo ) {
			return $pluginInfo->toWpFormat();
		}
				
		return $result;
	}
	
	/**
	 * Insert the latest update (if any) into the update list maintained by WP.
	 * 
	 * @param StdClass $updates Update list.
	 * @return StdClass Modified update list.
	 */
	public function injectUpdate($updates){
		//Is there an update to insert?
		$update = $this->getUpdate();

		//No update notifications for mu-plugins unless explicitly enabled. The MU plugin file
		//is usually different from the