Completed
Pull Request — master (#223)
by
unknown
01:56
created
classes/external/php/plugin-update-checker/Puc/v4p4/Plugin/Info.php 1 patch
Indentation   +125 added lines, -125 removed lines patch added patch discarded remove patch
@@ -1,130 +1,130 @@
 block discarded – undo
1 1
 <?php
2 2
 if ( !class_exists('Puc_v4p4_Plugin_Info', false) ):
3 3
 
4
-	/**
5
-	 * A container class for holding and transforming various plugin metadata.
6
-	 *
7
-	 * @author Janis Elsts
8
-	 * @copyright 2016
9
-	 * @access public
10
-	 */
11
-	class Puc_v4p4_Plugin_Info extends Puc_v4p4_Metadata {
12
-		//Most fields map directly to the contents of the plugin's info.json file.
13
-		//See the relevant docs for a description of their meaning.
14
-		public $name;
15
-		public $slug;
16
-		public $version;
17
-		public $homepage;
18
-		public $sections = array();
19
-		public $download_url;
20
-
21
-		public $banners;
22
-		public $icons = array();
23
-		public $translations = array();
24
-
25
-		public $author;
26
-		public $author_homepage;
27
-
28
-		public $requires;
29
-		public $tested;
30
-		public $upgrade_notice;
31
-
32
-		public $rating;
33
-		public $num_ratings;
34
-		public $downloaded;
35
-		public $active_installs;
36
-		public $last_updated;
37
-
38
-		public $id = 0; //The native WP.org API returns numeric plugin IDs, but they're not used for anything.
39
-
40
-		public $filename; //Plugin filename relative to the plugins directory.
41
-
42
-		/**
43
-		 * Create a new instance of Plugin Info from JSON-encoded plugin info
44
-		 * returned by an external update API.
45
-		 *
46
-		 * @param string $json Valid JSON string representing plugin info.
47
-		 * @return self|null New instance of Plugin Info, or NULL on error.
48
-		 */
49
-		public static function fromJson($json){
50
-			$instance = new self();
51
-
52
-			if ( !parent::createFromJson($json, $instance) ) {
53
-				return null;
54
-			}
55
-
56
-			//json_decode decodes assoc. arrays as objects. We want them as arrays.
57
-			$instance->sections = (array)$instance->sections;
58
-			$instance->icons = (array)$instance->icons;
59
-
60
-			return $instance;
61
-		}
62
-
63
-		/**
64
-		 * Very, very basic validation.
65
-		 *
66
-		 * @param StdClass $apiResponse
67
-		 * @return bool|WP_Error
68
-		 */
69
-		protected function validateMetadata($apiResponse) {
70
-			if (
71
-				!isset($apiResponse->name, $apiResponse->version)
72
-				|| empty($apiResponse->name)
73
-				|| empty($apiResponse->version)
74
-			) {
75
-				return new WP_Error(
76
-					'puc-invalid-metadata',
77
-					"The plugin metadata file does not contain the required 'name' and/or 'version' keys."
78
-				);
79
-			}
80
-			return true;
81
-		}
82
-
83
-
84
-		/**
85
-		 * Transform plugin info into the format used by the native WordPress.org API
86
-		 *
87
-		 * @return object
88
-		 */
89
-		public function toWpFormat(){
90
-			$info = new stdClass;
91
-
92
-			//The custom update API is built so that many fields have the same name and format
93
-			//as those returned by the native WordPress.org API. These can be assigned directly.
94
-			$sameFormat = array(
95
-				'name', 'slug', 'version', 'requires', 'tested', 'rating', 'upgrade_notice',
96
-				'num_ratings', 'downloaded', 'active_installs', 'homepage', 'last_updated',
97
-			);
98
-			foreach($sameFormat as $field){
99
-				if ( isset($this->$field) ) {
100
-					$info->$field = $this->$field;
101
-				} else {
102
-					$info->$field = null;
103
-				}
104
-			}
105
-
106
-			//Other fields need to be renamed and/or transformed.
107
-			$info->download_link = $this->download_url;
108
-			$info->author = $this->getFormattedAuthor();
109
-			$info->sections = array_merge(array('description' => ''), $this->sections);
110
-
111
-			if ( !empty($this->banners) ) {
112
-				//WP expects an array with two keys: "high" and "low". Both are optional.
113
-				//Docs: https://wordpress.org/plugins/about/faq/#banners
114
-				$info->banners = is_object($this->banners) ? get_object_vars($this->banners) : $this->banners;
115
-				$info->banners = array_intersect_key($info->banners, array('high' => true, 'low' => true));
116
-			}
117
-
118
-			return $info;
119
-		}
120
-
121
-		protected function getFormattedAuthor() {
122
-			if ( !empty($this->author_homepage) ){
123
-				/** @noinspection HtmlUnknownTarget */
124
-				return sprintf('<a href="%s">%s</a>', $this->author_homepage, $this->author);
125
-			}
126
-			return $this->author;
127
-		}
128
-	}
4
+    /**
5
+     * A container class for holding and transforming various plugin metadata.
6
+     *
7
+     * @author Janis Elsts
8
+     * @copyright 2016
9
+     * @access public
10
+     */
11
+    class Puc_v4p4_Plugin_Info extends Puc_v4p4_Metadata {
12
+        //Most fields map directly to the contents of the plugin's info.json file.
13
+        //See the relevant docs for a description of their meaning.
14
+        public $name;
15
+        public $slug;
16
+        public $version;
17
+        public $homepage;
18
+        public $sections = array();
19
+        public $download_url;
20
+
21
+        public $banners;
22
+        public $icons = array();
23
+        public $translations = array();
24
+
25
+        public $author;
26
+        public $author_homepage;
27
+
28
+        public $requires;
29
+        public $tested;
30
+        public $upgrade_notice;
31
+
32
+        public $rating;
33
+        public $num_ratings;
34
+        public $downloaded;
35
+        public $active_installs;
36
+        public $last_updated;
37
+
38
+        public $id = 0; //The native WP.org API returns numeric plugin IDs, but they're not used for anything.
39
+
40
+        public $filename; //Plugin filename relative to the plugins directory.
41
+
42
+        /**
43
+         * Create a new instance of Plugin Info from JSON-encoded plugin info
44
+         * returned by an external update API.
45
+         *
46
+         * @param string $json Valid JSON string representing plugin info.
47
+         * @return self|null New instance of Plugin Info, or NULL on error.
48
+         */
49
+        public static function fromJson($json){
50
+            $instance = new self();
51
+
52
+            if ( !parent::createFromJson($json, $instance) ) {
53
+                return null;
54
+            }
55
+
56
+            //json_decode decodes assoc. arrays as objects. We want them as arrays.
57
+            $instance->sections = (array)$instance->sections;
58
+            $instance->icons = (array)$instance->icons;
59
+
60
+            return $instance;
61
+        }
62
+
63
+        /**
64
+         * Very, very basic validation.
65
+         *
66
+         * @param StdClass $apiResponse
67
+         * @return bool|WP_Error
68
+         */
69
+        protected function validateMetadata($apiResponse) {
70
+            if (
71
+                !isset($apiResponse->name, $apiResponse->version)
72
+                || empty($apiResponse->name)
73
+                || empty($apiResponse->version)
74
+            ) {
75
+                return new WP_Error(
76
+                    'puc-invalid-metadata',
77
+                    "The plugin metadata file does not contain the required 'name' and/or 'version' keys."
78
+                );
79
+            }
80
+            return true;
81
+        }
82
+
83
+
84
+        /**
85
+         * Transform plugin info into the format used by the native WordPress.org API
86
+         *
87
+         * @return object
88
+         */
89
+        public function toWpFormat(){
90
+            $info = new stdClass;
91
+
92
+            //The custom update API is built so that many fields have the same name and format
93
+            //as those returned by the native WordPress.org API. These can be assigned directly.
94
+            $sameFormat = array(
95
+                'name', 'slug', 'version', 'requires', 'tested', 'rating', 'upgrade_notice',
96
+                'num_ratings', 'downloaded', 'active_installs', 'homepage', 'last_updated',
97
+            );
98
+            foreach($sameFormat as $field){
99
+                if ( isset($this->$field) ) {
100
+                    $info->$field = $this->$field;
101
+                } else {
102
+                    $info->$field = null;
103
+                }
104
+            }
105
+
106
+            //Other fields need to be renamed and/or transformed.
107
+            $info->download_link = $this->download_url;
108
+            $info->author = $this->getFormattedAuthor();
109
+            $info->sections = array_merge(array('description' => ''), $this->sections);
110
+
111
+            if ( !empty($this->banners) ) {
112
+                //WP expects an array with two keys: "high" and "low". Both are optional.
113
+                //Docs: https://wordpress.org/plugins/about/faq/#banners
114
+                $info->banners = is_object($this->banners) ? get_object_vars($this->banners) : $this->banners;
115
+                $info->banners = array_intersect_key($info->banners, array('high' => true, 'low' => true));
116
+            }
117
+
118
+            return $info;
119
+        }
120
+
121
+        protected function getFormattedAuthor() {
122
+            if ( !empty($this->author_homepage) ){
123
+                /** @noinspection HtmlUnknownTarget */
124
+                return sprintf('<a href="%s">%s</a>', $this->author_homepage, $this->author);
125
+            }
126
+            return $this->author;
127
+        }
128
+    }
129 129
 
130 130
 endif;
131 131
\ No newline at end of file
Please login to merge, or discard this patch.
external/php/plugin-update-checker/Puc/v4p4/Plugin/UpdateChecker.php 1 patch
Indentation   +735 added lines, -735 removed lines patch added patch discarded remove patch
@@ -1,740 +1,740 @@
 block discarded – undo
1 1
 <?php
2 2
 if ( !class_exists('Puc_v4p4_Plugin_UpdateChecker', false) ):
3 3
 
4
-	/**
5
-	 * A custom plugin update checker.
6
-	 *
7
-	 * @author Janis Elsts
8
-	 * @copyright 2016
9
-	 * @access public
10
-	 */
11
-	class Puc_v4p4_Plugin_UpdateChecker extends Puc_v4p4_UpdateChecker {
12
-		protected $updateTransient = 'update_plugins';
13
-		protected $translationType = 'plugin';
14
-
15
-		public $pluginAbsolutePath = ''; //Full path of the main plugin file.
16
-		public $pluginFile = '';  //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins.
17
-		public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory.
18
-
19
-		private $cachedInstalledVersion = null;
20
-		private $manualCheckErrorTransient = '';
21
-
22
-		/**
23
-		 * Class constructor.
24
-		 *
25
-		 * @param string $metadataUrl The URL of the plugin's metadata file.
26
-		 * @param string $pluginFile Fully qualified path to the main plugin file.
27
-		 * @param string $slug The plugin's 'slug'. If not specified, the filename part of $pluginFile sans '.php' will be used as the slug.
28
-		 * @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.
29
-		 * @param string $optionName Where to store book-keeping info about update checks. Defaults to 'external_updates-$slug'.
30
-		 * @param string $muPluginFile Optional. The plugin filename relative to the mu-plugins directory.
31
-		 */
32
-		public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = ''){
33
-			$this->pluginAbsolutePath = $pluginFile;
34
-			$this->pluginFile = plugin_basename($this->pluginAbsolutePath);
35
-			$this->muPluginFile = $muPluginFile;
36
-
37
-			//If no slug is specified, use the name of the main plugin file as the slug.
38
-			//For example, 'my-cool-plugin/cool-plugin.php' becomes 'cool-plugin'.
39
-			if ( empty($slug) ){
40
-				$slug = basename($this->pluginFile, '.php');
41
-			}
42
-
43
-			//Plugin slugs must be unique.
44
-			$slugCheckFilter = 'puc_is_slug_in_use-' . $this->slug;
45
-			$slugUsedBy = apply_filters($slugCheckFilter, false);
46
-			if ( $slugUsedBy ) {
47
-				$this->triggerError(sprintf(
48
-					'Plugin slug "%s" is already in use by %s. Slugs must be unique.',
49
-					htmlentities($this->slug),
50
-					htmlentities($slugUsedBy)
51
-				), E_USER_ERROR);
52
-			}
53
-			add_filter($slugCheckFilter, array($this, 'getAbsolutePath'));
54
-
55
-			//Backwards compatibility: If the plugin is a mu-plugin but no $muPluginFile is specified, assume
56
-			//it's the same as $pluginFile given that it's not in a subdirectory (WP only looks in the base dir).
57
-			if ( (strpbrk($this->pluginFile, '/\\') === false) && $this->isUnknownMuPlugin() ) {
58
-				$this->muPluginFile = $this->pluginFile;
59
-			}
60
-
61
-			//To prevent a crash during plugin uninstallation, remove updater hooks when the user removes the plugin.
62
-			//Details: https://github.com/YahnisElsts/plugin-update-checker/issues/138#issuecomment-335590964
63
-			add_action('uninstall_' . $this->pluginFile, array($this, 'removeHooks'));
64
-
65
-			$this->manualCheckErrorTransient = $this->getUniqueName('manual_check_errors');
66
-
67
-			parent::__construct($metadataUrl, dirname($this->pluginFile), $slug, $checkPeriod, $optionName);
68
-		}
69
-
70
-		/**
71
-		 * Create an instance of the scheduler.
72
-		 *
73
-		 * @param int $checkPeriod
74
-		 * @return Puc_v4p4_Scheduler
75
-		 */
76
-		protected function createScheduler($checkPeriod) {
77
-			$scheduler = new Puc_v4p4_Scheduler($this, $checkPeriod, array('load-plugins.php'));
78
-			register_deactivation_hook($this->pluginFile, array($scheduler, 'removeUpdaterCron'));
79
-			return $scheduler;
80
-		}
81
-
82
-		/**
83
-		 * Install the hooks required to run periodic update checks and inject update info
84
-		 * into WP data structures.
85
-		 *
86
-		 * @return void
87
-		 */
88
-		protected function installHooks(){
89
-			//Override requests for plugin information
90
-			add_filter('plugins_api', array($this, 'injectInfo'), 20, 3);
91
-
92
-			add_filter('plugin_row_meta', array($this, 'addViewDetailsLink'), 10, 3);
93
-			add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2);
94
-			add_action('admin_init', array($this, 'handleManualCheck'));
95
-			add_action('all_admin_notices', array($this, 'displayManualCheckResult'));
96
-
97
-			//Clear the version number cache when something - anything - is upgraded or WP clears the update cache.
98
-			add_filter('upgrader_post_install', array($this, 'clearCachedVersion'));
99
-			add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));
100
-
101
-			parent::installHooks();
102
-		}
103
-
104
-		/**
105
-		 * Remove update checker hooks.
106
-		 *
107
-		 * The intent is to prevent a fatal error that can happen if the plugin has an uninstall
108
-		 * hook. During uninstallation, WP includes the main plugin file (which creates a PUC instance),
109
-		 * the uninstall hook runs, WP deletes the plugin files and then updates some transients.
110
-		 * If PUC hooks are still around at this time, they could throw an error while trying to
111
-		 * autoload classes from files that no longer exist.
112
-		 *
113
-		 * The "site_transient_{$transient}" filter is the main problem here, but let's also remove
114
-		 * most other PUC hooks to be safe.
115
-		 *
116
-		 * @internal
117
-		 */
118
-		public function removeHooks() {
119
-			parent::removeHooks();
120
-
121
-			remove_filter('plugins_api', array($this, 'injectInfo'), 20);
122
-
123
-			remove_filter('plugin_row_meta', array($this, 'addViewDetailsLink'), 10);
124
-			remove_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10);
125
-			remove_action('admin_init', array($this, 'handleManualCheck'));
126
-			remove_action('all_admin_notices', array($this, 'displayManualCheckResult'));
127
-
128
-			remove_filter('upgrader_post_install', array($this, 'clearCachedVersion'));
129
-			remove_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));
130
-		}
131
-
132
-		/**
133
-		 * Retrieve plugin info from the configured API endpoint.
134
-		 *
135
-		 * @uses wp_remote_get()
136
-		 *
137
-		 * @param array $queryArgs Additional query arguments to append to the request. Optional.
138
-		 * @return Puc_v4p4_Plugin_Info
139
-		 */
140
-		public function requestInfo($queryArgs = array()) {
141
-			list($pluginInfo, $result) = $this->requestMetadata('Puc_v4p4_Plugin_Info', 'request_info', $queryArgs);
142
-
143
-			if ( $pluginInfo !== null ) {
144
-				/** @var Puc_v4p4_Plugin_Info $pluginInfo */
145
-				$pluginInfo->filename = $this->pluginFile;
146
-				$pluginInfo->slug = $this->slug;
147
-			}
148
-
149
-			$pluginInfo = apply_filters($this->getUniqueName('request_info_result'), $pluginInfo, $result);
150
-			return $pluginInfo;
151
-		}
152
-
153
-		/**
154
-		 * Retrieve the latest update (if any) from the configured API endpoint.
155
-		 *
156
-		 * @uses PluginUpdateChecker::requestInfo()
157
-		 *
158
-		 * @return Puc_v4p4_Update|null An instance of Plugin_Update, or NULL when no updates are available.
159
-		 */
160
-		public function requestUpdate() {
161
-			//For the sake of simplicity, this function just calls requestInfo()
162
-			//and transforms the result accordingly.
163
-			$pluginInfo = $this->requestInfo(array('checking_for_updates' => '1'));
164
-			if ( $pluginInfo === null ){
165
-				return null;
166
-			}
167
-			$update = Puc_v4p4_Plugin_Update::fromPluginInfo($pluginInfo);
168
-
169
-			$update = $this->filterUpdateResult($update);
170
-
171
-			return $update;
172
-		}
173
-
174
-		/**
175
-		 * Get the currently installed version of the plugin.
176
-		 *
177
-		 * @return string Version number.
178
-		 */
179
-		public function getInstalledVersion(){
180
-			if ( isset($this->cachedInstalledVersion) ) {
181
-				return $this->cachedInstalledVersion;
182
-			}
183
-
184
-			$pluginHeader = $this->getPluginHeader();
185
-			if ( isset($pluginHeader['Version']) ) {
186
-				$this->cachedInstalledVersion = $pluginHeader['Version'];
187
-				return $pluginHeader['Version'];
188
-			} else {
189
-				//This can happen if the filename points to something that is not a plugin.
190
-				$this->triggerError(
191
-					sprintf(
192
-						"Can't to read the Version header for '%s'. The filename is incorrect or is not a plugin.",
193
-						$this->pluginFile
194
-					),
195
-					E_USER_WARNING
196
-				);
197
-				return null;
198
-			}
199
-		}
200
-
201
-		/**
202
-		 * Get plugin's metadata from its file header.
203
-		 *
204
-		 * @return array
205
-		 */
206
-		protected function getPluginHeader() {
207
-			if ( !is_file($this->pluginAbsolutePath) ) {
208
-				//This can happen if the plugin filename is wrong.
209
-				$this->triggerError(
210
-					sprintf(
211
-						"Can't to read the plugin header for '%s'. The file does not exist.",
212
-						$this->pluginFile
213
-					),
214
-					E_USER_WARNING
215
-				);
216
-				return array();
217
-			}
218
-
219
-			if ( !function_exists('get_plugin_data') ){
220
-				/** @noinspection PhpIncludeInspection */
221
-				require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
222
-			}
223
-			return get_plugin_data($this->pluginAbsolutePath, false, false);
224
-		}
225
-
226
-		/**
227
-		 * @return array
228
-		 */
229
-		protected function getHeaderNames() {
230
-			return array(
231
-				'Name' => 'Plugin Name',
232
-				'PluginURI' => 'Plugin URI',
233
-				'Version' => 'Version',
234
-				'Description' => 'Description',
235
-				'Author' => 'Author',
236
-				'AuthorURI' => 'Author URI',
237
-				'TextDomain' => 'Text Domain',
238
-				'DomainPath' => 'Domain Path',
239
-				'Network' => 'Network',
240
-
241
-				//The newest WordPress version that this plugin requires or has been tested with.
242
-				//We support several different formats for compatibility with other libraries.
243
-				'Tested WP' => 'Tested WP',
244
-				'Requires WP' => 'Requires WP',
245
-				'Tested up to' => 'Tested up to',
246
-				'Requires at least' => 'Requires at least',
247
-			);
248
-		}
249
-
250
-
251
-		/**
252
-		 * Intercept plugins_api() calls that request information about our plugin and
253
-		 * use the configured API endpoint to satisfy them.
254
-		 *
255
-		 * @see plugins_api()
256
-		 *
257
-		 * @param mixed $result
258
-		 * @param string $action
259
-		 * @param array|object $args
260
-		 * @return mixed
261
-		 */
262
-		public function injectInfo($result, $action = null, $args = null){
263
-			$relevant = ($action == 'plugin_information') && isset($args->slug) && (
264
-					($args->slug == $this->slug) || ($args->slug == dirname($this->pluginFile))
265
-				);
266
-			if ( !$relevant ) {
267
-				return $result;
268
-			}
269
-
270
-			$pluginInfo = $this->requestInfo();
271
-			$pluginInfo = apply_filters($this->getUniqueName('pre_inject_info'), $pluginInfo);
272
-			if ( $pluginInfo ) {
273
-				return $pluginInfo->toWpFormat();
274
-			}
275
-
276
-			return $result;
277
-		}
278
-
279
-		protected function shouldShowUpdates() {
280
-			//No update notifications for mu-plugins unless explicitly enabled. The MU plugin file
281
-			//is usually different from the main plugin file so the update wouldn't show up properly anyway.
282
-			return !$this->isUnknownMuPlugin();
283
-		}
284
-
285
-		/**
286
-		 * @param stdClass|null $updates
287
-		 * @param stdClass $updateToAdd
288
-		 * @return stdClass
289
-		 */
290
-		protected function addUpdateToList($updates, $updateToAdd) {
291
-			if ( $this->isMuPlugin() ) {
292
-				//WP does not support automatic update installation for mu-plugins, but we can
293
-				//still display a notice.
294
-				$updateToAdd->package = null;
295
-			}
296
-			return parent::addUpdateToList($updates, $updateToAdd);
297
-		}
298
-
299
-		/**
300
-		 * @param stdClass|null $updates
301
-		 * @return stdClass|null
302
-		 */
303
-		protected function removeUpdateFromList($updates) {
304
-			$updates = parent::removeUpdateFromList($updates);
305
-			if ( !empty($this->muPluginFile) && isset($updates, $updates->response) ) {
306
-				unset($updates->response[$this->muPluginFile]);
307
-			}
308
-			return $updates;
309
-		}
310
-
311
-		/**
312
-		 * For plugins, the update array is indexed by the plugin filename relative to the "plugins"
313
-		 * directory. Example: "plugin-name/plugin.php".
314
-		 *
315
-		 * @return string
316
-		 */
317
-		protected function getUpdateListKey() {
318
-			if ( $this->isMuPlugin() ) {
319
-				return $this->muPluginFile;
320
-			}
321
-			return $this->pluginFile;
322
-		}
323
-
324
-		/**
325
-		 * Alias for isBeingUpgraded().
326
-		 *
327
-		 * @deprecated
328
-		 * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
329
-		 * @return bool
330
-		 */
331
-		public function isPluginBeingUpgraded($upgrader = null) {
332
-			return $this->isBeingUpgraded($upgrader);
333
-		}
334
-
335
-		/**
336
-		 * Is there an update being installed for this plugin, right now?
337
-		 *
338
-		 * @param WP_Upgrader|null $upgrader
339
-		 * @return bool
340
-		 */
341
-		public function isBeingUpgraded($upgrader = null) {
342
-			return $this->upgraderStatus->isPluginBeingUpgraded($this->pluginFile, $upgrader);
343
-		}
344
-
345
-		/**
346
-		 * Get the details of the currently available update, if any.
347
-		 *
348
-		 * If no updates are available, or if the last known update version is below or equal
349
-		 * to the currently installed version, this method will return NULL.
350
-		 *
351
-		 * Uses cached update data. To retrieve update information straight from
352
-		 * the metadata URL, call requestUpdate() instead.
353
-		 *
354
-		 * @return Puc_v4p4_Plugin_Update|null
355
-		 */
356
-		public function getUpdate() {
357
-			$update = parent::getUpdate();
358
-			if ( isset($update) ) {
359
-				/** @var Puc_v4p4_Plugin_Update $update */
360
-				$update->filename = $this->pluginFile;
361
-			}
362
-			return $update;
363
-		}
364
-
365
-		/**
366
-		 * Add a "Check for updates" link to the plugin row in the "Plugins" page. By default,
367
-		 * the new link will appear after the "Visit plugin site" link if present, otherwise
368
-		 * after the "View plugin details" link.
369
-		 *
370
-		 * You can change the link text by using the "puc_manual_check_link-$slug" filter.
371
-		 * Returning an empty string from the filter will disable the link.
372
-		 *
373
-		 * @param array $pluginMeta Array of meta links.
374
-		 * @param string $pluginFile
375
-		 * @return array
376
-		 */
377
-		public function addCheckForUpdatesLink($pluginMeta, $pluginFile) {
378
-			$isRelevant = ($pluginFile == $this->pluginFile)
379
-				|| (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);
380
-
381
-			if ( $isRelevant && $this->userCanInstallUpdates() ) {
382
-				$linkUrl = wp_nonce_url(
383
-					add_query_arg(
384
-						array(
385
-							'puc_check_for_updates' => 1,
386
-							'puc_slug' => $this->slug,
387
-						),
388
-						self_admin_url('plugins.php')
389
-					),
390
-					'puc_check_for_updates'
391
-				);
392
-
393
-				$linkText = apply_filters(
394
-					$this->getUniqueName('manual_check_link'),
395
-					__('Check for updates', 'plugin-update-checker')
396
-				);
397
-				if ( !empty($linkText) ) {
398
-					/** @noinspection HtmlUnknownTarget */
399
-					$pluginMeta[] = sprintf('<a href="%s">%s</a>', esc_attr($linkUrl), $linkText);
400
-				}
401
-			}
402
-			return $pluginMeta;
403
-		}
404
-
405
-		/**
406
-		 * Add a "View Details" link to the plugin row in the "Plugins" page. By default,
407
-		 * the new link will appear before the "Visit plugin site" link (if present).
408
-		 *
409
-		 * You can change the link text by using the "puc_view_details_link-$slug" filter.
410
-		 * Returning an empty string from the filter will disable the link.
411
-		 *
412
-		 * You can change the position of the link using the
413
-		 * "puc_view_details_link_position-$slug" filter.
414
-		 * Returning 'before' or 'after' will place the link immediately before/after the
415
-		 * "Visit plugin site" link
416
-		 * Returning 'append' places the link after any existing links at the time of the hook.
417
-		 * Returning 'replace' replaces the "Visit plugin site" link
418
-		 * Returning anything else disables the link when there is a "Visit plugin site" link.
419
-		 *
420
-		 * If there is no "Visit plugin site" link 'append' is always used!
421
-		 *
422
-		 * @param array $pluginMeta Array of meta links.
423
-		 * @param string $pluginFile
424
-		 * @param array $pluginData Array of plugin header data.
425
-		 * @return array
426
-		 */
427
-		public function addViewDetailsLink($pluginMeta, $pluginFile, $pluginData = array()) {
428
-			$isRelevant = ($pluginFile == $this->pluginFile)
429
-				|| (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);
430
-
431
-			if ( $isRelevant && $this->userCanInstallUpdates() && !isset($pluginData['slug']) ) {
432
-				$linkText = apply_filters($this->getUniqueName('view_details_link'), __('View details'));
433
-				if ( !empty($linkText) ) {
434
-					$viewDetailsLinkPosition = 'append';
435
-
436
-					//Find the "Visit plugin site" link (if present).
437
-					$visitPluginSiteLinkIndex = count($pluginMeta) - 1;
438
-					if ( $pluginData['PluginURI'] ) {
439
-						$escapedPluginUri = esc_url($pluginData['PluginURI']);
440
-						foreach ($pluginMeta as $linkIndex => $existingLink) {
441
-							if ( strpos($existingLink, $escapedPluginUri) !== false ) {
442
-								$visitPluginSiteLinkIndex = $linkIndex;
443
-								$viewDetailsLinkPosition = apply_filters(
444
-									$this->getUniqueName('view_details_link_position'),
445
-									'before'
446
-								);
447
-								break;
448
-							}
449
-						}
450
-					}
451
-
452
-					$viewDetailsLink = sprintf('<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
453
-						esc_url(network_admin_url('plugin-install.php?tab=plugin-information&plugin=' . urlencode($this->slug) .
454
-							'&TB_iframe=true&width=600&height=550')),
455
-						esc_attr(sprintf(__('More information about %s'), $pluginData['Name'])),
456
-						esc_attr($pluginData['Name']),
457
-						$linkText
458
-					);
459
-					switch ($viewDetailsLinkPosition) {
460
-						case 'before':
461
-							array_splice($pluginMeta, $visitPluginSiteLinkIndex, 0, $viewDetailsLink);
462
-							break;
463
-						case 'after':
464
-							array_splice($pluginMeta, $visitPluginSiteLinkIndex + 1, 0, $viewDetailsLink);
465
-							break;
466
-						case 'replace':
467
-							$pluginMeta[$visitPluginSiteLinkIndex] = $viewDetailsLink;
468
-							break;
469
-						case 'append':
470
-						default:
471
-							$pluginMeta[] = $viewDetailsLink;
472
-							break;
473
-					}
474
-				}
475
-			}
476
-			return $pluginMeta;
477
-		}
478
-
479
-
480
-		/**
481
-		 * Check for updates when the user clicks the "Check for updates" link.
482
-		 * @see self::addCheckForUpdatesLink()
483
-		 *
484
-		 * @return void
485
-		 */
486
-		public function handleManualCheck() {
487
-			$shouldCheck =
488
-				isset($_GET['puc_check_for_updates'], $_GET['puc_slug'])
489
-				&& $_GET['puc_slug'] == $this->slug
490
-				&& $this->userCanInstallUpdates()
491
-				&& check_admin_referer('puc_check_for_updates');
492
-
493
-			if ( $shouldCheck ) {
494
-				$update = $this->checkForUpdates();
495
-				$status = ($update === null) ? 'no_update' : 'update_available';
496
-
497
-				if ( ($update === null) && !empty($this->lastRequestApiErrors) ) {
498
-					//Some errors are not critical. For example, if PUC tries to retrieve the readme.txt
499
-					//file from GitHub and gets a 404, that's an API error, but it doesn't prevent updates
500
-					//from working. Maybe the plugin simply doesn't have a readme.
501
-					//Let's only show important errors.
502
-					$foundCriticalErrors = false;
503
-					$questionableErrorCodes = array(
504
-						'puc-github-http-error',
505
-						'puc-gitlab-http-error',
506
-						'puc-bitbucket-http-error',
507
-					);
508
-
509
-					foreach ($this->lastRequestApiErrors as $item) {
510
-						$wpError = $item['error'];
511
-						/** @var WP_Error $wpError */
512
-						if ( !in_array($wpError->get_error_code(), $questionableErrorCodes) ) {
513
-							$foundCriticalErrors = true;
514
-							break;
515
-						}
516
-					}
517
-
518
-					if ( $foundCriticalErrors ) {
519
-						$status = 'error';
520
-						set_site_transient($this->manualCheckErrorTransient, $this->lastRequestApiErrors, 60);
521
-					}
522
-				}
523
-
524
-				wp_redirect(add_query_arg(
525
-					array(
526
-						'puc_update_check_result' => $status,
527
-						'puc_slug' => $this->slug,
528
-					),
529
-					self_admin_url('plugins.php')
530
-				));
531
-			}
532
-		}
533
-
534
-		/**
535
-		 * Display the results of a manual update check.
536
-		 * @see self::handleManualCheck()
537
-		 *
538
-		 * You can change the result message by using the "puc_manual_check_message-$slug" filter.
539
-		 */
540
-		public function displayManualCheckResult() {
541
-			if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->slug) ) {
542
-				$status      = strval($_GET['puc_update_check_result']);
543
-				$title       = $this->getPluginTitle();
544
-				$noticeClass = 'updated notice-success';
545
-				$details     = '';
546
-
547
-				if ( $status == 'no_update' ) {
548
-					$message = sprintf(_x('The %s plugin is up to date.', 'the plugin title', 'plugin-update-checker'), $title);
549
-				} else if ( $status == 'update_available' ) {
550
-					$message = sprintf(_x('A new version of the %s plugin is available.', 'the plugin title', 'plugin-update-checker'), $title);
551
-				} else if ( $status === 'error' ) {
552
-					$message = sprintf(_x('Could not determine if updates are available for %s.', 'the plugin title', 'plugin-update-checker'), $title);
553
-					$noticeClass = 'error notice-error';
554
-
555
-					$details = $this->formatManualCheckErrors(get_site_transient($this->manualCheckErrorTransient));
556
-					delete_site_transient($this->manualCheckErrorTransient);
557
-				} else {
558
-					$message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), htmlentities($status));
559
-					$noticeClass = 'error notice-error';
560
-				}
561
-				printf(
562
-					'<div class="notice %s is-dismissible"><p>%s</p>%s</div>',
563
-					$noticeClass,
564
-					apply_filters($this->getUniqueName('manual_check_message'), $message, $status),
565
-					$details
566
-				);
567
-			}
568
-		}
569
-
570
-		/**
571
-		 * Format the list of errors that were thrown during an update check.
572
-		 *
573
-		 * @param array $errors
574
-		 * @return string
575
-		 */
576
-		protected function formatManualCheckErrors($errors) {
577
-			if ( empty($errors) ) {
578
-				return '';
579
-			}
580
-			$output = '';
581
-
582
-			$showAsList = count($errors) > 1;
583
-			if ( $showAsList ) {
584
-				$output .= '<ol>';
585
-				$formatString = '<li>%1$s <code>%2$s</code></li>';
586
-			} else {
587
-				$formatString = '<p>%1$s <code>%2$s</code></p>';
588
-			}
589
-			foreach ($errors as $item) {
590
-				$wpError = $item['error'];
591
-				/** @var WP_Error $wpError */
592
-				$output .= sprintf(
593
-					$formatString,
594
-					$wpError->get_error_message(),
595
-					$wpError->get_error_code()
596
-				);
597
-			}
598
-			if ( $showAsList ) {
599
-				$output .= '</ol>';
600
-			}
601
-
602
-			return $output;
603
-		}
604
-
605
-		/**
606
-		 * Get the translated plugin title.
607
-		 *
608
-		 * @return string
609
-		 */
610
-		protected function getPluginTitle() {
611
-			$title  = '';
612
-			$header = $this->getPluginHeader();
613
-			if ( $header && !empty($header['Name']) && isset($header['TextDomain']) ) {
614
-				$title = translate($header['Name'], $header['TextDomain']);
615
-			}
616
-			return $title;
617
-		}
618
-
619
-		/**
620
-		 * Check if the current user has the required permissions to install updates.
621
-		 *
622
-		 * @return bool
623
-		 */
624
-		public function userCanInstallUpdates() {
625
-			return current_user_can('update_plugins');
626
-		}
627
-
628
-		/**
629
-		 * Check if the plugin file is inside the mu-plugins directory.
630
-		 *
631
-		 * @return bool
632
-		 */
633
-		protected function isMuPlugin() {
634
-			static $cachedResult = null;
635
-
636
-			if ( $cachedResult === null ) {
637
-				//Convert both paths to the canonical form before comparison.
638
-				$muPluginDir = realpath(WPMU_PLUGIN_DIR);
639
-				$pluginPath  = realpath($this->pluginAbsolutePath);
640
-
641
-				$cachedResult = (strpos($pluginPath, $muPluginDir) === 0);
642
-			}
643
-
644
-			return $cachedResult;
645
-		}
646
-
647
-		/**
648
-		 * MU plugins are partially supported, but only when we know which file in mu-plugins
649
-		 * corresponds to this plugin.
650
-		 *
651
-		 * @return bool
652
-		 */
653
-		protected function isUnknownMuPlugin() {
654
-			return empty($this->muPluginFile) && $this->isMuPlugin();
655
-		}
656
-
657
-		/**
658
-		 * Clear the cached plugin version. This method can be set up as a filter (hook) and will
659
-		 * return the filter argument unmodified.
660
-		 *
661
-		 * @param mixed $filterArgument
662
-		 * @return mixed
663
-		 */
664
-		public function clearCachedVersion($filterArgument = null) {
665
-			$this->cachedInstalledVersion = null;
666
-			return $filterArgument;
667
-		}
668
-
669
-		/**
670
-		 * Get absolute path to the main plugin file.
671
-		 *
672
-		 * @return string
673
-		 */
674
-		public function getAbsolutePath() {
675
-			return $this->pluginAbsolutePath;
676
-		}
677
-
678
-		/**
679
-		 * @return string
680
-		 */
681
-		public function getAbsoluteDirectoryPath() {
682
-			return dirname($this->pluginAbsolutePath);
683
-		}
684
-
685
-		/**
686
-		 * Register a callback for filtering query arguments.
687
-		 *
688
-		 * The callback function should take one argument - an associative array of query arguments.
689
-		 * It should return a modified array of query arguments.
690
-		 *
691
-		 * @uses add_filter() This method is a convenience wrapper for add_filter().
692
-		 *
693
-		 * @param callable $callback
694
-		 * @return void
695
-		 */
696
-		public function addQueryArgFilter($callback){
697
-			$this->addFilter('request_info_query_args', $callback);
698
-		}
699
-
700
-		/**
701
-		 * Register a callback for filtering arguments passed to wp_remote_get().
702
-		 *
703
-		 * The callback function should take one argument - an associative array of arguments -
704
-		 * and return a modified array or arguments. See the WP documentation on wp_remote_get()
705
-		 * for details on what arguments are available and how they work.
706
-		 *
707
-		 * @uses add_filter() This method is a convenience wrapper for add_filter().
708
-		 *
709
-		 * @param callable $callback
710
-		 * @return void
711
-		 */
712
-		public function addHttpRequestArgFilter($callback) {
713
-			$this->addFilter('request_info_options', $callback);
714
-		}
715
-
716
-		/**
717
-		 * Register a callback for filtering the plugin info retrieved from the external API.
718
-		 *
719
-		 * The callback function should take two arguments. If the plugin info was retrieved
720
-		 * successfully, the first argument passed will be an instance of  PluginInfo. Otherwise,
721
-		 * it will be NULL. The second argument will be the corresponding return value of
722
-		 * wp_remote_get (see WP docs for details).
723
-		 *
724
-		 * The callback function should return a new or modified instance of PluginInfo or NULL.
725
-		 *
726
-		 * @uses add_filter() This method is a convenience wrapper for add_filter().
727
-		 *
728
-		 * @param callable $callback
729
-		 * @return void
730
-		 */
731
-		public function addResultFilter($callback) {
732
-			$this->addFilter('request_info_result', $callback, 10, 2);
733
-		}
734
-
735
-		protected function createDebugBarExtension() {
736
-			return new Puc_v4p4_DebugBar_PluginExtension($this);
737
-		}
738
-	}
4
+    /**
5
+     * A custom plugin update checker.
6
+     *
7
+     * @author Janis Elsts
8
+     * @copyright 2016
9
+     * @access public
10
+     */
11
+    class Puc_v4p4_Plugin_UpdateChecker extends Puc_v4p4_UpdateChecker {
12
+        protected $updateTransient = 'update_plugins';
13
+        protected $translationType = 'plugin';
14
+
15
+        public $pluginAbsolutePath = ''; //Full path of the main plugin file.
16
+        public $pluginFile = '';  //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins.
17
+        public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory.
18
+
19
+        private $cachedInstalledVersion = null;
20
+        private $manualCheckErrorTransient = '';
21
+
22
+        /**
23
+         * Class constructor.
24
+         *
25
+         * @param string $metadataUrl The URL of the plugin's metadata file.
26
+         * @param string $pluginFile Fully qualified path to the main plugin file.
27
+         * @param string $slug The plugin's 'slug'. If not specified, the filename part of $pluginFile sans '.php' will be used as the slug.
28
+         * @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.
29
+         * @param string $optionName Where to store book-keeping info about update checks. Defaults to 'external_updates-$slug'.
30
+         * @param string $muPluginFile Optional. The plugin filename relative to the mu-plugins directory.
31
+         */
32
+        public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = ''){
33
+            $this->pluginAbsolutePath = $pluginFile;
34
+            $this->pluginFile = plugin_basename($this->pluginAbsolutePath);
35
+            $this->muPluginFile = $muPluginFile;
36
+
37
+            //If no slug is specified, use the name of the main plugin file as the slug.
38
+            //For example, 'my-cool-plugin/cool-plugin.php' becomes 'cool-plugin'.
39
+            if ( empty($slug) ){
40
+                $slug = basename($this->pluginFile, '.php');
41
+            }
42
+
43
+            //Plugin slugs must be unique.
44
+            $slugCheckFilter = 'puc_is_slug_in_use-' . $this->slug;
45
+            $slugUsedBy = apply_filters($slugCheckFilter, false);
46
+            if ( $slugUsedBy ) {
47
+                $this->triggerError(sprintf(
48
+                    'Plugin slug "%s" is already in use by %s. Slugs must be unique.',
49
+                    htmlentities($this->slug),
50
+                    htmlentities($slugUsedBy)
51
+                ), E_USER_ERROR);
52
+            }
53
+            add_filter($slugCheckFilter, array($this, 'getAbsolutePath'));
54
+
55
+            //Backwards compatibility: If the plugin is a mu-plugin but no $muPluginFile is specified, assume
56
+            //it's the same as $pluginFile given that it's not in a subdirectory (WP only looks in the base dir).
57
+            if ( (strpbrk($this->pluginFile, '/\\') === false) && $this->isUnknownMuPlugin() ) {
58
+                $this->muPluginFile = $this->pluginFile;
59
+            }
60
+
61
+            //To prevent a crash during plugin uninstallation, remove updater hooks when the user removes the plugin.
62
+            //Details: https://github.com/YahnisElsts/plugin-update-checker/issues/138#issuecomment-335590964
63
+            add_action('uninstall_' . $this->pluginFile, array($this, 'removeHooks'));
64
+
65
+            $this->manualCheckErrorTransient = $this->getUniqueName('manual_check_errors');
66
+
67
+            parent::__construct($metadataUrl, dirname($this->pluginFile), $slug, $checkPeriod, $optionName);
68
+        }
69
+
70
+        /**
71
+         * Create an instance of the scheduler.
72
+         *
73
+         * @param int $checkPeriod
74
+         * @return Puc_v4p4_Scheduler
75
+         */
76
+        protected function createScheduler($checkPeriod) {
77
+            $scheduler = new Puc_v4p4_Scheduler($this, $checkPeriod, array('load-plugins.php'));
78
+            register_deactivation_hook($this->pluginFile, array($scheduler, 'removeUpdaterCron'));
79
+            return $scheduler;
80
+        }
81
+
82
+        /**
83
+         * Install the hooks required to run periodic update checks and inject update info
84
+         * into WP data structures.
85
+         *
86
+         * @return void
87
+         */
88
+        protected function installHooks(){
89
+            //Override requests for plugin information
90
+            add_filter('plugins_api', array($this, 'injectInfo'), 20, 3);
91
+
92
+            add_filter('plugin_row_meta', array($this, 'addViewDetailsLink'), 10, 3);
93
+            add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2);
94
+            add_action('admin_init', array($this, 'handleManualCheck'));
95
+            add_action('all_admin_notices', array($this, 'displayManualCheckResult'));
96
+
97
+            //Clear the version number cache when something - anything - is upgraded or WP clears the update cache.
98
+            add_filter('upgrader_post_install', array($this, 'clearCachedVersion'));
99
+            add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));
100
+
101
+            parent::installHooks();
102
+        }
103
+
104
+        /**
105
+         * Remove update checker hooks.
106
+         *
107
+         * The intent is to prevent a fatal error that can happen if the plugin has an uninstall
108
+         * hook. During uninstallation, WP includes the main plugin file (which creates a PUC instance),
109
+         * the uninstall hook runs, WP deletes the plugin files and then updates some transients.
110
+         * If PUC hooks are still around at this time, they could throw an error while trying to
111
+         * autoload classes from files that no longer exist.
112
+         *
113
+         * The "site_transient_{$transient}" filter is the main problem here, but let's also remove
114
+         * most other PUC hooks to be safe.
115
+         *
116
+         * @internal
117
+         */
118
+        public function removeHooks() {
119
+            parent::removeHooks();
120
+
121
+            remove_filter('plugins_api', array($this, 'injectInfo'), 20);
122
+
123
+            remove_filter('plugin_row_meta', array($this, 'addViewDetailsLink'), 10);
124
+            remove_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10);
125
+            remove_action('admin_init', array($this, 'handleManualCheck'));
126
+            remove_action('all_admin_notices', array($this, 'displayManualCheckResult'));
127
+
128
+            remove_filter('upgrader_post_install', array($this, 'clearCachedVersion'));
129
+            remove_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));
130
+        }
131
+
132
+        /**
133
+         * Retrieve plugin info from the configured API endpoint.
134
+         *
135
+         * @uses wp_remote_get()
136
+         *
137
+         * @param array $queryArgs Additional query arguments to append to the request. Optional.
138
+         * @return Puc_v4p4_Plugin_Info
139
+         */
140
+        public function requestInfo($queryArgs = array()) {
141
+            list($pluginInfo, $result) = $this->requestMetadata('Puc_v4p4_Plugin_Info', 'request_info', $queryArgs);
142
+
143
+            if ( $pluginInfo !== null ) {
144
+                /** @var Puc_v4p4_Plugin_Info $pluginInfo */
145
+                $pluginInfo->filename = $this->pluginFile;
146
+                $pluginInfo->slug = $this->slug;
147
+            }
148
+
149
+            $pluginInfo = apply_filters($this->getUniqueName('request_info_result'), $pluginInfo, $result);
150
+            return $pluginInfo;
151
+        }
152
+
153
+        /**
154
+         * Retrieve the latest update (if any) from the configured API endpoint.
155
+         *
156
+         * @uses PluginUpdateChecker::requestInfo()
157
+         *
158
+         * @return Puc_v4p4_Update|null An instance of Plugin_Update, or NULL when no updates are available.
159
+         */
160
+        public function requestUpdate() {
161
+            //For the sake of simplicity, this function just calls requestInfo()
162
+            //and transforms the result accordingly.
163
+            $pluginInfo = $this->requestInfo(array('checking_for_updates' => '1'));
164
+            if ( $pluginInfo === null ){
165
+                return null;
166
+            }
167
+            $update = Puc_v4p4_Plugin_Update::fromPluginInfo($pluginInfo);
168
+
169
+            $update = $this->filterUpdateResult($update);
170
+
171
+            return $update;
172
+        }
173
+
174
+        /**
175
+         * Get the currently installed version of the plugin.
176
+         *
177
+         * @return string Version number.
178
+         */
179
+        public function getInstalledVersion(){
180
+            if ( isset($this->cachedInstalledVersion) ) {
181
+                return $this->cachedInstalledVersion;
182
+            }
183
+
184
+            $pluginHeader = $this->getPluginHeader();
185
+            if ( isset($pluginHeader['Version']) ) {
186
+                $this->cachedInstalledVersion = $pluginHeader['Version'];
187
+                return $pluginHeader['Version'];
188
+            } else {
189
+                //This can happen if the filename points to something that is not a plugin.
190
+                $this->triggerError(
191
+                    sprintf(
192
+                        "Can't to read the Version header for '%s'. The filename is incorrect or is not a plugin.",
193
+                        $this->pluginFile
194
+                    ),
195
+                    E_USER_WARNING
196
+                );
197
+                return null;
198
+            }
199
+        }
200
+
201
+        /**
202
+         * Get plugin's metadata from its file header.
203
+         *
204
+         * @return array
205
+         */
206
+        protected function getPluginHeader() {
207
+            if ( !is_file($this->pluginAbsolutePath) ) {
208
+                //This can happen if the plugin filename is wrong.
209
+                $this->triggerError(
210
+                    sprintf(
211
+                        "Can't to read the plugin header for '%s'. The file does not exist.",
212
+                        $this->pluginFile
213
+                    ),
214
+                    E_USER_WARNING
215
+                );
216
+                return array();
217
+            }
218
+
219
+            if ( !function_exists('get_plugin_data') ){
220
+                /** @noinspection PhpIncludeInspection */
221
+                require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
222
+            }
223
+            return get_plugin_data($this->pluginAbsolutePath, false, false);
224
+        }
225
+
226
+        /**
227
+         * @return array
228
+         */
229
+        protected function getHeaderNames() {
230
+            return array(
231
+                'Name' => 'Plugin Name',
232
+                'PluginURI' => 'Plugin URI',
233
+                'Version' => 'Version',
234
+                'Description' => 'Description',
235
+                'Author' => 'Author',
236
+                'AuthorURI' => 'Author URI',
237
+                'TextDomain' => 'Text Domain',
238
+                'DomainPath' => 'Domain Path',
239
+                'Network' => 'Network',
240
+
241
+                //The newest WordPress version that this plugin requires or has been tested with.
242
+                //We support several different formats for compatibility with other libraries.
243
+                'Tested WP' => 'Tested WP',
244
+                'Requires WP' => 'Requires WP',
245
+                'Tested up to' => 'Tested up to',
246
+                'Requires at least' => 'Requires at least',
247
+            );
248
+        }
249
+
250
+
251
+        /**
252
+         * Intercept plugins_api() calls that request information about our plugin and
253
+         * use the configured API endpoint to satisfy them.
254
+         *
255
+         * @see plugins_api()
256
+         *
257
+         * @param mixed $result
258
+         * @param string $action
259
+         * @param array|object $args
260
+         * @return mixed
261
+         */
262
+        public function injectInfo($result, $action = null, $args = null){
263
+            $relevant = ($action == 'plugin_information') && isset($args->slug) && (
264
+                    ($args->slug == $this->slug) || ($args->slug == dirname($this->pluginFile))
265
+                );
266
+            if ( !$relevant ) {
267
+                return $result;
268
+            }
269
+
270
+            $pluginInfo = $this->requestInfo();
271
+            $pluginInfo = apply_filters($this->getUniqueName('pre_inject_info'), $pluginInfo);
272
+            if ( $pluginInfo ) {
273
+                return $pluginInfo->toWpFormat();
274
+            }
275
+
276
+            return $result;
277
+        }
278
+
279
+        protected function shouldShowUpdates() {
280
+            //No update notifications for mu-plugins unless explicitly enabled. The MU plugin file
281
+            //is usually different from the main plugin file so the update wouldn't show up properly anyway.
282
+            return !$this->isUnknownMuPlugin();
283
+        }
284
+
285
+        /**
286
+         * @param stdClass|null $updates
287
+         * @param stdClass $updateToAdd
288
+         * @return stdClass
289
+         */
290
+        protected function addUpdateToList($updates, $updateToAdd) {
291
+            if ( $this->isMuPlugin() ) {
292
+                //WP does not support automatic update installation for mu-plugins, but we can
293
+                //still display a notice.
294
+                $updateToAdd->package = null;
295
+            }
296
+            return parent::addUpdateToList($updates, $updateToAdd);
297
+        }
298
+
299
+        /**
300
+         * @param stdClass|null $updates
301
+         * @return stdClass|null
302
+         */
303
+        protected function removeUpdateFromList($updates) {
304
+            $updates = parent::removeUpdateFromList($updates);
305
+            if ( !empty($this->muPluginFile) && isset($updates, $updates->response) ) {
306
+                unset($updates->response[$this->muPluginFile]);
307
+            }
308
+            return $updates;
309
+        }
310
+
311
+        /**
312
+         * For plugins, the update array is indexed by the plugin filename relative to the "plugins"
313
+         * directory. Example: "plugin-name/plugin.php".
314
+         *
315
+         * @return string
316
+         */
317
+        protected function getUpdateListKey() {
318
+            if ( $this->isMuPlugin() ) {
319
+                return $this->muPluginFile;
320
+            }
321
+            return $this->pluginFile;
322
+        }
323
+
324
+        /**
325
+         * Alias for isBeingUpgraded().
326
+         *
327
+         * @deprecated
328
+         * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
329
+         * @return bool
330
+         */
331
+        public function isPluginBeingUpgraded($upgrader = null) {
332
+            return $this->isBeingUpgraded($upgrader);
333
+        }
334
+
335
+        /**
336
+         * Is there an update being installed for this plugin, right now?
337
+         *
338
+         * @param WP_Upgrader|null $upgrader
339
+         * @return bool
340
+         */
341
+        public function isBeingUpgraded($upgrader = null) {
342
+            return $this->upgraderStatus->isPluginBeingUpgraded($this->pluginFile, $upgrader);
343
+        }
344
+
345
+        /**
346
+         * Get the details of the currently available update, if any.
347
+         *
348
+         * If no updates are available, or if the last known update version is below or equal
349
+         * to the currently installed version, this method will return NULL.
350
+         *
351
+         * Uses cached update data. To retrieve update information straight from
352
+         * the metadata URL, call requestUpdate() instead.
353
+         *
354
+         * @return Puc_v4p4_Plugin_Update|null
355
+         */
356
+        public function getUpdate() {
357
+            $update = parent::getUpdate();
358
+            if ( isset($update) ) {
359
+                /** @var Puc_v4p4_Plugin_Update $update */
360
+                $update->filename = $this->pluginFile;
361
+            }
362
+            return $update;
363
+        }
364
+
365
+        /**
366
+         * Add a "Check for updates" link to the plugin row in the "Plugins" page. By default,
367
+         * the new link will appear after the "Visit plugin site" link if present, otherwise
368
+         * after the "View plugin details" link.
369
+         *
370
+         * You can change the link text by using the "puc_manual_check_link-$slug" filter.
371
+         * Returning an empty string from the filter will disable the link.
372
+         *
373
+         * @param array $pluginMeta Array of meta links.
374
+         * @param string $pluginFile
375
+         * @return array
376
+         */
377
+        public function addCheckForUpdatesLink($pluginMeta, $pluginFile) {
378
+            $isRelevant = ($pluginFile == $this->pluginFile)
379
+                || (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);
380
+
381
+            if ( $isRelevant && $this->userCanInstallUpdates() ) {
382
+                $linkUrl = wp_nonce_url(
383
+                    add_query_arg(
384
+                        array(
385
+                            'puc_check_for_updates' => 1,
386
+                            'puc_slug' => $this->slug,
387
+                        ),
388
+                        self_admin_url('plugins.php')
389
+                    ),
390
+                    'puc_check_for_updates'
391
+                );
392
+
393
+                $linkText = apply_filters(
394
+                    $this->getUniqueName('manual_check_link'),
395
+                    __('Check for updates', 'plugin-update-checker')
396
+                );
397
+                if ( !empty($linkText) ) {
398
+                    /** @noinspection HtmlUnknownTarget */
399
+                    $pluginMeta[] = sprintf('<a href="%s">%s</a>', esc_attr($linkUrl), $linkText);
400
+                }
401
+            }
402
+            return $pluginMeta;
403
+        }
404
+
405
+        /**
406
+         * Add a "View Details" link to the plugin row in the "Plugins" page. By default,
407
+         * the new link will appear before the "Visit plugin site" link (if present).
408
+         *
409
+         * You can change the link text by using the "puc_view_details_link-$slug" filter.
410
+         * Returning an empty string from the filter will disable the link.
411
+         *
412
+         * You can change the position of the link using the
413
+         * "puc_view_details_link_position-$slug" filter.
414
+         * Returning 'before' or 'after' will place the link immediately before/after the
415
+         * "Visit plugin site" link
416
+         * Returning 'append' places the link after any existing links at the time of the hook.
417
+         * Returning 'replace' replaces the "Visit plugin site" link
418
+         * Returning anything else disables the link when there is a "Visit plugin site" link.
419
+         *
420
+         * If there is no "Visit plugin site" link 'append' is always used!
421
+         *
422
+         * @param array $pluginMeta Array of meta links.
423
+         * @param string $pluginFile
424
+         * @param array $pluginData Array of plugin header data.
425
+         * @return array
426
+         */
427
+        public function addViewDetailsLink($pluginMeta, $pluginFile, $pluginData = array()) {
428
+            $isRelevant = ($pluginFile == $this->pluginFile)
429
+                || (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);
430
+
431
+            if ( $isRelevant && $this->userCanInstallUpdates() && !isset($pluginData['slug']) ) {
432
+                $linkText = apply_filters($this->getUniqueName('view_details_link'), __('View details'));
433
+                if ( !empty($linkText) ) {
434
+                    $viewDetailsLinkPosition = 'append';
435
+
436
+                    //Find the "Visit plugin site" link (if present).
437
+                    $visitPluginSiteLinkIndex = count($pluginMeta) - 1;
438
+                    if ( $pluginData['PluginURI'] ) {
439
+                        $escapedPluginUri = esc_url($pluginData['PluginURI']);
440
+                        foreach ($pluginMeta as $linkIndex => $existingLink) {
441
+                            if ( strpos($existingLink, $escapedPluginUri) !== false ) {
442
+                                $visitPluginSiteLinkIndex = $linkIndex;
443
+                                $viewDetailsLinkPosition = apply_filters(
444
+                                    $this->getUniqueName('view_details_link_position'),
445
+                                    'before'
446
+                                );
447
+                                break;
448
+                            }
449
+                        }
450
+                    }
451
+
452
+                    $viewDetailsLink = sprintf('<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
453
+                        esc_url(network_admin_url('plugin-install.php?tab=plugin-information&plugin=' . urlencode($this->slug) .
454
+                            '&TB_iframe=true&width=600&height=550')),
455
+                        esc_attr(sprintf(__('More information about %s'), $pluginData['Name'])),
456
+                        esc_attr($pluginData['Name']),
457
+                        $linkText
458
+                    );
459
+                    switch ($viewDetailsLinkPosition) {
460
+                        case 'before':
461
+                            array_splice($pluginMeta, $visitPluginSiteLinkIndex, 0, $viewDetailsLink);
462
+                            break;
463
+                        case 'after':
464
+                            array_splice($pluginMeta, $visitPluginSiteLinkIndex + 1, 0, $viewDetailsLink);
465
+                            break;
466
+                        case 'replace':
467
+                            $pluginMeta[$visitPluginSiteLinkIndex] = $viewDetailsLink;
468
+                            break;
469
+                        case 'append':
470
+                        default:
471
+                            $pluginMeta[] = $viewDetailsLink;
472
+                            break;
473
+                    }
474
+                }
475
+            }
476
+            return $pluginMeta;
477
+        }
478
+
479
+
480
+        /**
481
+         * Check for updates when the user clicks the "Check for updates" link.
482
+         * @see self::addCheckForUpdatesLink()
483
+         *
484
+         * @return void
485
+         */
486
+        public function handleManualCheck() {
487
+            $shouldCheck =
488
+                isset($_GET['puc_check_for_updates'], $_GET['puc_slug'])
489
+                && $_GET['puc_slug'] == $this->slug
490
+                && $this->userCanInstallUpdates()
491
+                && check_admin_referer('puc_check_for_updates');
492
+
493
+            if ( $shouldCheck ) {
494
+                $update = $this->checkForUpdates();
495
+                $status = ($update === null) ? 'no_update' : 'update_available';
496
+
497
+                if ( ($update === null) && !empty($this->lastRequestApiErrors) ) {
498
+                    //Some errors are not critical. For example, if PUC tries to retrieve the readme.txt
499
+                    //file from GitHub and gets a 404, that's an API error, but it doesn't prevent updates
500
+                    //from working. Maybe the plugin simply doesn't have a readme.
501
+                    //Let's only show important errors.
502
+                    $foundCriticalErrors = false;
503
+                    $questionableErrorCodes = array(
504
+                        'puc-github-http-error',
505
+                        'puc-gitlab-http-error',
506
+                        'puc-bitbucket-http-error',
507
+                    );
508
+
509
+                    foreach ($this->lastRequestApiErrors as $item) {
510
+                        $wpError = $item['error'];
511
+                        /** @var WP_Error $wpError */
512
+                        if ( !in_array($wpError->get_error_code(), $questionableErrorCodes) ) {
513
+                            $foundCriticalErrors = true;
514
+                            break;
515
+                        }
516
+                    }
517
+
518
+                    if ( $foundCriticalErrors ) {
519
+                        $status = 'error';
520
+                        set_site_transient($this->manualCheckErrorTransient, $this->lastRequestApiErrors, 60);
521
+                    }
522
+                }
523
+
524
+                wp_redirect(add_query_arg(
525
+                    array(
526
+                        'puc_update_check_result' => $status,
527
+                        'puc_slug' => $this->slug,
528
+                    ),
529
+                    self_admin_url('plugins.php')
530
+                ));
531
+            }
532
+        }
533
+
534
+        /**
535
+         * Display the results of a manual update check.
536
+         * @see self::handleManualCheck()
537
+         *
538
+         * You can change the result message by using the "puc_manual_check_message-$slug" filter.
539
+         */
540
+        public function displayManualCheckResult() {
541
+            if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->slug) ) {
542
+                $status      = strval($_GET['puc_update_check_result']);
543
+                $title       = $this->getPluginTitle();
544
+                $noticeClass = 'updated notice-success';
545
+                $details     = '';
546
+
547
+                if ( $status == 'no_update' ) {
548
+                    $message = sprintf(_x('The %s plugin is up to date.', 'the plugin title', 'plugin-update-checker'), $title);
549
+                } else if ( $status == 'update_available' ) {
550
+                    $message = sprintf(_x('A new version of the %s plugin is available.', 'the plugin title', 'plugin-update-checker'), $title);
551
+                } else if ( $status === 'error' ) {
552
+                    $message = sprintf(_x('Could not determine if updates are available for %s.', 'the plugin title', 'plugin-update-checker'), $title);
553
+                    $noticeClass = 'error notice-error';
554
+
555
+                    $details = $this->formatManualCheckErrors(get_site_transient($this->manualCheckErrorTransient));
556
+                    delete_site_transient($this->manualCheckErrorTransient);
557
+                } else {
558
+                    $message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), htmlentities($status));
559
+                    $noticeClass = 'error notice-error';
560
+                }
561
+                printf(
562
+                    '<div class="notice %s is-dismissible"><p>%s</p>%s</div>',
563
+                    $noticeClass,
564
+                    apply_filters($this->getUniqueName('manual_check_message'), $message, $status),
565
+                    $details
566
+                );
567
+            }
568
+        }
569
+
570
+        /**
571
+         * Format the list of errors that were thrown during an update check.
572
+         *
573
+         * @param array $errors
574
+         * @return string
575
+         */
576
+        protected function formatManualCheckErrors($errors) {
577
+            if ( empty($errors) ) {
578
+                return '';
579
+            }
580
+            $output = '';
581
+
582
+            $showAsList = count($errors) > 1;
583
+            if ( $showAsList ) {
584
+                $output .= '<ol>';
585
+                $formatString = '<li>%1$s <code>%2$s</code></li>';
586
+            } else {
587
+                $formatString = '<p>%1$s <code>%2$s</code></p>';
588
+            }
589
+            foreach ($errors as $item) {
590
+                $wpError = $item['error'];
591
+                /** @var WP_Error $wpError */
592
+                $output .= sprintf(
593
+                    $formatString,
594
+                    $wpError->get_error_message(),
595
+                    $wpError->get_error_code()
596
+                );
597
+            }
598
+            if ( $showAsList ) {
599
+                $output .= '</ol>';
600
+            }
601
+
602
+            return $output;
603
+        }
604
+
605
+        /**
606
+         * Get the translated plugin title.
607
+         *
608
+         * @return string
609
+         */
610
+        protected function getPluginTitle() {
611
+            $title  = '';
612
+            $header = $this->getPluginHeader();
613
+            if ( $header && !empty($header['Name']) && isset($header['TextDomain']) ) {
614
+                $title = translate($header['Name'], $header['TextDomain']);
615
+            }
616
+            return $title;
617
+        }
618
+
619
+        /**
620
+         * Check if the current user has the required permissions to install updates.
621
+         *
622
+         * @return bool
623
+         */
624
+        public function userCanInstallUpdates() {
625
+            return current_user_can('update_plugins');
626
+        }
627
+
628
+        /**
629
+         * Check if the plugin file is inside the mu-plugins directory.
630
+         *
631
+         * @return bool
632
+         */
633
+        protected function isMuPlugin() {
634
+            static $cachedResult = null;
635
+
636
+            if ( $cachedResult === null ) {
637
+                //Convert both paths to the canonical form before comparison.
638
+                $muPluginDir = realpath(WPMU_PLUGIN_DIR);
639
+                $pluginPath  = realpath($this->pluginAbsolutePath);
640
+
641
+                $cachedResult = (strpos($pluginPath, $muPluginDir) === 0);
642
+            }
643
+
644
+            return $cachedResult;
645
+        }
646
+
647
+        /**
648
+         * MU plugins are partially supported, but only when we know which file in mu-plugins
649
+         * corresponds to this plugin.
650
+         *
651
+         * @return bool
652
+         */
653
+        protected function isUnknownMuPlugin() {
654
+            return empty($this->muPluginFile) && $this->isMuPlugin();
655
+        }
656
+
657
+        /**
658
+         * Clear the cached plugin version. This method can be set up as a filter (hook) and will
659
+         * return the filter argument unmodified.
660
+         *
661
+         * @param mixed $filterArgument
662
+         * @return mixed
663
+         */
664
+        public function clearCachedVersion($filterArgument = null) {
665
+            $this->cachedInstalledVersion = null;
666
+            return $filterArgument;
667
+        }
668
+
669
+        /**
670
+         * Get absolute path to the main plugin file.
671
+         *
672
+         * @return string
673
+         */
674
+        public function getAbsolutePath() {
675
+            return $this->pluginAbsolutePath;
676
+        }
677
+
678
+        /**
679
+         * @return string
680
+         */
681
+        public function getAbsoluteDirectoryPath() {
682
+            return dirname($this->pluginAbsolutePath);
683
+        }
684
+
685
+        /**
686
+         * Register a callback for filtering query arguments.
687
+         *
688
+         * The callback function should take one argument - an associative array of query arguments.
689
+         * It should return a modified array of query arguments.
690
+         *
691
+         * @uses add_filter() This method is a convenience wrapper for add_filter().
692
+         *
693
+         * @param callable $callback
694
+         * @return void
695
+         */
696
+        public function addQueryArgFilter($callback){
697
+            $this->addFilter('request_info_query_args', $callback);
698
+        }
699
+
700
+        /**
701
+         * Register a callback for filtering arguments passed to wp_remote_get().
702
+         *
703
+         * The callback function should take one argument - an associative array of arguments -
704
+         * and return a modified array or arguments. See the WP documentation on wp_remote_get()
705
+         * for details on what arguments are available and how they work.
706
+         *
707
+         * @uses add_filter() This method is a convenience wrapper for add_filter().
708
+         *
709
+         * @param callable $callback
710
+         * @return void
711
+         */
712
+        public function addHttpRequestArgFilter($callback) {
713
+            $this->addFilter('request_info_options', $callback);
714
+        }
715
+
716
+        /**
717
+         * Register a callback for filtering the plugin info retrieved from the external API.
718
+         *
719
+         * The callback function should take two arguments. If the plugin info was retrieved
720
+         * successfully, the first argument passed will be an instance of  PluginInfo. Otherwise,
721
+         * it will be NULL. The second argument will be the corresponding return value of
722
+         * wp_remote_get (see WP docs for details).
723
+         *
724
+         * The callback function should return a new or modified instance of PluginInfo or NULL.
725
+         *
726
+         * @uses add_filter() This method is a convenience wrapper for add_filter().
727
+         *
728
+         * @param callable $callback
729
+         * @return void
730
+         */
731
+        public function addResultFilter($callback) {
732
+            $this->addFilter('request_info_result', $callback, 10, 2);
733
+        }
734
+
735
+        protected function createDebugBarExtension() {
736
+            return new Puc_v4p4_DebugBar_PluginExtension($this);
737
+        }
738
+    }
739 739
 
