Updated update server address
authorChuck Scott <cscott@gaslightmedia.com>
Fri, 7 Oct 2016 20:57:55 +0000 (16:57 -0400)
committerChuck Scott <cscott@gaslightmedia.com>
Fri, 7 Oct 2016 20:57:55 +0000 (16:57 -0400)
index.php
lib/opentools-update-checker/opentools-update-checker.php
lib/plugin-update-checker/plugin-update-checker.php

index 6231550..b15f765 100644 (file)
--- a/index.php
+++ b/index.php
@@ -163,8 +163,7 @@ function serverStatsController($model)
  */
 require GLM_SERVERSTATS_PLUGIN_LIB_PATH.'/opentools-update-checker/opentools-update-checker.php';
 ${GLM_SERVERSTATS_PREFIX."updateChecker"} = new OpenToolsPluginUpdateChecker(
-//     'http://gaslightmedia.gaslightmedia.com/GlmUpdateServer/?action=get_metadata&slug='.GLM_SERVERSTATS_PLUGIN_SLUG,
-    'http://192.168.44.51/update_server/?action=get_metadata&slug='.GLM_SERVERSTATS_PLUGIN_SLUG,
+     'http://www.gaslightmedia.com/update_server/?action=get_metadata&slug='.GLM_SERVERSTATS_PLUGIN_SLUG,
     __FILE__,
     GLM_SERVERSTATS_PLUGIN_SLUG
 );
index a4f4ad5..853ef8f 100644 (file)
@@ -18,228 +18,228 @@ if (!class_exists('OpenToolsPluginUpdateChecker')):
 require dirname(__FILE__).'/../plugin-update-checker/plugin-update-checker.php';
 
 class OpenToolsPluginUpdateChecker extends PluginUpdateChecker_3_1 {
-       protected $credvars = array();
-       protected $ajaxurl = '';
-       public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '')
-       {
-               parent::__construct($metadataUrl, $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile);
-               $this->installOTHooks();
-       }
-       public function declareCredentials($credential_def) {
-               $this->credvars = $credential_def;
-               // Append the update credentials to the update server link
-               $this->addQueryArgFilter(array($this, 'appendQueryArgsCredentials'));
-
-       }
-
-       protected function installOTHooks()
-       {
-               $this->ajaxurl = is_network_admin()?network_admin_url( 'admin-ajax.php' ): admin_url( 'admin-ajax.php' );
-
-               add_action('admin_print_scripts-plugins.php', array($this, 'addCredentialCheckScripts'));
-               add_action('admin_print_styles-plugins.php',  array($this, 'addCredentialCheckStyles'));
-
-//             add_filter('plugin_row_meta', array($this, 'displayUpdateCredentialsLink'), 9, 2);
-               add_filter('plugin_action_links_'.$this->pluginFile, array($this, 'displayUpdateCredentialsLink'), 9, 2);
-
-               add_action( 'wp_ajax_getUpdateCredentialsRow',          array( &$this, 'getUpdateCredentialsRow') );
-               add_action( 'wp_ajax_submitUpdateCredentials',          array( &$this, 'submitUpdateCredentials') );
-       }
-
-       protected function getCredentials($slug)
-       {
-               $credentials = array('validated' => FALSE);
-               foreach ($this->credvars as $credkey => $credname) {
-                       $credentials[$credkey] = get_option('otup_credentials_'.$slug.'_'.$credkey);
-               }
-               $credentials['validated']    = get_option('otup_credentials_validated_'.$slug);
-               return $credentials;
-       }
-
-       protected function setCredentials($slug, $credentials, $validated = false)
-       {
-               foreach ($credentials as $credkey => $credvalue) {
-                       update_option('otup_credentials_'.$slug.'_'.$credkey, $credvalue, false);
-               }
-               update_option('otup_credentials_validated_'.$slug,    $validated,   false);
-       }
-
-
-       public function addCredentialCheckScripts() {
-               wp_register_script( 'opentools-updatecheck', plugins_url('js/opentools-updatecheck.js', __FILE__), array('jquery'));
-               wp_enqueue_script( 'opentools-updatecheck');
-       }
-
-       public function addCredentialCheckStyles() {
-               wp_register_style( 'opentools-updatecheck', plugins_url('css/opentools-updatecheck.css', __FILE__));
-               wp_enqueue_style( 'opentools-updatecheck');
-       }
-
-       /** Append the ordernumber and order password to the update server URL
-        */
-       public function appendQueryArgsCredentials($queryArgs) {
-               $credentials = $this->getCredentials($this->slug);
-               foreach ($credentials as $credkey => $credvalue) {
-                       $queryArgs[$credkey] = $credvalue;
-               }
-               return $queryArgs;
-       }
-
-       /**
-        * Add a "Update Credentials" link to the plugin row in the "Plugins" page. By default,
-        * the new link will appear after the "Visit plugin site" link.
-        *
-        * You can change the link text by using the "otup_enter_update_credentials-$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 displayUpdateCredentialsLink($links, $pluginFile) {
-               $isRelevant = ($pluginFile == $this->pluginFile)
-                             || (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);
-               $isRelevant = $isRelevant && !empty($this->credvars);
-
-               if ( $isRelevant && current_user_can('update_plugins') ) {
-                       $credentials = $this->getCredentials($this->slug);
-                       $textcolor = $credentials['validated']?'<span>':'<span style="color: #c50034;">';
-                       $linkText = apply_filters('otup_enter_update_credentials-' . $this->slug, $textcolor.__('Update License Key</span>', 'oton-updates').'</span>');
-                       if ( !empty($linkText) ) {
-                               $iconyesno = $credentials['validated']?'yes':'no';
-                               $link = sprintf('<a href="#" onClick=\'return showUpdateCredentialsRow(this);\' class="dashicons-before dashicons-'.$iconyesno.' otup_credentials_link_'.$this->slug.'" data-slug="%s" data-nonce="%s" data-ajaxurl="%s" >%s</a>', esc_attr($this->slug), esc_attr(wp_create_nonce( 'otup_enter_update_credentials' )), esc_attr($this->ajaxurl), $linkText);
-                               array_unshift($links, $link);
-                       }
-               }
-               return $links;
-       }
-
-
-       /**
-       * If the user has clicked on the "Update Credentials" link, display the input boxes after the plugin row.
-       *
-       * @param string $file
-       * @param array  $plugin_data
-       * @return false|void
-       */
-       public function getUpdateCredentialsRow() {
-               $json = array('row' => '', 'message'=>'Unsuccessful');
-
-               $showCredentials = isset($_REQUEST['slug'])
-                       && $_REQUEST['slug'] == $this->slug
-                       && current_user_can('update_plugins')
-                       && check_ajax_referer('otup_enter_update_credentials');
-               $showCredentials = $showCredentials && !empty($this->credvars);
-
-               if ( $showCredentials && (is_network_admin() || !is_multisite() )) {
-                       $slug = $this->slug;
-                       if ( is_network_admin() ) {
-                               $active_class = is_plugin_active_for_network( $this->pluginFile ) ? 'active': '';
-                       } else {
-                               $active_class = is_plugin_active( $this->pluginFile ) ? 'active' : '';
-                       }
-
-                       $current_credentials = $this->getCredentials($slug);
-
-                       $tr = '<tr class="' . $active_class . ' otup_update_credentials" id="' . esc_attr( $slug . '-credentials' ) . '" >';
-                       $tr .= '<th colspan="3" class="check-column colspanchange">';
-                       $tr .= '<div class="update-credentials">';
-                       $tr .= '<div class="update-credentials-message">';
-                       $tr .= '</div>';
-                       $tr .= '<div class="update-credentials-form">';
-
-                       foreach ($this->credvars as $credkey => $credname) {
-                               $tr .= $credname    . " <input type=\"text\" name=\"otup_update_credentials[$slug][$credkey]\" value=\"" . esc_attr($current_credentials[$credkey]) . "\">&nbsp;&nbsp;&nbsp;";
-                       }
-
-                       $tr .= sprintf('<input type="submit" class="button otup_update_credentials_submit" onclick="return submitUpdateCredentials(this);" data-slug="%s" data-nonce="%s" data-ajaxurl="%s" data-credentialvars=\'%s\'>',
-                               esc_attr($this->slug),
-                               esc_attr(wp_create_nonce( 'otup_enter_update_credentials_'.$slug )),
-                               esc_attr($this->ajaxurl),
-                               esc_attr(json_encode(array_keys($this->credvars)))
-                       );
-
-                       $tr .= '</div>';
-                       $tr .= '</div></th></tr>';
-                       $json['row'] = $tr;
-                       $json['message'] = '';
-               } else {
-                       $json['message'] = __("No permissions to modify update credentials", "opentools-updatecheck");
-               }
-               wp_send_json($json);
-       }
-
-
-       /**
-        * Check the submitted update credentials for correctness and save them
-        *
-        * @return void
-        */
-       public function submitUpdateCredentials() {
-               $json = array('message' => '', 'success' => FALSE);
-               $slug = isset($_REQUEST['slug'])?($_REQUEST['slug']):"INVALIDSLUG";
-
-               $submitCredentials = $slug == $this->slug
-                       && current_user_can('update_plugins')
-                       && check_ajax_referer('otup_enter_update_credentials_'.$slug);
-               $submitCredentials = $submitCredentials && !empty($this->credvars);
-
-               if ( $submitCredentials ) {
-                       $credentials = array();
-                       foreach ($this->credvars as $credkey=>$credname) {
-                               if (isset($_REQUEST[$credkey])) {
-                                       $credentials[$credkey] = $_REQUEST[$credkey];
-                               }
-                       }
-
-                       $message = "";
-                       $validated = $this->checkUpdateCredentials($credentials, $message);
-                       $this->setCredentials($this->slug, $credentials, $validated);
-
-                       $json['success'] = $validated;
-
-                       if ($validated) {
-                               if ( is_network_admin() ) {
-                                       $active_class = is_plugin_active_for_network( $this->pluginFile ) ? 'active': '';
-                               } else {
-                                       $active_class = is_plugin_active( $this->pluginFile ) ? 'active' : '';
-                               }
-
-                               $json['message'] .= __("License Key accepted. Automatic updates for this plugin are enabled.", "opentools-updatecheck");
-                       } else {
-                               $json['message'] = $message;
-                       }
-               } else {
-                       $json['message'] = __("No permissions to modify update credentials", "opentools-updatecheck");
-               }
-               wp_send_json($json);
-       }
-
-       public function checkUpdateCredentials($credentials, &$message)
-       {
-               $this->setCredentials($this->slug, $credentials);
-               $success = FALSE;
-               $updateinfo = $this->requestInfo(array());
-
-               if ($updateinfo && isset($updateinfo->download_url)) {
-                       $downloadurl = $updateinfo->download_url;
-                       $downloadurl = apply_filters('puc_check_download_query_args-'.$this->slug, $downloadurl);
-
-                       $headers = get_headers($downloadurl);
-                       list($version, $status_code, $msg) = explode(' ',$headers[0], 3);
-
-                       // Check the HTTP Status code
-                       $message = $msg;
-                       $success = ($status_code==200);
-               } else {
-                       $message = __('Unable to verify your License Key. Please check your License Key and try again.');
-               }
-               return $success;
-       }
-
-       public function addAccessCheckQueryArgFilter($callback){
-               add_filter('puc_check_download_query_args-'.$this->slug, $callback);
-       }
+    protected $credvars = array();
+    protected $ajaxurl = '';
+    public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '')
+    {
+        parent::__construct($metadataUrl, $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile);
+        $this->installOTHooks();
+    }
+    public function declareCredentials($credential_def) {
+        $this->credvars = $credential_def;
+        // Append the update credentials to the update server link
+        $this->addQueryArgFilter(array($this, 'appendQueryArgsCredentials'));
+
+    }
+
+    protected function installOTHooks()
+    {
+        $this->ajaxurl = is_network_admin()?network_admin_url( 'admin-ajax.php' ): admin_url( 'admin-ajax.php' );
+
+        add_action('admin_print_scripts-plugins.php', array($this, 'addCredentialCheckScripts'));
+        add_action('admin_print_styles-plugins.php',  array($this, 'addCredentialCheckStyles'));
+
+//         add_filter('plugin_row_meta', array($this, 'displayUpdateCredentialsLink'), 9, 2);
+        add_filter('plugin_action_links_'.$this->pluginFile, array($this, 'displayUpdateCredentialsLink'), 9, 2);
+
+        add_action( 'wp_ajax_getUpdateCredentialsRow',        array( &$this, 'getUpdateCredentialsRow') );
+        add_action( 'wp_ajax_submitUpdateCredentials',        array( &$this, 'submitUpdateCredentials') );
+    }
+
+    protected function getCredentials($slug)
+    {
+        $credentials = array('validated' => FALSE);
+        foreach ($this->credvars as $credkey => $credname) {
+            $credentials[$credkey] = get_option('otup_credentials_'.$slug.'_'.$credkey);
+        }
+        $credentials['validated']    = get_option('otup_credentials_validated_'.$slug);
+        return $credentials;
+    }
+
+    protected function setCredentials($slug, $credentials, $validated = false)
+    {
+        foreach ($credentials as $credkey => $credvalue) {
+            update_option('otup_credentials_'.$slug.'_'.$credkey, $credvalue, false);
+        }
+        update_option('otup_credentials_validated_'.$slug,    $validated,   false);
+    }
+
+
+    public function addCredentialCheckScripts() {
+        wp_register_script( 'opentools-updatecheck', plugins_url('js/opentools-updatecheck.js', __FILE__), array('jquery'));
+        wp_enqueue_script( 'opentools-updatecheck');
+    }
+
+    public function addCredentialCheckStyles() {
+        wp_register_style( 'opentools-updatecheck', plugins_url('css/opentools-updatecheck.css', __FILE__));
+        wp_enqueue_style( 'opentools-updatecheck');
+    }
+
+    /** Append the ordernumber and order password to the update server URL
+     */
+    public function appendQueryArgsCredentials($queryArgs) {
+        $credentials = $this->getCredentials($this->slug);
+        foreach ($credentials as $credkey => $credvalue) {
+            $queryArgs[$credkey] = $credvalue;
+        }
+        return $queryArgs;
+    }
+
+    /**
+     * Add a "Update Credentials" link to the plugin row in the "Plugins" page. By default,
+     * the new link will appear after the "Visit plugin site" link.
+     *
+     * You can change the link text by using the "otup_enter_update_credentials-$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 displayUpdateCredentialsLink($links, $pluginFile) {
+        $isRelevant = ($pluginFile == $this->pluginFile)
+                      || (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);
+        $isRelevant = $isRelevant && !empty($this->credvars);
+
+        if ( $isRelevant && current_user_can('update_plugins') ) {
+            $credentials = $this->getCredentials($this->slug);
+            $textcolor = $credentials['validated']?'<span>':'<span style="color: #c50034;">';
+            $linkText = apply_filters('otup_enter_update_credentials-' . $this->slug, $textcolor.__('Update License Key</span>', 'oton-updates').'</span>');
+            if ( !empty($linkText) ) {
+                $iconyesno = $credentials['validated']?'yes':'no';
+                $link = sprintf('<a href="#" onClick=\'return showUpdateCredentialsRow(this);\' class="dashicons-before dashicons-'.$iconyesno.' otup_credentials_link_'.$this->slug.'" data-slug="%s" data-nonce="%s" data-ajaxurl="%s" >%s</a>', esc_attr($this->slug), esc_attr(wp_create_nonce( 'otup_enter_update_credentials' )), esc_attr($this->ajaxurl), $linkText);
+                array_unshift($links, $link);
+            }
+        }
+        return $links;
+    }
+
+
+    /**
+    * If the user has clicked on the "Update Credentials" link, display the input boxes after the plugin row.
+    *
+    * @param string $file
+    * @param array  $plugin_data
+    * @return false|void
+    */
+    public function getUpdateCredentialsRow() {
+        $json = array('row' => '', 'message'=>'Unsuccessful');
+
+        $showCredentials = isset($_REQUEST['slug'])
+            && $_REQUEST['slug'] == $this->slug
+            && current_user_can('update_plugins')
+            && check_ajax_referer('otup_enter_update_credentials');
+        $showCredentials = $showCredentials && !empty($this->credvars);
+
+        if ( $showCredentials && (is_network_admin() || !is_multisite() )) {
+            $slug = $this->slug;
+            if ( is_network_admin() ) {
+                $active_class = is_plugin_active_for_network( $this->pluginFile ) ? 'active': '';
+            } else {
+                $active_class = is_plugin_active( $this->pluginFile ) ? 'active' : '';
+            }
+
+            $current_credentials = $this->getCredentials($slug);
+
+            $tr = '<tr class="' . $active_class . ' otup_update_credentials" id="' . esc_attr( $slug . '-credentials' ) . '" >';
+            $tr .= '<th colspan="3" class="check-column colspanchange">';
+            $tr .= '<div class="update-credentials">';
+            $tr .= '<div class="update-credentials-message">';
+            $tr .= '</div>';
+            $tr .= '<div class="update-credentials-form">';
+
+            foreach ($this->credvars as $credkey => $credname) {
+                $tr .= $credname    . " <input type=\"text\" name=\"otup_update_credentials[$slug][$credkey]\" value=\"" . esc_attr($current_credentials[$credkey]) . "\">&nbsp;&nbsp;&nbsp;";
+            }
+
+            $tr .= sprintf('<input type="submit" class="button otup_update_credentials_submit" onclick="return submitUpdateCredentials(this);" data-slug="%s" data-nonce="%s" data-ajaxurl="%s" data-credentialvars=\'%s\'>',
+                esc_attr($this->slug),
+                esc_attr(wp_create_nonce( 'otup_enter_update_credentials_'.$slug )),
+                esc_attr($this->ajaxurl),
+                esc_attr(json_encode(array_keys($this->credvars)))
+            );
+
+            $tr .= '</div>';
+            $tr .= '</div></th></tr>';
+            $json['row'] = $tr;
+            $json['message'] = '';
+        } else {
+            $json['message'] = __("No permissions to modify update credentials", "opentools-updatecheck");
+        }
+        wp_send_json($json);
+    }
+
+
+    /**
+     * Check the submitted update credentials for correctness and save them
+     *
+     * @return void
+     */
+    public function submitUpdateCredentials() {
+        $json = array('message' => '', 'success' => FALSE);
+        $slug = isset($_REQUEST['slug'])?($_REQUEST['slug']):"INVALIDSLUG";
+
+        $submitCredentials = $slug == $this->slug
+            && current_user_can('update_plugins')
+            && check_ajax_referer('otup_enter_update_credentials_'.$slug);
+        $submitCredentials = $submitCredentials && !empty($this->credvars);
+
+        if ( $submitCredentials ) {
+            $credentials = array();
+            foreach ($this->credvars as $credkey=>$credname) {
+                if (isset($_REQUEST[$credkey])) {
+                    $credentials[$credkey] = $_REQUEST[$credkey];
+                }
+            }
+
+            $message = "";
+            $validated = $this->checkUpdateCredentials($credentials, $message);
+            $this->setCredentials($this->slug, $credentials, $validated);
+
+            $json['success'] = $validated;
+
+            if ($validated) {
+                if ( is_network_admin() ) {
+                    $active_class = is_plugin_active_for_network( $this->pluginFile ) ? 'active': '';
+                } else {
+                    $active_class = is_plugin_active( $this->pluginFile ) ? 'active' : '';
+                }
+
+                $json['message'] .= __("License Key accepted. Automatic updates for this plugin are enabled.", "opentools-updatecheck");
+            } else {
+                $json['message'] = $message;
+            }
+        } else {
+            $json['message'] = __("No permissions to modify update credentials", "opentools-updatecheck");
+        }
+        wp_send_json($json);
+    }
+
+    public function checkUpdateCredentials($credentials, &$message)
+    {
+        $this->setCredentials($this->slug, $credentials);
+        $success = FALSE;
+        $updateinfo = $this->requestInfo(array());
+
+        if ($updateinfo && isset($updateinfo->download_url)) {
+            $downloadurl = $updateinfo->download_url;
+            $downloadurl = apply_filters('puc_check_download_query_args-'.$this->slug, $downloadurl);
+
+            $headers = get_headers($downloadurl);
+            list($version, $status_code, $msg) = explode(' ',$headers[0], 3);
+
+            // Check the HTTP Status code
+            $message = $msg;
+            $success = ($status_code==200);
+        } else {
+            $message = __('Unable to verify your License Key. Please check your License Key and try again.');
+        }
+        return $success;
+    }
+
+    public function addAccessCheckQueryArgFilter($callback){
+        add_filter('puc_check_download_query_args-'.$this->slug, $callback);
+    }
 
 };
 
