class-update-manager.php 5.84 KB
Newer Older
imac's avatar
imac committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216
<?php

if ( ! class_exists( "Yoast_Update_Manager", false ) ) {

	class Yoast_Update_Manager {

		/**
		 * @var Yoast_Product
		 */
		protected $product;

		/**
		 * @var Yoast_License_Manager
		 */
		protected $license_manager;

		/**
		 * @var string
		 */
		protected $error_message = '';

		/**
		 * @var object
		 */
		protected $update_response = null;

		/**
		 * @var string The transient name storing the API response
		 */
		private $response_transient_key = '';

		/**
		 * @var string The transient name that stores failed request tries
		 */
		private $request_failed_transient_key = '';

		/**
		 * Constructor
		 *
		 * @param Yoast_Product         $product         The product.
		 * @param Yoast_License_Manager $license_manager The License Manager.
		 */
		public function __construct( Yoast_Product $product, $license_manager ) {
			$this->product         = $product;
			$this->license_manager = $license_manager;

			// generate transient names
			$this->response_transient_key       = $this->product->get_transient_prefix() . '-update-response';
			$this->request_failed_transient_key = $this->product->get_transient_prefix() . '-update-request-failed';

			// maybe delete transient
			$this->maybe_delete_transients();
		}

		/**
		 * Deletes the various transients
		 * If we're on the update-core.php?force-check=1 page
		 */
		private function maybe_delete_transients() {
			global $pagenow;

			if ( $pagenow === 'update-core.php' && isset( $_GET['force-check'] ) ) {
				delete_transient( $this->response_transient_key );
				delete_transient( $this->request_failed_transient_key );
			}
		}

		/**
		 * If the update check returned a WP_Error, show it to the user
		 */
		public function show_update_error() {

			if ( $this->error_message === '' ) {
				return;
			}

			?>
			<div class="notice notice-error yoast-notice-error">
				<p><?php printf( __( '%s failed to check for updates because of the following error: <em>%s</em>', $this->product->get_text_domain() ), $this->product->get_item_name(), $this->error_message ); ?></p>
			</div>
			<?php
		}

		/**
		 * Calls the API and, if successfull, returns the object delivered by the API.
		 *
		 * @uses         get_bloginfo()
		 * @uses         wp_remote_post()
		 * @uses         is_wp_error()
		 *
		 * @return false||object
		 */
		private function call_remote_api() {

			// only check if the failed transient is not set (or if it's expired)
			if ( get_transient( $this->request_failed_transient_key ) !== false ) {
				return false;
			}

			// start request process
			global $wp_version;

			// set a transient to prevent failed update checks on every page load
			// this transient will be removed if a request succeeds
			set_transient( $this->request_failed_transient_key, 'failed', 10800 );

			// setup api parameters
			$api_params = array(
				'edd_action'   => 'get_version',
				'license'      => $this->license_manager->get_license_key(),
				'item_name'    => $this->product->get_item_name(),
				'wp_version'   => $wp_version,
				'item_version' => $this->product->get_version(),
				'url'          => home_url(),
				'slug'         => $this->product->get_slug(),
			);

			// Add product ID from product if it is implemented.
			if ( method_exists( $this->product, 'get_product_id' ) ) {
				$product_id = $this->product->get_product_id();
				if ( $product_id > 0 ) {
					$api_params['product_id'] = $this->product->get_product_id();
				}
			}

			// setup request parameters
			$request_params = array(
				'method' => 'POST',
				'body'   => $api_params
			);

			require_once dirname( __FILE__ ) . '/class-api-request.php';
			$request = new Yoast_API_Request( $this->product->get_api_url(), $request_params );

			if ( $request->is_valid() !== true ) {

				// show error message
				$this->error_message = $request->get_error_message();
				add_action( 'admin_notices', array( $this, 'show_update_error' ) );

				return false;
			}

			// request succeeded, delete transient indicating a request failed
			delete_transient( $this->request_failed_transient_key );

			// decode response
			$response = $request->get_response();

			// check if response returned that a given site was inactive
			if ( isset( $response->license_check ) && ! empty( $response->license_check ) && $response->license_check != 'valid' ) {

				// deactivate local license
				$this->license_manager->set_license_status( 'invalid' );

				// show notice to let the user know we deactivated his/her license
				$this->error_message = __( "This site has not been activated properly on yoast.com and thus cannot check for future updates. Please activate your site with a valid license key.", $this->product->get_text_domain() );
				add_action( 'admin_notices', array( $this, 'show_update_error' ) );
			}

			$response->sections = maybe_unserialize( $response->sections );

			// store response
			set_transient( $this->response_transient_key, $response, 10800 );

			return $response;
		}

		/**
		 * Gets the remote product data (from the EDD API)
		 *
		 * - If it was previously fetched in the current requests, this gets it from the instance property
		 * - Next, it tries the 3-hour transient
		 * - Next, it calls the remote API and stores the result
		 *
		 * @return object
		 */
		protected function get_remote_data() {

			// always use property if it's set
			if ( null !== $this->update_response ) {
				return $this->update_response;
			}

			// get cached remote data
			$data = $this->get_cached_remote_data();

			// if cache is empty or expired, call remote api
			if ( $data === false ) {
				$data = $this->call_remote_api();
			}

			$this->update_response = $data;

			return $data;
		}

		/**
		 * Gets the remote product data from a 3-hour transient
		 *
		 * @return bool|mixed
		 */
		private function get_cached_remote_data() {

			$data = get_transient( $this->response_transient_key );

			if ( $data ) {
				return $data;
			}

			return false;
		}

	}

}