740 740
 endif;
Please login to merge, or discard this patch.
classes/external/php/plugin-update-checker/Puc/v4p4/UpgraderStatus.php 1 patch
Indentation   +194 added lines, -194 removed lines patch added patch discarded remove patch
@@ -1,199 +1,199 @@
 block discarded – undo
1 1
 <?php
2 2
 if ( !class_exists('Puc_v4p4_UpgraderStatus', false) ):
3 3
 
4
-	/**
5
-	 * A utility class that helps figure out which plugin or theme WordPress is upgrading.
6
-	 *
7
-	 * It may seem strange to have a separate class just for that, but the task is surprisingly complicated.
8
-	 * Core classes like Plugin_Upgrader don't expose the plugin file name during an in-progress update (AFAICT).
9
-	 * This class uses a few workarounds and heuristics to get the file name.
10
-	 */
11
-	class Puc_v4p4_UpgraderStatus {
12
-		private $currentType = null; //"plugin" or "theme".
13
-		private $currentId = null;   //Plugin basename or theme directory name.
14
-
15
-		public function __construct() {
16
-			//Keep track of which plugin/theme WordPress is currently upgrading.
17
-			add_filter('upgrader_pre_install', array($this, 'setUpgradedThing'), 10, 2);
18
-			add_filter('upgrader_package_options', array($this, 'setUpgradedPluginFromOptions'), 10, 1);
19
-			add_filter('upgrader_post_install', array($this, 'clearUpgradedThing'), 10, 1);
20
-			add_action('upgrader_process_complete', array($this, 'clearUpgradedThing'), 10, 1);
21
-		}
22
-
23
-		/**
24
-		 * Is there and update being installed RIGHT NOW, for a specific plugin?
25
-		 *
26
-		 * Caution: This method is unreliable. WordPress doesn't make it easy to figure out what it is upgrading,
27
-		 * and upgrader implementations are liable to change without notice.
28
-		 *
29
-		 * @param string $pluginFile The plugin to check.
30
-		 * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
31
-		 * @return bool True if the plugin identified by $pluginFile is being upgraded.
32
-		 */
33
-		public function isPluginBeingUpgraded($pluginFile, $upgrader = null) {
34
-			return $this->isBeingUpgraded('plugin', $pluginFile, $upgrader);
35
-		}
36
-
37
-		/**
38
-		 * Is there an update being installed for a specific theme?
39
-		 *
40
-		 * @param string $stylesheet Theme directory name.
41
-		 * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
42
-		 * @return bool
43
-		 */
44
-		public function isThemeBeingUpgraded($stylesheet, $upgrader = null) {
45
-			return $this->isBeingUpgraded('theme', $stylesheet, $upgrader);
46
-		}
47
-
48
-		/**
49
-		 * Check if a specific theme or plugin is being upgraded.
50
-		 *
51
-		 * @param string $type
52
-		 * @param string $id
53
-		 * @param Plugin_Upgrader|WP_Upgrader|null $upgrader
54
-		 * @return bool
55
-		 */
56
-		protected function isBeingUpgraded($type, $id, $upgrader = null) {
57
-			if ( isset($upgrader) ) {
58
-				list($currentType, $currentId) = $this->getThingBeingUpgradedBy($upgrader);
59
-				if ( $currentType !== null ) {
60
-					$this->currentType = $currentType;
61
-					$this->currentId = $currentId;
62
-				}
63
-			}
64
-			return ($this->currentType === $type) && ($this->currentId === $id);
65
-		}
66
-
67
-		/**
68
-		 * Figure out which theme or plugin is being upgraded by a WP_Upgrader instance.
69
-		 *
70
-		 * Returns an array with two items. The first item is the type of the thing that's being
71
-		 * upgraded: "plugin" or "theme". The second item is either the plugin basename or
72
-		 * the theme directory name. If we can't determine what the upgrader is doing, both items
73
-		 * will be NULL.
74
-		 *
75
-		 * Examples:
76
-		 *      ['plugin', 'plugin-dir-name/plugin.php']
77
-		 *      ['theme', 'theme-dir-name']
78
-		 *
79
-		 * @param Plugin_Upgrader|WP_Upgrader $upgrader
80
-		 * @return array
81
-		 */
82
-		private function getThingBeingUpgradedBy($upgrader) {
83
-			if ( !isset($upgrader, $upgrader->skin) ) {
84
-				return array(null, null);
85
-			}
86
-
87
-			//Figure out which plugin or theme is being upgraded.
88
-			$pluginFile = null;
89
-			$themeDirectoryName = null;
90
-
91
-			$skin = $upgrader->skin;
92
-			if ( isset($skin->theme_info) && ($skin->theme_info instanceof WP_Theme) ) {
93
-				$themeDirectoryName = $skin->theme_info->get_stylesheet();
94
-			} elseif ( $skin instanceof Plugin_Upgrader_Skin ) {
95
-				if ( isset($skin->plugin) && is_string($skin->plugin) && ($skin->plugin !== '') ) {
96
-					$pluginFile = $skin->plugin;
97
-				}
98
-			} elseif ( $skin instanceof Theme_Upgrader_Skin ) {
99
-				if ( isset($skin->theme) && is_string($skin->theme) && ($skin->theme !== '') ) {
100
-					$themeDirectoryName = $skin->theme;
101
-				}
102
-			} elseif ( isset($skin->plugin_info) && is_array($skin->plugin_info) ) {
103
-				//This case is tricky because Bulk_Plugin_Upgrader_Skin (etc) doesn't actually store the plugin
104
-				//filename anywhere. Instead, it has the plugin headers in $plugin_info. So the best we can
105
-				//do is compare those headers to the headers of installed plugins.
106
-				$pluginFile = $this->identifyPluginByHeaders($skin->plugin_info);
107
-			}
108
-
109
-			if ( $pluginFile !== null ) {
110
-				return array('plugin', $pluginFile);
111
-			} elseif ( $themeDirectoryName !== null ) {
112
-				return array('theme', $themeDirectoryName);
113
-			}
114
-			return array(null, null);
115
-		}
116
-
117
-		/**
118
-		 * Identify an installed plugin based on its headers.
119
-		 *
120
-		 * @param array $searchHeaders The plugin file header to look for.
121
-		 * @return string|null Plugin basename ("foo/bar.php"), or NULL if we can't identify the plugin.
122
-		 */
123
-		private function identifyPluginByHeaders($searchHeaders) {
124
-			if ( !function_exists('get_plugins') ){
125
-				/** @noinspection PhpIncludeInspection */
126
-				require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
127
-			}
128
-
129
-			$installedPlugins = get_plugins();
130
-			$matches = array();
131
-			foreach($installedPlugins as $pluginBasename => $headers) {
132
-				$diff1 = array_diff_assoc($headers, $searchHeaders);
133
-				$diff2 = array_diff_assoc($searchHeaders, $headers);
134
-				if ( empty($diff1) && empty($diff2) ) {
135
-					$matches[] = $pluginBasename;
136
-				}
137
-			}
138
-
139
-			//It's possible (though very unlikely) that there could be two plugins with identical
140
-			//headers. In that case, we can't unambiguously identify the plugin that's being upgraded.
141
-			if ( count($matches) !== 1 ) {
142
-				return null;
143
-			}
144
-
145
-			return reset($matches);
146
-		}
147
-
148
-		/**
149
-		 * @access private
150
-		 *
151
-		 * @param mixed $input
152
-		 * @param array $hookExtra
153
-		 * @return mixed Returns $input unaltered.
154
-		 */
155
-		public function setUpgradedThing($input, $hookExtra) {
156
-			if ( !empty($hookExtra['plugin']) && is_string($hookExtra['plugin']) ) {
157
-				$this->currentId = $hookExtra['plugin'];
158
-				$this->currentType = 'plugin';
159
-			} elseif ( !empty($hookExtra['theme']) && is_string($hookExtra['theme']) ) {
160
-				$this->currentId = $hookExtra['theme'];
161
-				$this->currentType = 'theme';
162
-			} else {
163
-				$this->currentType = null;
164
-				$this->currentId = null;
165
-			}
166
-			return $input;
167
-		}
168
-
169
-		/**
170
-		 * @access private
171
-		 *
172
-		 * @param array $options
173
-		 * @return array
174
-		 */
175
-		public function setUpgradedPluginFromOptions($options) {
176
-			if ( isset($options['hook_extra']['plugin']) && is_string($options['hook_extra']['plugin']) ) {
177
-				$this->currentType = 'plugin';
178
-				$this->currentId = $options['hook_extra']['plugin'];
179
-			} else {
180
-				$this->currentType = null;
181
-				$this->currentId = null;
182
-			}
183
-			return $options;
184
-		}
185
-
186
-		/**
187
-		 * @access private
188
-		 *
189
-		 * @param mixed $input
190
-		 * @return mixed Returns $input unaltered.
191
-		 */
192
-		public function clearUpgradedThing($input = null) {
193
-			$this->currentId = null;
194
-			$this->currentType = null;
195
-			return $input;
196
-		}
197
-	}
4
+    /**
5
+     * A utility class that helps figure out which plugin or theme WordPress is upgrading.
6
+     *
7
+     * It may seem strange to have a separate class just for that, but the task is surprisingly complicated.
8
+     * Core classes like Plugin_Upgrader don't expose the plugin file name during an in-progress update (AFAICT).
9
+     * This class uses a few workarounds and heuristics to get the file name.
10
+     */
11
+    class Puc_v4p4_UpgraderStatus {
12
+        private $currentType = null; //"plugin" or "theme".
13
+        private $currentId = null;   //Plugin basename or theme directory name.
14
+
15
+        public function __construct() {
16
+            //Keep track of which plugin/theme WordPress is currently upgrading.
17
+            add_filter('upgrader_pre_install', array($this, 'setUpgradedThing'), 10, 2);
18
+            add_filter('upgrader_package_options', array($this, 'setUpgradedPluginFromOptions'), 10, 1);
19
+            add_filter('upgrader_post_install', array($this, 'clearUpgradedThing'), 10, 1);
20
+            add_action('upgrader_process_complete', array($this, 'clearUpgradedThing'), 10, 1);
21
+        }
22
+
23
+        /**
24
+         * Is there and update being installed RIGHT NOW, for a specific plugin?
25
+         *
26
+         * Caution: This method is unreliable. WordPress doesn't make it easy to figure out what it is upgrading,
27
+         * and upgrader implementations are liable to change without notice.
28
+         *
29
+         * @param string $pluginFile The plugin to check.
30
+         * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
31
+         * @return bool True if the plugin identified by $pluginFile is being upgraded.
32
+         */
33
+        public function isPluginBeingUpgraded($pluginFile, $upgrader = null) {
34
+            return $this->isBeingUpgraded('plugin', $pluginFile, $upgrader);
35
+        }
36
+
37
+        /**
38
+         * Is there an update being installed for a specific theme?
39
+         *
40
+         * @param string $stylesheet Theme directory name.
41
+         * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
42
+         * @return bool
43
+         */
44
+        public function isThemeBeingUpgraded($stylesheet, $upgrader = null) {
45
+            return $this->isBeingUpgraded('theme', $stylesheet, $upgrader);
46
+        }
47
+
48
+        /**
49
+         * Check if a specific theme or plugin is being upgraded.
50
+         *
51
+         * @param string $type
52
+         * @param string $id
53
+         * @param Plugin_Upgrader|WP_Upgrader|null $upgrader
54
+         * @return bool
55
+         */
56
+        protected function isBeingUpgraded($type, $id, $upgrader = null) {
57
+            if ( isset($upgrader) ) {
58
+                list($currentType, $currentId) = $this->getThingBeingUpgradedBy($upgrader);
59
+                if ( $currentType !== null ) {
60
+                    $this->currentType = $currentType;
61
+                    $this->currentId = $currentId;
62
+                }
63
+            }
64
+            return ($this->currentType === $type) && ($this->currentId === $id);
65
+        }
66
+
67
+        /**
68
+         * Figure out which theme or plugin is being upgraded by a WP_Upgrader instance.
69
+         *
70
+         * Returns an array with two items. The first item is the type of the thing that's being
71
+         * upgraded: "plugin" or "theme". The second item is either the plugin basename or
72
+         * the theme directory name. If we can't determine what the upgrader is doing, both items
73
+         * will be NULL.
74
+         *
75
+         * Examples:
76
+         *      ['plugin', 'plugin-dir-name/plugin.php']
77
+         *      ['theme', 'theme-dir-name']
78
+         *
79
+         * @param Plugin_Upgrader|WP_Upgrader $upgrader
80
+         * @return array
81
+         */
82
+        private function getThingBeingUpgradedBy($upgrader) {
83
+            if ( !isset($upgrader, $upgrader->skin) ) {
84
+                return array(null, null);
85
+            }
86
+
87
+            //Figure out which plugin or theme is being upgraded.
88
+            $pluginFile = null;
89
+            $themeDirectoryName = null;
90
+
91
+            $skin = $upgrader->skin;
92
+            if ( isset($skin->theme_info) && ($skin->theme_info instanceof WP_Theme) ) {
93
+                $themeDirectoryName = $skin->theme_info->get_stylesheet();
94
+            } elseif ( $skin instanceof Plugin_Upgrader_Skin ) {
95
+                if ( isset($skin->plugin) && is_string($skin->plugin) && ($skin->plugin !== '') ) {
96
+                    $pluginFile = $skin->plugin;
97
+                }
98
+            } elseif ( $skin instanceof Theme_Upgrader_Skin ) {
99
+                if ( isset($skin->theme) && is_string($skin->theme) && ($skin->theme !== '') ) {
100
+                    $themeDirectoryName = $skin->theme;
101
+                }
102
+            } elseif ( isset($skin->plugin_info) && is_array($skin->plugin_info) ) {
103
+                //This case is tricky because Bulk_Plugin_Upgrader_Skin (etc) doesn't actually store the plugin
104
+                //filename anywhere. Instead, it has the plugin headers in $plugin_info. So the best we can
105
+                //do is compare those headers to the headers of installed plugins.
106
+                $pluginFile = $this->identifyPluginByHeaders($skin->plugin_info);
107
+            }
108
+
109
+            if ( $pluginFile !== null ) {
110
+                return array('plugin', $pluginFile);
111
+            } elseif ( $themeDirectoryName !== null ) {
112
+                return array('theme', $themeDirectoryName);
113
+            }
114
+            return array(null, null);
115
+        }
116
+
117
+        /**
118
+         * Identify an installed plugin based on its headers.
119
+         *
120
+         * @param array $searchHeaders The plugin file header to look for.
121
+         * @return string|null Plugin basename ("foo/bar.php"), or NULL if we can't identify the plugin.
122
+         */
123
+        private function identifyPluginByHeaders($searchHeaders) {
124
+            if ( !function_exists('get_plugins') ){
125
+                /** @noinspection PhpIncludeInspection */
126
+                require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
127
+            }
128
+
129
+            $installedPlugins = get_plugins();
130
+            $matches = array();
131
+            foreach($installedPlugins as $pluginBasename => $headers) {
132
+                $diff1 = array_diff_assoc($headers, $searchHeaders);
133
+                $diff2 = array_diff_assoc($searchHeaders, $headers);
134
+                if ( empty($diff1) && empty($diff2) ) {
135
+                    $matches[] = $pluginBasename;
136
+                }
137
+            }
138
+
139
+            //It's possible (though very unlikely) that there could be two plugins with identical
140
+            //headers. In that case, we can't unambiguously identify the plugin that's being upgraded.
141
+            if ( count($matches) !== 1 ) {
142
+                return null;
143
+            }
144
+
145
+            return reset($matches);
146
+        }
147
+
148
+        /**
149
+         * @access private
150
+         *
151
+         * @param mixed $input
152
+         * @param array $hookExtra
153
+         * @return mixed Returns $input unaltered.
154
+         */
155
+        public function setUpgradedThing($input, $hookExtra) {
156
+            if ( !empty($hookExtra['plugin']) && is_string($hookExtra['plugin']) ) {
157
+                $this->currentId = $hookExtra['plugin'];
158
+                $this->currentType = 'plugin';
159
+            } elseif ( !empty($hookExtra['theme']) && is_string($hookExtra['theme']) ) {
160
+                $this->currentId = $hookExtra['theme'];
161
+                $this->currentType = 'theme';
162
+            } else {
163
+                $this->currentType = null;
164
+                $this->currentId = null;
165
+            }
166
+            return $input;
167
+        }
168
+
169
+        /**
170
+         * @access private
171
+         *
172
+         * @param array $options
173
+         * @return array
174
+         */
175
+        public function setUpgradedPluginFromOptions($options) {
176
+            if ( isset($options['hook_extra']['plugin']) && is_string($options['hook_extra']['plugin']) ) {
177
+                $this->currentType = 'plugin';
178
+                $this->currentId = $options['hook_extra']['plugin'];
179
+            } else {
180
+                $this->currentType = null;
181
+                $this->currentId = null;
182
+            }
183
+            return $options;
184
+        }
185
+
186
+        /**
187
+         * @access private
188
+         *
189
+         * @param mixed $input
190
+         * @return mixed Returns $input unaltered.
191
+         */
192
+        public function clearUpgradedThing($input = null) {
193
+            $this->currentId = null;
194
+            $this->currentType = null;
195
+            return $input;
196
+        }
197
+    }
198 198
 