index 0193de2..4855629 100644 (file)
@@ -18,936 +18,936 @@ if ( !class_exists('PluginUpdateChecker_3_1', false) ):
  * @access public\r
  */\r
 class PluginUpdateChecker_3_1 {\r
-       public $metadataUrl = ''; //The URL of the plugin's metadata file.\r
-       public $pluginAbsolutePath = ''; //Full path of the main plugin file.\r
-       public $pluginFile = '';  //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins.\r
-       public $slug = '';        //Plugin slug.\r
-       public $optionName = '';  //Where to store the update info.\r
-       public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory.\r
-\r
-       public $debugMode = false; //Set to TRUE to enable error reporting. Errors are raised using trigger_error()\r
+    public $metadataUrl = ''; //The URL of the plugin's metadata file.\r
+    public $pluginAbsolutePath = ''; //Full path of the main plugin file.\r
+    public $pluginFile = '';  //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins.\r
+    public $slug = '';        //Plugin slug.\r
+    public $optionName = '';  //Where to store the update info.\r
+    public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory.\r
+\r
+    public $debugMode = false; //Set to TRUE to enable error reporting. Errors are raised using trigger_error()\r
                                //and should be logged to the standard PHP error log.\r
-       public $scheduler;\r
-\r
-       protected $upgraderStatus;\r
-\r
-       private $debugBarPlugin = null;\r
-       private $cachedInstalledVersion = null;\r
-\r
-       private $metadataHost = ''; //The host component of $metadataUrl.\r
-\r
-       /**\r
-        * Class constructor.\r
-        *\r
-        * @param string $metadataUrl The URL of the plugin's metadata file.\r
-        * @param string $pluginFile Fully qualified path to the main plugin file.\r
-        * @param string $slug The plugin's 'slug'. If not specified, the filename part of $pluginFile sans '.php' will be used as the slug.\r
-        * @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.\r
-        * @param string $optionName Where to store book-keeping info about update checks. Defaults to 'external_updates-$slug'.\r
-        * @param string $muPluginFile Optional. The plugin filename relative to the mu-plugins directory.\r
-        */\r
-       public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = ''){\r
-               $this->metadataUrl = $metadataUrl;\r
-               $this->pluginAbsolutePath = $pluginFile;\r
-               $this->pluginFile = plugin_basename($this->pluginAbsolutePath);\r
-               $this->muPluginFile = $muPluginFile;\r
-               $this->slug = $slug;\r
-               $this->optionName = $optionName;\r
-               $this->debugMode = (bool)(constant('WP_DEBUG'));\r
-\r
-               //If no slug is specified, use the name of the main plugin file as the slug.\r
-               //For example, 'my-cool-plugin/cool-plugin.php' becomes 'cool-plugin'.\r
-               if ( empty($this->slug) ){\r
-                       $this->slug = basename($this->pluginFile, '.php');\r
-               }\r
-\r
-               if ( empty($this->optionName) ){\r
-                       $this->optionName = 'external_updates-' . $this->slug;\r
-               }\r
-\r
-               //Backwards compatibility: If the plugin is a mu-plugin but no $muPluginFile is specified, assume\r
-               //it's the same as $pluginFile given that it's not in a subdirectory (WP only looks in the base dir).\r
-               if ( (strpbrk($this->pluginFile, '/\\') === false) && $this->isUnknownMuPlugin() ) {\r
-                       $this->muPluginFile = $this->pluginFile;\r
-               }\r
-\r
-               $this->scheduler = $this->createScheduler($checkPeriod);\r
-               $this->upgraderStatus = new PucUpgraderStatus_3_1();\r
-\r
-               $this->installHooks();\r
-       }\r
-\r
-       /**\r
-        * Create an instance of the scheduler.\r
-        *\r
-        * This is implemented as a method to make it possible for plugins to subclass the update checker\r
-        * and substitute their own scheduler.\r
-        *\r
-        * @param int $checkPeriod\r
-        * @return PucScheduler_3_1\r
-        */\r
-       protected function createScheduler($checkPeriod) {\r
-               return new PucScheduler_3_1($this, $checkPeriod);\r
-       }\r
-\r
-       /**\r
-        * Install the hooks required to run periodic update checks and inject update info\r
-        * into WP data structures.\r
-        *\r
-        * @return void\r
-        */\r
-       protected function installHooks(){\r
-               //Override requests for plugin information\r
-               add_filter('plugins_api', array($this, 'injectInfo'), 20, 3);\r
-\r
-               //Insert our update info into the update array maintained by WP.\r
-               add_filter('site_transient_update_plugins', array($this,'injectUpdate')); //WP 3.0+\r
-               add_filter('transient_update_plugins', array($this,'injectUpdate')); //WP 2.8+\r
-               add_filter('site_transient_update_plugins', array($this, 'injectTranslationUpdates'));\r
-\r
-               add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2);\r
-               add_action('admin_init', array($this, 'handleManualCheck'));\r
-               add_action('all_admin_notices', array($this, 'displayManualCheckResult'));\r
-\r
-               //Clear the version number cache when something - anything - is upgraded or WP clears the update cache.\r
-               add_filter('upgrader_post_install', array($this, 'clearCachedVersion'));\r
-               add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));\r
-               //Clear translation updates when WP clears the update cache.\r
-               //This needs to be done directly because the library doesn't actually remove obsolete plugin updates,\r
-               //it just hides them (see getUpdate()). We can't do that with translations - too much disk I/O.\r
-               add_action('delete_site_transient_update_plugins', array($this, 'clearCachedTranslationUpdates'));\r
-\r
-               if ( did_action('plugins_loaded') ) {\r
-                       $this->initDebugBarPanel();\r
-               } else {\r
-                       add_action('plugins_loaded', array($this, 'initDebugBarPanel'));\r
-               }\r
-\r
-               //Rename the update directory to be the same as the existing directory.\r
-               add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3);\r
-\r
-               //Enable language support (i18n).\r
-               load_plugin_textdomain('plugin-update-checker', false, plugin_basename(dirname(__FILE__)) . '/languages');\r
-\r
-               //Allow HTTP requests to the metadata URL even if it's on a local host.\r
-               $this->metadataHost = @parse_url($this->metadataUrl, PHP_URL_HOST);\r
-               add_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10, 2);\r
-       }\r
-\r
-       /**\r
-        * Explicitly allow HTTP requests to the metadata URL.\r
-        *\r
-        * WordPress has a security feature where the HTTP API will reject all requests that are sent to\r
-        * another site hosted on the same server as the current site (IP match), a local host, or a local\r
-        * IP, unless the host exactly matches the current site.\r
-        *\r
-        * This feature is opt-in (at least in WP 4.4). Apparently some people enable it.\r
-        *\r
-        * That can be a problem when you're developing your plugin and you decide to host the update information\r
-        * on the same server as your test site. Update requests will mysteriously fail.\r
-        *\r
-        * We fix that by adding an exception for the metadata host.\r
-        *\r
-        * @param bool $allow\r
-        * @param string $host\r
-        * @return bool\r
-        */\r
-       public function allowMetadataHost($allow, $host) {\r
-               if ( strtolower($host) === strtolower($this->metadataHost) ) {\r
-                       return true;\r
-               }\r
-               return $allow;\r
-       }\r
-\r
-       /**\r
-        * Retrieve plugin info from the configured API endpoint.\r
-        *\r
-        * @uses wp_remote_get()\r
-        *\r
-        * @param array $queryArgs Additional query arguments to append to the request. Optional.\r
-        * @return PluginInfo_3_1\r
-        */\r
-       public function requestInfo($queryArgs = array()){\r
-               //Query args to append to the URL. Plugins can add their own by using a filter callback (see addQueryArgFilter()).\r
-               $installedVersion = $this->getInstalledVersion();\r
-               $queryArgs['installed_version'] = ($installedVersion !== null) ? $installedVersion : '';\r
-               $queryArgs = apply_filters('puc_request_info_query_args-'.$this->slug, $queryArgs);\r
-\r
-               //Various options for the wp_remote_get() call. Plugins can filter these, too.\r
-               $options = array(\r
-                       'timeout' => 10, //seconds\r
-                       'headers' => array(\r
-                               'Accept' => 'application/json'\r
-                       ),\r
-               );\r
-               $options = apply_filters('puc_request_info_options-'.$this->slug, $options);\r
-\r
-               //The plugin info should be at 'http://your-api.com/url/here/$slug/info.json'\r
-               $url = $this->metadataUrl;\r
-               if ( !empty($queryArgs) ){\r
-                       $url = add_query_arg($queryArgs, $url);\r
-               }\r
-\r
-               $result = wp_remote_get(\r
-                       $url,\r
-                       $options\r
-               );\r
-\r
-               //Try to parse the response\r
-               $status = $this->validateApiResponse($result);\r
-               $pluginInfo = null;\r
-               if ( !is_wp_error($status) ){\r
-                       $pluginInfo = PluginInfo_3_1::fromJson($result['body']);\r
-                       if ( $pluginInfo !== null ) {\r
-                               $pluginInfo->filename = $this->pluginFile;\r
-                               $pluginInfo->slug = $this->slug;\r
-                       }\r
-               } else {\r
-                       $this->triggerError(\r
-                               sprintf('The URL %s does not point to a valid plugin metadata file. ', $url)\r
-                                       . $status->get_error_message(),\r
-                               E_USER_WARNING\r
-                       );\r
-               }\r
-\r
-               $pluginInfo = apply_filters('puc_request_info_result-'.$this->slug, $pluginInfo, $result);\r
-               return $pluginInfo;\r
-       }\r
-\r
-       /**\r
-        * Check if $result is a successful update API response.\r
-        *\r
-        * @param array|WP_Error $result\r
-        * @return true|WP_Error\r
-        */\r
-       private function validateApiResponse($result) {\r
-\r
-               if ( is_wp_error($result) ) { /** @var WP_Error $result */\r
-                       return new WP_Error($result->get_error_code(), 'WP HTTP Error: ' . $result->get_error_message());\r
-               }\r
-\r
-               if ( !isset($result['response']['code']) ) {\r
-                       return new WP_Error('puc_no_response_code', 'wp_remote_get() returned an unexpected result.');\r
-               }\r
-\r
-               if ( $result['response']['code'] !== 200 ) {\r
-                       return new WP_Error(\r
-                               'puc_unexpected_response_code',\r
-                               'HTTP response code is ' . $result['response']['code'] . ' (expected: 200)'\r
-                       );\r
-               }\r
-\r
-               if ( empty($result['body']) ) {\r
-                       return new WP_Error('puc_empty_response', 'The metadata file appears to be empty.');\r
-               }\r
-\r
-               return true;\r
-       }\r
-\r
-       /**\r
-        * Retrieve the latest update (if any) from the configured API endpoint.\r
-        *\r
-        * @uses PluginUpdateChecker::requestInfo()\r
-        *\r
-        * @return PluginUpdate_3_1 An instance of PluginUpdate, or NULL when no updates are available.\r
-        */\r
-       public function requestUpdate(){\r
-               //For the sake of simplicity, this function just calls requestInfo()\r
-               //and transforms the result accordingly.\r
-               $pluginInfo = $this->requestInfo(array('checking_for_updates' => '1'));\r
-               if ( $pluginInfo == null ){\r
-                       return null;\r
-               }\r
-               $update = PluginUpdate_3_1::fromPluginInfo($pluginInfo);\r
-\r
-               //Keep only those translation updates that apply to this site.\r
-               $update->translations = $this->filterApplicableTranslations($update->translations);\r
-\r
-               return $update;\r
-       }\r
-\r
-       /**\r
-        * Filter a list of translation updates and return a new list that contains only updates\r
-        * that apply to the current site.\r
-        *\r
-        * @param array $translations\r
-        * @return array\r
-        */\r
-       private function filterApplicableTranslations($translations) {\r
-               $languages = array_flip(array_values(get_available_languages()));\r
-               $installedTranslations = wp_get_installed_translations('plugins');\r
-               if ( isset($installedTranslations[$this->slug]) ) {\r
-                       $installedTranslations = $installedTranslations[$this->slug];\r
-               } else {\r
-                       $installedTranslations = array();\r
-               }\r
-\r
-               $applicableTranslations = array();\r
-               foreach($translations as $translation) {\r
-                       //Does it match one of the available core languages?\r
-                       $isApplicable = array_key_exists($translation->language, $languages);\r
-                       //Is it more recent than an already-installed translation?\r
-                       if ( isset($installedTranslations[$translation->language]) ) {\r
-                               $updateTimestamp = strtotime($translation->updated);\r
-                               $installedTimestamp = strtotime($installedTranslations[$translation->language]['PO-Revision-Date']);\r
-                               $isApplicable = $updateTimestamp > $installedTimestamp;\r
-                       }\r
-\r
-                       if ( $isApplicable ) {\r
-                               $applicableTranslations[] = $translation;\r
-                       }\r
-               }\r
-\r
-               return $applicableTranslations;\r
-       }\r
-\r
-       /**\r
-        * Get the currently installed version of the plugin.\r
-        *\r
-        * @return string Version number.\r
-        */\r
-       public function getInstalledVersion(){\r
-               if ( isset($this->cachedInstalledVersion) ) {\r
-                       return $this->cachedInstalledVersion;\r
-               }\r
-\r
-               $pluginHeader = $this->getPluginHeader();\r
-               if ( isset($pluginHeader['Version']) ) {\r
-                       $this->cachedInstalledVersion = $pluginHeader['Version'];\r
-                       return $pluginHeader['Version'];\r
-               } else {\r
-                       //This can happen if the filename points to something that is not a plugin.\r
-                       $this->triggerError(\r
-                               sprintf(\r
-                                       "Can't to read the Version header for '%s'. The filename is incorrect or is not a plugin.",\r
-                                       $this->pluginFile\r
-                               ),\r
-                               E_USER_WARNING\r
-                       );\r
-                       return null;\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Get plugin's metadata from its file header.\r
-        *\r
-        * @return array\r
-        */\r
-       protected function getPluginHeader() {\r
-               if ( !is_file($this->pluginAbsolutePath) ) {\r
-                       //This can happen if the plugin filename is wrong.\r
-                       $this->triggerError(\r
-                               sprintf(\r
-                                       "Can't to read the plugin header for '%s'. The file does not exist.",\r
-                                       $this->pluginFile\r
-                               ),\r
-                               E_USER_WARNING\r
-                       );\r
-                       return array();\r
-               }\r
-\r
-               if ( !function_exists('get_plugin_data') ){\r
-                       /** @noinspection PhpIncludeInspection */\r
-                       require_once( ABSPATH . '/wp-admin/includes/plugin.php' );\r
-               }\r
-               return get_plugin_data($this->pluginAbsolutePath, false, false);\r
-       }\r
-\r
-       /**\r
-        * Check for plugin updates.\r
-        * The results are stored in the DB option specified in $optionName.\r
-        *\r
-        * @return PluginUpdate_3_1|null\r
-        */\r
-       public function checkForUpdates(){\r
-               $installedVersion = $this->getInstalledVersion();\r
-               //Fail silently if we can't find the plugin or read its header.\r
-               if ( $installedVersion === null ) {\r
-                       $this->triggerError(\r
-                               sprintf('Skipping update check for %s - installed version unknown.', $this->pluginFile),\r
-                               E_USER_WARNING\r
-                       );\r
-                       return null;\r
-               }\r
-\r
-               $state = $this->getUpdateState();\r
-               if ( empty($state) ){\r
-                       $state = new stdClass;\r
-                       $state->lastCheck = 0;\r
-                       $state->checkedVersion = '';\r
-                       $state->update = null;\r
-               }\r
-\r
-               $state->lastCheck = time();\r
-               $state->checkedVersion = $installedVersion;\r
-               $this->setUpdateState($state); //Save before checking in case something goes wrong\r
-\r
-               $state->update = $this->requestUpdate();\r
-               $this->setUpdateState($state);\r
-\r
-               return $this->getUpdate();\r
-       }\r
-\r
-       /**\r
-        * Load the update checker state from the DB.\r
-        *\r
-        * @return stdClass|null\r
-        */\r
-       public function getUpdateState() {\r
-               $state = get_site_option($this->optionName, null);\r
-               if ( empty($state) || !is_object($state)) {\r
-                       $state = null;\r
-               }\r
-\r
-               if ( isset($state, $state->update) && is_object($state->update) ) {\r
-                       $state->update = PluginUpdate_3_1::fromObject($state->update);\r
-               }\r
-               return $state;\r
-       }\r
-\r
-\r
-       /**\r
-        * Persist the update checker state to the DB.\r
-        *\r
-        * @param StdClass $state\r
-        * @return void\r
-        */\r
-       private function setUpdateState($state) {\r
-               if ( isset($state->update) && is_object($state->update) && method_exists($state->update, 'toStdClass') ) {\r
-                       $update = $state->update; /** @var PluginUpdate_3_1 $update */\r
-                       $state->update = $update->toStdClass();\r
-               }\r
-               update_site_option($this->optionName, $state);\r
-       }\r
-\r
-       /**\r
-        * Reset update checker state - i.e. last check time, cached update data and so on.\r
-        *\r
-        * Call this when your plugin is being uninstalled, or if you want to\r
-        * clear the update cache.\r
-        */\r
-       public function resetUpdateState() {\r
-               delete_site_option($this->optionName);\r
-       }\r
-\r
-       /**\r
-        * Intercept plugins_api() calls that request information about our plugin and\r
-        * use the configured API endpoint to satisfy them.\r
-        *\r
-        * @see plugins_api()\r
-        *\r
-        * @param mixed $result\r
-        * @param string $action\r
-        * @param array|object $args\r
-        * @return mixed\r
-        */\r
-       public function injectInfo($result, $action = null, $args = null){\r
-       $relevant = ($action == 'plugin_information') && isset($args->slug) && (\r
-                       ($args->slug == $this->slug) || ($args->slug == dirname($this->pluginFile))\r
-               );\r
-               if ( !$relevant ) {\r
-                       return $result;\r
-               }\r
-\r
-               $pluginInfo = $this->requestInfo();\r
-               $pluginInfo = apply_filters('puc_pre_inject_info-' . $this->slug, $pluginInfo);\r
-               if ( $pluginInfo ) {\r
-                       return $pluginInfo->toWpFormat();\r
-               }\r
-\r
-               return $result;\r
-       }\r
-\r
-       /**\r
-        * Insert the latest update (if any) into the update list maintained by WP.\r
-        *\r
-        * @param StdClass $updates Update list.\r
-        * @return StdClass Modified update list.\r
-        */\r
-       public function injectUpdate($updates){\r
-               //Is there an update to insert?\r
-               $update = $this->getUpdate();\r
-\r
-               //No update notifications for mu-plugins unless explicitly enabled. The MU plugin file\r
-               //is usually different from the main plugin file so the update wouldn't show up properly anyway.\r
-               if ( $this->isUnknownMuPlugin() ) {\r
-                       $update = null;\r
-               }\r
-\r
-               if ( !empty($update) ) {\r
-                       //Let plugins filter the update info before it's passed on to WordPress.\r
-                       $update = apply_filters('puc_pre_inject_update-' . $this->slug, $update);\r
-                       $updates = $this->addUpdateToList($updates, $update);\r
-               } else {\r
-                       //Clean up any stale update info.\r
-                       $updates = $this->removeUpdateFromList($updates);\r
-               }\r
-\r
-               return $updates;\r
-       }\r
-\r
-       /**\r
-        * @param StdClass|null $updates\r
-        * @param PluginUpdate_3_1 $updateToAdd\r
-        * @return StdClass\r
-        */\r
-       private function addUpdateToList($updates, $updateToAdd) {\r
-               if ( !is_object($updates) ) {\r
-                       $updates = new stdClass();\r
-                       $updates->response = array();\r
-               }\r
-\r
-               $wpUpdate = $updateToAdd->toWpFormat();\r
-               $pluginFile = $this->pluginFile;\r
-\r
-               if ( $this->isMuPlugin() ) {\r
-                       //WP does not support automatic update installation for mu-plugins, but we can still display a notice.\r
-                       $wpUpdate->package = null;\r
-                       $pluginFile = $this->muPluginFile;\r
-               }\r
-               $updates->response[$pluginFile] = $wpUpdate;\r
-               return $updates;\r
-       }\r
-\r
-       /**\r
-        * @param stdClass|null $updates\r
-        * @return stdClass|null\r
-        */\r
-       private function removeUpdateFromList($updates) {\r
-               if ( isset($updates, $updates->response) ) {\r
-                       unset($updates->response[$this->pluginFile]);\r
-                       if ( !empty($this->muPluginFile) ) {\r
-                               unset($updates->response[$this->muPluginFile]);\r
-                       }\r
-               }\r
-               return $updates;\r
-       }\r
-\r
-       /**\r
-        * Insert translation updates into the list maintained by WordPress.\r
-        *\r
-        * @param stdClass $updates\r
-        * @return stdClass\r
-        */\r
-       public function injectTranslationUpdates($updates) {\r
-               $translationUpdates = $this->getTranslationUpdates();\r
-               if ( empty($translationUpdates) ) {\r
-                       return $updates;\r
-               }\r
-\r
-               //Being defensive.\r
-               if ( !is_object($updates) ) {\r
-                       $updates = new stdClass();\r
-               }\r
-               if ( !isset($updates->translations) ) {\r
-                       $updates->translations = array();\r
-               }\r
-\r
-               //In case there's a name collision with a plugin hosted on wordpress.org,\r
-               //remove any preexisting updates that match our plugin.\r
-               $translationType = 'plugin';\r
-               $filteredTranslations = array();\r
-               foreach($updates->translations as $translation) {\r
-                       if ( ($translation['type'] === $translationType) && ($translation['slug'] === $this->slug) ) {\r
-                               continue;\r
-                       }\r
-                       $filteredTranslations[] = $translation;\r
-               }\r
-               $updates->translations = $filteredTranslations;\r
-\r
-               //Add our updates to the list.\r
-               foreach($translationUpdates as $update) {\r
-                       $convertedUpdate = array_merge(\r
-                               array(\r
-                                       'type' => $translationType,\r
-                                       'slug' => $this->slug,\r
-                                       'autoupdate' => 0,\r
-                                       //AFAICT, WordPress doesn't actually use the "version" field for anything.\r
-                                       //But lets make sure it's there, just in case.\r
-                                       'version' => isset($update->version) ? $update->version : ('1.' . strtotime($update->updated)),\r
-                               ),\r
-                               (array)$update\r
-                       );\r
-\r
-                       $updates->translations[] = $convertedUpdate;\r
-               }\r
-\r
-               return $updates;\r
-       }\r
-\r
-       /**\r
-        * Rename the update directory to match the existing plugin directory.\r
-        *\r
-        * When WordPress installs a plugin or theme update, it assumes that the ZIP file will contain\r
-        * exactly one directory, and that the directory name will be the same as the directory where\r
-        * the plugin/theme is currently installed.\r
-        *\r
-        * GitHub and other repositories provide ZIP downloads, but they often use directory names like\r
-        * "project-branch" or "project-tag-hash". We need to change the name to the actual plugin folder.\r
-        *\r
-        * This is a hook callback. Don't call it from a plugin.\r
-        *\r
-        * @param string $source The directory to copy to /wp-content/plugins. Usually a subdirectory of $remoteSource.\r
-        * @param string $remoteSource WordPress has extracted the update to this directory.\r
-        * @param WP_Upgrader $upgrader\r
-        * @return string|WP_Error\r
-        */\r
-       public function fixDirectoryName($source, $remoteSource, $upgrader) {\r
-               global $wp_filesystem; /** @var WP_Filesystem_Base $wp_filesystem */\r
-\r
-               //Basic sanity checks.\r
-               if ( !isset($source, $remoteSource, $upgrader, $upgrader->skin, $wp_filesystem) ) {\r
-                       return $source;\r
-               }\r
-\r
-               //If WordPress is upgrading anything other than our plugin, leave the directory name unchanged.\r
-               if ( !$this->isPluginBeingUpgraded($upgrader) ) {\r
-                       return $source;\r
-               }\r
-\r
-               //Rename the source to match the existing plugin directory.\r
-               $pluginDirectoryName = dirname($this->pluginFile);\r
-               if ( $pluginDirectoryName === '.' ) {\r
-                       return $source;\r
-               }\r
-               $correctedSource = trailingslashit($remoteSource) . $pluginDirectoryName . '/';\r
-               if ( $source !== $correctedSource ) {\r
-                       //The update archive should contain a single directory that contains the rest of plugin files. Otherwise,\r
-                       //WordPress will try to copy the entire working directory ($source == $remoteSource). We can't rename\r
-                       //$remoteSource because that would break WordPress code that cleans up temporary files after update.\r
-                       if ( $this->isBadDirectoryStructure($remoteSource) ) {\r
-                               return new WP_Error(\r
-                                       'puc-incorrect-directory-structure',\r
-                                       sprintf(\r
-                                               'The directory structure of the update is incorrect. All plugin files should be inside ' .\r
-                                               'a directory named <span class="code">%s</span>, not at the root of the ZIP file.',\r
-                                               htmlentities($this->slug)\r
-                                       )\r
-                               );\r
-                       }\r
-\r
-                       /** @var WP_Upgrader_Skin $upgrader->skin */\r
-                       $upgrader->skin->feedback(sprintf(\r
-                               'Renaming %s to %s&#8230;',\r
-                               '<span class="code">' . basename($source) . '</span>',\r
-                               '<span class="code">' . $pluginDirectoryName . '</span>'\r
-                       ));\r
-\r
-                       if ( $wp_filesystem->move($source, $correctedSource, true) ) {\r
-                               $upgrader->skin->feedback('Plugin directory successfully renamed.');\r
-                               return $correctedSource;\r
-                       } else {\r
-                               return new WP_Error(\r
-                                       'puc-rename-failed',\r
-                                       'Unable to rename the update to match the existing plugin directory.'\r
-                               );\r
-                       }\r
-               }\r
-\r
-               return $source;\r
-       }\r
-\r
-       /**\r
-        * Check for incorrect update directory structure. An update must contain a single directory,\r
-        * all other files should be inside that directory.\r
-        *\r
-        * @param string $remoteSource Directory path.\r
-        * @return bool\r
-        */\r
-       private function isBadDirectoryStructure($remoteSource) {\r
-               global $wp_filesystem; /** @var WP_Filesystem_Base $wp_filesystem */\r
-\r
-               $sourceFiles = $wp_filesystem->dirlist($remoteSource);\r
-               if ( is_array($sourceFiles) ) {\r
-                       $sourceFiles = array_keys($sourceFiles);\r
-                       $firstFilePath = trailingslashit($remoteSource) . $sourceFiles[0];\r
-                       return (count($sourceFiles) > 1) || (!$wp_filesystem->is_dir($firstFilePath));\r
-               }\r
-\r
-               //Assume it's fine.\r
-               return false;\r
-       }\r
-\r
-       /**\r
-        * Is there and update being installed RIGHT NOW, for this specific plugin?\r
-        *\r
-        * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.\r
-        * @return bool\r
-        */\r
-       public function isPluginBeingUpgraded($upgrader = null) {\r
-               return $this->upgraderStatus->isPluginBeingUpgraded($this->pluginFile, $upgrader);\r
-       }\r
-\r
-       /**\r
-        * Get the details of the currently available update, if any.\r
-        *\r
-        * If no updates are available, or if the last known update version is below or equal\r
-        * to the currently installed version, this method will return NULL.\r
-        *\r
-        * Uses cached update data. To retrieve update information straight from\r
-        * the metadata URL, call requestUpdate() instead.\r
-        *\r
-        * @return PluginUpdate_3_1|null\r
-        */\r
-       public function getUpdate() {\r
-               $state = $this->getUpdateState(); /** @var StdClass $state */\r
-\r
-               //Is there an update available?\r
-               if ( isset($state, $state->update) ) {\r
-                       $update = $state->update;\r
-                       //Check if the update is actually newer than the currently installed version.\r
-                       $installedVersion = $this->getInstalledVersion();\r
-                       if ( ($installedVersion !== null) && version_compare($update->version, $installedVersion, '>') ){\r
-                               $update->filename = $this->pluginFile;\r
-                               return $update;\r
-                       }\r
-               }\r
-               return null;\r
-       }\r
-\r
-       /**\r
-        * Get a list of available translation updates.\r
-        *\r
-        * This method will return an empty array if there are no updates.\r
-        * Uses cached update data.\r
-        *\r
-        * @return array\r
-        */\r
-       public function getTranslationUpdates() {\r
-               $state = $this->getUpdateState();\r
-               if ( isset($state, $state->update, $state->update->translations) ) {\r
-                       return $state->update->translations;\r
-               }\r
-               return array();\r
-       }\r
-\r
-       /**\r
-        * Remove all cached translation updates.\r
-        *\r
-        * @see wp_clean_update_cache\r
-        */\r
-       public function clearCachedTranslationUpdates() {\r
-               $state = $this->getUpdateState();\r
-               if ( isset($state, $state->update, $state->update->translations) ) {\r
-                       $state->update->translations = array();\r
-                       $this->setUpdateState($state);\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Add a "Check for updates" link to the plugin row in the "Plugins" page. By default,\r
-        * the new link will appear after the "Visit plugin site" link.\r
-        *\r
-        * You can change the link text by using the "puc_manual_check_link-$slug" filter.\r
-        * Returning an empty string from the filter will disable the link.\r
-        *\r
-        * @param array $pluginMeta Array of meta links.\r
-        * @param string $pluginFile\r
-        * @return array\r
-        */\r
-       public function addCheckForUpdatesLink($pluginMeta, $pluginFile) {\r
-               $isRelevant = ($pluginFile == $this->pluginFile)\r
-                             || (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);\r
-\r
-               if ( $isRelevant && current_user_can('update_plugins') ) {\r
-                       $linkUrl = wp_nonce_url(\r
-                               add_query_arg(\r
-                                       array(\r
-                                               'puc_check_for_updates' => 1,\r
-                                               'puc_slug' => $this->slug,\r
-                                       ),\r
-                                       self_admin_url('plugins.php')\r
-                               ),\r
-                               'puc_check_for_updates'\r
-                       );\r
-\r
-                       $linkText = apply_filters('puc_manual_check_link-' . $this->slug, __('Check for updates', 'plugin-update-checker'));\r
-                       if ( !empty($linkText) ) {\r
-                               $pluginMeta[] = sprintf('<a href="%s">%s</a>', esc_attr($linkUrl), $linkText);\r
-                       }\r
-               }\r
-               return $pluginMeta;\r
-       }\r
-\r
-       /**\r
-        * Check for updates when the user clicks the "Check for updates" link.\r
-        * @see self::addCheckForUpdatesLink()\r
-        *\r
-        * @return void\r
-        */\r
-       public function handleManualCheck() {\r
-               $shouldCheck =\r
-                          isset($_GET['puc_check_for_updates'], $_GET['puc_slug'])\r
-                       && $_GET['puc_slug'] == $this->slug\r
-                       && current_user_can('update_plugins')\r
-                       && check_admin_referer('puc_check_for_updates');\r
-\r
-               if ( $shouldCheck ) {\r
-                       $update = $this->checkForUpdates();\r
-                       $status = ($update === null) ? 'no_update' : 'update_available';\r
-                       wp_redirect(add_query_arg(\r
-                               array(\r
-                                       'puc_update_check_result' => $status,\r
-                                       'puc_slug' => $this->slug,\r
-                               ),\r
-                               self_admin_url('plugins.php')\r
-                       ));\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Display the results of a manual update check.\r
-        * @see self::handleManualCheck()\r
-        *\r
-        * You can change the result message by using the "puc_manual_check_message-$slug" filter.\r
-        */\r
-       public function displayManualCheckResult() {\r
-               if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->slug) ) {\r
-                       $status = strval($_GET['puc_update_check_result']);\r
-                       if ( $status == 'no_update' ) {\r
-                               $message = __('This plugin is up to date.', 'plugin-update-checker');\r
-                       } else if ( $status == 'update_available' ) {\r
-                               $message = __('A new version of this plugin is available.', 'plugin-update-checker');\r
-                       } else {\r
-                               $message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), htmlentities($status));\r
-                       }\r
-                       printf(\r
-                               '<div class="updated notice is-dismissible"><p>%s</p></div>',\r
-                               apply_filters('puc_manual_check_message-' . $this->slug, $message, $status)\r
-                       );\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Check if the plugin file is inside the mu-plugins directory.\r
-        *\r
-        * @return bool\r
-        */\r
-       protected function isMuPlugin() {\r
-               static $cachedResult = null;\r
-\r
-               if ( $cachedResult === null ) {\r
-                       //Convert both paths to the canonical form before comparison.\r
-                       $muPluginDir = realpath(WPMU_PLUGIN_DIR);\r
-                       $pluginPath  = realpath($this->pluginAbsolutePath);\r
-\r
-                       $cachedResult = (strpos($pluginPath, $muPluginDir) === 0);\r
-               }\r
-\r
-               return $cachedResult;\r
-       }\r
-\r
-       /**\r
-        * MU plugins are partially supported, but only when we know which file in mu-plugins\r
-        * corresponds to this plugin.\r
-        *\r
-        * @return bool\r
-        */\r
-       protected function isUnknownMuPlugin() {\r
-               return empty($this->muPluginFile) && $this->isMuPlugin();\r
-       }\r
-\r
-       /**\r
-        * Clear the cached plugin version. This method can be set up as a filter (hook) and will\r
-        * return the filter argument unmodified.\r
-        *\r
-        * @param mixed $filterArgument\r
-        * @return mixed\r
-        */\r
-       public function clearCachedVersion($filterArgument = null) {\r
-               $this->cachedInstalledVersion = null;\r
-               return $filterArgument;\r
-       }\r
-\r
-       /**\r
-        * Register a callback for filtering query arguments.\r
-        *\r
-        * The callback function should take one argument - an associative array of query arguments.\r
-        * It should return a modified array of query arguments.\r
-        *\r
-        * @uses add_filter() This method is a convenience wrapper for add_filter().\r
-        *\r
-        * @param callable $callback\r
-        * @return void\r
-        */\r
-       public function addQueryArgFilter($callback){\r
-               add_filter('puc_request_info_query_args-'.$this->slug, $callback);\r
-       }\r
-\r
-       /**\r
-        * Register a callback for filtering arguments passed to wp_remote_get().\r
-        *\r
-        * The callback function should take one argument - an associative array of arguments -\r
-        * and return a modified array or arguments. See the WP documentation on wp_remote_get()\r
-        * for details on what arguments are available and how they work.\r
-        *\r
-        * @uses add_filter() This method is a convenience wrapper for add_filter().\r
-        *\r
-        * @param callable $callback\r
-        * @return void\r
-        */\r
-       public function addHttpRequestArgFilter($callback){\r
-               add_filter('puc_request_info_options-'.$this->slug, $callback);\r
-       }\r
-\r
-       /**\r
-        * Register a callback for filtering the plugin info retrieved from the external API.\r
-        *\r
-        * The callback function should take two arguments. If the plugin info was retrieved\r
-        * successfully, the first argument passed will be an instance of  PluginInfo. Otherwise,\r
-        * it will be NULL. The second argument will be the corresponding return value of\r
-        * wp_remote_get (see WP docs for details).\r
-        *\r
-        * The callback function should return a new or modified instance of PluginInfo or NULL.\r
-        *\r
-        * @uses add_filter() This method is a convenience wrapper for add_filter().\r
-        *\r
-        * @param callable $callback\r
-        * @return void\r
-        */\r
-       public function addResultFilter($callback){\r
-               add_filter('puc_request_info_result-'.$this->slug, $callback, 10, 2);\r
-       }\r
-\r
-       /**\r
-        * Register a callback for one of the update checker filters.\r
-        *\r
-        * Identical to add_filter(), except it automatically adds the "puc_" prefix\r
-        * and the "-$plugin_slug" suffix to the filter name. For example, "request_info_result"\r
-        * becomes "puc_request_info_result-your_plugin_slug".\r
-        *\r
-        * @param string $tag\r
-        * @param callable $callback\r
-        * @param int $priority\r
-        * @param int $acceptedArgs\r
-        */\r
-       public function addFilter($tag, $callback, $priority = 10, $acceptedArgs = 1) {\r
-               add_filter('puc_' . $tag . '-' . $this->slug, $callback, $priority, $acceptedArgs);\r
-       }\r
-\r
-       /**\r
-        * Initialize the update checker Debug Bar plugin/add-on thingy.\r
-        */\r
-       public function initDebugBarPanel() {\r
-               $debugBarPlugin = dirname(__FILE__) . '/debug-bar-plugin.php';\r
-               if ( class_exists('Debug_Bar', false) && file_exists($debugBarPlugin) ) {\r
-                       /** @noinspection PhpIncludeInspection */\r
-                       require_once $debugBarPlugin;\r
-                       $this->debugBarPlugin = new PucDebugBarPlugin_3_1($this);\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Trigger a PHP error, but only when $debugMode is enabled.\r
-        *\r
-        * @param string $message\r
-        * @param int $errorType\r
-        */\r
-       protected function triggerError($message, $errorType) {\r
-               if ( $this->debugMode ) {\r
-                       trigger_error($message, $errorType);\r
-               }\r
-       }\r
+    public $scheduler;\r
+\r
+    protected $upgraderStatus;\r
+\r
+    private $debugBarPlugin = null;\r
+    private $cachedInstalledVersion = null;\r
+\r
+    private $metadataHost = ''; //The host component of $metadataUrl.\r
+\r
+    /**\r
+     * Class constructor.\r
+     *\r
+     * @param string $metadataUrl The URL of the plugin's metadata file.\r
+     * @param string $pluginFile Fully qualified path to the main plugin file.\r
+     * @param string $slug The plugin's 'slug'. If not specified, the filename part of $pluginFile sans '.php' will be used as the slug.\r
+     * @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.\r
+     * @param string $optionName Where to store book-keeping info about update checks. Defaults to 'external_updates-$slug'.\r
+     * @param string $muPluginFile Optional. The plugin filename relative to the mu-plugins directory.\r
+     */\r
+    public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = ''){\r
+        $this->metadataUrl = $metadataUrl;\r
+        $this->pluginAbsolutePath = $pluginFile;\r
+        $this->pluginFile = plugin_basename($this->pluginAbsolutePath);\r
+        $this->muPluginFile = $muPluginFile;\r
+        $this->slug = $slug;\r
+        $this->optionName = $optionName;\r
+        $this->debugMode = (bool)(constant('WP_DEBUG'));\r
+\r
+        //If no slug is specified, use the name of the main plugin file as the slug.\r
+        //For example, 'my-cool-plugin/cool-plugin.php' becomes 'cool-plugin'.\r
+        if ( empty($this->slug) ){\r
+            $this->slug = basename($this->pluginFile, '.php');\r
+        }\r
+\r
+        if ( empty($this->optionName) ){\r
+            $this->optionName = 'external_updates-' . $this->slug;\r
+        }\r
+\r
+        //Backwards compatibility: If the plugin is a mu-plugin but no $muPluginFile is specified, assume\r
+        //it's the same as $pluginFile given that it's not in a subdirectory (WP only looks in the base dir).\r
+        if ( (strpbrk($this->pluginFile, '/\\') === false) && $this->isUnknownMuPlugin() ) {\r
+            $this->muPluginFile = $this->pluginFile;\r
+        }\r
+\r
+        $this->scheduler = $this->createScheduler($checkPeriod);\r
+        $this->upgraderStatus = new PucUpgraderStatus_3_1();\r
+\r
+        $this->installHooks();\r
+    }\r
+\r
+    /**\r
+     * Create an instance of the scheduler.\r
+     *\r
+     * This is implemented as a method to make it possible for plugins to subclass the update checker\r
+     * and substitute their own scheduler.\r
+     *\r
+     * @param int $checkPeriod\r
+     * @return PucScheduler_3_1\r
+     */\r
+    protected function createScheduler($checkPeriod) {\r
+        return new PucScheduler_3_1($this, $checkPeriod);\r
+    }\r
+\r
+    /**\r
+     * Install the hooks required to run periodic update checks and inject update info\r
+     * into WP data structures.\r
+     *\r
+     * @return void\r
+     */\r
+    protected function installHooks(){\r
+        //Override requests for plugin information\r
+        add_filter('plugins_api', array($this, 'injectInfo'), 20, 3);\r
+\r
+        //Insert our update info into the update array maintained by WP.\r
+        add_filter('site_transient_update_plugins', array($this,'injectUpdate')); //WP 3.0+\r
+        add_filter('transient_update_plugins', array($this,'injectUpdate')); //WP 2.8+\r
+        add_filter('site_transient_update_plugins', array($this, 'injectTranslationUpdates'));\r
+\r
+        add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2);\r
+        add_action('admin_init', array($this, 'handleManualCheck'));\r
+        add_action('all_admin_notices', array($this, 'displayManualCheckResult'));\r
+\r
+        //Clear the version number cache when something - anything - is upgraded or WP clears the update cache.\r
+        add_filter('upgrader_post_install', array($this, 'clearCachedVersion'));\r
+        add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));\r
+        //Clear translation updates when WP clears the update cache.\r
+        //This needs to be done directly because the library doesn't actually remove obsolete plugin updates,\r
+        //it just hides them (see getUpdate()). We can't do that with translations - too much disk I/O.\r
+        add_action('delete_site_transient_update_plugins', array($this, 'clearCachedTranslationUpdates'));\r
+\r
+        if ( did_action('plugins_loaded') ) {\r
+            $this->initDebugBarPanel();\r
+        } else {\r
+            add_action('plugins_loaded', array($this, 'initDebugBarPanel'));\r
+        }\r
+\r
+        //Rename the update directory to be the same as the existing directory.\r
+        add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3);\r
+\r
+        //Enable language support (i18n).\r
+        load_plugin_textdomain('plugin-update-checker', false, plugin_basename(dirname(__FILE__)) . '/languages');\r
+\r
+        //Allow HTTP requests to the metadata URL even if it's on a local host.\r
+        $this->metadataHost = @parse_url($this->metadataUrl, PHP_URL_HOST);\r
+        add_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10, 2);\r
+    }\r
+\r
+    /**\r
+     * Explicitly allow HTTP requests to the metadata URL.\r
+     *\r
+     * WordPress has a security feature where the HTTP API will reject all requests that are sent to\r
+     * another site hosted on the same server as the current site (IP match), a local host, or a local\r
+     * IP, unless the host exactly matches the current site.\r
+     *\r
+     * This feature is opt-in (at least in WP 4.4). Apparently some people enable it.\r
+     *\r
+     * That can be a problem when you're developing your plugin and you decide to host the update information\r
+     * on the same server as your test site. Update requests will mysteriously fail.\r
+     *\r
+     * We fix that by adding an exception for the metadata host.\r
+     *\r
+     * @param bool $allow\r
+     * @param string $host\r
+     * @return bool\r
+     */\r
+    public function allowMetadataHost($allow, $host) {\r
+        if ( strtolower($host) === strtolower($this->metadataHost) ) {\r
+            return true;\r
+        }\r
+        return $allow;\r
+    }\r
+\r
+    /**\r
+     * Retrieve plugin info from the configured API endpoint.\r
+     *\r
+     * @uses wp_remote_get()\r
+     *\r
+     * @param array $queryArgs Additional query arguments to append to the request. Optional.\r
+     * @return PluginInfo_3_1\r
+     */\r
+    public function requestInfo($queryArgs = array()){\r
+        //Query args to append to the URL. Plugins can add their own by using a filter callback (see addQueryArgFilter()).\r
+        $installedVersion = $this->getInstalledVersion();\r
+        $queryArgs['installed_version'] = ($installedVersion !== null) ? $installedVersion : '';\r
+        $queryArgs = apply_filters('puc_request_info_query_args-'.$this->slug, $queryArgs);\r
+\r
+        //Various options for the wp_remote_get() call. Plugins can filter these, too.\r
+        $options = array(\r
+            'timeout' => 10, //seconds\r
+            'headers' => array(\r
+                'Accept' => 'application/json'\r
+            ),\r
+        );\r
+        $options = apply_filters('puc_request_info_options-'.$this->slug, $options);\r
+\r
+        //The plugin info should be at 'http://your-api.com/url/here/$slug/info.json'\r
+        $url = $this->metadataUrl;\r
+        if ( !empty($queryArgs) ){\r
+            $url = add_query_arg($queryArgs, $url);\r
+        }\r
+\r
+        $result = wp_remote_get(\r
+            $url,\r
+            $options\r
+        );\r
+\r
+        //Try to parse the response\r
+        $status = $this->validateApiResponse($result);\r
+        $pluginInfo = null;\r
+        if ( !is_wp_error($status) ){\r
+            $pluginInfo = PluginInfo_3_1::fromJson($result['body']);\r
+            if ( $pluginInfo !== null ) {\r
+                $pluginInfo->filename = $this->pluginFile;\r
+                $pluginInfo->slug = $this->slug;\r
+            }\r
+        } else {\r
+            $this->triggerError(\r
+                sprintf('The URL %s does not point to a valid plugin metadata file. ', $url)\r
+                    . $status->get_error_message(),\r
+                E_USER_WARNING\r
+            );\r
+        }\r
+\r
+        $pluginInfo = apply_filters('puc_request_info_result-'.$this->slug, $pluginInfo, $result);\r
+        return $pluginInfo;\r
+    }\r
+\r
+    /**\r
+     * Check if $result is a successful update API response.\r
+     *\r
+     * @param array|WP_Error $result\r
+     * @return true|WP_Error\r
+     */\r
+    private function validateApiResponse($result) {\r
+\r
+        if ( is_wp_error($result) ) { /** @var WP_Error $result */\r
+            return new WP_Error($result->get_error_code(), 'WP HTTP Error: ' . $result->get_error_message());\r
+        }\r
+\r
+        if ( !isset($result['response']['code']) ) {\r
+            return new WP_Error('puc_no_response_code', 'wp_remote_get() returned an unexpected result.');\r
+        }\r
+\r
+        if ( $result['response']['code'] !== 200 ) {\r
+            return new WP_Error(\r
+                'puc_unexpected_response_code',\r
+                'HTTP response code is ' . $result['response']['code'] . ' (expected: 200)'\r
+            );\r
+        }\r
+\r
+        if ( empty($result['body']) ) {\r
+            return new WP_Error('puc_empty_response', 'The metadata file appears to be empty.');\r
+        }\r
+\r
+        return true;\r
+    }\r
+\r
+    /**\r
+     * Retrieve the latest update (if any) from the configured API endpoint.\r
+     *\r
+     * @uses PluginUpdateChecker::requestInfo()\r
+     *\r
+     * @return PluginUpdate_3_1 An instance of PluginUpdate, or NULL when no updates are available.\r
+     */\r
+    public function requestUpdate(){\r
+        //For the sake of simplicity, this function just calls requestInfo()\r
+        //and transforms the result accordingly.\r
+        $pluginInfo = $this->requestInfo(array('checking_for_updates' => '1'));\r
+        if ( $pluginInfo == null ){\r
+            return null;\r
+        }\r
+        $update = PluginUpdate_3_1::fromPluginInfo($pluginInfo);\r
+\r
+        //Keep only those translation updates that apply to this site.\r
+        $update->translations = $this->filterApplicableTranslations($update->translations);\r
+\r
+        return $update;\r
+    }\r
+\r
+    /**\r
+     * Filter a list of translation updates and return a new list that contains only updates\r
+     * that apply to the current site.\r
+     *\r
+     * @param array $translations\r
+     * @return array\r
+     */\r
+    private function filterApplicableTranslations($translations) {\r
+        $languages = array_flip(array_values(get_available_languages()));\r
+        $installedTranslations = wp_get_installed_translations('plugins');\r
+        if ( isset($installedTranslations[$this->slug]) ) {\r
+            $installedTranslations = $installedTranslations[$this->slug];\r
+        } else {\r
+            $installedTranslations = array();\r
+        }\r
+\r
+        $applicableTranslations = array();\r
+        foreach($translations as $translation) {\r
+            //Does it match one of the available core languages?\r
+            $isApplicable = array_key_exists($translation->language, $languages);\r
+            //Is it more recent than an already-installed translation?\r
+            if ( isset($installedTranslations[$translation->language]) ) {\r
+                $updateTimestamp = strtotime($translation->updated);\r
+                $installedTimestamp = strtotime($installedTranslations[$translation->language]['PO-Revision-Date']);\r
+                $isApplicable = $updateTimestamp > $installedTimestamp;\r
+            }\r
+\r
+            if ( $isApplicable ) {\r
+                $applicableTranslations[] = $translation;\r
+            }\r
+        }\r
+\r
+        return $applicableTranslations;\r
+    }\r
+\r
+    /**\r
+     * Get the currently installed version of the plugin.\r
+     *\r
+     * @return string Version number.\r
+     */\r
+    public function getInstalledVersion(){\r
+        if ( isset($this->cachedInstalledVersion) ) {\r
+            return $this->cachedInstalledVersion;\r
+        }\r
+\r
+        $pluginHeader = $this->getPluginHeader();\r
+        if ( isset($pluginHeader['Version']) ) {\r
+            $this->cachedInstalledVersion = $pluginHeader['Version'];\r
+            return $pluginHeader['Version'];\r
+        } else {\r
+            //This can happen if the filename points to something that is not a plugin.\r
+            $this->triggerError(\r
+                sprintf(\r
+                    "Can't to read the Version header for '%s'. The filename is incorrect or is not a plugin.",\r
+                    $this->pluginFile\r
+                ),\r
+                E_USER_WARNING\r
+            );\r
+            return null;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Get plugin's metadata from its file header.\r
+     *\r
+     * @return array\r
+     */\r
+    protected function getPluginHeader() {\r
+        if ( !is_file($this->pluginAbsolutePath) ) {\r
+            //This can happen if the plugin filename is wrong.\r
+            $this->triggerError(\r
+                sprintf(\r
+                    "Can't to read the plugin header for '%s'. The file does not exist.",\r
+                    $this->pluginFile\r
+                ),\r
+                E_USER_WARNING\r
+            );\r
+            return array();\r
+        }\r
+\r
+        if ( !function_exists('get_plugin_data') ){\r
+            /** @noinspection PhpIncludeInspection */\r
+            require_once( ABSPATH . '/wp-admin/includes/plugin.php' );\r
+        }\r
+        return get_plugin_data($this->pluginAbsolutePath, false, false);\r
+    }\r
+\r
+    /**\r
+     * Check for plugin updates.\r
+     * The results are stored in the DB option specified in $optionName.\r
+     *\r
+     * @return PluginUpdate_3_1|null\r
+     */\r
+    public function checkForUpdates(){\r
+        $installedVersion = $this->getInstalledVersion();\r
+        //Fail silently if we can't find the plugin or read its header.\r
+        if ( $installedVersion === null ) {\r
+            $this->triggerError(\r
+                sprintf('Skipping update check for %s - installed version unknown.', $this->pluginFile),\r
+                E_USER_WARNING\r
+            );\r
+            return null;\r
+        }\r
+\r
+        $state = $this->getUpdateState();\r
+        if ( empty($state) ){\r
+            $state = new stdClass;\r
+            $state->lastCheck = 0;\r
+            $state->checkedVersion = '';\r
+            $state->update = null;\r
+        }\r
+\r
+        $state->lastCheck = time();\r
+        $state->checkedVersion = $installedVersion;\r
+        $this->setUpdateState($state); //Save before checking in case something goes wrong\r
+\r
+        $state->update = $this->requestUpdate();\r
+        $this->setUpdateState($state);\r
+\r
+        return $this->getUpdate();\r
+    }\r
+\r
+    /**\r
+     * Load the update checker state from the DB.\r
+     *\r
+     * @return stdClass|null\r
+     */\r
+    public function getUpdateState() {\r
+        $state = get_site_option($this->optionName, null);\r
+        if ( empty($state) || !is_object($state)) {\r
+            $state = null;\r
+        }\r
+\r
+        if ( isset($state, $state->update) && is_object($state->update) ) {\r
+            $state->update = PluginUpdate_3_1::fromObject($state->update);\r
+        }\r
+        return $state;\r
+    }\r
+\r
+\r
+    /**\r
+     * Persist the update checker state to the DB.\r
+     *\r
+     * @param StdClass $state\r
+     * @return void\r
+     */\r
+    private function setUpdateState($state) {\r
+        if ( isset($state->update) && is_object($state->update) && method_exists($state->update, 'toStdClass') ) {\r
+            $update = $state->update; /** @var PluginUpdate_3_1 $update */\r
+            $state->update = $update->toStdClass();\r
+        }\r
+        update_site_option($this->optionName, $state);\r
+    }\r
+\r
+    /**\r
+     * Reset update checker state - i.e. last check time, cached update data and so on.\r
+     *\r
+     * Call this when your plugin is being uninstalled, or if you want to\r
+     * clear the update cache.\r
+     */\r
+    public function resetUpdateState() {\r
+        delete_site_option($this->optionName);\r
+    }\r
+\r
+    /**\r
+     * Intercept plugins_api() calls that request information about our plugin and\r
+     * use the configured API endpoint to satisfy them.\r
+     *\r
+     * @see plugins_api()\r
+     *\r
+     * @param mixed $result\r
+     * @param string $action\r
+     * @param array|object $args\r
+     * @return mixed\r
+     */\r
+    public function injectInfo($result, $action = null, $args = null){\r
+        $relevant = ($action == 'plugin_information') && isset($args->slug) && (\r
+            ($args->slug == $this->slug) || ($args->slug == dirname($this->pluginFile))\r
+        );\r
+        if ( !$relevant ) {\r
+            return $result;\r
+        }\r
+\r
+        $pluginInfo = $this->requestInfo();\r
+        $pluginInfo = apply_filters('puc_pre_inject_info-' . $this->slug, $pluginInfo);\r
+        if ( $pluginInfo ) {\r
+            return $pluginInfo->toWpFormat();\r
+        }\r
+\r
+        return $result;\r
+    }\r
+\r
+    /**\r
+     * Insert the latest update (if any) into the update list maintained by WP.\r
+     *\r
+     * @param StdClass $updates Update list.\r
+     * @return StdClass Modified update list.\r
+     */\r
+    public function injectUpdate($updates){\r
+        //Is there an update to insert?\r
+        $update = $this->getUpdate();\r
+\r
+        //No update notifications for mu-plugins unless explicitly enabled. The MU plugin file\r
+        //is usually different from the main plugin file so the update wouldn't show up properly anyway.\r
+        if ( $this->isUnknownMuPlugin() ) {\r
+            $update = null;\r
+        }\r
+\r
+        if ( !empty($update) ) {\r
+            //Let plugins filter the update info before it's passed on to WordPress.\r
+            $update = apply_filters('puc_pre_inject_update-' . $this->slug, $update);\r
+            $updates = $this->addUpdateToList($updates, $update);\r
+        } else {\r
+            //Clean up any stale update info.\r
+            $updates = $this->removeUpdateFromList($updates);\r
+        }\r
+\r
+        return $updates;\r
+    }\r
+\r
+    /**\r
+     * @param StdClass|null $updates\r
+     * @param PluginUpdate_3_1 $updateToAdd\r
+     * @return StdClass\r
+     */\r
+    private function addUpdateToList($updates, $updateToAdd) {\r
+        if ( !is_object($updates) ) {\r
+            $updates = new stdClass();\r
+            $updates->response = array();\r
+        }\r
+\r
+        $wpUpdate = $updateToAdd->toWpFormat();\r
+        $pluginFile = $this->pluginFile;\r
+\r
+        if ( $this->isMuPlugin() ) {\r
+            //WP does not support automatic update installation for mu-plugins, but we can still display a notice.\r
+            $wpUpdate->package = null;\r
+            $pluginFile = $this->muPluginFile;\r
+        }\r
+        $updates->response[$pluginFile] = $wpUpdate;\r
+        return $updates;\r
+    }\r
+\r
+    /**\r
+     * @param stdClass|null $updates\r
+     * @return stdClass|null\r
+     */\r
+    private function removeUpdateFromList($updates) {\r
+        if ( isset($updates, $updates->response) ) {\r
+            unset($updates->response[$this->pluginFile]);\r
+            if ( !empty($this->muPluginFile) ) {\r
+                unset($updates->response[$this->muPluginFile]);\r
+            }\r
+        }\r
+        return $updates;\r
+    }\r
+\r
+    /**\r
+     * Insert translation updates into the list maintained by WordPress.\r
+     *\r
+     * @param stdClass $updates\r
+     * @return stdClass\r
+     */\r
+    public function injectTranslationUpdates($updates) {\r
+        $translationUpdates = $this->getTranslationUpdates();\r
+        if ( empty($translationUpdates) ) {\r
+            return $updates;\r
+        }\r
+\r
+        //Being defensive.\r
+        if ( !is_object($updates) ) {\r
+            $updates = new stdClass();\r
+        }\r
+        if ( !isset($updates->translations) ) {\r
+            $updates->translations = array();\r
+        }\r
+\r
+        //In case there's a name collision with a plugin hosted on wordpress.org,\r
+        //remove any preexisting updates that match our plugin.\r
+        $translationType = 'plugin';\r
+        $filteredTranslations = array();\r
+        foreach($updates->translations as $translation) {\r
+            if ( ($translation['type'] === $translationType) && ($translation['slug'] === $this->slug) ) {\r
+                continue;\r
+            }\r
+            $filteredTranslations[] = $translation;\r
+        }\r
+        $updates->translations = $filteredTranslations;\r
+\r
+        //Add our updates to the list.\r
+        foreach($translationUpdates as $update) {\r
+            $convertedUpdate = array_merge(\r
+                array(\r
+                    'type' => $translationType,\r
+                    'slug' => $this->slug,\r
+                    'autoupdate' => 0,\r
+                    //AFAICT, WordPress doesn't actually use the "version" field for anything.\r
+                    //But lets make sure it's there, just in case.\r
+                    'version' => isset($update->version) ? $update->version : ('1.' . strtotime($update->updated)),\r
+                ),\r
+                (array)$update\r
+            );\r
+\r
+            $updates->translations[] = $convertedUpdate;\r
+        }\r
+\r
+        return $updates;\r
+    }\r
+\r
+    /**\r
+     * Rename the update directory to match the existing plugin directory.\r
+     *\r
+     * When WordPress installs a plugin or theme update, it assumes that the ZIP file will contain\r
+     * exactly one directory, and that the directory name will be the same as the directory where\r
+     * the plugin/theme is currently installed.\r
+     *\r
+     * GitHub and other repositories provide ZIP downloads, but they often use directory names like\r
+     * "project-branch" or "project-tag-hash". We need to change the name to the actual plugin folder.\r
+     *\r
+     * This is a hook callback. Don't call it from a plugin.\r
+     *\r
+     * @param string $source The directory to copy to /wp-content/plugins. Usually a subdirectory of $remoteSource.\r
+     * @param string $remoteSource WordPress has extracted the update to this directory.\r
+     * @param WP_Upgrader $upgrader\r
+     * @return string|WP_Error\r
+     */\r
+    public function fixDirectoryName($source, $remoteSource, $upgrader) {\r
+        global $wp_filesystem; /** @var WP_Filesystem_Base $wp_filesystem */\r
+\r
+        //Basic sanity checks.\r
+        if ( !isset($source, $remoteSource, $upgrader, $upgrader->skin, $wp_filesystem) ) {\r
+            return $source;\r
+        }\r
+\r
+        //If WordPress is upgrading anything other than our plugin, leave the directory name unchanged.\r
+        if ( !$this->isPluginBeingUpgraded($upgrader) ) {\r
+            return $source;\r
+        }\r
+\r
+        //Rename the source to match the existing plugin directory.\r
+        $pluginDirectoryName = dirname($this->pluginFile);\r
+        if ( $pluginDirectoryName === '.' ) {\r
+            return $source;\r
+        }\r
+        $correctedSource = trailingslashit($remoteSource) . $pluginDirectoryName . '/';\r
+        if ( $source !== $correctedSource ) {\r
+            //The update archive should contain a single directory that contains the rest of plugin files. Otherwise,\r
+            //WordPress will try to copy the entire working directory ($source == $remoteSource). We can't rename\r
+            //$remoteSource because that would break WordPress code that cleans up temporary files after update.\r
+            if ( $this->isBadDirectoryStructure($remoteSource) ) {\r
+                return new WP_Error(\r
+                    'puc-incorrect-directory-structure',\r
+                    sprintf(\r
+                        'The directory structure of the update is incorrect. All plugin files should be inside ' .\r
+                        'a directory named <span class="code">%s</span>, not at the root of the ZIP file.',\r
+                        htmlentities($this->slug)\r
+                    )\r
+                );\r
+            }\r
+\r
+            /** @var WP_Upgrader_Skin $upgrader->skin */\r
+            $upgrader->skin->feedback(sprintf(\r
+                'Renaming %s to %s&#8230;',\r
+                '<span class="code">' . basename($source) . '</span>',\r
+                '<span class="code">' . $pluginDirectoryName . '</span>'\r
+            ));\r
+\r
+            if ( $wp_filesystem->move($source, $correctedSource, true) ) {\r
+                $upgrader->skin->feedback('Plugin directory successfully renamed.');\r
+                return $correctedSource;\r
+            } else {\r
+                return new WP_Error(\r
+                    'puc-rename-failed',\r
+                    'Unable to rename the update to match the existing plugin directory.'\r
+                );\r
+            }\r
+        }\r
+\r
+        return $source;\r
+    }\r
+\r
+    /**\r
+     * Check for incorrect update directory structure. An update must contain a single directory,\r
+     * all other files should be inside that directory.\r
+     *\r
+     * @param string $remoteSource Directory path.\r
+     * @return bool\r
+     */\r
+    private function isBadDirectoryStructure($remoteSource) {\r
+        global $wp_filesystem; /** @var WP_Filesystem_Base $wp_filesystem */\r
+\r
+        $sourceFiles = $wp_filesystem->dirlist($remoteSource);\r
+        if ( is_array($sourceFiles) ) {\r
+            $sourceFiles = array_keys($sourceFiles);\r
+            $firstFilePath = trailingslashit($remoteSource) . $sourceFiles[0];\r
+            return (count($sourceFiles) > 1) || (!$wp_filesystem->is_dir($firstFilePath));\r
+        }\r
+\r
+        //Assume it's fine.\r
+        return false;\r
+    }\r
+\r
+    /**\r
+     * Is there and update being installed RIGHT NOW, for this specific plugin?\r
+     *\r
+     * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.\r
+     * @return bool\r
+     */\r
+    public function isPluginBeingUpgraded($upgrader = null) {\r
+        return $this->upgraderStatus->isPluginBeingUpgraded($this->pluginFile, $upgrader);\r
+    }\r
+\r
+    /**\r
+     * Get the details of the currently available update, if any.\r
+     *\r
+     * If no updates are available, or if the last known update version is below or equal\r
+     * to the currently installed version, this method will return NULL.\r
+     *\r
+     * Uses cached update data. To retrieve update information straight from\r
+     * the metadata URL, call requestUpdate() instead.\r
+     *\r
+     * @return PluginUpdate_3_1|null\r
+     */\r
+    public function getUpdate() {\r
+        $state = $this->getUpdateState(); /** @var StdClass $state */\r
+\r
+        //Is there an update available?\r
+        if ( isset($state, $state->update) ) {\r
+            $update = $state->update;\r
+            //Check if the update is actually newer than the currently installed version.\r
+            $installedVersion = $this->getInstalledVersion();\r
+            if ( ($installedVersion !== null) && version_compare($update->version, $installedVersion, '>') ){\r
+                $update->filename = $this->pluginFile;\r
+                return $update;\r
+            }\r
+        }\r
+        return null;\r
+    }\r
+\r
+    /**\r
+     * Get a list of available translation updates.\r
+     *\r
+     * This method will return an empty array if there are no updates.\r
+     * Uses cached update data.\r
+     *\r
+     * @return array\r
+     */\r
+    public function getTranslationUpdates() {\r
+        $state = $this->getUpdateState();\r
+        if ( isset($state, $state->update, $state->update->translations) ) {\r
+            return $state->update->translations;\r
+        }\r
+        return array();\r
+    }\r
+\r
+    /**\r
+     * Remove all cached translation updates.\r
+     *\r
+     * @see wp_clean_update_cache\r
+     */\r
+    public function clearCachedTranslationUpdates() {\r
+        $state = $this->getUpdateState();\r
+        if ( isset($state, $state->update, $state->update->translations) ) {\r
+            $state->update->translations = array();\r
+            $this->setUpdateState($state);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Add a "Check for updates" link to the plugin row in the "Plugins" page. By default,\r
+     * the new link will appear after the "Visit plugin site" link.\r
+     *\r
+     * You can change the link text by using the "puc_manual_check_link-$slug" filter.\r
+     * Returning an empty string from the filter will disable the link.\r
+     *\r
+     * @param array $pluginMeta Array of meta links.\r
+     * @param string $pluginFile\r
+     * @return array\r
+     */\r
+    public function addCheckForUpdatesLink($pluginMeta, $pluginFile) {\r
+        $isRelevant = ($pluginFile == $this->pluginFile)\r
+                      || (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);\r
+\r
+        if ( $isRelevant && current_user_can('update_plugins') ) {\r
+            $linkUrl = wp_nonce_url(\r
+                add_query_arg(\r
+                    array(\r
+                        'puc_check_for_updates' => 1,\r
+                        'puc_slug' => $this->slug,\r
+                    ),\r
+                    self_admin_url('plugins.php')\r
+                ),\r
+                'puc_check_for_updates'\r
+            );\r
+\r
+            $linkText = apply_filters('puc_manual_check_link-' . $this->slug, __('Check for updates', 'plugin-update-checker'));\r
+            if ( !empty($linkText) ) {\r
+                $pluginMeta[] = sprintf('<a href="%s">%s</a>', esc_attr($linkUrl), $linkText);\r
+            }\r
+        }\r
+        return $pluginMeta;\r
+    }\r
+\r
+    /**\r
+     * Check for updates when the user clicks the "Check for updates" link.\r
+     * @see self::addCheckForUpdatesLink()\r
+     *\r
+     * @return void\r
+     */\r
+    public function handleManualCheck() {\r
+        $shouldCheck =\r
+               isset($_GET['puc_check_for_updates'], $_GET['puc_slug'])\r
+            && $_GET['puc_slug'] == $this->slug\r
+            && current_user_can('update_plugins')\r
+            && check_admin_referer('puc_check_for_updates');\r
+\r
+        if ( $shouldCheck ) {\r
+            $update = $this->checkForUpdates();\r
+            $status = ($update === null) ? 'no_update' : 'update_available';\r
+            wp_redirect(add_query_arg(\r
+                array(\r
+                    'puc_update_check_result' => $status,\r
+                    'puc_slug' => $this->slug,\r
+                ),\r
+                self_admin_url('plugins.php')\r
+            ));\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Display the results of a manual update check.\r
+     * @see self::handleManualCheck()\r
+     *\r
+     * You can change the result message by using the "puc_manual_check_message-$slug" filter.\r
+     */\r
+    public function displayManualCheckResult() {\r
+        if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->slug) ) {\r
+            $status = strval($_GET['puc_update_check_result']);\r
+            if ( $status == 'no_update' ) {\r
+                $message = __('This plugin is up to date.', 'plugin-update-checker');\r
+            } else if ( $status == 'update_available' ) {\r
+                $message = __('A new version of this plugin is available.', 'plugin-update-checker');\r
+            } else {\r
+                $message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), htmlentities($status));\r
+            }\r
+            printf(\r
+                '<div class="updated notice is-dismissible"><p>%s</p></div>',\r
+                apply_filters('puc_manual_check_message-' . $this->slug, $message, $status)\r
+            );\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Check if the plugin file is inside the mu-plugins directory.\r
+     *\r
+     * @return bool\r
+     */\r
+    protected function isMuPlugin() {\r
+        static $cachedResult = null;\r
+\r
+        if ( $cachedResult === null ) {\r
+            //Convert both paths to the canonical form before comparison.\r
+            $muPluginDir = realpath(WPMU_PLUGIN_DIR);\r
+            $pluginPath  = realpath($this->pluginAbsolutePath);\r
+\r
+            $cachedResult = (strpos($pluginPath, $muPluginDir) === 0);\r
+        }\r
+\r
+        return $cachedResult;\r
+    }\r
+\r
+    /**\r
+     * MU plugins are partially supported, but only when we know which file in mu-plugins\r
+     * corresponds to this plugin.\r
+     *\r
+     * @return bool\r
+     */\r
+    protected function isUnknownMuPlugin() {\r
+        return empty($this->muPluginFile) && $this->isMuPlugin();\r
+    }\r
+\r
+    /**\r
+     * Clear the cached plugin version. This method can be set up as a filter (hook) and will\r
+     * return the filter argument unmodified.\r
+     *\r
+     * @param mixed $filterArgument\r
+     * @return mixed\r
+     */\r
+    public function clearCachedVersion($filterArgument = null) {\r
+        $this->cachedInstalledVersion = null;\r
+        return $filterArgument;\r
+    }\r
+\r
+    /**\r
+     * Register a callback for filtering query arguments.\r
+     *\r
+     * The callback function should take one argument - an associative array of query arguments.\r
+     * It should return a modified array of query arguments.\r
+     *\r
+     * @uses add_filter() This method is a convenience wrapper for add_filter().\r
+     *\r
+     * @param callable $callback\r
+     * @return void\r
+     */\r
+    public function addQueryArgFilter($callback){\r
+        add_filter('puc_request_info_query_args-'.$this->slug, $callback);\r
+    }\r
+\r
+    /**\r
+     * Register a callback for filtering arguments passed to wp_remote_get().\r
+     *\r
+     * The callback function should take one argument - an associative array of arguments -\r
+     * and return a modified array or arguments. See the WP documentation on wp_remote_get()\r
+     * for details on what arguments are available and how they work.\r
+     *\r
+     * @uses add_filter() This method is a convenience wrapper for add_filter().\r
+     *\r
+     * @param callable $callback\r
+     * @return void\r
+     */\r
+    public function addHttpRequestArgFilter($callback){\r
+        add_filter('puc_request_info_options-'.$this->slug, $callback);\r
+    }\r
+\r
+    /**\r
+     * Register a callback for filtering the plugin info retrieved from the external API.\r
+     *\r
+     * The callback function should take two arguments. If the plugin info was retrieved\r
+     * successfully, the first argument passed will be an instance of  PluginInfo. Otherwise,\r
+     * it will be NULL. The second argument will be the corresponding return value of\r
+     * wp_remote_get (see WP docs for details).\r
+     *\r
+     * The callback function should return a new or modified instance of PluginInfo or NULL.\r
+     *\r
+     * @uses add_filter() This method is a convenience wrapper for add_filter().\r
+     *\r
+     * @param callable $callback\r
+     * @return void\r
+     */\r
+    public function addResultFilter($callback){\r
+        add_filter('puc_request_info_result-'.$this->slug, $callback, 10, 2);\r
+    }\r
+\r
+    /**\r
+     * Register a callback for one of the update checker filters.\r
+     *\r
+     * Identical to add_filter(), except it automatically adds the "puc_" prefix\r
+     * and the "-$plugin_slug" suffix to the filter name. For example, "request_info_result"\r
+     * becomes "puc_request_info_result-your_plugin_slug".\r
+     *\r
+     * @param string $tag\r
+     * @param callable $callback\r
+     * @param int $priority\r
+     * @param int $acceptedArgs\r
+     */\r
+    public function addFilter($tag, $callback, $priority = 10, $acceptedArgs = 1) {\r
+        add_filter('puc_' . $tag . '-' . $this->slug, $callback, $priority, $acceptedArgs);\r
+    }\r
+\r
+    /**\r
+     * Initialize the update checker Debug Bar plugin/add-on thingy.\r
+     */\r
+    public function initDebugBarPanel() {\r
+        $debugBarPlugin = dirname(__FILE__) . '/debug-bar-plugin.php';\r
+        if ( class_exists('Debug_Bar', false) && file_exists($debugBarPlugin) ) {\r
+            /** @noinspection PhpIncludeInspection */\r
+            require_once $debugBarPlugin;\r
+            $this->debugBarPlugin = new PucDebugBarPlugin_3_1($this);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Trigger a PHP error, but only when $debugMode is enabled.\r
+     *\r
+     * @param string $message\r
+     * @param int $errorType\r
+     */\r
+    protected function triggerError($message, $errorType) {\r
+        if ( $this->debugMode ) {\r
+            trigger_error($message, $errorType);\r
+        }\r
+    }\r
 }\r
 \r
 endif;\r
@@ -963,133 +963,133 @@ if ( !class_exists('PluginInfo_3_1', false) ):
  * @access public\r
  */\r
 class PluginInfo_3_1 {\r
-       //Most fields map directly to the contents of the plugin's info.json file.\r
-       //See the relevant docs for a description of their meaning.\r
-       public $name;\r
-       public $slug;\r
-       public $version;\r
-       public $homepage;\r
-       public $sections = array();\r
-       public $banners;\r
-       public $translations = array();\r
-       public $download_url;\r
-\r
-       public $author;\r
-       public $author_homepage;\r
-\r
-       public $requires;\r
-       public $tested;\r
-       public $upgrade_notice;\r
-\r
-       public $rating;\r
-       public $num_ratings;\r
-       public $downloaded;\r
-       public $active_installs;\r
-       public $last_updated;\r
-\r
-       public $id = 0; //The native WP.org API returns numeric plugin IDs, but they're not used for anything.\r
-\r
-       public $filename; //Plugin filename relative to the plugins directory.\r
-\r
-       /**\r
-        * Create a new instance of PluginInfo from JSON-encoded plugin info\r
-        * returned by an external update API.\r
-        *\r
-        * @param string $json Valid JSON string representing plugin info.\r
-        * @return PluginInfo_3_1|null New instance of PluginInfo, or NULL on error.\r
-        */\r
-       public static function fromJson($json){\r
-               /** @var StdClass $apiResponse */\r
-               $apiResponse = json_decode($json);\r
-               if ( empty($apiResponse) || !is_object($apiResponse) ){\r
-                       trigger_error(\r
-                               "Failed to parse plugin metadata. Try validating your .json file with http://jsonlint.com/\n".print_r($apiResponse,1),\r
-                               E_USER_NOTICE\r
-                       );\r
-                       return null;\r
-               }\r
-\r
-               $valid = self::validateMetadata($apiResponse);\r
-               if ( is_wp_error($valid) ){\r
-                       trigger_error($valid->get_error_message(), E_USER_NOTICE);\r
-                       return null;\r
-               }\r
-\r
-               $info = new self();\r
-               foreach(get_object_vars($apiResponse) as $key => $value){\r
-                       $info->$key = $value;\r
-               }\r
-\r
-               //json_decode decodes assoc. arrays as objects. We want it as an array.\r
-               $info->sections = (array)$info->sections;\r
-\r
-               return $info;\r
-       }\r
-\r
-       /**\r
-        * Very, very basic validation.\r
-        *\r
-        * @param StdClass $apiResponse\r
-        * @return bool|WP_Error\r
-        */\r
-       protected static function validateMetadata($apiResponse) {\r
-               if (\r
-                       !isset($apiResponse->name, $apiResponse->version)\r
-                       || empty($apiResponse->name)\r
-                       || empty($apiResponse->version)\r
-               ) {\r
-                       return new WP_Error(\r
-                               'puc-invalid-metadata',\r
-                               "The plugin metadata file does not contain the required 'name' and/or 'version' keys."\r
-                       );\r
-               }\r
-               return true;\r
-       }\r
-\r
-\r
-       /**\r
-        * Transform plugin info into the format used by the native WordPress.org API\r
-        *\r
-        * @return object\r
-        */\r
-       public function toWpFormat(){\r
-               $info = new stdClass;\r
-\r
-               //The custom update API is built so that many fields have the same name and format\r
-               //as those returned by the native WordPress.org API. These can be assigned directly.\r
-               $sameFormat = array(\r
-                       'name', 'slug', 'version', 'requires', 'tested', 'rating', 'upgrade_notice',\r
-                       'num_ratings', 'downloaded', 'active_installs', 'homepage', 'last_updated',\r
-               );\r
-               foreach($sameFormat as $field){\r
-                       if ( isset($this->$field) ) {\r
-                               $info->$field = $this->$field;\r
-                       } else {\r
-                               $info->$field = null;\r
-                       }\r
-               }\r
-\r
-               //Other fields need to be renamed and/or transformed.\r
-               $info->download_link = $this->download_url;\r
-               $info->author = $this->getFormattedAuthor();\r
-               $info->sections = array_merge(array('description' => ''), $this->sections);\r
-\r
-               if ( !empty($this->banners) ) {\r
-                       //WP expects an array with two keys: "high" and "low". Both are optional.\r
-                       //Docs: https://wordpress.org/plugins/about/faq/#banners\r
-                       $info->banners = is_object($this->banners) ? get_object_vars($this->banners) : $this->banners;\r
-                       $info->banners = array_intersect_key($info->banners, array('high' => true, 'low' => true));\r
-               }\r
-\r
-               return $info;\r
-       }\r
-\r
-       protected function getFormattedAuthor() {\r
-               if ( !empty($this->author_homepage) ){\r
-                       return sprintf('<a href="%s">%s</a>', $this->author_homepage, $this->author);\r
-               }\r
-               return $this->author;\r
-       }\r
+    //Most fields map directly to the contents of the plugin's info.json file.\r
+    //See the relevant docs for a description of their meaning.\r
+    public $name;\r
+    public $slug;\r
+    public $version;\r
+    public $homepage;\r
+    public $sections = array();\r
+    public $banners;\r
+    public $translations = array();\r
+    public $download_url;\r
+\r
+    public $author;\r
+    public $author_homepage;\r
+\r
+    public $requires;\r
+    public $tested;\r
+    public $upgrade_notice;\r
+\r
+    public $rating;\r
+    public $num_ratings;\r
+    public $downloaded;\r
+    public $active_installs;\r
+    public $last_updated;\r
+\r
+    public $id = 0; //The native WP.org API returns numeric plugin IDs, but they're not used for anything.\r
+\r
+    public $filename; //Plugin filename relative to the plugins directory.\r
+\r
+    /**\r
+     * Create a new instance of PluginInfo from JSON-encoded plugin info\r
+     * returned by an external update API.\r
+     *\r
+     * @param string $json Valid JSON string representing plugin info.\r
+     * @return PluginInfo_3_1|null New instance of PluginInfo, or NULL on error.\r
+     */\r
+    public static function fromJson($json){\r
+        /** @var StdClass $apiResponse */\r
+        $apiResponse = json_decode($json);\r
+        if ( empty($apiResponse) || !is_object($apiResponse) ){\r
+            trigger_error(\r
+                "Failed to parse plugin metadata. Try validating your .json file with http://jsonlint.com/\n".print_r($apiResponse,1),\r
+                E_USER_NOTICE\r
+            );\r
+            return null;\r
+        }\r
+\r
+        $valid = self::validateMetadata($apiResponse);\r
+        if ( is_wp_error($valid) ){\r
+            trigger_error($valid->get_error_message(), E_USER_NOTICE);\r
+            return null;\r
+        }\r
+\r
+        $info = new self();\r
+        foreach(get_object_vars($apiResponse) as $key => $value){\r
+            $info->$key = $value;\r
+        }\r
+\r
+        //json_decode decodes assoc. arrays as objects. We want it as an array.\r
+        $info->sections = (array)$info->sections;\r
+\r
+        return $info;\r
+    }\r
+\r
+    /**\r
+     * Very, very basic validation.\r
+     *\r
+     * @param StdClass $apiResponse\r
+     * @return bool|WP_Error\r
+     */\r
+    protected static function validateMetadata($apiResponse) {\r
+        if (\r
+            !isset($apiResponse->name, $apiResponse->version)\r
+            || empty($apiResponse->name)\r
+            || empty($apiResponse->version)\r
+        ) {\r
+            return new WP_Error(\r
+                'puc-invalid-metadata',\r
+                "The plugin metadata file does not contain the required 'name' and/or 'version' keys."\r
+            );\r
+        }\r
+        return true;\r
+    }\r
+\r
+\r
+    /**\r
+     * Transform plugin info into the format used by the native WordPress.org API\r
+     *\r
+     * @return object\r
+     */\r
+    public function toWpFormat(){\r
+        $info = new stdClass;\r
+\r
+        //The custom update API is built so that many fields have the same name and format\r
+        //as those returned by the native WordPress.org API. These can be assigned directly.\r
+        $sameFormat = array(\r
+            'name', 'slug', 'version', 'requires', 'tested', 'rating', 'upgrade_notice',\r
+            'num_ratings', 'downloaded', 'active_installs', 'homepage', 'last_updated',\r
+        );\r
+        foreach($sameFormat as $field){\r
+            if ( isset($this->$field) ) {\r
+                $info->$field = $this->$field;\r
+            } else {\r
+                $info->$field = null;\r
+            }\r
+        }\r
+\r
+        //Other fields need to be renamed and/or transformed.\r
+        $info->download_link = $this->download_url;\r
+        $info->author = $this->getFormattedAuthor();\r
+        $info->sections = array_merge(array('description' => ''), $this->sections);\r
+\r
+        if ( !empty($this->banners) ) {\r
+            //WP expects an array with two keys: "high" and "low". Both are optional.\r
+            //Docs: https://wordpress.org/plugins/about/faq/#banners\r
+            $info->banners = is_object($this->banners) ? get_object_vars($this->banners) : $this->banners;\r
+            $info->banners = array_intersect_key($info->banners, array('high' => true, 'low' => true));\r
+        }\r
+\r
+        return $info;\r
+    }\r
+\r
+    protected function getFormattedAuthor() {\r
+        if ( !empty($this->author_homepage) ){\r
+            return sprintf('<a href="%s">%s</a>', $this->author_homepage, $this->author);\r
+        }\r
+        return $this->author;\r
+    }\r
 }\r
 \r
 endif;\r
@@ -1105,117 +1105,117 @@ if ( !class_exists('PluginUpdate_3_1', false) ):
  * @access public\r
  */\r
 class PluginUpdate_3_1 {\r
-       public $id = 0;\r
-       public $slug;\r
-       public $version;\r
-       public $homepage;\r
-       public $download_url;\r
-       public $upgrade_notice;\r
-       public $tested;\r
-       public $translations = array();\r
-       public $filename; //Plugin filename relative to the plugins directory.\r
-\r
-       private static $fields = array(\r
-               'id', 'slug', 'version', 'homepage', 'tested',\r
-               'download_url', 'upgrade_notice', 'filename',\r
-               'translations'\r
-       );\r
-\r
-       /**\r
-        * Create a new instance of PluginUpdate from its JSON-encoded representation.\r
-        *\r
-        * @param string $json\r
-        * @return PluginUpdate_3_1|null\r
-        */\r
-       public static function fromJson($json){\r
-               //Since update-related information is simply a subset of the full plugin info,\r
-               //we can parse the update JSON as if it was a plugin info string, then copy over\r
-               //the parts that we care about.\r
-               $pluginInfo = PluginInfo_3_1::fromJson($json);\r
-               if ( $pluginInfo != null ) {\r
-                       return self::fromPluginInfo($pluginInfo);\r
-               } else {\r
-                       return null;\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Create a new instance of PluginUpdate based on an instance of PluginInfo.\r
-        * Basically, this just copies a subset of fields from one object to another.\r
-        *\r
-        * @param PluginInfo_3_1 $info\r
-        * @return PluginUpdate_3_1\r
-        */\r
-       public static function fromPluginInfo($info){\r
-               return self::fromObject($info);\r
-       }\r
-\r
-       /**\r
-        * Create a new instance of PluginUpdate by copying the necessary fields from\r
-        * another object.\r
-        *\r
-        * @param StdClass|PluginInfo_3_1|PluginUpdate_3_1 $object The source object.\r
-        * @return PluginUpdate_3_1 The new copy.\r
-        */\r
-       public static function fromObject($object) {\r
-               $update = new self();\r
-               $fields = self::$fields;\r
-               if ( !empty($object->slug) ) {\r
-                       $fields = apply_filters('puc_retain_fields-' . $object->slug, $fields);\r
-               }\r
-               foreach($fields as $field){\r
-                       if (property_exists($object, $field)) {\r
-                               $update->$field = $object->$field;\r
-                       }\r
-               }\r
-               return $update;\r
-       }\r
-\r
-       /**\r
-        * Create an instance of StdClass that can later be converted back to\r
-        * a PluginUpdate. Useful for serialization and caching, as it avoids\r
-        * the "incomplete object" problem if the cached value is loaded before\r
-        * this class.\r
-        *\r
-        * @return StdClass\r
-        */\r
-       public function toStdClass() {\r
-               $object = new stdClass();\r
-               $fields = self::$fields;\r
-               if ( !empty($this->slug) ) {\r
-                       $fields = apply_filters('puc_retain_fields-' . $this->slug, $fields);\r
-               }\r
-               foreach($fields as $field){\r
-                       if (property_exists($this, $field)) {\r
-                               $object->$field = $this->$field;\r
-                       }\r
-               }\r
-               return $object;\r
-       }\r
-\r
-\r
-       /**\r
-        * Transform the update into the format used by WordPress native plugin API.\r
-        *\r
-        * @return object\r
-        */\r
-       public function toWpFormat(){\r
-               $update = new stdClass;\r
-\r
-               $update->id = $this->id;\r
-               $update->slug = $this->slug;\r
-               $update->new_version = $this->version;\r
-               $update->url = $this->homepage;\r
-               $update->package = $this->download_url;\r
-               $update->tested = $this->tested;\r
-               $update->plugin = $this->filename;\r
-\r
-               if ( !empty($this->upgrade_notice) ){\r
-                       $update->upgrade_notice = $this->upgrade_notice;\r
-               }\r
-\r
-               return $update;\r
-       }\r
+    public $id = 0;\r
+    public $slug;\r
+    public $version;\r
+    public $homepage;\r
+    public $download_url;\r
+    public $upgrade_notice;\r
+    public $tested;\r
+    public $translations = array();\r
+    public $filename; //Plugin filename relative to the plugins directory.\r
+\r
+    private static $fields = array(\r
+        'id', 'slug', 'version', 'homepage', 'tested',\r
+        'download_url', 'upgrade_notice', 'filename',\r
+        'translations'\r
+    );\r
+\r
+    /**\r
+     * Create a new instance of PluginUpdate from its JSON-encoded representation.\r
+     *\r
+     * @param string $json\r
+     * @return PluginUpdate_3_1|null\r
+     */\r
+    public static function fromJson($json){\r
+        //Since update-related information is simply a subset of the full plugin info,\r
+        //we can parse the update JSON as if it was a plugin info string, then copy over\r
+        //the parts that we care about.\r
+        $pluginInfo = PluginInfo_3_1::fromJson($json);\r
+        if ( $pluginInfo != null ) {\r
+            return self::fromPluginInfo($pluginInfo);\r
+        } else {\r
+            return null;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Create a new instance of PluginUpdate based on an instance of PluginInfo.\r
+     * Basically, this just copies a subset of fields from one object to another.\r
+     *\r
+     * @param PluginInfo_3_1 $info\r
+     * @return PluginUpdate_3_1\r
+     */\r
+    public static function fromPluginInfo($info){\r
+        return self::fromObject($info);\r
+    }\r
+\r
+    /**\r
+     * Create a new instance of PluginUpdate by copying the necessary fields from\r
+     * another object.\r
+     *\r
+     * @param StdClass|PluginInfo_3_1|PluginUpdate_3_1 $object The source object.\r
+     * @return PluginUpdate_3_1 The new copy.\r
+     */\r
+    public static function fromObject($object) {\r
+        $update = new self();\r
+        $fields = self::$fields;\r
+        if ( !empty($object->slug) ) {\r
+            $fields = apply_filters('puc_retain_fields-' . $object->slug, $fields);\r
+        }\r
+        foreach($fields as $field){\r
+            if (property_exists($object, $field)) {\r
+                $update->$field = $object->$field;\r
+            }\r
+        }\r
+        return $update;\r
+    }\r
+\r
+    /**\r
+     * Create an instance of StdClass that can later be converted back to\r
+     * a PluginUpdate. Useful for serialization and caching, as it avoids\r
+     * the "incomplete object" problem if the cached value is loaded before\r
+     * this class.\r
+     *\r
+     * @return StdClass\r
+     */\r
+    public function toStdClass() {\r
+        $object = new stdClass();\r
+        $fields = self::$fields;\r
+        if ( !empty($this->slug) ) {\r
+            $fields = apply_filters('puc_retain_fields-' . $this->slug, $fields);\r
+        }\r
+        foreach($fields as $field){\r
+            if (property_exists($this, $field)) {\r
+                $object->$field = $this->$field;\r
+            }\r
+        }\r
+        return $object;\r
+    }\r
+\r
+\r
+    /**\r
+     * Transform the update into the format used by WordPress native plugin API.\r
+     *\r
+     * @return object\r
+     */\r
+    public function toWpFormat(){\r
+        $update = new stdClass;\r
+\r
+        $update->id = $this->id;\r
+        $update->slug = $this->slug;\r
+        $update->new_version = $this->version;\r
+        $update->url = $this->homepage;\r
+        $update->package = $this->download_url;\r
+        $update->tested = $this->tested;\r
+        $update->plugin = $this->filename;\r
+\r
+        if ( !empty($this->upgrade_notice) ){\r
+            $update->upgrade_notice = $this->upgrade_notice;\r
+        }\r
+\r
+        return $update;\r
+    }\r
 }\r
 \r
 endif;\r
@@ -1229,171 +1229,171 @@ if ( !class_exists('PucScheduler_3_1', false) ):
  * @version 3.0\r
  */\r
 class PucScheduler_3_1 {\r
-       public $checkPeriod = 12; //How often to check for updates (in hours).\r
-       public $throttleRedundantChecks = false; //Check less often if we already know that an update is available.\r
-       public $throttledCheckPeriod = 72;\r
-\r
-       /**\r
-        * @var PluginUpdateChecker_3_1\r
-        */\r
-       protected $updateChecker;\r
-\r
-       private $cronHook = null;\r
-\r
-       /**\r
-        * Scheduler constructor.\r
-        *\r
-        * @param PluginUpdateChecker_3_1 $updateChecker\r
-        * @param int $checkPeriod How often to check for updates (in hours).\r
-        */\r
-       public function __construct($updateChecker, $checkPeriod) {\r
-               $this->updateChecker = $updateChecker;\r
-               $this->checkPeriod = $checkPeriod;\r
-\r
-               //Set up the periodic update checks\r
-               $this->cronHook = 'check_plugin_updates-' . $this->updateChecker->slug;\r
-               if ( $this->checkPeriod > 0 ){\r
-\r
-                       //Trigger the check via Cron.\r
-                       //Try to use one of the default schedules if possible as it's less likely to conflict\r
-                       //with other plugins and their custom schedules.\r
-                       $defaultSchedules = array(\r
-                               1  => 'hourly',\r
-                               12 => 'twicedaily',\r
-                               24 => 'daily',\r
-                       );\r
-                       if ( array_key_exists($this->checkPeriod, $defaultSchedules) ) {\r
-                               $scheduleName = $defaultSchedules[$this->checkPeriod];\r
-                       } else {\r
-                               //Use a custom cron schedule.\r
-                               $scheduleName = 'every' . $this->checkPeriod . 'hours';\r
-                               add_filter('cron_schedules', array($this, '_addCustomSchedule'));\r
-                       }\r
-\r
-                       if ( !wp_next_scheduled($this->cronHook) && !defined('WP_INSTALLING') ) {\r
-                               wp_schedule_event(time(), $scheduleName, $this->cronHook);\r
-                       }\r
-                       add_action($this->cronHook, array($this, 'maybeCheckForUpdates'));\r
-\r
-                       register_deactivation_hook($this->updateChecker->pluginFile, array($this, '_removeUpdaterCron'));\r
-\r
-                       //In case Cron is disabled or unreliable, we also manually trigger\r
-                       //the periodic checks while the user is browsing the Dashboard.\r
-                       add_action( 'admin_init', array($this, 'maybeCheckForUpdates') );\r
-\r
-                       //Like WordPress itself, we check more often on certain pages.\r
-                       /** @see wp_update_plugins */\r
-                       add_action('load-update-core.php', array($this, 'maybeCheckForUpdates'));\r
-                       add_action('load-plugins.php', array($this, 'maybeCheckForUpdates'));\r
-                       add_action('load-update.php', array($this, 'maybeCheckForUpdates'));\r
-                       //This hook fires after a bulk update is complete.\r
-                       add_action('upgrader_process_complete', array($this, 'maybeCheckForUpdates'), 11, 0);\r
-\r
-               } else {\r
-                       //Periodic checks are disabled.\r
-                       wp_clear_scheduled_hook($this->cronHook);\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Check for updates if the configured check interval has already elapsed.\r
-        * Will use a shorter check interval on certain admin pages like "Dashboard -> Updates" or when doing cron.\r
-        *\r
-        * You can override the default behaviour by using the "puc_check_now-$slug" filter.\r
-        * The filter callback will be passed three parameters:\r
-        *     - Current decision. TRUE = check updates now, FALSE = don't check now.\r
-        *     - Last check time as a Unix timestamp.\r
-        *     - Configured check period in hours.\r
-        * Return TRUE to check for updates immediately, or FALSE to cancel.\r
-        *\r
-        * This method is declared public because it's a hook callback. Calling it directly is not recommended.\r
-        */\r
-       public function maybeCheckForUpdates(){\r
-               if ( empty($this->checkPeriod) ){\r
-                       return;\r
-               }\r
-\r
-               $state = $this->updateChecker->getUpdateState();\r
-               $shouldCheck =\r
-                       empty($state) ||\r
-                       !isset($state->lastCheck) ||\r
-                       ( (time() - $state->lastCheck) >= $this->getEffectiveCheckPeriod() );\r
-\r
-               //Let plugin authors substitute their own algorithm.\r
-               $shouldCheck = apply_filters(\r
-                       'puc_check_now-' . $this->updateChecker->slug,\r
-                       $shouldCheck,\r
-                       (!empty($state) && isset($state->lastCheck)) ? $state->lastCheck : 0,\r
-                       $this->checkPeriod\r
-               );\r
-\r
-               if ( $shouldCheck ) {\r
-                       $this->updateChecker->checkForUpdates();\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Calculate the actual check period based on the current status and environment.\r
-        *\r
-        * @return int Check period in seconds.\r
-        */\r
-       protected function getEffectiveCheckPeriod() {\r
-               $currentFilter = current_filter();\r
-               if ( in_array($currentFilter, array('load-update-core.php', 'upgrader_process_complete')) ) {\r
-                       //Check more often when the user visits "Dashboard -> Updates" or does a bulk update.\r
-                       $period = 60;\r
-               } else if ( in_array($currentFilter, array('load-plugins.php', 'load-update.php')) ) {\r
-                       //Also check more often on the "Plugins" page and /wp-admin/update.php.\r
-                       $period = 3600;\r
-               } else if ( $this->throttleRedundantChecks && ($this->updateChecker->getUpdate() !== null) ) {\r
-                       //Check less frequently if it's already known that an update is available.\r
-                       $period = $this->throttledCheckPeriod * 3600;\r
-               } else if ( defined('DOING_CRON') && constant('DOING_CRON') ) {\r
-                       //WordPress cron schedules are not exact, so lets do an update check even\r
-                       //if slightly less than $checkPeriod hours have elapsed since the last check.\r
-                       $cronFuzziness = 20 * 60;\r
-                       $period = $this->checkPeriod * 3600 - $cronFuzziness;\r
-               } else {\r
-                       $period = $this->checkPeriod * 3600;\r
-               }\r
-\r
-               return $period;\r
-       }\r
-\r
-       /**\r
-        * Add our custom schedule to the array of Cron schedules used by WP.\r
-        *\r
-        * @param array $schedules\r
-        * @return array\r
-        */\r
-       public function _addCustomSchedule($schedules){\r
-               if ( $this->checkPeriod && ($this->checkPeriod > 0) ){\r
-                       $scheduleName = 'every' . $this->checkPeriod . 'hours';\r
-                       $schedules[$scheduleName] = array(\r
-                               'interval' => $this->checkPeriod * 3600,\r
-                               'display' => sprintf('Every %d hours', $this->checkPeriod),\r
-                       );\r
-               }\r
-               return $schedules;\r
-       }\r
-\r
-       /**\r
-        * Remove the scheduled cron event that the library uses to check for updates.\r
-        *\r
-        * @return void\r
-        */\r
-       public function _removeUpdaterCron(){\r
-               wp_clear_scheduled_hook($this->cronHook);\r
-       }\r
-\r
-       /**\r
-        * Get the name of the update checker's WP-cron hook. Mostly useful for debugging.\r
-        *\r
-        * @return string\r
-        */\r
-       public function getCronHookName() {\r
-               return $this->cronHook;\r
-       }\r
+    public $checkPeriod = 12; //How often to check for updates (in hours).\r
+    public $throttleRedundantChecks = false; //Check less often if we already know that an update is available.\r
+    public $throttledCheckPeriod = 72;\r
+\r
+    /**\r
+     * @var PluginUpdateChecker_3_1\r
+     */\r
+    protected $updateChecker;\r
+\r
+    private $cronHook = null;\r
+\r
+    /**\r
+     * Scheduler constructor.\r
+     *\r
+     * @param PluginUpdateChecker_3_1 $updateChecker\r
+     * @param int $checkPeriod How often to check for updates (in hours).\r
+     */\r
+    public function __construct($updateChecker, $checkPeriod) {\r
+        $this->updateChecker = $updateChecker;\r
+        $this->checkPeriod = $checkPeriod;\r
+\r
+        //Set up the periodic update checks\r
+        $this->cronHook = 'check_plugin_updates-' . $this->updateChecker->slug;\r
+        if ( $this->checkPeriod > 0 ){\r
+\r
+            //Trigger the check via Cron.\r
+            //Try to use one of the default schedules if possible as it's less likely to conflict\r
+            //with other plugins and their custom schedules.\r
+            $defaultSchedules = array(\r
+                1  => 'hourly',\r
+                12 => 'twicedaily',\r
+                24 => 'daily',\r
+            );\r
+            if ( array_key_exists($this->checkPeriod, $defaultSchedules) ) {\r
+                $scheduleName = $defaultSchedules[$this->checkPeriod];\r
+            } else {\r
+                //Use a custom cron schedule.\r
+                $scheduleName = 'every' . $this->checkPeriod . 'hours';\r
+                add_filter('cron_schedules', array($this, '_addCustomSchedule'));\r
+            }\r
+\r
+            if ( !wp_next_scheduled($this->cronHook) && !defined('WP_INSTALLING') ) {\r
+                wp_schedule_event(time(), $scheduleName, $this->cronHook);\r
+            }\r
+            add_action($this->cronHook, array($this, 'maybeCheckForUpdates'));\r
+\r
+            register_deactivation_hook($this->updateChecker->pluginFile, array($this, '_removeUpdaterCron'));\r
+\r
+            //In case Cron is disabled or unreliable, we also manually trigger\r
+            //the periodic checks while the user is browsing the Dashboard.\r
+            add_action( 'admin_init', array($this, 'maybeCheckForUpdates') );\r
+\r
+            //Like WordPress itself, we check more often on certain pages.\r
+            /** @see wp_update_plugins */\r
+            add_action('load-update-core.php', array($this, 'maybeCheckForUpdates'));\r
+            add_action('load-plugins.php', array($this, 'maybeCheckForUpdates'));\r
+            add_action('load-update.php', array($this, 'maybeCheckForUpdates'));\r
+            //This hook fires after a bulk update is complete.\r
+            add_action('upgrader_process_complete', array($this, 'maybeCheckForUpdates'), 11, 0);\r
+\r
+        } else {\r
+            //Periodic checks are disabled.\r
+            wp_clear_scheduled_hook($this->cronHook);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Check for updates if the configured check interval has already elapsed.\r
+     * Will use a shorter check interval on certain admin pages like "Dashboard -> Updates" or when doing cron.\r
+     *\r
+     * You can override the default behaviour by using the "puc_check_now-$slug" filter.\r
+     * The filter callback will be passed three parameters:\r
+     *     - Current decision. TRUE = check updates now, FALSE = don't check now.\r
+     *     - Last check time as a Unix timestamp.\r
+     *     - Configured check period in hours.\r
+     * Return TRUE to check for updates immediately, or FALSE to cancel.\r
+     *\r
+     * This method is declared public because it's a hook callback. Calling it directly is not recommended.\r
+     */\r
+    public function maybeCheckForUpdates(){\r
+        if ( empty($this->checkPeriod) ){\r
+            return;\r
+        }\r
+\r
+        $state = $this->updateChecker->getUpdateState();\r
+        $shouldCheck =\r
+            empty($state) ||\r
+            !isset($state->lastCheck) ||\r
+            ( (time() - $state->lastCheck) >= $this->getEffectiveCheckPeriod() );\r
+\r
+        //Let plugin authors substitute their own algorithm.\r
+        $shouldCheck = apply_filters(\r
+            'puc_check_now-' . $this->updateChecker->slug,\r
+            $shouldCheck,\r
+            (!empty($state) && isset($state->lastCheck)) ? $state->lastCheck : 0,\r
+            $this->checkPeriod\r
+        );\r
+\r
+        if ( $shouldCheck ) {\r
+            $this->updateChecker->checkForUpdates();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Calculate the actual check period based on the current status and environment.\r
+     *\r
+     * @return int Check period in seconds.\r
+     */\r
+    protected function getEffectiveCheckPeriod() {\r
+        $currentFilter = current_filter();\r
+        if ( in_array($currentFilter, array('load-update-core.php', 'upgrader_process_complete')) ) {\r
+            //Check more often when the user visits "Dashboard -> Updates" or does a bulk update.\r
+            $period = 60;\r
+        } else if ( in_array($currentFilter, array('load-plugins.php', 'load-update.php')) ) {\r
+            //Also check more often on the "Plugins" page and /wp-admin/update.php.\r
+            $period = 3600;\r
+        } else if ( $this->throttleRedundantChecks && ($this->updateChecker->getUpdate() !== null) ) {\r
+            //Check less frequently if it's already known that an update is available.\r
+            $period = $this->throttledCheckPeriod * 3600;\r
+        } else if ( defined('DOING_CRON') && constant('DOING_CRON') ) {\r
+            //WordPress cron schedules are not exact, so lets do an update check even\r
+            //if slightly less than $checkPeriod hours have elapsed since the last check.\r
+            $cronFuzziness = 20 * 60;\r
+            $period = $this->checkPeriod * 3600 - $cronFuzziness;\r
+        } else {\r
+            $period = $this->checkPeriod * 3600;\r
+        }\r
+\r
+        return $period;\r
+    }\r
+\r
+    /**\r
+     * Add our custom schedule to the array of Cron schedules used by WP.\r
+     *\r
+     * @param array $schedules\r
+     * @return array\r
+     */\r
+    public function _addCustomSchedule($schedules){\r
+        if ( $this->checkPeriod && ($this->checkPeriod > 0) ){\r
+            $scheduleName = 'every' . $this->checkPeriod . 'hours';\r
+            $schedules[$scheduleName] = array(\r
+                'interval' => $this->checkPeriod * 3600,\r
+                'display' => sprintf('Every %d hours', $this->checkPeriod),\r
+            );\r
+        }\r
+        return $schedules;\r
+    }\r
+\r
+    /**\r
+     * Remove the scheduled cron event that the library uses to check for updates.\r
+     *\r
+     * @return void\r
+     */\r
+    public function _removeUpdaterCron(){\r
+        wp_clear_scheduled_hook($this->cronHook);\r
+    }\r
+\r
+    /**\r
+     * Get the name of the update checker's WP-cron hook. Mostly useful for debugging.\r
+     *\r
+     * @return string\r
+     */\r
+    public function getCronHookName() {\r
+        return $this->cronHook;\r
+    }\r
 }\r
 \r
 endif;\r
@@ -1409,136 +1409,136 @@ if ( !class_exists('PucUpgraderStatus_3_1', false) ):
  * This class uses a few workarounds and heuristics to get the file name.\r
  */\r
 class PucUpgraderStatus_3_1 {\r
-       private $upgradedPluginFile = null; //The plugin that is currently being upgraded by WordPress.\r
-\r
-       public function __construct() {\r
-               //Keep track of which plugin WordPress is currently upgrading.\r
-               add_filter('upgrader_pre_install', array($this, 'setUpgradedPlugin'), 10, 2);\r
-               add_filter('upgrader_package_options', array($this, 'setUpgradedPluginFromOptions'), 10, 1);\r
-               add_filter('upgrader_post_install', array($this, 'clearUpgradedPlugin'), 10, 1);\r
-               add_action('upgrader_process_complete', array($this, 'clearUpgradedPlugin'), 10, 1);\r
-       }\r
-\r
-       /**\r
-        * Is there and update being installed RIGHT NOW, for a specific plugin?\r
-        *\r
-        * Caution: This method is unreliable. WordPress doesn't make it easy to figure out what it is upgrading,\r
-        * and upgrader implementations are liable to change without notice.\r
-        *\r
-        * @param string $pluginFile The plugin to check.\r
-        * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.\r
-        * @return bool True if the plugin identified by $pluginFile is being upgraded.\r
-        */\r
-       public function isPluginBeingUpgraded($pluginFile, $upgrader = null) {\r
-               if ( isset($upgrader) ) {\r
-                       $upgradedPluginFile = $this->getPluginBeingUpgradedBy($upgrader);\r
-                       if ( !empty($upgradedPluginFile) ) {\r
-                               $this->upgradedPluginFile = $upgradedPluginFile;\r
-                       }\r
-               }\r
-               return ( !empty($this->upgradedPluginFile) && ($this->upgradedPluginFile === $pluginFile) );\r
-       }\r
-\r
-       /**\r
-        * Get the file name of the plugin that's currently being upgraded.\r
-        *\r
-        * @param Plugin_Upgrader|WP_Upgrader $upgrader\r
-        * @return string|null\r
-        */\r
-       private function getPluginBeingUpgradedBy($upgrader) {\r
-               if ( !isset($upgrader, $upgrader->skin) ) {\r
-                       return null;\r
-               }\r
-\r
-               //Figure out which plugin is being upgraded.\r
-               $pluginFile = null;\r
-               $skin = $upgrader->skin;\r
-               if ( $skin instanceof Plugin_Upgrader_Skin ) {\r
-                       if ( isset($skin->plugin) && is_string($skin->plugin) && ($skin->plugin !== '') ) {\r
-                               $pluginFile = $skin->plugin;\r
-                       }\r
-               } elseif ( isset($skin->plugin_info) && is_array($skin->plugin_info) ) {\r
-                       //This case is tricky because Bulk_Plugin_Upgrader_Skin (etc) doesn't actually store the plugin\r
-                       //filename anywhere. Instead, it has the plugin headers in $plugin_info. So the best we can\r
-                       //do is compare those headers to the headers of installed plugins.\r
-                       $pluginFile = $this->identifyPluginByHeaders($skin->plugin_info);\r
-               }\r
-\r
-               return $pluginFile;\r
-       }\r
-\r
-       /**\r
-        * Identify an installed plugin based on its headers.\r
-        *\r
-        * @param array $searchHeaders The plugin file header to look for.\r
-        * @return string|null Plugin basename ("foo/bar.php"), or NULL if we can't identify the plugin.\r
-        */\r
-       private function identifyPluginByHeaders($searchHeaders) {\r
-               if ( !function_exists('get_plugins') ){\r
-                       /** @noinspection PhpIncludeInspection */\r
-                       require_once( ABSPATH . '/wp-admin/includes/plugin.php' );\r
-               }\r
-\r
-               $installedPlugins = get_plugins();\r
-               $matches = array();\r
-               foreach($installedPlugins as $pluginBasename => $headers) {\r
-                       $diff1 = array_diff_assoc($headers, $searchHeaders);\r
-                       $diff2 = array_diff_assoc($searchHeaders, $headers);\r
-                       if ( empty($diff1) && empty($diff2) ) {\r
-                               $matches[] = $pluginBasename;\r
-                       }\r
-               }\r
-\r
-               //It's possible (though very unlikely) that there could be two plugins with identical\r
-               //headers. In that case, we can't unambiguously identify the plugin that's being upgraded.\r
-               if ( count($matches) !== 1 ) {\r
-                       return null;\r
-               }\r
-\r
-               return reset($matches);\r
-       }\r
-\r
-       /**\r
-        * @access private\r
-        *\r
-        * @param mixed $input\r
-        * @param array $hookExtra\r
-        * @return mixed Returns $input unaltered.\r
-        */\r
-       public function setUpgradedPlugin($input, $hookExtra) {\r
-               if (!empty($hookExtra['plugin']) && is_string($hookExtra['plugin'])) {\r
-                       $this->upgradedPluginFile = $hookExtra['plugin'];\r
-               } else {\r
-                       $this->upgradedPluginFile = null;\r
-               }\r
-               return $input;\r
-       }\r
-\r
-       /**\r
-        * @access private\r
-        *\r
-        * @param array $options\r
-        * @return array\r
-        */\r
-       public function setUpgradedPluginFromOptions($options) {\r
-               if (isset($options['hook_extra']['plugin']) && is_string($options['hook_extra']['plugin'])) {\r
-                       $this->upgradedPluginFile = $options['hook_extra']['plugin'];\r
-               } else {\r
-                       $this->upgradedPluginFile = null;\r
-               }\r
-               return $options;\r
-       }\r
-\r
-       /**\r
-        * @access private\r
-        *\r
-        * @param mixed $input\r
-        * @return mixed Returns $input unaltered.\r
-        */\r
-       public function clearUpgradedPlugin($input = null) {\r
-               $this->upgradedPluginFile = null;\r
-               return $input;\r
-       }\r
+    private $upgradedPluginFile = null; //The plugin that is currently being upgraded by WordPress.\r
+\r
+    public function __construct() {\r
+        //Keep track of which plugin WordPress is currently upgrading.\r
+        add_filter('upgrader_pre_install', array($this, 'setUpgradedPlugin'), 10, 2);\r
+        add_filter('upgrader_package_options', array($this, 'setUpgradedPluginFromOptions'), 10, 1);\r
+        add_filter('upgrader_post_install', array($this, 'clearUpgradedPlugin'), 10, 1);\r
+        add_action('upgrader_process_complete', array($this, 'clearUpgradedPlugin'), 10, 1);\r
+    }\r
+\r
+    /**\r
+     * Is there and update being installed RIGHT NOW, for a specific plugin?\r
+     *\r
+     * Caution: This method is unreliable. WordPress doesn't make it easy to figure out what it is upgrading,\r
+     * and upgrader implementations are liable to change without notice.\r
+     *\r
+     * @param string $pluginFile The plugin to check.\r
+     * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.\r
+     * @return bool True if the plugin identified by $pluginFile is being upgraded.\r
+     */\r
+    public function isPluginBeingUpgraded($pluginFile, $upgrader = null) {\r
+        if ( isset($upgrader) ) {\r
+            $upgradedPluginFile = $this->getPluginBeingUpgradedBy($upgrader);\r
+            if ( !empty($upgradedPluginFile) ) {\r
+                $this->upgradedPluginFile = $upgradedPluginFile;\r
+            }\r
+        }\r
+        return ( !empty($this->upgradedPluginFile) && ($this->upgradedPluginFile === $pluginFile) );\r
+    }\r
+\r
+    /**\r
+     * Get the file name of the plugin that's currently being upgraded.\r
+     *\r
+     * @param Plugin_Upgrader|WP_Upgrader $upgrader\r
+     * @return string|null\r
+     */\r
+    private function getPluginBeingUpgradedBy($upgrader) {\r
+        if ( !isset($upgrader, $upgrader->skin) ) {\r
+            return null;\r
+        }\r
+\r
+        //Figure out which plugin is being upgraded.\r
+        $pluginFile = null;\r
+        $skin = $upgrader->skin;\r
+        if ( $skin instanceof Plugin_Upgrader_Skin ) {\r
+            if ( isset($skin->plugin) && is_string($skin->plugin) && ($skin->plugin !== '') ) {\r
+                $pluginFile = $skin->plugin;\r
+            }\r
+        } elseif ( isset($skin->plugin_info) && is_array($skin->plugin_info) ) {\r
+            //This case is tricky because Bulk_Plugin_Upgrader_Skin (etc) doesn't actually store the plugin\r
+            //filename anywhere. Instead, it has the plugin headers in $plugin_info. So the best we can\r
+            //do is compare those headers to the headers of installed plugins.\r
+            $pluginFile = $this->identifyPluginByHeaders($skin->plugin_info);\r
+        }\r
+\r
+        return $pluginFile;\r
+    }\r
+\r
+    /**\r
+     * Identify an installed plugin based on its headers.\r
+     *\r
+     * @param array $searchHeaders The plugin file header to look for.\r
+     * @return string|null Plugin basename ("foo/bar.php"), or NULL if we can't identify the plugin.\r
+     */\r
+    private function identifyPluginByHeaders($searchHeaders) {\r
+        if ( !function_exists('get_plugins') ){\r
+            /** @noinspection PhpIncludeInspection */\r
+            require_once( ABSPATH . '/wp-admin/includes/plugin.php' );\r
+        }\r
+\r
+        $installedPlugins = get_plugins();\r
+        $matches = array();\r
+        foreach($installedPlugins as $pluginBasename => $headers) {\r
+            $diff1 = array_diff_assoc($headers, $searchHeaders);\r
+            $diff2 = array_diff_assoc($searchHeaders, $headers);\r
+            if ( empty($diff1) && empty($diff2) ) {\r
+                $matches[] = $pluginBasename;\r
+            }\r
+        }\r
+\r
+        //It's possible (though very unlikely) that there could be two plugins with identical\r
+        //headers. In that case, we can't unambiguously identify the plugin that's being upgraded.\r
+        if ( count($matches) !== 1 ) {\r
+            return null;\r
+        }\r
+\r
+        return reset($matches);\r
+    }\r
+\r
+    /**\r
+     * @access private\r
+     *\r
+     * @param mixed $input\r
+     * @param array $hookExtra\r
+     * @return mixed Returns $input unaltered.\r
+     */\r
+    public function setUpgradedPlugin($input, $hookExtra) {\r
+        if (!empty($hookExtra['plugin']) && is_string($hookExtra['plugin'])) {\r
+            $this->upgradedPluginFile = $hookExtra['plugin'];\r
+        } else {\r
+            $this->upgradedPluginFile = null;\r
+        }\r
+        return $input;\r
+    }\r
+\r
+    /**\r
+     * @access private\r
+     *\r
+     * @param array $options\r
+     * @return array\r
+     */\r
+    public function setUpgradedPluginFromOptions($options) {\r
+        if (isset($options['hook_extra']['plugin']) && is_string($options['hook_extra']['plugin'])) {\r
+            $this->upgradedPluginFile = $options['hook_extra']['plugin'];\r
+        } else {\r
+            $this->upgradedPluginFile = null;\r
+        }\r
+        return $options;\r
+    }\r
+\r
+    /**\r
+     * @access private\r
+     *\r
+     * @param mixed $input\r
+     * @return mixed Returns $input unaltered.\r
+     */\r
+    public function clearUpgradedPlugin($input = null) {\r
+        $this->upgradedPluginFile = null;\r
+        return $input;\r
+    }\r
 }\r
 \r
 endif;\r
@@ -1559,76 +1559,76 @@ if ( !class_exists('PucFactory', false) ):
  * to get the class name and then create it with <code>new $class(...)</code>.\r
  */\r
 class PucFactory {\r
-       protected static $classVersions = array();\r
-       protected static $sorted = false;\r
-\r
-       /**\r
-        * Create a new instance of PluginUpdateChecker.\r
-        *\r
-        * @see PluginUpdateChecker::__construct()\r
-        *\r
-        * @param $metadataUrl\r
-        * @param $pluginFile\r
-        * @param string $slug\r
-        * @param int $checkPeriod\r
-        * @param string $optionName\r
-        * @param string $muPluginFile\r
-        * @return PluginUpdateChecker_3_1\r
-        */\r
-       public static function buildUpdateChecker($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') {\r
-               $class = self::getLatestClassVersion('PluginUpdateChecker');\r
-               return new $class($metadataUrl, $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile);\r
-       }\r
-\r
-       /**\r
-        * Get the specific class name for the latest available version of a class.\r
-        *\r
-        * @param string $class\r
-        * @return string|null\r
-        */\r
-       public static function getLatestClassVersion($class) {\r
-               if ( !self::$sorted ) {\r
-                       self::sortVersions();\r
-               }\r
-\r
-               if ( isset(self::$classVersions[$class]) ) {\r
-                       return reset(self::$classVersions[$class]);\r
-               } else {\r
-                       return null;\r
-               }\r
-       }\r
-\r
-       /**\r
-        * Sort available class versions in descending order (i.e. newest first).\r
-        */\r
-       protected static function sortVersions() {\r
-               foreach ( self::$classVersions as $class => $versions ) {\r
-                       uksort($versions, array(__CLASS__, 'compareVersions'));\r
-                       self::$classVersions[$class] = $versions;\r
-               }\r
-               self::$sorted = true;\r
-       }\r
-\r
-       protected static function compareVersions($a, $b) {\r
-               return -version_compare($a, $b);\r
-       }\r
-\r
-       /**\r
-        * Register a version of a class.\r
-        *\r
-        * @access private This method is only for internal use by the library.\r
-        *\r
-        * @param string $generalClass Class name without version numbers, e.g. 'PluginUpdateChecker'.\r
-        * @param string $versionedClass Actual class name, e.g. 'PluginUpdateChecker_1_2'.\r
-        * @param string $version Version number, e.g. '1.2'.\r
-        */\r
-       public static function addVersion($generalClass, $versionedClass, $version) {\r
-               if ( !isset(self::$classVersions[$generalClass]) ) {\r
-                       self::$classVersions[$generalClass] = array();\r
-               }\r
-               self::$classVersions[$generalClass][$version] = $versionedClass;\r
-               self::$sorted = false;\r
-       }\r
+    protected static $classVersions = array();\r
+    protected static $sorted = false;\r
+\r
+    /**\r
+     * Create a new instance of PluginUpdateChecker.\r
+     *\r
+     * @see PluginUpdateChecker::__construct()\r
+     *\r
+     * @param $metadataUrl\r
+     * @param $pluginFile\r
+     * @param string $slug\r
+     * @param int $checkPeriod\r
+     * @param string $optionName\r
+     * @param string $muPluginFile\r
+     * @return PluginUpdateChecker_3_1\r
+     */\r
+    public static function buildUpdateChecker($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') {\r
+        $class = self::getLatestClassVersion('PluginUpdateChecker');\r
+        return new $class($metadataUrl, $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile);\r
+    }\r
+\r
+    /**\r
+     * Get the specific class name for the latest available version of a class.\r
+     *\r
+     * @param string $class\r
+     * @return string|null\r
+     */\r
+    public static function getLatestClassVersion($class) {\r
+        if ( !self::$sorted ) {\r
+            self::sortVersions();\r
+        }\r
+\r
+        if ( isset(self::$classVersions[$class]) ) {\r
+            return reset(self::$classVersions[$class]);\r
+        } else {\r
+            return null;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Sort available class versions in descending order (i.e. newest first).\r
+     */\r
+    protected static function sortVersions() {\r
+        foreach ( self::$classVersions as $class => $versions ) {\r
+            uksort($versions, array(__CLASS__, 'compareVersions'));\r
+            self::$classVersions[$class] = $versions;\r
+        }\r
+        self::$sorted = true;\r
+    }\r
+\r
+    protected static function compareVersions($a, $b) {\r
+        return -version_compare($a, $b);\r
+    }\r
+\r
+    /**\r
+     * Register a version of a class.\r
+     *\r
+     * @access private This method is only for internal use by the library.\r
+     *\r
+     * @param string $generalClass Class name without version numbers, e.g. 'PluginUpdateChecker'.\r
+     * @param string $versionedClass Actual class name, e.g. 'PluginUpdateChecker_1_2'.\r
+     * @param string $version Version number, e.g. '1.2'.\r
+     */\r
+    public static function addVersion($generalClass, $versionedClass, $version) {\r
+        if ( !isset(self::$classVersions[$generalClass]) ) {\r
+            self::$classVersions[$generalClass] = array();\r
+        }\r
+        self::$classVersions[$generalClass][$version] = $versionedClass;\r
+        self::$sorted = false;\r
+    }\r
 }\r
 \r
 endif;\r