199 199
 endif;
200 200
\ No newline at end of file
Please login to merge, or discard this patch.
classes/external/php/plugin-update-checker/Puc/v4p4/UpdateChecker.php 1 patch
Indentation   +861 added lines, -861 removed lines patch added patch discarded remove patch
@@ -2,895 +2,895 @@
 block discarded – undo
2 2
 
3 3
 if ( !class_exists('Puc_v4p4_UpdateChecker', false) ):
4 4
 
5
-	abstract class Puc_v4p4_UpdateChecker {
6
-		protected $filterSuffix = '';
7
-		protected $updateTransient = '';
8
-		protected $translationType = ''; //"plugin" or "theme".
9
-
10
-		/**
11
-		 * Set to TRUE to enable error reporting. Errors are raised using trigger_error()
12
-		 * and should be logged to the standard PHP error log.
13
-		 * @var bool
14
-		 */
15
-		public $debugMode = false;
16
-
17
-		/**
18
-		 * @var string Where to store the update info.
19
-		 */
20
-		public $optionName = '';
21
-
22
-		/**
23
-		 * @var string The URL of the metadata file.
24
-		 */
25
-		public $metadataUrl = '';
26
-
27
-		/**
28
-		 * @var string Plugin or theme directory name.
29
-		 */
30
-		public $directoryName = '';
31
-
32
-		/**
33
-		 * @var string The slug that will be used in update checker hooks and remote API requests.
34
-		 * Usually matches the directory name unless the plugin/theme directory has been renamed.
35
-		 */
36
-		public $slug = '';
37
-
38
-		/**
39
-		 * @var Puc_v4p4_Scheduler
40
-		 */
41
-		public $scheduler;
42
-
43
-		/**
44
-		 * @var Puc_v4p4_UpgraderStatus
45
-		 */
46
-		protected $upgraderStatus;
47
-
48
-		/**
49
-		 * @var Puc_v4p4_StateStore
50
-		 */
51
-		protected $updateState;
52
-
53
-		/**
54
-		 * @var array List of API errors triggered during the last checkForUpdates() call.
55
-		 */
56
-		protected $lastRequestApiErrors = array();
57
-
58
-		public function __construct($metadataUrl, $directoryName, $slug = null, $checkPeriod = 12, $optionName = '') {
59
-			$this->debugMode = (bool)(constant('WP_DEBUG'));
60
-			$this->metadataUrl = $metadataUrl;
61
-			$this->directoryName = $directoryName;
62
-			$this->slug = !empty($slug) ? $slug : $this->directoryName;
63
-
64
-			$this->optionName = $optionName;
65
-			if ( empty($this->optionName) ) {
66
-				//BC: Initially the library only supported plugin updates and didn't use type prefixes
67
-				//in the option name. Lets use the same prefix-less name when possible.
68
-				if ( $this->filterSuffix === '' ) {
69
-					$this->optionName = 'external_updates-' . $this->slug;
70
-				} else {
71
-					$this->optionName = $this->getUniqueName('external_updates');
72
-				}
73
-			}
74
-
75
-			$this->scheduler = $this->createScheduler($checkPeriod);
76
-			$this->upgraderStatus = new Puc_v4p4_UpgraderStatus();
77
-			$this->updateState = new Puc_v4p4_StateStore($this->optionName);
78
-
79
-			if ( did_action('init') ) {
80
-				$this->loadTextDomain();
81
-			} else {
82
-				add_action('init', array($this, 'loadTextDomain'));
83
-			}
84
-
85
-			$this->installHooks();
86
-		}
87
-
88
-		/**
89
-		 * @internal
90
-		 */
91
-		public function loadTextDomain() {
92
-			//We're not using load_plugin_textdomain() or its siblings because figuring out where
93
-			//the library is located (plugin, mu-plugin, theme, custom wp-content paths) is messy.
94
-			$domain = 'plugin-update-checker';
95
-			$locale = apply_filters(
96
-				'plugin_locale',
97
-				(is_admin() && function_exists('get_user_locale')) ? get_user_locale() : get_locale(),
98
-				$domain
99
-			);
100
-
101
-			$moFile = $domain . '-' . $locale . '.mo';
102
-			$path = realpath(dirname(__FILE__) . '/../../languages');
103
-
104
-			if ($path && file_exists($path)) {
105
-				load_textdomain($domain, $path . '/' . $moFile);
106
-			}
107
-		}
108
-
109
-		protected function installHooks() {
110
-			//Insert our update info into the update array maintained by WP.
111
-			add_filter('site_transient_' . $this->updateTransient, array($this,'injectUpdate'));
112
-
113
-			//Insert translation updates into the update list.
114
-			add_filter('site_transient_' . $this->updateTransient, array($this, 'injectTranslationUpdates'));
115
-
116
-			//Clear translation updates when WP clears the update cache.
117
-			//This needs to be done directly because the library doesn't actually remove obsolete plugin updates,
118
-			//it just hides them (see getUpdate()). We can't do that with translations - too much disk I/O.
119
-			add_action(
120
-				'delete_site_transient_' . $this->updateTransient,
121
-				array($this, 'clearCachedTranslationUpdates')
122
-			);
123
-
124
-			//Rename the update directory to be the same as the existing directory.
125
-			if ( $this->directoryName !== '.' ) {
126
-				add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3);
127
-			}
128
-
129
-			//Allow HTTP requests to the metadata URL even if it's on a local host.
130
-			add_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10, 2);
131
-
132
-			//DebugBar integration.
133
-			if ( did_action('plugins_loaded') ) {
134
-				$this->maybeInitDebugBar();
135
-			} else {
136
-				add_action('plugins_loaded', array($this, 'maybeInitDebugBar'));
137
-			}
138
-		}
139
-
140
-		/**
141
-		 * Remove hooks that were added by this update checker instance.
142
-		 */
143
-		protected function removeHooks() {
144
-			remove_filter('site_transient_' . $this->updateTransient, array($this,'injectUpdate'));
145
-			remove_filter('site_transient_' . $this->updateTransient, array($this, 'injectTranslationUpdates'));
146
-			remove_action(
147
-				'delete_site_transient_' . $this->updateTransient,
148
-				array($this, 'clearCachedTranslationUpdates')
149
-			);
150
-
151
-			remove_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10);
152
-			remove_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10);
153
-			remove_action('plugins_loaded', array($this, 'maybeInitDebugBar'));
154
-
155
-			remove_action('init', array($this, 'loadTextDomain'));
156
-		}
157
-
158
-		/**
159
-		 * Check if the current user has the required permissions to install updates.
160
-		 *
161
-		 * @return bool
162
-		 */
163
-		abstract public function userCanInstallUpdates();
164
-
165
-		/**
166
-		 * Explicitly allow HTTP requests to the metadata URL.
167
-		 *
168
-		 * WordPress has a security feature where the HTTP API will reject all requests that are sent to
169
-		 * another site hosted on the same server as the current site (IP match), a local host, or a local
170
-		 * IP, unless the host exactly matches the current site.
171
-		 *
172
-		 * This feature is opt-in (at least in WP 4.4). Apparently some people enable it.
173
-		 *
174
-		 * That can be a problem when you're developing your plugin and you decide to host the update information
175
-		 * on the same server as your test site. Update requests will mysteriously fail.
176
-		 *
177
-		 * We fix that by adding an exception for the metadata host.
178
-		 *
179
-		 * @param bool $allow
180
-		 * @param string $host
181
-		 * @return bool
182
-		 */
183
-		public function allowMetadataHost($allow, $host) {
184
-			static $metadataHost = 0; //Using 0 instead of NULL because parse_url can return NULL.
185
-			if ( $metadataHost === 0 ) {
186
-				$metadataHost = @parse_url($this->metadataUrl, PHP_URL_HOST);
187
-			}
188
-
189
-			if ( is_string($metadataHost) && (strtolower($host) === strtolower($metadataHost)) ) {
190
-				return true;
191
-			}
192
-			return $allow;
193
-		}
194
-
195
-		/**
196
-		 * Create an instance of the scheduler.
197
-		 *
198
-		 * This is implemented as a method to make it possible for plugins to subclass the update checker
199
-		 * and substitute their own scheduler.
200
-		 *
201
-		 * @param int $checkPeriod
202
-		 * @return Puc_v4p4_Scheduler
203
-		 */
204
-		abstract protected function createScheduler($checkPeriod);
205
-
206
-		/**
207
-		 * Check for updates. The results are stored in the DB option specified in $optionName.
208
-		 *
209
-		 * @return Puc_v4p4_Update|null
210
-		 */
211
-		public function checkForUpdates() {
212
-			$installedVersion = $this->getInstalledVersion();
213
-			//Fail silently if we can't find the plugin/theme or read its header.
214
-			if ( $installedVersion === null ) {
215
-				$this->triggerError(
216
-					sprintf('Skipping update check for %s - installed version unknown.', $this->slug),
217
-					E_USER_WARNING
218
-				);
219
-				return null;
220
-			}
221
-
222
-			//Start collecting API errors.
223
-			$this->lastRequestApiErrors = array();
224
-			add_action('puc_api_error', array($this, 'collectApiErrors'), 10, 4);
225
-
226
-			$state = $this->updateState;
227
-			$state->setLastCheckToNow()
228
-				->setCheckedVersion($installedVersion)
229
-				->save(); //Save before checking in case something goes wrong
230
-
231
-			$state->setUpdate($this->requestUpdate());
232
-			$state->save();
233
-
234
-			//Stop collecting API errors.
235
-			remove_action('puc_api_error', array($this, 'collectApiErrors'), 10);
236
-
237
-			return $this->getUpdate();
238
-		}
239
-
240
-		/**
241
-		 * Load the update checker state from the DB.
242
-		 *
243
-		 * @return Puc_v4p4_StateStore
244
-		 */
245
-		public function getUpdateState() {
246
-			return $this->updateState->lazyLoad();
247
-		}
248
-
249
-		/**
250
-		 * Reset update checker state - i.e. last check time, cached update data and so on.
251
-		 *
252
-		 * Call this when your plugin is being uninstalled, or if you want to
253
-		 * clear the update cache.
254
-		 */
255
-		public function resetUpdateState() {
256
-			$this->updateState->delete();
257
-		}
258
-
259
-		/**
260
-		 * Get the details of the currently available update, if any.
261
-		 *
262
-		 * If no updates are available, or if the last known update version is below or equal
263
-		 * to the currently installed version, this method will return NULL.
264
-		 *
265
-		 * Uses cached update data. To retrieve update information straight from
266
-		 * the metadata URL, call requestUpdate() instead.
267
-		 *
268
-		 * @return Puc_v4p4_Update|null
269
-		 */
270
-		public function getUpdate() {
271
-			$update = $this->updateState->getUpdate();
272
-
273
-			//Is there an update available?
274
-			if ( isset($update) ) {
275
-				//Check if the update is actually newer than the currently installed version.
276
-				$installedVersion = $this->getInstalledVersion();
277
-				if ( ($installedVersion !== null) && version_compare($update->version, $installedVersion, '>') ){
278
-					return $update;
279
-				}
280
-			}
281
-			return null;
282
-		}
283
-
284
-		/**
285
-		 * Retrieve the latest update (if any) from the configured API endpoint.
286
-		 *
287
-		 * Subclasses should run the update through filterUpdateResult before returning it.
288
-		 *
289
-		 * @return Puc_v4p4_Update An instance of Update, or NULL when no updates are available.
290
-		 */
291
-		abstract public function requestUpdate();
292
-
293
-		/**
294
-		 * Filter the result of a requestUpdate() call.
295
-		 *
296
-		 * @param Puc_v4p4_Update|null $update
297
-		 * @param array|WP_Error|null $httpResult The value returned by wp_remote_get(), if any.
298
-		 * @return Puc_v4p4_Update
299
-		 */
300
-		protected function filterUpdateResult($update, $httpResult = null) {
301
-			//Let plugins/themes modify the update.
302
-			$update = apply_filters($this->getUniqueName('request_update_result'), $update, $httpResult);
303
-
304
-			if ( isset($update, $update->translations) ) {
305
-				//Keep only those translation updates that apply to this site.
306
-				$update->translations = $this->filterApplicableTranslations($update->translations);
307
-			}
308
-
309
-			return $update;
310
-		}
311
-
312
-		/**
313
-		 * Get the currently installed version of the plugin or theme.
314
-		 *
315
-		 * @return string|null Version number.
316
-		 */
317
-		abstract public function getInstalledVersion();
318
-
319
-		/**
320
-		 * Get the full path of the plugin or theme directory.
321
-		 *
322
-		 * @return string
323
-		 */
324
-		abstract public function getAbsoluteDirectoryPath();
325
-
326
-		/**
327
-		 * Trigger a PHP error, but only when $debugMode is enabled.
328
-		 *
329
-		 * @param string $message
330
-		 * @param int $errorType
331
-		 */
332
-		protected function triggerError($message, $errorType) {
333
-			if ($this->debugMode) {
334
-				trigger_error($message, $errorType);
335
-			}
336
-		}
337
-
338
-		/**
339
-		 * Get the full name of an update checker filter, action or DB entry.
340
-		 *
341
-		 * This method adds the "puc_" prefix and the "-$slug" suffix to the filter name.
342
-		 * For example, "pre_inject_update" becomes "puc_pre_inject_update-plugin-slug".
343
-		 *
344
-		 * @param string $baseTag
345
-		 * @return string
346
-		 */
347
-		public function getUniqueName($baseTag) {
348
-			$name = 'puc_' . $baseTag;
349
-			if ($this->filterSuffix !== '') {
350
-				$name .= '_' . $this->filterSuffix;
351
-			}
352
-			return $name . '-' . $this->slug;
353
-		}
354
-
355
-		/**
356
-		 * Store API errors that are generated when checking for updates.
357
-		 *
358
-		 * @internal
359
-		 * @param WP_Error $error
360
-		 * @param array|null $httpResponse
361
-		 * @param string|null $url
362
-		 * @param string|null $slug
363
-		 */
364
-		public function collectApiErrors($error, $httpResponse = null, $url = null, $slug = null) {
365
-			if ( isset($slug) && ($slug !== $this->slug) ) {
366
-				return;
367
-			}
368
-
369
-			$this->lastRequestApiErrors[] = array(
370
-				'error'        => $error,
371
-				'httpResponse' => $httpResponse,
372
-				'url'          => $url,
373
-			);
374
-		}
375
-
376
-		/**
377
-		 * @return array
378
-		 */
379
-		public function getLastRequestApiErrors() {
380
-			return $this->lastRequestApiErrors;
381
-		}
382
-
383
-		/* -------------------------------------------------------------------
5
+    abstract class Puc_v4p4_UpdateChecker {
6
+        protected $filterSuffix = '';
7
+        protected $updateTransient = '';
8
+        protected $translationType = ''; //"plugin" or "theme".
9
+
10
+        /**
11
+         * Set to TRUE to enable error reporting. Errors are raised using trigger_error()
12
+         * and should be logged to the standard PHP error log.
13
+         * @var bool
14
+         */
15
+        public $debugMode = false;
16
+
17
+        /**
18
+         * @var string Where to store the update info.
19
+         */
20
+        public $optionName = '';
21
+
22
+        /**
23
+         * @var string The URL of the metadata file.
24
+         */
25
+        public $metadataUrl = '';
26
+
27
+        /**
28
+         * @var string Plugin or theme directory name.
29
+         */
30
+        public $directoryName = '';
31
+
32
+        /**
33
+         * @var string The slug that will be used in update checker hooks and remote API requests.
34
+         * Usually matches the directory name unless the plugin/theme directory has been renamed.
35
+         */
36
+        public $slug = '';
37
+
38
+        /**
39
+         * @var Puc_v4p4_Scheduler
40
+         */
41
+        public $scheduler;
42
+
43
+        /**
44
+         * @var Puc_v4p4_UpgraderStatus
45
+         */
46
+        protected $upgraderStatus;
47
+
48
+        /**
49
+         * @var Puc_v4p4_StateStore
50
+         */
51
+        protected $updateState;
52
+
53
+        /**
54
+         * @var array List of API errors triggered during the last checkForUpdates() call.
55
+         */
56
+        protected $lastRequestApiErrors = array();
57
+
58
+        public function __construct($metadataUrl, $directoryName, $slug = null, $checkPeriod = 12, $optionName = '') {
59
+            $this->debugMode = (bool)(constant('WP_DEBUG'));
60
+            $this->metadataUrl = $metadataUrl;
61
+            $this->directoryName = $directoryName;
62
+            $this->slug = !empty($slug) ? $slug : $this->directoryName;
63
+
64
+            $this->optionName = $optionName;
65
+            if ( empty($this->optionName) ) {
66
+                //BC: Initially the library only supported plugin updates and didn't use type prefixes
67
+                //in the option name. Lets use the same prefix-less name when possible.
68
+                if ( $this->filterSuffix === '' ) {
69
+                    $this->optionName = 'external_updates-' . $this->slug;
70
+                } else {
71
+                    $this->optionName = $this->getUniqueName('external_updates');
72
+                }
73
+            }
74
+
75
+            $this->scheduler = $this->createScheduler($checkPeriod);
76
+            $this->upgraderStatus = new Puc_v4p4_UpgraderStatus();
77
+            $this->updateState = new Puc_v4p4_StateStore($this->optionName);
78
+
79
+            if ( did_action('init') ) {
80
+                $this->loadTextDomain();
81
+            } else {
82
+                add_action('init', array($this, 'loadTextDomain'));
83
+            }
84
+
85
+            $this->installHooks();
86
+        }
87
+
88
+        /**
89
+         * @internal
90
+         */
91
+        public function loadTextDomain() {
92
+            //We're not using load_plugin_textdomain() or its siblings because figuring out where
93
+            //the library is located (plugin, mu-plugin, theme, custom wp-content paths) is messy.
94
+            $domain = 'plugin-update-checker';
95
+            $locale = apply_filters(
96
+                'plugin_locale',
97
+                (is_admin() && function_exists('get_user_locale')) ? get_user_locale() : get_locale(),
98
+                $domain
99
+            );
100
+
101
+            $moFile = $domain . '-' . $locale . '.mo';
102
+            $path = realpath(dirname(__FILE__) . '/../../languages');
103
+
104
+            if ($path && file_exists($path)) {
105
+                load_textdomain($domain, $path . '/' . $moFile);
106
+            }
107
+        }
108
+
109
+        protected function installHooks() {
110
+            //Insert our update info into the update array maintained by WP.
111
+            add_filter('site_transient_' . $this->updateTransient, array($this,'injectUpdate'));
112
+
113
+            //Insert translation updates into the update list.
114
+            add_filter('site_transient_' . $this->updateTransient, array($this, 'injectTranslationUpdates'));
115
+
116
+            //Clear translation updates when WP clears the update cache.
117
+            //This needs to be done directly because the library doesn't actually remove obsolete plugin updates,
118
+            //it just hides them (see getUpdate()). We can't do that with translations - too much disk I/O.
119
+            add_action(
120
+                'delete_site_transient_' . $this->updateTransient,
121
+                array($this, 'clearCachedTranslationUpdates')
122
+            );
123
+
124
+            //Rename the update directory to be the same as the existing directory.
125
+            if ( $this->directoryName !== '.' ) {
126
+                add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3);
127
+            }
128
+
129
+            //Allow HTTP requests to the metadata URL even if it's on a local host.
130
+            add_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10, 2);
131
+
132
+            //DebugBar integration.
133
+            if ( did_action('plugins_loaded') ) {
134
+                $this->maybeInitDebugBar();
135
+            } else {
136
+                add_action('plugins_loaded', array($this, 'maybeInitDebugBar'));
137
+            }
138
+        }
139
+
140
+        /**
141
+         * Remove hooks that were added by this update checker instance.
142
+         */
143
+        protected function removeHooks() {
144
+            remove_filter('site_transient_' . $this->updateTransient, array($this,'injectUpdate'));
145
+            remove_filter('site_transient_' . $this->updateTransient, array($this, 'injectTranslationUpdates'));
146
+            remove_action(
147
+                'delete_site_transient_' . $this->updateTransient,
148
+                array($this, 'clearCachedTranslationUpdates')
149
+            );
150
+
151
+            remove_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10);
152
+            remove_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10);
153
+            remove_action('plugins_loaded', array($this, 'maybeInitDebugBar'));
154
+
155
+            remove_action('init', array($this, 'loadTextDomain'));
156
+        }
157
+
158
+        /**
159
+         * Check if the current user has the required permissions to install updates.
160
+         *
161
+         * @return bool
162
+         */
163
+        abstract public function userCanInstallUpdates();
164
+
165
+        /**
166
+         * Explicitly allow HTTP requests to the metadata URL.
167
+         *
168
+         * WordPress has a security feature where the HTTP API will reject all requests that are sent to
169
+         * another site hosted on the same server as the current site (IP match), a local host, or a local
170
+         * IP, unless the host exactly matches the current site.
171
+         *
172
+         * This feature is opt-in (at least in WP 4.4). Apparently some people enable it.
173
+         *
174
+         * That can be a problem when you're developing your plugin and you decide to host the update information
175
+         * on the same server as your test site. Update requests will mysteriously fail.
176
+         *
177
+         * We fix that by adding an exception for the metadata host.
178
+         *
179
+         * @param bool $allow
180
+         * @param string $host
181
+         * @return bool
182
+         */
183
+        public function allowMetadataHost($allow, $host) {
184
+            static $metadataHost = 0; //Using 0 instead of NULL because parse_url can return NULL.
185
+            if ( $metadataHost === 0 ) {
186
+                $metadataHost = @parse_url($this->metadataUrl, PHP_URL_HOST);
187
+            }
188
+
189
+            if ( is_string($metadataHost) && (strtolower($host) === strtolower($metadataHost)) ) {
190
+                return true;
191
+            }
192
+            return $allow;
193
+        }
194
+
195
+        /**
196
+         * Create an instance of the scheduler.
197
+         *
198
+         * This is implemented as a method to make it possible for plugins to subclass the update checker
199
+         * and substitute their own scheduler.
200
+         *
201
+         * @param int $checkPeriod
202
+         * @return Puc_v4p4_Scheduler
203
+         */
204
+        abstract protected function createScheduler($checkPeriod);
205
+
206
+        /**
207
+         * Check for updates. The results are stored in the DB option specified in $optionName.
208
+         *
209
+         * @return Puc_v4p4_Update|null
210
+         */
211
+        public function checkForUpdates() {
212
+            $installedVersion = $this->getInstalledVersion();
213
+            //Fail silently if we can't find the plugin/theme or read its header.
214
+            if ( $installedVersion === null ) {
215
+                $this->triggerError(
216
+                    sprintf('Skipping update check for %s - installed version unknown.', $this->slug),
217
+                    E_USER_WARNING
218
+                );
219
+                return null;
220
+            }
221
+
222
+            //Start collecting API errors.
223
+            $this->lastRequestApiErrors = array();
224
+            add_action('puc_api_error', array($this, 'collectApiErrors'), 10, 4);
225
+
226
+            $state = $this->updateState;
227
+            $state->setLastCheckToNow()
228
+                ->setCheckedVersion($installedVersion)
229
+                ->save(); //Save before checking in case something goes wrong
230
+
231
+            $state->setUpdate($this->requestUpdate());
232
+            $state->save();
233
+
234
+            //Stop collecting API errors.
235
+            remove_action('puc_api_error', array($this, 'collectApiErrors'), 10);
236
+
237
+            return $this->getUpdate();
238
+        }
239
+
240
+        /**
241
+         * Load the update checker state from the DB.
242
+         *
243
+         * @return Puc_v4p4_StateStore
244
+         */
245
+        public function getUpdateState() {
246
+            return $this->updateState->lazyLoad();
247
+        }
248
+
249
+        /**
250
+         * Reset update checker state - i.e. last check time, cached update data and so on.
251
+         *
252
+         * Call this when your plugin is being uninstalled, or if you want to
253
+         * clear the update cache.
254
+         */
255
+        public function resetUpdateState() {
256
+            $this->updateState->delete();
257
+        }
258
+
259
+        /**
260
+         * Get the details of the currently available update, if any.
261
+         *
262
+         * If no updates are available, or if the last known update version is below or equal
263
+         * to the currently installed version, this method will return NULL.
264
+         *
265
+         * Uses cached update data. To retrieve update information straight from
266
+         * the metadata URL, call requestUpdate() instead.
267
+         *
268
+         * @return Puc_v4p4_Update|null
269
+         */
270
+        public function getUpdate() {
271
+            $update = $this->updateState->getUpdate();
272
+
273
+            //Is there an update available?
274
+            if ( isset($update) ) {
275
+                //Check if the update is actually newer than the currently installed version.
276
+                $installedVersion = $this->getInstalledVersion();
277
+                if ( ($installedVersion !== null) && version_compare($update->version, $installedVersion, '>') ){
278
+                    return $update;
279
+                }
280
+            }
281
+            return null;
282
+        }
283
+
284
+        /**
285
+         * Retrieve the latest update (if any) from the configured API endpoint.
286
+         *
287
+         * Subclasses should run the update through filterUpdateResult before returning it.
288
+         *
289
+         * @return Puc_v4p4_Update An instance of Update, or NULL when no updates are available.
290
+         */
291
+        abstract public function requestUpdate();
292
+
293
+        /**
294
+         * Filter the result of a requestUpdate() call.
295
+         *
296
+         * @param Puc_v4p4_Update|null $update
297
+         * @param array|WP_Error|null $httpResult The value returned by wp_remote_get(), if any.
298
+         * @return Puc_v4p4_Update
299
+         */
300
+        protected function filterUpdateResult($update, $httpResult = null) {
301
+            //Let plugins/themes modify the update.
302
+            $update = apply_filters($this->getUniqueName('request_update_result'), $update, $httpResult);
303
+
304
+            if ( isset($update, $update->translations) ) {
305
+                //Keep only those translation updates that apply to this site.
306
+                $update->translations = $this->filterApplicableTranslations($update->translations);
307
+            }
308
+
309
+            return $update;
310
+        }
311
+
312
+        /**
313
+         * Get the currently installed version of the plugin or theme.
314
+         *
315
+         * @return string|null Version number.
316
+         */
317
+        abstract public function getInstalledVersion();
318
+
319
+        /**
320
+         * Get the full path of the plugin or theme directory.
321
+         *
322
+         * @return string
323
+         */
324
+        abstract public function getAbsoluteDirectoryPath();
325
+
326
+        /**
327
+         * Trigger a PHP error, but only when $debugMode is enabled.
328
+         *
329
+         * @param string $message
330
+         * @param int $errorType
331
+         */
332
+        protected function triggerError($message, $errorType) {
333
+            if ($this->debugMode) {
334
+                trigger_error($message, $errorType);
335
+            }
336
+        }
337
+
338
+        /**
339
+         * Get the full name of an update checker filter, action or DB entry.
340
+         *
341
+         * This method adds the "puc_" prefix and the "-$slug" suffix to the filter name.
342
+         * For example, "pre_inject_update" becomes "puc_pre_inject_update-plugin-slug".
343
+         *
344
+         * @param string $baseTag
345
+         * @return string
346
+         */
347
+        public function getUniqueName($baseTag) {
348
+            $name = 'puc_' . $baseTag;
349
+            if ($this->filterSuffix !== '') {
350
+                $name .= '_' . $this->filterSuffix;
351
+            }
352
+            return $name . '-' . $this->slug;
353
+        }
354
+
355
+        /**
356
+         * Store API errors that are generated when checking for updates.
357
+         *
358
+         * @internal
359
+         * @param WP_Error $error
360
+         * @param array|null $httpResponse
361
+         * @param string|null $url
362
+         * @param string|null $slug
363
+         */
364
+        public function collectApiErrors($error, $httpResponse = null, $url = null, $slug = null) {
365
+            if ( isset($slug) && ($slug !== $this->slug) ) {
366
+                return;
367
+            }
368
+
369
+            $this->lastRequestApiErrors[] = array(
370
+                'error'        => $error,
371
+                'httpResponse' => $httpResponse,
372
+                'url'          => $url,
373
+            );
374
+        }
375
+
376
+        /**
377
+         * @return array
378
+         */
379
+        public function getLastRequestApiErrors() {
380
+            return $this->lastRequestApiErrors;
381
+        }
382
+
383
+        /* -------------------------------------------------------------------
384 384
 		 * PUC filters and filter utilities
385 385
 		 * -------------------------------------------------------------------
386 386
 		 */
387 387
 
388
-		/**
389
-		 * Register a callback for one of the update checker filters.
390
-		 *
391
-		 * Identical to add_filter(), except it automatically adds the "puc_" prefix
392
-		 * and the "-$slug" suffix to the filter name. For example, "request_info_result"
393
-		 * becomes "puc_request_info_result-your_plugin_slug".
394
-		 *
395
-		 * @param string $tag
396
-		 * @param callable $callback
397
-		 * @param int $priority
398
-		 * @param int $acceptedArgs
399
-		 */
400
-		public function addFilter($tag, $callback, $priority = 10, $acceptedArgs = 1) {
401
-			add_filter($this->getUniqueName($tag), $callback, $priority, $acceptedArgs);
402
-		}
403
-
404
-		/* -------------------------------------------------------------------
388
+        /**
389
+         * Register a callback for one of the update checker filters.
390
+         *
391
+         * Identical to add_filter(), except it automatically adds the "puc_" prefix
392
+         * and the "-$slug" suffix to the filter name. For example, "request_info_result"
393
+         * becomes "puc_request_info_result-your_plugin_slug".
394
+         *
395
+         * @param string $tag
396
+         * @param callable $callback
397
+         * @param int $priority
398
+         * @param int $acceptedArgs
399
+         */
400
+        public function addFilter($tag, $callback, $priority = 10, $acceptedArgs = 1) {
401
+            add_filter($this->getUniqueName($tag), $callback, $priority, $acceptedArgs);
402
+        }
403
+
404
+        /* -------------------------------------------------------------------
405 405
 		 * Inject updates
406 406
 		 * -------------------------------------------------------------------
407 407
 		 */
408 408
 
409
-		/**
410
-		 * Insert the latest update (if any) into the update list maintained by WP.
411
-		 *
412
-		 * @param stdClass $updates Update list.
413
-		 * @return stdClass Modified update list.
414
-		 */
415
-		public function injectUpdate($updates) {
416
-			//Is there an update to insert?
417
-			$update = $this->getUpdate();
418
-
419
-			if ( !$this->shouldShowUpdates() ) {
420
-				$update = null;
421
-			}
422
-
423
-			if ( !empty($update) ) {
424
-				//Let plugins filter the update info before it's passed on to WordPress.
425
-				$update = apply_filters($this->getUniqueName('pre_inject_update'), $update);
426
-				$updates = $this->addUpdateToList($updates, $update->toWpFormat());
427
-			} else {
428
-				//Clean up any stale update info.
429
-				$updates = $this->removeUpdateFromList($updates);
430
-			}
431
-
432
-			return $updates;
433
-		}
434
-
435
-		/**
436
-		 * @param stdClass|null $updates
437
-		 * @param stdClass|array $updateToAdd
438
-		 * @return stdClass
439
-		 */
440
-		protected function addUpdateToList($updates, $updateToAdd) {
441
-			if ( !is_object($updates) ) {
442
-				$updates = new stdClass();
443
-				$updates->response = array();
444
-			}
445
-
446
-			$updates->response[$this->getUpdateListKey()] = $updateToAdd;
447
-			return $updates;
448
-		}
449
-
450
-		/**
451
-		 * @param stdClass|null $updates
452
-		 * @return stdClass|null
453
-		 */
454
-		protected function removeUpdateFromList($updates) {
455
-			if ( isset($updates, $updates->response) ) {
456
-				unset($updates->response[$this->getUpdateListKey()]);
457
-			}
458
-			return $updates;
459
-		}
460
-
461
-		/**
462
-		 * Get the key that will be used when adding updates to the update list that's maintained
463
-		 * by the WordPress core. The list is always an associative array, but the key is different
464
-		 * for plugins and themes.
465
-		 *
466
-		 * @return string
467
-		 */
468
-		abstract protected function getUpdateListKey();
469
-
470
-		/**
471
-		 * Should we show available updates?
472
-		 *
473
-		 * Usually the answer is "yes", but there are exceptions. For example, WordPress doesn't
474
-		 * support automatic updates installation for mu-plugins, so PUC usually won't show update
475
-		 * notifications in that case. See the plugin-specific subclass for details.
476
-		 *
477
-		 * Note: This method only applies to updates that are displayed (or not) in the WordPress
478
-		 * admin. It doesn't affect APIs like requestUpdate and getUpdate.
479
-		 *
480
-		 * @return bool
481
-		 */
482
-		protected function shouldShowUpdates() {
483
-			return true;
484
-		}
485
-
486
-		/* -------------------------------------------------------------------
409
+        /**
410
+         * Insert the latest update (if any) into the update list maintained by WP.
411
+         *
412
+         * @param stdClass $updates Update list.
413
+         * @return stdClass Modified update list.
414
+         */
415
+        public function injectUpdate($updates) {
416
+            //Is there an update to insert?
417
+            $update = $this->getUpdate();
418
+
419
+            if ( !$this->shouldShowUpdates() ) {
420
+                $update = null;
421
+            }
422
+
423
+            if ( !empty($update) ) {
424
+                //Let plugins filter the update info before it's passed on to WordPress.
425
+                $update = apply_filters($this->getUniqueName('pre_inject_update'), $update);
426
+                $updates = $this->addUpdateToList($updates, $update->toWpFormat());
427
+            } else {
428
+                //Clean up any stale update info.
429
+                $updates = $this->removeUpdateFromList($updates);
430
+            }
431
+
432
+            return $updates;
433
+        }
434
+
435
+        /**
436
+         * @param stdClass|null $updates
437
+         * @param stdClass|array $updateToAdd
438
+         * @return stdClass
439
+         */
440
+        protected function addUpdateToList($updates, $updateToAdd) {
441
+            if ( !is_object($updates) ) {
442
+                $updates = new stdClass();
443
+                $updates->response = array();
444
+            }
445
+
446
+            $updates->response[$this->getUpdateListKey()] = $updateToAdd;
447
+            return $updates;
448
+        }
449
+
450
+        /**
451
+         * @param stdClass|null $updates
452
+         * @return stdClass|null
453
+         */
454
+        protected function removeUpdateFromList($updates) {
455
+            if ( isset($updates, $updates->response) ) {
456
+                unset($updates->response[$this->getUpdateListKey()]);
457
+            }
458
+            return $updates;
459
+        }
460
+
461
+        /**
462
+         * Get the key that will be used when adding updates to the update list that's maintained
463
+         * by the WordPress core. The list is always an associative array, but the key is different
464
+         * for plugins and themes.
465
+         *
466
+         * @return string
467
+         */
468
+        abstract protected function getUpdateListKey();
469
+
470
+        /**
471
+         * Should we show available updates?
472
+         *
473
+         * Usually the answer is "yes", but there are exceptions. For example, WordPress doesn't
474
+         * support automatic updates installation for mu-plugins, so PUC usually won't show update
475
+         * notifications in that case. See the plugin-specific subclass for details.
476
+         *
477
+         * Note: This method only applies to updates that are displayed (or not) in the WordPress
478
+         * admin. It doesn't affect APIs like requestUpdate and getUpdate.
479
+         *
480
+         * @return bool
481
+         */
482
+        protected function shouldShowUpdates() {
483
+            return true;
484
+        }
485
+
486
+        /* -------------------------------------------------------------------
487 487
 		 * JSON-based update API
488 488
 		 * -------------------------------------------------------------------
489 489
 		 */
490 490
 
491
-		/**
492
-		 * Retrieve plugin or theme metadata from the JSON document at $this->metadataUrl.
493
-		 *
494
-		 * @param string $metaClass Parse the JSON as an instance of this class. It must have a static fromJson method.
495
-		 * @param string $filterRoot
496
-		 * @param array $queryArgs Additional query arguments.
497
-		 * @return array [Puc_v4p4_Metadata|null, array|WP_Error] A metadata instance and the value returned by wp_remote_get().
498
-		 */
499
-		protected function requestMetadata($metaClass, $filterRoot, $queryArgs = array()) {
500
-			//Query args to append to the URL. Plugins can add their own by using a filter callback (see addQueryArgFilter()).
501
-			$queryArgs = array_merge(
502
-				array(
503
-					'installed_version' => strval($this->getInstalledVersion()),
504
-					'php' => phpversion(),
505
-					'locale' => get_locale(),
506
-				),
507
-				$queryArgs
508
-			);
509
-			$queryArgs = apply_filters($this->getUniqueName($filterRoot . '_query_args'), $queryArgs);
510
-
511
-			//Various options for the wp_remote_get() call. Plugins can filter these, too.
512
-			$options = array(
513
-				'timeout' => 10, //seconds
514
-				'headers' => array(
515
-					'Accept' => 'application/json',
516
-				),
517
-			);
518
-			$options = apply_filters($this->getUniqueName($filterRoot . '_options'), $options);
519
-
520
-			//The metadata file should be at 'http://your-api.com/url/here/$slug/info.json'
521
-			$url = $this->metadataUrl;
522
-			if ( !empty($queryArgs) ){
523
-				$url = add_query_arg($queryArgs, $url);
524
-			}
525
-
526
-			$result = wp_remote_get($url, $options);
527
-
528
-			$result = apply_filters($this->getUniqueName('request_metadata_http_result'), $result, $url, $options);
491
+        /**
492
+         * Retrieve plugin or theme metadata from the JSON document at $this->metadataUrl.
493
+         *
494
+         * @param string $metaClass Parse the JSON as an instance of this class. It must have a static fromJson method.
495
+         * @param string $filterRoot
496
+         * @param array $queryArgs Additional query arguments.
497
+         * @return array [Puc_v4p4_Metadata|null, array|WP_Error] A metadata instance and the value returned by wp_remote_get().
498
+         */
499
+        protected function requestMetadata($metaClass, $filterRoot, $queryArgs = array()) {
500
+            //Query args to append to the URL. Plugins can add their own by using a filter callback (see addQueryArgFilter()).
501
+            $queryArgs = array_merge(
502
+                array(
503
+                    'installed_version' => strval($this->getInstalledVersion()),
504
+                    'php' => phpversion(),
505
+                    'locale' => get_locale(),
506
+                ),
507
+                $queryArgs
508
+            );
509
+            $queryArgs = apply_filters($this->getUniqueName($filterRoot . '_query_args'), $queryArgs);
510
+
511
+            //Various options for the wp_remote_get() call. Plugins can filter these, too.
512
+            $options = array(
513
+                'timeout' => 10, //seconds
514
+                'headers' => array(
515
+                    'Accept' => 'application/json',
516
+                ),
517
+            );
518
+            $options = apply_filters($this->getUniqueName($filterRoot . '_options'), $options);
519
+
520
+            //The metadata file should be at 'http://your-api.com/url/here/$slug/info.json'
521
+            $url = $this->metadataUrl;
522
+            if ( !empty($queryArgs) ){
523
+                $url = add_query_arg($queryArgs, $url);
524
+            }
525
+
526
+            $result = wp_remote_get($url, $options);
527
+
528
+            $result = apply_filters($this->getUniqueName('request_metadata_http_result'), $result, $url, $options);
529 529
 			
530
-			//Try to parse the response
531
-			$status = $this->validateApiResponse($result);
532
-			$metadata = null;
533
-			if ( !is_wp_error($status) ){
534
-				$metadata = call_user_func(array($metaClass, 'fromJson'), $result['body']);
535
-			} else {
536
-				do_action('puc_api_error', $status, $result, $url, $this->slug);
537
-				$this->triggerError(
538
-					sprintf('The URL %s does not point to a valid metadata file. ', $url)
539
-					. $status->get_error_message(),
540
-					E_USER_WARNING
541
-				);
542
-			}
543
-
544
-			return array($metadata, $result);
545
-		}
546
-
547
-		/**
548
-		 * Check if $result is a successful update API response.
549
-		 *
550
-		 * @param array|WP_Error $result
551
-		 * @return true|WP_Error
552
-		 */
553
-		protected function validateApiResponse($result) {
554
-			if ( is_wp_error($result) ) { /** @var WP_Error $result */
555
-				return new WP_Error($result->get_error_code(), 'WP HTTP Error: ' . $result->get_error_message());
556
-			}
557
-
558
-			if ( !isset($result['response']['code']) ) {
559
-				return new WP_Error(
560
-					'puc_no_response_code',
561
-					'wp_remote_get() returned an unexpected result.'
562
-				);
563
-			}
564
-
565
-			if ( $result['response']['code'] !== 200 ) {
566
-				return new WP_Error(
567
-					'puc_unexpected_response_code',
568
-					'HTTP response code is ' . $result['response']['code'] . ' (expected: 200)'
569
-				);
570
-			}
571
-
572
-			if ( empty($result['body']) ) {
573
-				return new WP_Error('puc_empty_response', 'The metadata file appears to be empty.');
574
-			}
575
-
576
-			return true;
577
-		}
578
-
579
-		/* -------------------------------------------------------------------
530
+            //Try to parse the response
531
+            $status = $this->validateApiResponse($result);
532
+            $metadata = null;
533
+            if ( !is_wp_error($status) ){
534
+                $metadata = call_user_func(array($metaClass, 'fromJson'), $result['body']);
535
+            } else {
536
+                do_action('puc_api_error', $status, $result, $url, $this->slug);
537
+                $this->triggerError(
538
+                    sprintf('The URL %s does not point to a valid metadata file. ', $url)
539
+                    . $status->get_error_message(),
540
+                    E_USER_WARNING
541
+                );
542
+            }
543
+
544
+            return array($metadata, $result);
545
+        }
546
+
547
+        /**
548
+         * Check if $result is a successful update API response.
549
+         *
550
+         * @param array|WP_Error $result
551
+         * @return true|WP_Error
552
+         */
553
+        protected function validateApiResponse($result) {
554
+            if ( is_wp_error($result) ) { /** @var WP_Error $result */
555
+                return new WP_Error($result->get_error_code(), 'WP HTTP Error: ' . $result->get_error_message());
556
+            }
557
+
558
+            if ( !isset($result['response']['code']) ) {
559
+                return new WP_Error(
560
+                    'puc_no_response_code',
561
+                    'wp_remote_get() returned an unexpected result.'
562
+                );
563
+            }
564
+
565
+            if ( $result['response']['code'] !== 200 ) {
566
+                return new WP_Error(
567
+                    'puc_unexpected_response_code',
568
+                    'HTTP response code is ' . $result['response']['code'] . ' (expected: 200)'
569
+                );
570
+            }
571
+
572
+            if ( empty($result['body']) ) {
573
+                return new WP_Error('puc_empty_response', 'The metadata file appears to be empty.');
574
+            }
575
+
576
+            return true;
577
+        }
578
+
579
+        /* -------------------------------------------------------------------
580 580
 		 * Language packs / Translation updates
581 581
 		 * -------------------------------------------------------------------
582 582
 		 */
583 583
 
584
-		/**
585
-		 * Filter a list of translation updates and return a new list that contains only updates
586
-		 * that apply to the current site.
587
-		 *
588
-		 * @param array $translations
589
-		 * @return array
590
-		 */
591
-		protected function filterApplicableTranslations($translations) {
592
-			$languages = array_flip(array_values(get_available_languages()));
593
-			$installedTranslations = $this->getInstalledTranslations();
594
-
595
-			$applicableTranslations = array();
596
-			foreach($translations as $translation) {
597
-				//Does it match one of the available core languages?
598
-				$isApplicable = array_key_exists($translation->language, $languages);
599
-				//Is it more recent than an already-installed translation?
600
-				if ( isset($installedTranslations[$translation->language]) ) {
601
-					$updateTimestamp = strtotime($translation->updated);
602
-					$installedTimestamp = strtotime($installedTranslations[$translation->language]['PO-Revision-Date']);
603
-					$isApplicable = $updateTimestamp > $installedTimestamp;
604
-				}
605
-
606
-				if ( $isApplicable ) {
607
-					$applicableTranslations[] = $translation;
608
-				}
609
-			}
610
-
611
-			return $applicableTranslations;
612
-		}
613
-
614
-		/**
615
-		 * Get a list of installed translations for this plugin or theme.
616
-		 *
617
-		 * @return array
618
-		 */
619
-		protected function getInstalledTranslations() {
620
-			if ( !function_exists('wp_get_installed_translations') ) {
621
-				return array();
622
-			}
623
-			$installedTranslations = wp_get_installed_translations($this->translationType . 's');
624
-			if ( isset($installedTranslations[$this->directoryName]) ) {
625
-				$installedTranslations = $installedTranslations[$this->directoryName];
626
-			} else {
627
-				$installedTranslations = array();
628
-			}
629
-			return $installedTranslations;
630
-		}
631
-
632
-		/**
633
-		 * Insert translation updates into the list maintained by WordPress.
634
-		 *
635
-		 * @param stdClass $updates
636
-		 * @return stdClass
637
-		 */
638
-		public function injectTranslationUpdates($updates) {
639
-			$translationUpdates = $this->getTranslationUpdates();
640
-			if ( empty($translationUpdates) ) {
641
-				return $updates;
642
-			}
643
-
644
-			//Being defensive.
645
-			if ( !is_object($updates) ) {
646
-				$updates = new stdClass();
647
-			}
648
-			if ( !isset($updates->translations) ) {
649
-				$updates->translations = array();
650
-			}
651
-
652
-			//In case there's a name collision with a plugin or theme hosted on wordpress.org,
653
-			//remove any preexisting updates that match our thing.
654
-			$updates->translations = array_values(array_filter(
655
-				$updates->translations,
656
-				array($this, 'isNotMyTranslation')
657
-			));
658
-
659
-			//Add our updates to the list.
660
-			foreach($translationUpdates as $update) {
661
-				$convertedUpdate = array_merge(
662
-					array(
663
-						'type' => $this->translationType,
664
-						'slug' => $this->directoryName,
665
-						'autoupdate' => 0,
666
-						//AFAICT, WordPress doesn't actually use the "version" field for anything.
667
-						//But lets make sure it's there, just in case.
668
-						'version' => isset($update->version) ? $update->version : ('1.' . strtotime($update->updated)),
669
-					),
670
-					(array)$update
671
-				);
672
-
673
-				$updates->translations[] = $convertedUpdate;
674
-			}
675
-
676
-			return $updates;
677
-		}
678
-
679
-		/**
680
-		 * Get a list of available translation updates.
681
-		 *
682
-		 * This method will return an empty array if there are no updates.
683
-		 * Uses cached update data.
684
-		 *
685
-		 * @return array
686
-		 */
687
-		public function getTranslationUpdates() {
688
-			return $this->updateState->getTranslations();
689
-		}
690
-
691
-		/**
692
-		 * Remove all cached translation updates.
693
-		 *
694
-		 * @see wp_clean_update_cache
695
-		 */
696
-		public function clearCachedTranslationUpdates() {
697
-			$this->updateState->setTranslations(array());
698
-		}
699
-
700
-		/**
701
-		 * Filter callback. Keeps only translations that *don't* match this plugin or theme.
702
-		 *
703
-		 * @param array $translation
704
-		 * @return bool
705
-		 */
706
-		protected function isNotMyTranslation($translation) {
707
-			$isMatch = isset($translation['type'], $translation['slug'])
708
-				&& ($translation['type'] === $this->translationType)
709
-				&& ($translation['slug'] === $this->directoryName);
710
-
711
-			return !$isMatch;
712
-		}
713
-
714
-		/* -------------------------------------------------------------------
584
+        /**
585
+         * Filter a list of translation updates and return a new list that contains only updates
586
+         * that apply to the current site.
587
+         *
588
+         * @param array $translations
589
+         * @return array
590
+         */
591
+        protected function filterApplicableTranslations($translations) {
592
+            $languages = array_flip(array_values(get_available_languages()));
593
+            $installedTranslations = $this->getInstalledTranslations();
594
+
595
+            $applicableTranslations = array();
596
+            foreach($translations as $translation) {
597
+                //Does it match one of the available core languages?
598
+                $isApplicable = array_key_exists($translation->language, $languages);
599
+                //Is it more recent than an already-installed translation?
600
+                if ( isset($installedTranslations[$translation->language]) ) {
601
+                    $updateTimestamp = strtotime($translation->updated);
602
+                    $installedTimestamp = strtotime($installedTranslations[$translation->language]['PO-Revision-Date']);
603
+                    $isApplicable = $updateTimestamp > $installedTimestamp;
604
+                }
605
+
606
+                if ( $isApplicable ) {
607
+                    $applicableTranslations[] = $translation;
608
+                }
609
+            }
610
+
611
+            return $applicableTranslations;
612
+        }
613
+
614
+        /**
615
+         * Get a list of installed translations for this plugin or theme.
616
+         *
617
+         * @return array
618
+         */
619
+        protected function getInstalledTranslations() {
620
+            if ( !function_exists('wp_get_installed_translations') ) {
621
+                return array();
622
+            }
623
+            $installedTranslations = wp_get_installed_translations($this->translationType . 's');
624
+            if ( isset($installedTranslations[$this->directoryName]) ) {
625
+                $installedTranslations = $installedTranslations[$this->directoryName];
626
+            } else {
627
+                $installedTranslations = array();
628
+            }
629
+            return $installedTranslations;
630
+        }
631
+
632
+        /**
633
+         * Insert translation updates into the list maintained by WordPress.
634
+         *
635
+         * @param stdClass $updates
636
+         * @return stdClass
637
+         */
638
+        public function injectTranslationUpdates($updates) {
639
+            $translationUpdates = $this->getTranslationUpdates();
640
+            if ( empty($translationUpdates) ) {
641
+                return $updates;
642
+            }
643
+
644
+            //Being defensive.
645
+            if ( !is_object($updates) ) {
646
+                $updates = new stdClass();
647
+            }
648
+            if ( !isset($updates->translations) ) {
649
+                $updates->translations = array();
650
+            }
651
+
652
+            //In case there's a name collision with a plugin or theme hosted on wordpress.org,
653
+            //remove any preexisting updates that match our thing.
654
+            $updates->translations = array_values(array_filter(
655
+                $updates->translations,
656
+                array($this, 'isNotMyTranslation')
657
+            ));
658
+
659
+            //Add our updates to the list.
660
+            foreach($translationUpdates as $update) {
661
+                $convertedUpdate = array_merge(
662
+                    array(
663
+                        'type' => $this->translationType,
664
+                        'slug' => $this->directoryName,
665
+                        'autoupdate' => 0,
666
+                        //AFAICT, WordPress doesn't actually use the "version" field for anything.
667
+                        //But lets make sure it's there, just in case.
668
+                        'version' => isset($update->version) ? $update->version : ('1.' . strtotime($update->updated)),
669
+                    ),
670
+                    (array)$update
671
+                );
672
+
673
+                $updates->translations[] = $convertedUpdate;
674
+            }
675
+
676
+            return $updates;
677
+        }
678
+
679
+        /**
680
+         * Get a list of available translation updates.
681
+         *
682
+         * This method will return an empty array if there are no updates.
683
+         * Uses cached update data.
684
+         *
685
+         * @return array
686
+         */
687
+        public function getTranslationUpdates() {
688
+            return $this->updateState->getTranslations();
689
+        }
690
+
691
+        /**
692
+         * Remove all cached translation updates.
693
+         *
694
+         * @see wp_clean_update_cache
695
+         */
696
+        public function clearCachedTranslationUpdates() {
697
+            $this->updateState->setTranslations(array());
698
+        }
699
+
700
+        /**
701
+         * Filter callback. Keeps only translations that *don't* match this plugin or theme.
702
+         *
703
+         * @param array $translation
704
+         * @return bool
705
+         */
706
+        protected function isNotMyTranslation($translation) {
707
+            $isMatch = isset($translation['type'], $translation['slug'])
708
+                && ($translation['type'] === $this->translationType)
709
+                && ($translation['slug'] === $this->directoryName);
710
+
711
+            return !$isMatch;
712
+        }
713
+
714
+        /* -------------------------------------------------------------------
715 715
 		 * Fix directory name when installing updates
716 716
 		 * -------------------------------------------------------------------
717 717
 		 */
718 718
 
719
-		/**
720
-		 * Rename the update directory to match the existing plugin/theme directory.
721
-		 *
722
-		 * When WordPress installs a plugin or theme update, it assumes that the ZIP file will contain
723
-		 * exactly one directory, and that the directory name will be the same as the directory where
724
-		 * the plugin or theme is currently installed.
725
-		 *
726
-		 * GitHub and other repositories provide ZIP downloads, but they often use directory names like
727
-		 * "project-branch" or "project-tag-hash". We need to change the name to the actual plugin folder.
728
-		 *
729
-		 * This is a hook callback. Don't call it from a plugin.
730
-		 *
731
-		 * @access protected
732
-		 *
733
-		 * @param string $source The directory to copy to /wp-content/plugins or /wp-content/themes. Usually a subdirectory of $remoteSource.
734
-		 * @param string $remoteSource WordPress has extracted the update to this directory.
735
-		 * @param WP_Upgrader $upgrader
736
-		 * @return string|WP_Error
737
-		 */
738
-		public function fixDirectoryName($source, $remoteSource, $upgrader) {
739
-			global $wp_filesystem;
740
-			/** @var WP_Filesystem_Base $wp_filesystem */
741
-
742
-			//Basic sanity checks.
743
-			if ( !isset($source, $remoteSource, $upgrader, $upgrader->skin, $wp_filesystem) ) {
744
-				return $source;
745
-			}
746
-
747
-			//If WordPress is upgrading anything other than our plugin/theme, leave the directory name unchanged.
748
-			if ( !$this->isBeingUpgraded($upgrader) ) {
749
-				return $source;
750
-			}
751
-
752
-			//Rename the source to match the existing directory.
753
-			$correctedSource = trailingslashit($remoteSource) . $this->directoryName . '/';
754
-			if ( $source !== $correctedSource ) {
755
-				//The update archive should contain a single directory that contains the rest of plugin/theme files.
756
-				//Otherwise, WordPress will try to copy the entire working directory ($source == $remoteSource).
757
-				//We can't rename $remoteSource because that would break WordPress code that cleans up temporary files
758
-				//after update.
759
-				if ( $this->isBadDirectoryStructure($remoteSource) ) {
760
-					return new WP_Error(
761
-						'puc-incorrect-directory-structure',
762
-						sprintf(
763
-							'The directory structure of the update is incorrect. All files should be inside ' .
764
-							'a directory named <span class="code">%s</span>, not at the root of the ZIP archive.',
765
-							htmlentities($this->slug)
766
-						)
767
-					);
768
-				}
769
-
770
-				/** @var WP_Upgrader_Skin $upgrader ->skin */
771
-				$upgrader->skin->feedback(sprintf(
772
-					'Renaming %s to %s&#8230;',
773
-					'<span class="code">' . basename($source) . '</span>',
774
-					'<span class="code">' . $this->directoryName . '</span>'
775
-				));
776
-
777
-				if ( $wp_filesystem->move($source, $correctedSource, true) ) {
778
-					$upgrader->skin->feedback('Directory successfully renamed.');
779
-					return $correctedSource;
780
-				} else {
781
-					return new WP_Error(
782
-						'puc-rename-failed',
783
-						'Unable to rename the update to match the existing directory.'
784
-					);
785
-				}
786
-			}
787
-
788
-			return $source;
789
-		}
790
-
791
-		/**
792
-		 * Is there an update being installed right now, for this plugin or theme?
793
-		 *
794
-		 * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
795
-		 * @return bool
796
-		 */
797
-		abstract public function isBeingUpgraded($upgrader = null);
798
-
799
-		/**
800
-		 * Check for incorrect update directory structure. An update must contain a single directory,
801
-		 * all other files should be inside that directory.
802
-		 *
803
-		 * @param string $remoteSource Directory path.
804
-		 * @return bool
805
-		 */
806
-		protected function isBadDirectoryStructure($remoteSource) {
807
-			global $wp_filesystem;
808
-			/** @var WP_Filesystem_Base $wp_filesystem */
809
-
810
-			$sourceFiles = $wp_filesystem->dirlist($remoteSource);
811
-			if ( is_array($sourceFiles) ) {
812
-				$sourceFiles = array_keys($sourceFiles);
813
-				$firstFilePath = trailingslashit($remoteSource) . $sourceFiles[0];
814
-				return (count($sourceFiles) > 1) || (!$wp_filesystem->is_dir($firstFilePath));
815
-			}
816
-
817
-			//Assume it's fine.
818
-			return false;
819
-		}
820
-
821
-		/* -------------------------------------------------------------------
719
+        /**
720
+         * Rename the update directory to match the existing plugin/theme directory.
721
+         *
722
+         * When WordPress installs a plugin or theme update, it assumes that the ZIP file will contain
723
+         * exactly one directory, and that the directory name will be the same as the directory where
724
+         * the plugin or theme is currently installed.
725
+         *
726
+         * GitHub and other repositories provide ZIP downloads, but they often use directory names like
727
+         * "project-branch" or "project-tag-hash". We need to change the name to the actual plugin folder.
728
+         *
729
+         * This is a hook callback. Don't call it from a plugin.
730
+         *
731
+         * @access protected
732
+         *
733
+         * @param string $source The directory to copy to /wp-content/plugins or /wp-content/themes. Usually a subdirectory of $remoteSource.
734
+         * @param string $remoteSource WordPress has extracted the update to this directory.
735
+         * @param WP_Upgrader $upgrader
736
+         * @return string|WP_Error
737
+         */
738
+        public function fixDirectoryName($source, $remoteSource, $upgrader) {
739
+            global $wp_filesystem;
740
+            /** @var WP_Filesystem_Base $wp_filesystem */
741
+
742
+            //Basic sanity checks.
743
+            if ( !isset($source, $remoteSource, $upgrader, $upgrader->skin, $wp_filesystem) ) {
744
+                return $source;
745
+            }
746
+
747
+            //If WordPress is upgrading anything other than our plugin/theme, leave the directory name unchanged.
748
+            if ( !$this->isBeingUpgraded($upgrader) ) {
749
+                return $source;
750
+            }
751
+
752
+            //Rename the source to match the existing directory.
753
+            $correctedSource = trailingslashit($remoteSource) . $this->directoryName . '/';
754
+            if ( $source !== $correctedSource ) {
755
+                //The update archive should contain a single directory that contains the rest of plugin/theme files.
756
+                //Otherwise, WordPress will try to copy the entire working directory ($source == $remoteSource).
757
+                //We can't rename $remoteSource because that would break WordPress code that cleans up temporary files
758
+                //after update.
759
+                if ( $this->isBadDirectoryStructure($remoteSource) ) {
760
+                    return new WP_Error(
761
+                        'puc-incorrect-directory-structure',
762
+                        sprintf(
763
+                            'The directory structure of the update is incorrect. All files should be inside ' .
764
+                            'a directory named <span class="code">%s</span>, not at the root of the ZIP archive.',
765
+                            htmlentities($this->slug)
766
+                        )
767
+                    );
768
+                }
769
+
770
+                /** @var WP_Upgrader_Skin $upgrader ->skin */
771
+                $upgrader->skin->feedback(sprintf(
772
+                    'Renaming %s to %s&#8230;',
773
+                    '<span class="code">' . basename($source) . '</span>',
774
+                    '<span class="code">' . $this->directoryName . '</span>'
775
+                ));
776
+
777
+                if ( $wp_filesystem->move($source, $correctedSource, true) ) {
778
+                    $upgrader->skin->feedback('Directory successfully renamed.');
779
+                    return $correctedSource;
780
+                } else {
781
+                    return new WP_Error(
782
+                        'puc-rename-failed',
783
+                        'Unable to rename the update to match the existing directory.'
784
+                    );
785
+                }
786
+            }
787
+
788
+            return $source;
789
+        }
790
+
791
+        /**
792
+         * Is there an update being installed right now, for this plugin or theme?
793
+         *
794
+         * @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
795
+         * @return bool
796
+         */
797
+        abstract public function isBeingUpgraded($upgrader = null);
798
+
799
+        /**
800
+         * Check for incorrect update directory structure. An update must contain a single directory,
801
+         * all other files should be inside that directory.
802
+         *
803
+         * @param string $remoteSource Directory path.
804
+         * @return bool
805
+         */
806
+        protected function isBadDirectoryStructure($remoteSource) {
807
+            global $wp_filesystem;
808
+            /** @var WP_Filesystem_Base $wp_filesystem */
809
+
810
+            $sourceFiles = $wp_filesystem->dirlist($remoteSource);
811
+            if ( is_array($sourceFiles) ) {
812
+                $sourceFiles = array_keys($sourceFiles);
813
+                $firstFilePath = trailingslashit($remoteSource) . $sourceFiles[0];
814
+                return (count($sourceFiles) > 1) || (!$wp_filesystem->is_dir($firstFilePath));
815
+            }
816
+
817
+            //Assume it's fine.
818
+            return false;
819
+        }
820
+
821
+        /* -------------------------------------------------------------------
822 822
 		 * File header parsing
823 823
 		 * -------------------------------------------------------------------
824 824
 		 */
825 825
 
826
-		/**
827
-		 * Parse plugin or theme metadata from the header comment.
828
-		 *
829
-		 * This is basically a simplified version of the get_file_data() function from /wp-includes/functions.php.
830
-		 * It's intended as a utility for subclasses that detect updates by parsing files in a VCS.
831
-		 *
832
-		 * @param string|null $content File contents.
833
-		 * @return string[]
834
-		 */
835
-		public function getFileHeader($content) {
836
-			$content = (string) $content;
837
-
838
-			//WordPress only looks at the first 8 KiB of the file, so we do the same.
839
-			$content = substr($content, 0, 8192);
840
-			//Normalize line endings.
841
-			$content = str_replace("\r", "\n", $content);
842
-
843
-			$headers = $this->getHeaderNames();
844
-			$results = array();
845
-			foreach ($headers as $field => $name) {
846
-				$success = preg_match('/^[ \t\/*#@]*' . preg_quote($name, '/') . ':(.*)$/mi', $content, $matches);
847
-
848
-				if ( ($success === 1) && $matches[1] ) {
849
-					$value = $matches[1];
850
-					if ( function_exists('_cleanup_header_comment') ) {
851
-						$value = _cleanup_header_comment($value);
852
-					}
853
-					$results[$field] = $value;
854
-				} else {
855
-					$results[$field] = '';
856
-				}
857
-			}
858
-
859
-			return $results;
860
-		}
861
-
862
-		/**
863
-		 * @return array Format: ['HeaderKey' => 'Header Name']
864
-		 */
865
-		abstract protected function getHeaderNames();
866
-
867
-		/* -------------------------------------------------------------------
826
+        /**
827
+         * Parse plugin or theme metadata from the header comment.
828
+         *
829
+         * This is basically a simplified version of the get_file_data() function from /wp-includes/functions.php.
830
+         * It's intended as a utility for subclasses that detect updates by parsing files in a VCS.
831
+         *
832
+         * @param string|null $content File contents.
833
+         * @return string[]
834
+         */
835
+        public function getFileHeader($content) {
836
+            $content = (string) $content;
837
+
838
+            //WordPress only looks at the first 8 KiB of the file, so we do the same.
839
+            $content = substr($content, 0, 8192);
840
+            //Normalize line endings.
841
+            $content = str_replace("\r", "\n", $content);
842
+
843
+            $headers = $this->getHeaderNames();
844
+            $results = array();
845
+            foreach ($headers as $field => $name) {
846
+                $success = preg_match('/^[ \t\/*#@]*' . preg_quote($name, '/') . ':(.*)$/mi', $content, $matches);
847
+
848
+                if ( ($success === 1) && $matches[1] ) {
849
+                    $value = $matches[1];
850
+                    if ( function_exists('_cleanup_header_comment') ) {
851
+                        $value = _cleanup_header_comment($value);
852
+                    }
853
+                    $results[$field] = $value;
854
+                } else {
855
+                    $results[$field] = '';
856
+                }
857
+            }
858
+
859
+            return $results;
860
+        }
861
+
862
+        /**
863
+         * @return array Format: ['HeaderKey' => 'Header Name']
864
+         */
865
+        abstract protected function getHeaderNames();
866
+
867
+        /* -------------------------------------------------------------------
868 868
 		 * DebugBar integration
869 869
 		 * -------------------------------------------------------------------
870 870
 		 */
871 871
 
872
-		/**
873
-		 * Initialize the update checker Debug Bar plugin/add-on thingy.
874
-		 */
875
-		public function maybeInitDebugBar() {
876
-			if ( class_exists('Debug_Bar', false) && file_exists(dirname(__FILE__ . '/DebugBar')) ) {
877
-				$this->createDebugBarExtension();
878
-			}
879
-		}
880
-
881
-		protected function createDebugBarExtension() {
882
-			return new Puc_v4p4_DebugBar_Extension($this);
883
-		}
884
-
885
-		/**
886
-		 * Display additional configuration details in the Debug Bar panel.
887
-		 *
888
-		 * @param Puc_v4p4_DebugBar_Panel $panel
889
-		 */
890
-		public function onDisplayConfiguration($panel) {
891
-			//Do nothing. Subclasses can use this to add additional info to the panel.
892
-		}
893
-
894
-	}
872
+        /**
873
+         * Initialize the update checker Debug Bar plugin/add-on thingy.
874
+         */
875
+        public function maybeInitDebugBar() {
876
+            if ( class_exists('Debug_Bar', false) && file_exists(dirname(__FILE__ . '/DebugBar')) ) {
877
+                $this->createDebugBarExtension();
878
+            }
879
+        }
880
+
881
+        protected function createDebugBarExtension() {
882
+            return new Puc_v4p4_DebugBar_Extension($this);
883
+        }
884
+
885
+        /**
886
+         * Display additional configuration details in the Debug Bar panel.
887
+         *
888
+         * @param Puc_v4p4_DebugBar_Panel $panel
889
+         */
890
+        public function onDisplayConfiguration($panel) {
891
+            //Do nothing. Subclasses can use this to add additional info to the panel.
892
+        }
893
+
894
+    }
895 895
 
896 896
 endif;
Please login to merge, or discard this patch.
classes/external/php/plugin-update-checker/Puc/v4/Factory.php 1 patch
Indentation   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -1,6 +1,6 @@
 block discarded – undo
1 1
 <?php
2 2
 if ( !class_exists('Puc_v4_Factory', false) ):
3 3
 
4
-	class Puc_v4_Factory extends Puc_v4p4_Factory { }
4
+    class Puc_v4_Factory extends Puc_v4p4_Factory { }
5 5
 
6 6
 endif;
7 7
\ No newline at end of file
Please login to merge, or discard this patch.