Version Description
- [New] For those who can't manage to automatically update their local copy of our premium add-ons, we've added a link to manually download the zip file. You'll find it under the Plugins screen, once you've entered and saved the corresponding license key under Slimstat > Add-ons.
- [Update] The Update Checker library has been updated to version 4.4.
- [Fix] The world map was not being displayed correctly if no data points were available for the selected time range (thank you, Michel).
Download this release
Release Info
Developer | coolmann |
Plugin | Slimstat Analytics |
Version | 4.7.5.3 |
Comparing to | |
See all releases |
Code changes from version 4.7.5.2 to 4.7.5.3
- admin/update-checker/Puc/v4/Factory.php +1 -266
- admin/update-checker/Puc/{v4p2 → v4p4}/Autoloader.php +2 -2
- admin/update-checker/Puc/v4p4/DebugBar/Extension.php +177 -0
- admin/update-checker/Puc/v4p4/DebugBar/Panel.php +165 -0
- admin/update-checker/Puc/v4p4/DebugBar/PluginExtension.php +33 -0
- admin/update-checker/Puc/v4p4/DebugBar/PluginPanel.php +38 -0
- admin/update-checker/Puc/v4p4/DebugBar/ThemePanel.php +21 -0
- admin/update-checker/Puc/v4p4/Factory.php +292 -0
- admin/update-checker/Puc/{v4p2 → v4p4}/Metadata.php +6 -6
- admin/update-checker/Puc/v4p4/OAuthSignature.php +88 -0
- admin/update-checker/Puc/{v4p2 → v4p4}/Plugin/Info.php +7 -4
- admin/update-checker/Puc/{v4p2 → v4p4}/Plugin/Update.php +30 -11
- admin/update-checker/Puc/{v4p2 → v4p4}/Plugin/UpdateChecker.php +214 -16
- admin/update-checker/Puc/{v4p2 → v4p4}/Scheduler.php +5 -5
- admin/update-checker/Puc/{v4p2 → v4p4}/StateStore.php +9 -9
- admin/update-checker/Puc/{v4p2 → v4p4}/Theme/Update.php +4 -4
- admin/update-checker/Puc/{v4p2 → v4p4}/Theme/UpdateChecker.php +21 -7
- admin/update-checker/Puc/{v4p2 → v4p4}/Update.php +2 -2
- admin/update-checker/Puc/{v4p2 → v4p4}/UpdateChecker.php +119 -15
- admin/update-checker/Puc/{v4p2 → v4p4}/UpgraderStatus.php +2 -2
- admin/update-checker/Puc/{v4p2 → v4p4}/Utils.php +2 -2
- admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/Api.php +68 -9
- admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/BaseChecker.php +7 -2
- admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/BitBucketApi.php +18 -13
- admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/GitHubApi.php +133 -22
- admin/update-checker/Puc/v4p4/Vcs/GitLabApi.php +274 -0
- admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/PluginUpdateChecker.php +35 -11
- admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/Reference.php +2 -2
- admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/ThemeUpdateChecker.php +33 -9
- admin/update-checker/license.txt +2 -2
- admin/update-checker/plugin-update-checker.php +11 -9
- admin/update-checker/vendor/ParsedownLegacy.php +1535 -0
- admin/view/addons.php +1 -1
- admin/view/wp-slimstat-reports.php +2 -2
- admin/wp-slimstat-admin.php +2 -2
- readme.txt +6 -1
- wp-slimstat.php +31 -5
admin/update-checker/Puc/v4/Factory.php
CHANGED
@@ -1,271 +1,6 @@
|
|
1 |
<?php
|
2 |
if ( !class_exists('Puc_v4_Factory', false) ):
|
3 |
|
4 |
-
|
5 |
-
* A factory that builds update checker instances.
|
6 |
-
*
|
7 |
-
* When multiple versions of the same class have been loaded (e.g. PluginUpdateChecker 4.0
|
8 |
-
* and 4.1), this factory will always use the latest available minor version. Register class
|
9 |
-
* versions by calling {@link PucFactory::addVersion()}.
|
10 |
-
*
|
11 |
-
* At the moment it can only build instances of the UpdateChecker class. Other classes are
|
12 |
-
* intended mainly for internal use and refer directly to specific implementations.
|
13 |
-
*/
|
14 |
-
class Puc_v4_Factory {
|
15 |
-
protected static $classVersions = array();
|
16 |
-
protected static $sorted = false;
|
17 |
-
|
18 |
-
protected static $myMajorVersion = '';
|
19 |
-
protected static $latestCompatibleVersion = '';
|
20 |
-
|
21 |
-
/**
|
22 |
-
* Create a new instance of the update checker.
|
23 |
-
*
|
24 |
-
* This method automatically detects if you're using it for a plugin or a theme and chooses
|
25 |
-
* the appropriate implementation for your update source (JSON file, GitHub, BitBucket, etc).
|
26 |
-
*
|
27 |
-
* @see Puc_v4p2_UpdateChecker::__construct
|
28 |
-
*
|
29 |
-
* @param string $metadataUrl The URL of the metadata file, a GitHub repository, or another supported update source.
|
30 |
-
* @param string $fullPath Full path to the main plugin file or to the theme directory.
|
31 |
-
* @param string $slug Custom slug. Defaults to the name of the main plugin file or the theme directory.
|
32 |
-
* @param int $checkPeriod How often to check for updates (in hours).
|
33 |
-
* @param string $optionName Where to store book-keeping info about update checks.
|
34 |
-
* @param string $muPluginFile The plugin filename relative to the mu-plugins directory.
|
35 |
-
* @return Puc_v4p2_Plugin_UpdateChecker|Puc_v4p2_Theme_UpdateChecker|Puc_v4p2_Vcs_BaseChecker
|
36 |
-
*/
|
37 |
-
public static function buildUpdateChecker($metadataUrl, $fullPath, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') {
|
38 |
-
$fullPath = wp_normalize_path($fullPath);
|
39 |
-
$id = null;
|
40 |
-
|
41 |
-
//Plugin or theme?
|
42 |
-
$themeDirectory = self::getThemeDirectoryName($fullPath);
|
43 |
-
if ( self::isPluginFile($fullPath) ) {
|
44 |
-
$type = 'Plugin';
|
45 |
-
$id = $fullPath;
|
46 |
-
} else if ( $themeDirectory !== null ) {
|
47 |
-
$type = 'Theme';
|
48 |
-
$id = $themeDirectory;
|
49 |
-
} else {
|
50 |
-
throw new RuntimeException(sprintf(
|
51 |
-
'The update checker cannot determine if "%s" is a plugin or a theme. ' .
|
52 |
-
'This is a bug. Please contact the PUC developer.',
|
53 |
-
htmlentities($fullPath)
|
54 |
-
));
|
55 |
-
}
|
56 |
-
|
57 |
-
//Which hosting service does the URL point to?
|
58 |
-
$service = self::getVcsService($metadataUrl);
|
59 |
-
|
60 |
-
$apiClass = null;
|
61 |
-
if ( empty($service) ) {
|
62 |
-
//The default is to get update information from a remote JSON file.
|
63 |
-
$checkerClass = $type . '_UpdateChecker';
|
64 |
-
} else {
|
65 |
-
//You can also use a VCS repository like GitHub.
|
66 |
-
$checkerClass = 'Vcs_' . $type . 'UpdateChecker';
|
67 |
-
$apiClass = $service . 'Api';
|
68 |
-
}
|
69 |
-
|
70 |
-
$checkerClass = self::getCompatibleClassVersion($checkerClass);
|
71 |
-
if ( $checkerClass === null ) {
|
72 |
-
trigger_error(
|
73 |
-
sprintf(
|
74 |
-
'PUC %s does not support updates for %ss %s',
|
75 |
-
htmlentities(self::$latestCompatibleVersion),
|
76 |
-
strtolower($type),
|
77 |
-
$service ? ('hosted on ' . htmlentities($service)) : 'using JSON metadata'
|
78 |
-
),
|
79 |
-
E_USER_ERROR
|
80 |
-
);
|
81 |
-
return null;
|
82 |
-
}
|
83 |
-
|
84 |
-
if ( !isset($apiClass) ) {
|
85 |
-
//Plain old update checker.
|
86 |
-
return new $checkerClass($metadataUrl, $id, $slug, $checkPeriod, $optionName, $muPluginFile);
|
87 |
-
} else {
|
88 |
-
//VCS checker + an API client.
|
89 |
-
$apiClass = self::getCompatibleClassVersion($apiClass);
|
90 |
-
if ( $apiClass === null ) {
|
91 |
-
trigger_error(sprintf(
|
92 |
-
'PUC %s does not support %s',
|
93 |
-
htmlentities(self::$latestCompatibleVersion),
|
94 |
-
htmlentities($service)
|
95 |
-
), E_USER_ERROR);
|
96 |
-
return null;
|
97 |
-
}
|
98 |
-
|
99 |
-
return new $checkerClass(
|
100 |
-
new $apiClass($metadataUrl),
|
101 |
-
$id,
|
102 |
-
$slug,
|
103 |
-
$checkPeriod,
|
104 |
-
$optionName,
|
105 |
-
$muPluginFile
|
106 |
-
);
|
107 |
-
}
|
108 |
-
}
|
109 |
-
|
110 |
-
/**
|
111 |
-
* Check if the path points to a plugin file.
|
112 |
-
*
|
113 |
-
* @param string $absolutePath Normalized path.
|
114 |
-
* @return bool
|
115 |
-
*/
|
116 |
-
protected static function isPluginFile($absolutePath) {
|
117 |
-
//Is the file inside the "plugins" or "mu-plugins" directory?
|
118 |
-
$pluginDir = wp_normalize_path(WP_PLUGIN_DIR);
|
119 |
-
$muPluginDir = wp_normalize_path(WPMU_PLUGIN_DIR);
|
120 |
-
if ( (strpos($absolutePath, $pluginDir) === 0) || (strpos($absolutePath, $muPluginDir) === 0) ) {
|
121 |
-
return true;
|
122 |
-
}
|
123 |
-
|
124 |
-
//Is it a file at all? Caution: is_file() can fail if the parent dir. doesn't have the +x permission set.
|
125 |
-
if ( !is_file($absolutePath) ) {
|
126 |
-
return false;
|
127 |
-
}
|
128 |
-
|
129 |
-
//Does it have a valid plugin header?
|
130 |
-
//This is a last-ditch check for plugins symlinked from outside the WP root.
|
131 |
-
if ( function_exists('get_file_data') ) {
|
132 |
-
$headers = get_file_data($absolutePath, array('Name' => 'Plugin Name'), 'plugin');
|
133 |
-
return !empty($headers['Name']);
|
134 |
-
}
|
135 |
-
|
136 |
-
return false;
|
137 |
-
}
|
138 |
-
|
139 |
-
/**
|
140 |
-
* Get the name of the theme's directory from a full path to a file inside that directory.
|
141 |
-
* E.g. "/abc/public_html/wp-content/themes/foo/whatever.php" => "foo".
|
142 |
-
*
|
143 |
-
* Note that subdirectories are currently not supported. For example,
|
144 |
-
* "/xyz/wp-content/themes/my-theme/includes/whatever.php" => NULL.
|
145 |
-
*
|
146 |
-
* @param string $absolutePath Normalized path.
|
147 |
-
* @return string|null Directory name, or NULL if the path doesn't point to a theme.
|
148 |
-
*/
|
149 |
-
protected static function getThemeDirectoryName($absolutePath) {
|
150 |
-
if ( is_file($absolutePath) ) {
|
151 |
-
$absolutePath = dirname($absolutePath);
|
152 |
-
}
|
153 |
-
|
154 |
-
if ( file_exists($absolutePath . '/style.css') ) {
|
155 |
-
return basename($absolutePath);
|
156 |
-
}
|
157 |
-
return null;
|
158 |
-
}
|
159 |
-
|
160 |
-
/**
|
161 |
-
* Get the name of the hosting service that the URL points to.
|
162 |
-
*
|
163 |
-
* @param string $metadataUrl
|
164 |
-
* @return string|null
|
165 |
-
*/
|
166 |
-
private static function getVcsService($metadataUrl) {
|
167 |
-
$service = null;
|
168 |
-
|
169 |
-
//Which hosting service does the URL point to?
|
170 |
-
$host = @parse_url($metadataUrl, PHP_URL_HOST);
|
171 |
-
$path = @parse_url($metadataUrl, PHP_URL_PATH);
|
172 |
-
//Check if the path looks like "/user-name/repository".
|
173 |
-
$usernameRepoRegex = '@^/?([^/]+?)/([^/#?&]+?)/?$@';
|
174 |
-
if ( preg_match($usernameRepoRegex, $path) ) {
|
175 |
-
$knownServices = array(
|
176 |
-
'github.com' => 'GitHub',
|
177 |
-
'bitbucket.org' => 'BitBucket',
|
178 |
-
'gitlab.com' => 'GitLab',
|
179 |
-
);
|
180 |
-
if ( isset($knownServices[$host]) ) {
|
181 |
-
$service = $knownServices[$host];
|
182 |
-
}
|
183 |
-
}
|
184 |
-
|
185 |
-
return $service;
|
186 |
-
}
|
187 |
-
|
188 |
-
/**
|
189 |
-
* Get the latest version of the specified class that has the same major version number
|
190 |
-
* as this factory class.
|
191 |
-
*
|
192 |
-
* @param string $class Partial class name.
|
193 |
-
* @return string|null Full class name.
|
194 |
-
*/
|
195 |
-
protected static function getCompatibleClassVersion($class) {
|
196 |
-
if ( isset(self::$classVersions[$class][self::$latestCompatibleVersion]) ) {
|
197 |
-
return self::$classVersions[$class][self::$latestCompatibleVersion];
|
198 |
-
}
|
199 |
-
return null;
|
200 |
-
}
|
201 |
-
|
202 |
-
/**
|
203 |
-
* Get the specific class name for the latest available version of a class.
|
204 |
-
*
|
205 |
-
* @param string $class
|
206 |
-
* @return null|string
|
207 |
-
*/
|
208 |
-
public static function getLatestClassVersion($class) {
|
209 |
-
if ( !self::$sorted ) {
|
210 |
-
self::sortVersions();
|
211 |
-
}
|
212 |
-
|
213 |
-
if ( isset(self::$classVersions[$class]) ) {
|
214 |
-
return reset(self::$classVersions[$class]);
|
215 |
-
} else {
|
216 |
-
return null;
|
217 |
-
}
|
218 |
-
}
|
219 |
-
|
220 |
-
/**
|
221 |
-
* Sort available class versions in descending order (i.e. newest first).
|
222 |
-
*/
|
223 |
-
protected static function sortVersions() {
|
224 |
-
foreach ( self::$classVersions as $class => $versions ) {
|
225 |
-
uksort($versions, array(__CLASS__, 'compareVersions'));
|
226 |
-
self::$classVersions[$class] = $versions;
|
227 |
-
}
|
228 |
-
self::$sorted = true;
|
229 |
-
}
|
230 |
-
|
231 |
-
protected static function compareVersions($a, $b) {
|
232 |
-
return -version_compare($a, $b);
|
233 |
-
}
|
234 |
-
|
235 |
-
/**
|
236 |
-
* Register a version of a class.
|
237 |
-
*
|
238 |
-
* @access private This method is only for internal use by the library.
|
239 |
-
*
|
240 |
-
* @param string $generalClass Class name without version numbers, e.g. 'PluginUpdateChecker'.
|
241 |
-
* @param string $versionedClass Actual class name, e.g. 'PluginUpdateChecker_1_2'.
|
242 |
-
* @param string $version Version number, e.g. '1.2'.
|
243 |
-
*/
|
244 |
-
public static function addVersion($generalClass, $versionedClass, $version) {
|
245 |
-
if ( empty(self::$myMajorVersion) ) {
|
246 |
-
$nameParts = explode('_', __CLASS__, 3);
|
247 |
-
self::$myMajorVersion = substr(ltrim($nameParts[1], 'v'), 0, 1);
|
248 |
-
}
|
249 |
-
|
250 |
-
//Store the greatest version number that matches our major version.
|
251 |
-
$components = explode('.', $version);
|
252 |
-
if ( $components[0] === self::$myMajorVersion ) {
|
253 |
-
|
254 |
-
if (
|
255 |
-
empty(self::$latestCompatibleVersion)
|
256 |
-
|| version_compare($version, self::$latestCompatibleVersion, '>')
|
257 |
-
) {
|
258 |
-
self::$latestCompatibleVersion = $version;
|
259 |
-
}
|
260 |
-
|
261 |
-
}
|
262 |
-
|
263 |
-
if ( !isset(self::$classVersions[$generalClass]) ) {
|
264 |
-
self::$classVersions[$generalClass] = array();
|
265 |
-
}
|
266 |
-
self::$classVersions[$generalClass][$version] = $versionedClass;
|
267 |
-
self::$sorted = false;
|
268 |
-
}
|
269 |
-
}
|
270 |
|
271 |
endif;
|
1 |
<?php
|
2 |
if ( !class_exists('Puc_v4_Factory', false) ):
|
3 |
|
4 |
+
class Puc_v4_Factory extends Puc_v4p4_Factory { }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
endif;
|
admin/update-checker/Puc/{v4p2 → v4p4}/Autoloader.php
RENAMED
@@ -1,8 +1,8 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
if ( !class_exists('
|
4 |
|
5 |
-
class
|
6 |
private $prefix = '';
|
7 |
private $rootDir = '';
|
8 |
private $libraryDir = '';
|
1 |
<?php
|
2 |
|
3 |
+
if ( !class_exists('Puc_v4p4_Autoloader', false) ):
|
4 |
|
5 |
+
class Puc_v4p4_Autoloader {
|
6 |
private $prefix = '';
|
7 |
private $rootDir = '';
|
8 |
private $libraryDir = '';
|
admin/update-checker/Puc/v4p4/DebugBar/Extension.php
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_DebugBar_Extension', false) ):
|
3 |
+
|
4 |
+
class Puc_v4p4_DebugBar_Extension {
|
5 |
+
const RESPONSE_BODY_LENGTH_LIMIT = 4000;
|
6 |
+
|
7 |
+
/** @var Puc_v4p4_UpdateChecker */
|
8 |
+
protected $updateChecker;
|
9 |
+
protected $panelClass = 'Puc_v4p4_DebugBar_Panel';
|
10 |
+
|
11 |
+
public function __construct($updateChecker, $panelClass = null) {
|
12 |
+
$this->updateChecker = $updateChecker;
|
13 |
+
if ( isset($panelClass) ) {
|
14 |
+
$this->panelClass = $panelClass;
|
15 |
+
}
|
16 |
+
|
17 |
+
add_filter('debug_bar_panels', array($this, 'addDebugBarPanel'));
|
18 |
+
add_action('debug_bar_enqueue_scripts', array($this, 'enqueuePanelDependencies'));
|
19 |
+
|
20 |
+
add_action('wp_ajax_puc_v4_debug_check_now', array($this, 'ajaxCheckNow'));
|
21 |
+
}
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Register the PUC Debug Bar panel.
|
25 |
+
*
|
26 |
+
* @param array $panels
|
27 |
+
* @return array
|
28 |
+
*/
|
29 |
+
public function addDebugBarPanel($panels) {
|
30 |
+
if ( $this->updateChecker->userCanInstallUpdates() ) {
|
31 |
+
$panels[] = new $this->panelClass($this->updateChecker);
|
32 |
+
}
|
33 |
+
return $panels;
|
34 |
+
}
|
35 |
+
|
36 |
+
/**
|
37 |
+
* Enqueue our Debug Bar scripts and styles.
|
38 |
+
*/
|
39 |
+
public function enqueuePanelDependencies() {
|
40 |
+
wp_enqueue_style(
|
41 |
+
'puc-debug-bar-style-v4',
|
42 |
+
$this->getLibraryUrl("/css/puc-debug-bar.css"),
|
43 |
+
array('debug-bar'),
|
44 |
+
'20171124'
|
45 |
+
);
|
46 |
+
|
47 |
+
wp_enqueue_script(
|
48 |
+
'puc-debug-bar-js-v4',
|
49 |
+
$this->getLibraryUrl("/js/debug-bar.js"),
|
50 |
+
array('jquery'),
|
51 |
+
'20170516'
|
52 |
+
);
|
53 |
+
}
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Run an update check and output the result. Useful for making sure that
|
57 |
+
* the update checking process works as expected.
|
58 |
+
*/
|
59 |
+
public function ajaxCheckNow() {
|
60 |
+
if ( $_POST['uid'] !== $this->updateChecker->getUniqueName('uid') ) {
|
61 |
+
return;
|
62 |
+
}
|
63 |
+
$this->preAjaxRequest();
|
64 |
+
$update = $this->updateChecker->checkForUpdates();
|
65 |
+
if ( $update !== null ) {
|
66 |
+
echo "An update is available:";
|
67 |
+
echo '<pre>', htmlentities(print_r($update, true)), '</pre>';
|
68 |
+
} else {
|
69 |
+
echo 'No updates found.';
|
70 |
+
}
|
71 |
+
|
72 |
+
$errors = $this->updateChecker->getLastRequestApiErrors();
|
73 |
+
if ( !empty($errors) ) {
|
74 |
+
printf('<p>The update checker encountered %d API error%s.</p>', count($errors), (count($errors) > 1) ? 's' : '');
|
75 |
+
|
76 |
+
foreach (array_values($errors) as $num => $item) {
|
77 |
+
$wpError = $item['error'];
|
78 |
+
/** @var WP_Error $wpError */
|
79 |
+
printf('<h4>%d) %s</h4>', $num + 1, esc_html($wpError->get_error_message()));
|
80 |
+
|
81 |
+
echo '<dl>';
|
82 |
+
printf('<dt>Error code:</dt><dd><code>%s</code></dd>', esc_html($wpError->get_error_code()));
|
83 |
+
|
84 |
+
if ( isset($item['url']) ) {
|
85 |
+
printf('<dt>Requested URL:</dt><dd><code>%s</code></dd>', esc_html($item['url']));
|
86 |
+
}
|
87 |
+
|
88 |
+
if ( isset($item['httpResponse']) ) {
|
89 |
+
if ( is_wp_error($item['httpResponse']) ) {
|
90 |
+
$httpError = $item['httpResponse'];
|
91 |
+
/** @var WP_Error $httpError */
|
92 |
+
printf(
|
93 |
+
'<dt>WordPress HTTP API error:</dt><dd>%s (<code>%s</code>)</dd>',
|
94 |
+
esc_html($httpError->get_error_message()),
|
95 |
+
esc_html($httpError->get_error_code())
|
96 |
+
);
|
97 |
+
} else {
|
98 |
+
//Status code.
|
99 |
+
printf(
|
100 |
+
'<dt>HTTP status:</dt><dd><code>%d %s</code></dd>',
|
101 |
+
wp_remote_retrieve_response_code($item['httpResponse']),
|
102 |
+
wp_remote_retrieve_response_message($item['httpResponse'])
|
103 |
+
);
|
104 |
+
|
105 |
+
//Headers.
|
106 |
+
echo '<dt>Response headers:</dt><dd><pre>';
|
107 |
+
foreach (wp_remote_retrieve_headers($item['httpResponse']) as $name => $value) {
|
108 |
+
printf("%s: %s\n", esc_html($name), esc_html($value));
|
109 |
+
}
|
110 |
+
echo '</pre></dd>';
|
111 |
+
|
112 |
+
//Body.
|
113 |
+
$body = wp_remote_retrieve_body($item['httpResponse']);
|
114 |
+
if ( $body === '' ) {
|
115 |
+
$body = '(Empty response.)';
|
116 |
+
} else if ( strlen($body) > self::RESPONSE_BODY_LENGTH_LIMIT ) {
|
117 |
+
$length = strlen($body);
|
118 |
+
$body = substr($body, 0, self::RESPONSE_BODY_LENGTH_LIMIT)
|
119 |
+
. sprintf("\n(Long string truncated. Total length: %d bytes.)", $length);
|
120 |
+
}
|
121 |
+
|
122 |
+
printf('<dt>Response body:</dt><dd><pre>%s</pre></dd>', esc_html($body));
|
123 |
+
}
|
124 |
+
}
|
125 |
+
echo '<dl>';
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
exit;
|
130 |
+
}
|
131 |
+
|
132 |
+
/**
|
133 |
+
* Check access permissions and enable error display (for debugging).
|
134 |
+
*/
|
135 |
+
protected function preAjaxRequest() {
|
136 |
+
if ( !$this->updateChecker->userCanInstallUpdates() ) {
|
137 |
+
die('Access denied');
|
138 |
+
}
|
139 |
+
check_ajax_referer('puc-ajax');
|
140 |
+
|
141 |
+
error_reporting(E_ALL);
|
142 |
+
@ini_set('display_errors', 'On');
|
143 |
+
}
|
144 |
+
|
145 |
+
/**
|
146 |
+
* @param string $filePath
|
147 |
+
* @return string
|
148 |
+
*/
|
149 |
+
private function getLibraryUrl($filePath) {
|
150 |
+
$absolutePath = realpath(dirname(__FILE__) . '/../../../' . ltrim($filePath, '/'));
|
151 |
+
|
152 |
+
//Where is the library located inside the WordPress directory structure?
|
153 |
+
$absolutePath = Puc_v4p4_Factory::normalizePath($absolutePath);
|
154 |
+
|
155 |
+
$pluginDir = Puc_v4p4_Factory::normalizePath(WP_PLUGIN_DIR);
|
156 |
+
$muPluginDir = Puc_v4p4_Factory::normalizePath(WPMU_PLUGIN_DIR);
|
157 |
+
$themeDir = Puc_v4p4_Factory::normalizePath(get_theme_root());
|
158 |
+
|
159 |
+
if ( (strpos($absolutePath, $pluginDir) === 0) || (strpos($absolutePath, $muPluginDir) === 0) ) {
|
160 |
+
//It's part of a plugin.
|
161 |
+
return plugins_url(basename($absolutePath), $absolutePath);
|
162 |
+
} else if ( strpos($absolutePath, $themeDir) === 0 ) {
|
163 |
+
//It's part of a theme.
|
164 |
+
$relativePath = substr($absolutePath, strlen($themeDir) + 1);
|
165 |
+
$template = substr($relativePath, 0, strpos($relativePath, '/'));
|
166 |
+
$baseUrl = get_theme_root_uri($template);
|
167 |
+
|
168 |
+
if ( !empty($baseUrl) && $relativePath ) {
|
169 |
+
return $baseUrl . '/' . $relativePath;
|
170 |
+
}
|
171 |
+
}
|
172 |
+
|
173 |
+
return '';
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
endif;
|
admin/update-checker/Puc/v4p4/DebugBar/Panel.php
ADDED
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p4_DebugBar_Panel', false) && class_exists('Debug_Bar_Panel', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p4_DebugBar_Panel extends Debug_Bar_Panel {
|
6 |
+
/** @var Puc_v4p4_UpdateChecker */
|
7 |
+
protected $updateChecker;
|
8 |
+
|
9 |
+
private $responseBox = '<div class="puc-ajax-response" style="display: none;"></div>';
|
10 |
+
|
11 |
+
public function __construct($updateChecker) {
|
12 |
+
$this->updateChecker = $updateChecker;
|
13 |
+
$title = sprintf(
|
14 |
+
'<span class="puc-debug-menu-link-%s">PUC (%s)</span>',
|
15 |
+
esc_attr($this->updateChecker->getUniqueName('uid')),
|
16 |
+
$this->updateChecker->slug
|
17 |
+
);
|
18 |
+
parent::__construct($title);
|
19 |
+
}
|
20 |
+
|
21 |
+
public function render() {
|
22 |
+
printf(
|
23 |
+
'<div class="puc-debug-bar-panel-v4" id="%1$s" data-slug="%2$s" data-uid="%3$s" data-nonce="%4$s">',
|
24 |
+
esc_attr($this->updateChecker->getUniqueName('debug-bar-panel')),
|
25 |
+
esc_attr($this->updateChecker->slug),
|
26 |
+
esc_attr($this->updateChecker->getUniqueName('uid')),
|
27 |
+
esc_attr(wp_create_nonce('puc-ajax'))
|
28 |
+
);
|
29 |
+
|
30 |
+
$this->displayConfiguration();
|
31 |
+
$this->displayStatus();
|
32 |
+
$this->displayCurrentUpdate();
|
33 |
+
|
34 |
+
echo '</div>';
|
35 |
+
}
|
36 |
+
|
37 |
+
private function displayConfiguration() {
|
38 |
+
echo '<h3>Configuration</h3>';
|
39 |
+
echo '<table class="puc-debug-data">';
|
40 |
+
$this->displayConfigHeader();
|
41 |
+
$this->row('Slug', htmlentities($this->updateChecker->slug));
|
42 |
+
$this->row('DB option', htmlentities($this->updateChecker->optionName));
|
43 |
+
|
44 |
+
$requestInfoButton = $this->getMetadataButton();
|
45 |
+
$this->row('Metadata URL', htmlentities($this->updateChecker->metadataUrl) . ' ' . $requestInfoButton . $this->responseBox);
|
46 |
+
|
47 |
+
$scheduler = $this->updateChecker->scheduler;
|
48 |
+
if ( $scheduler->checkPeriod > 0 ) {
|
49 |
+
$this->row('Automatic checks', 'Every ' . $scheduler->checkPeriod . ' hours');
|
50 |
+
} else {
|
51 |
+
$this->row('Automatic checks', 'Disabled');
|
52 |
+
}
|
53 |
+
|
54 |
+
if ( isset($scheduler->throttleRedundantChecks) ) {
|
55 |
+
if ( $scheduler->throttleRedundantChecks && ($scheduler->checkPeriod > 0) ) {
|
56 |
+
$this->row(
|
57 |
+
'Throttling',
|
58 |
+
sprintf(
|
59 |
+
'Enabled. If an update is already available, check for updates every %1$d hours instead of every %2$d hours.',
|
60 |
+
$scheduler->throttledCheckPeriod,
|
61 |
+
$scheduler->checkPeriod
|
62 |
+
)
|
63 |
+
);
|
64 |
+
} else {
|
65 |
+
$this->row('Throttling', 'Disabled');
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
$this->updateChecker->onDisplayConfiguration($this);
|
70 |
+
|
71 |
+
echo '</table>';
|
72 |
+
}
|
73 |
+
|
74 |
+
protected function displayConfigHeader() {
|
75 |
+
//Do nothing. This should be implemented in subclasses.
|
76 |
+
}
|
77 |
+
|
78 |
+
protected function getMetadataButton() {
|
79 |
+
return '';
|
80 |
+
}
|
81 |
+
|
82 |
+
private function displayStatus() {
|
83 |
+
echo '<h3>Status</h3>';
|
84 |
+
echo '<table class="puc-debug-data">';
|
85 |
+
$state = $this->updateChecker->getUpdateState();
|
86 |
+
$checkNowButton = '';
|
87 |
+
if ( function_exists('get_submit_button') ) {
|
88 |
+
$checkNowButton = get_submit_button(
|
89 |
+
'Check Now',
|
90 |
+
'secondary',
|
91 |
+
'puc-check-now-button',
|
92 |
+
false,
|
93 |
+
array('id' => $this->updateChecker->getUniqueName('check-now-button'))
|
94 |
+
);
|
95 |
+
}
|
96 |
+
|
97 |
+
if ( $state->getLastCheck() > 0 ) {
|
98 |
+
$this->row('Last check', $this->formatTimeWithDelta($state->getLastCheck()) . ' ' . $checkNowButton . $this->responseBox);
|
99 |
+
} else {
|
100 |
+
$this->row('Last check', 'Never');
|
101 |
+
}
|
102 |
+
|
103 |
+
$nextCheck = wp_next_scheduled($this->updateChecker->scheduler->getCronHookName());
|
104 |
+
$this->row('Next automatic check', $this->formatTimeWithDelta($nextCheck));
|
105 |
+
|
106 |
+
if ( $state->getCheckedVersion() !== '' ) {
|
107 |
+
$this->row('Checked version', htmlentities($state->getCheckedVersion()));
|
108 |
+
$this->row('Cached update', $state->getUpdate());
|
109 |
+
}
|
110 |
+
$this->row('Update checker class', htmlentities(get_class($this->updateChecker)));
|
111 |
+
echo '</table>';
|
112 |
+
}
|
113 |
+
|
114 |
+
private function displayCurrentUpdate() {
|
115 |
+
$update = $this->updateChecker->getUpdate();
|
116 |
+
if ( $update !== null ) {
|
117 |
+
echo '<h3>An Update Is Available</h3>';
|
118 |
+
echo '<table class="puc-debug-data">';
|
119 |
+
$fields = $this->getUpdateFields();
|
120 |
+
foreach($fields as $field) {
|
121 |
+
if ( property_exists($update, $field) ) {
|
122 |
+
$this->row(ucwords(str_replace('_', ' ', $field)), htmlentities($update->$field));
|
123 |
+
}
|
124 |
+
}
|
125 |
+
echo '</table>';
|
126 |
+
} else {
|
127 |
+
echo '<h3>No updates currently available</h3>';
|
128 |
+
}
|
129 |
+
}
|
130 |
+
|
131 |
+
protected function getUpdateFields() {
|
132 |
+
return array('version', 'download_url', 'slug',);
|
133 |
+
}
|
134 |
+
|
135 |
+
private function formatTimeWithDelta($unixTime) {
|
136 |
+
if ( empty($unixTime) ) {
|
137 |
+
return 'Never';
|
138 |
+
}
|
139 |
+
|
140 |
+
$delta = time() - $unixTime;
|
141 |
+
$result = human_time_diff(time(), $unixTime);
|
142 |
+
if ( $delta < 0 ) {
|
143 |
+
$result = 'after ' . $result;
|
144 |
+
} else {
|
145 |
+
$result = $result . ' ago';
|
146 |
+
}
|
147 |
+
$result .= ' (' . $this->formatTimestamp($unixTime) . ')';
|
148 |
+
return $result;
|
149 |
+
}
|
150 |
+
|
151 |
+
private function formatTimestamp($unixTime) {
|
152 |
+
return gmdate('Y-m-d H:i:s', $unixTime + (get_option('gmt_offset') * 3600));
|
153 |
+
}
|
154 |
+
|
155 |
+
public function row($name, $value) {
|
156 |
+
if ( is_object($value) || is_array($value) ) {
|
157 |
+
$value = '<pre>' . htmlentities(print_r($value, true)) . '</pre>';
|
158 |
+
} else if ($value === null) {
|
159 |
+
$value = '<code>null</code>';
|
160 |
+
}
|
161 |
+
printf('<tr><th scope="row">%1$s</th> <td>%2$s</td></tr>', $name, $value);
|
162 |
+
}
|
163 |
+
}
|
164 |
+
|
165 |
+
endif;
|
admin/update-checker/Puc/v4p4/DebugBar/PluginExtension.php
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_DebugBar_PluginExtension', false) ):
|
3 |
+
|
4 |
+
class Puc_v4p4_DebugBar_PluginExtension extends Puc_v4p4_DebugBar_Extension {
|
5 |
+
/** @var Puc_v4p4_Plugin_UpdateChecker */
|
6 |
+
protected $updateChecker;
|
7 |
+
|
8 |
+
public function __construct($updateChecker) {
|
9 |
+
parent::__construct($updateChecker, 'Puc_v4p4_DebugBar_PluginPanel');
|
10 |
+
|
11 |
+
add_action('wp_ajax_puc_v4_debug_request_info', array($this, 'ajaxRequestInfo'));
|
12 |
+
}
|
13 |
+
|
14 |
+
/**
|
15 |
+
* Request plugin info and output it.
|
16 |
+
*/
|
17 |
+
public function ajaxRequestInfo() {
|
18 |
+
if ( $_POST['uid'] !== $this->updateChecker->getUniqueName('uid') ) {
|
19 |
+
return;
|
20 |
+
}
|
21 |
+
$this->preAjaxRequest();
|
22 |
+
$info = $this->updateChecker->requestInfo();
|
23 |
+
if ( $info !== null ) {
|
24 |
+
echo 'Successfully retrieved plugin info from the metadata URL:';
|
25 |
+
echo '<pre>', htmlentities(print_r($info, true)), '</pre>';
|
26 |
+
} else {
|
27 |
+
echo 'Failed to retrieve plugin info from the metadata URL.';
|
28 |
+
}
|
29 |
+
exit;
|
30 |
+
}
|
31 |
+
}
|
32 |
+
|
33 |
+
endif;
|
admin/update-checker/Puc/v4p4/DebugBar/PluginPanel.php
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p4_DebugBar_PluginPanel', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p4_DebugBar_PluginPanel extends Puc_v4p4_DebugBar_Panel {
|
6 |
+
/**
|
7 |
+
* @var Puc_v4p4_Plugin_UpdateChecker
|
8 |
+
*/
|
9 |
+
protected $updateChecker;
|
10 |
+
|
11 |
+
protected function displayConfigHeader() {
|
12 |
+
$this->row('Plugin file', htmlentities($this->updateChecker->pluginFile));
|
13 |
+
parent::displayConfigHeader();
|
14 |
+
}
|
15 |
+
|
16 |
+
protected function getMetadataButton() {
|
17 |
+
$requestInfoButton = '';
|
18 |
+
if ( function_exists('get_submit_button') ) {
|
19 |
+
$requestInfoButton = get_submit_button(
|
20 |
+
'Request Info',
|
21 |
+
'secondary',
|
22 |
+
'puc-request-info-button',
|
23 |
+
false,
|
24 |
+
array('id' => $this->updateChecker->getUniqueName('request-info-button'))
|
25 |
+
);
|
26 |
+
}
|
27 |
+
return $requestInfoButton;
|
28 |
+
}
|
29 |
+
|
30 |
+
protected function getUpdateFields() {
|
31 |
+
return array_merge(
|
32 |
+
parent::getUpdateFields(),
|
33 |
+
array('homepage', 'upgrade_notice', 'tested',)
|
34 |
+
);
|
35 |
+
}
|
36 |
+
}
|
37 |
+
|
38 |
+
endif;
|
admin/update-checker/Puc/v4p4/DebugBar/ThemePanel.php
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p4_DebugBar_ThemePanel', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p4_DebugBar_ThemePanel extends Puc_v4p4_DebugBar_Panel {
|
6 |
+
/**
|
7 |
+
* @var Puc_v4p4_Theme_UpdateChecker
|
8 |
+
*/
|
9 |
+
protected $updateChecker;
|
10 |
+
|
11 |
+
protected function displayConfigHeader() {
|
12 |
+
$this->row('Theme directory', htmlentities($this->updateChecker->directoryName));
|
13 |
+
parent::displayConfigHeader();
|
14 |
+
}
|
15 |
+
|
16 |
+
protected function getUpdateFields() {
|
17 |
+
return array_merge(parent::getUpdateFields(), array('details_url'));
|
18 |
+
}
|
19 |
+
}
|
20 |
+
|
21 |
+
endif;
|
admin/update-checker/Puc/v4p4/Factory.php
ADDED
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Factory', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* A factory that builds update checker instances.
|
6 |
+
*
|
7 |
+
* When multiple versions of the same class have been loaded (e.g. PluginUpdateChecker 4.0
|
8 |
+
* and 4.1), this factory will always use the latest available minor version. Register class
|
9 |
+
* versions by calling {@link PucFactory::addVersion()}.
|
10 |
+
*
|
11 |
+
* At the moment it can only build instances of the UpdateChecker class. Other classes are
|
12 |
+
* intended mainly for internal use and refer directly to specific implementations.
|
13 |
+
*/
|
14 |
+
class Puc_v4p4_Factory {
|
15 |
+
protected static $classVersions = array();
|
16 |
+
protected static $sorted = false;
|
17 |
+
|
18 |
+
protected static $myMajorVersion = '';
|
19 |
+
protected static $latestCompatibleVersion = '';
|
20 |
+
|
21 |
+
/**
|
22 |
+
* Create a new instance of the update checker.
|
23 |
+
*
|
24 |
+
* This method automatically detects if you're using it for a plugin or a theme and chooses
|
25 |
+
* the appropriate implementation for your update source (JSON file, GitHub, BitBucket, etc).
|
26 |
+
*
|
27 |
+
* @see Puc_v4p4_UpdateChecker::__construct
|
28 |
+
*
|
29 |
+
* @param string $metadataUrl The URL of the metadata file, a GitHub repository, or another supported update source.
|
30 |
+
* @param string $fullPath Full path to the main plugin file or to the theme directory.
|
31 |
+
* @param string $slug Custom slug. Defaults to the name of the main plugin file or the theme directory.
|
32 |
+
* @param int $checkPeriod How often to check for updates (in hours).
|
33 |
+
* @param string $optionName Where to store book-keeping info about update checks.
|
34 |
+
* @param string $muPluginFile The plugin filename relative to the mu-plugins directory.
|
35 |
+
* @return Puc_v4p4_Plugin_UpdateChecker|Puc_v4p4_Theme_UpdateChecker|Puc_v4p4_Vcs_BaseChecker
|
36 |
+
*/
|
37 |
+
public static function buildUpdateChecker($metadataUrl, $fullPath, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') {
|
38 |
+
$fullPath = self::normalizePath($fullPath);
|
39 |
+
$id = null;
|
40 |
+
|
41 |
+
//Plugin or theme?
|
42 |
+
$themeDirectory = self::getThemeDirectoryName($fullPath);
|
43 |
+
if ( self::isPluginFile($fullPath) ) {
|
44 |
+
$type = 'Plugin';
|
45 |
+
$id = $fullPath;
|
46 |
+
} else if ( $themeDirectory !== null ) {
|
47 |
+
$type = 'Theme';
|
48 |
+
$id = $themeDirectory;
|
49 |
+
} else {
|
50 |
+
throw new RuntimeException(sprintf(
|
51 |
+
'The update checker cannot determine if "%s" is a plugin or a theme. ' .
|
52 |
+
'This is a bug. Please contact the PUC developer.',
|
53 |
+
htmlentities($fullPath)
|
54 |
+
));
|
55 |
+
}
|
56 |
+
|
57 |
+
//Which hosting service does the URL point to?
|
58 |
+
$service = self::getVcsService($metadataUrl);
|
59 |
+
|
60 |
+
$apiClass = null;
|
61 |
+
if ( empty($service) ) {
|
62 |
+
//The default is to get update information from a remote JSON file.
|
63 |
+
$checkerClass = $type . '_UpdateChecker';
|
64 |
+
} else {
|
65 |
+
//You can also use a VCS repository like GitHub.
|
66 |
+
$checkerClass = 'Vcs_' . $type . 'UpdateChecker';
|
67 |
+
$apiClass = $service . 'Api';
|
68 |
+
}
|
69 |
+
|
70 |
+
$checkerClass = self::getCompatibleClassVersion($checkerClass);
|
71 |
+
if ( $checkerClass === null ) {
|
72 |
+
trigger_error(
|
73 |
+
sprintf(
|
74 |
+
'PUC %s does not support updates for %ss %s',
|
75 |
+
htmlentities(self::$latestCompatibleVersion),
|
76 |
+
strtolower($type),
|
77 |
+
$service ? ('hosted on ' . htmlentities($service)) : 'using JSON metadata'
|
78 |
+
),
|
79 |
+
E_USER_ERROR
|
80 |
+
);
|
81 |
+
return null;
|
82 |
+
}
|
83 |
+
|
84 |
+
if ( !isset($apiClass) ) {
|
85 |
+
//Plain old update checker.
|
86 |
+
return new $checkerClass($metadataUrl, $id, $slug, $checkPeriod, $optionName, $muPluginFile);
|
87 |
+
} else {
|
88 |
+
//VCS checker + an API client.
|
89 |
+
$apiClass = self::getCompatibleClassVersion($apiClass);
|
90 |
+
if ( $apiClass === null ) {
|
91 |
+
trigger_error(sprintf(
|
92 |
+
'PUC %s does not support %s',
|
93 |
+
htmlentities(self::$latestCompatibleVersion),
|
94 |
+
htmlentities($service)
|
95 |
+
), E_USER_ERROR);
|
96 |
+
return null;
|
97 |
+
}
|
98 |
+
|
99 |
+
return new $checkerClass(
|
100 |
+
new $apiClass($metadataUrl),
|
101 |
+
$id,
|
102 |
+
$slug,
|
103 |
+
$checkPeriod,
|
104 |
+
$optionName,
|
105 |
+
$muPluginFile
|
106 |
+
);
|
107 |
+
}
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
*
|
112 |
+
* Normalize a filesystem path. Introduced in WP 3.9.
|
113 |
+
* Copying here allows use of the class on earlier versions.
|
114 |
+
* This version adapted from WP 4.8.2 (unchanged since 4.5.0)
|
115 |
+
*
|
116 |
+
* @param string $path Path to normalize.
|
117 |
+
* @return string Normalized path.
|
118 |
+
*/
|
119 |
+
public static function normalizePath($path) {
|
120 |
+
if ( function_exists('wp_normalize_path') ) {
|
121 |
+
return wp_normalize_path($path);
|
122 |
+
}
|
123 |
+
$path = str_replace('\\', '/', $path);
|
124 |
+
$path = preg_replace('|(?<=.)/+|', '/', $path);
|
125 |
+
if ( substr($path, 1, 1) === ':' ) {
|
126 |
+
$path = ucfirst($path);
|
127 |
+
}
|
128 |
+
return $path;
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Check if the path points to a plugin file.
|
133 |
+
*
|
134 |
+
* @param string $absolutePath Normalized path.
|
135 |
+
* @return bool
|
136 |
+
*/
|
137 |
+
protected static function isPluginFile($absolutePath) {
|
138 |
+
//Is the file inside the "plugins" or "mu-plugins" directory?
|
139 |
+
$pluginDir = self::normalizePath(WP_PLUGIN_DIR);
|
140 |
+
$muPluginDir = self::normalizePath(WPMU_PLUGIN_DIR);
|
141 |
+
if ( (strpos($absolutePath, $pluginDir) === 0) || (strpos($absolutePath, $muPluginDir) === 0) ) {
|
142 |
+
return true;
|
143 |
+
}
|
144 |
+
|
145 |
+
//Is it a file at all? Caution: is_file() can fail if the parent dir. doesn't have the +x permission set.
|
146 |
+
if ( !is_file($absolutePath) ) {
|
147 |
+
return false;
|
148 |
+
}
|
149 |
+
|
150 |
+
//Does it have a valid plugin header?
|
151 |
+
//This is a last-ditch check for plugins symlinked from outside the WP root.
|
152 |
+
if ( function_exists('get_file_data') ) {
|
153 |
+
$headers = get_file_data($absolutePath, array('Name' => 'Plugin Name'), 'plugin');
|
154 |
+
return !empty($headers['Name']);
|
155 |
+
}
|
156 |
+
|
157 |
+
return false;
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Get the name of the theme's directory from a full path to a file inside that directory.
|
162 |
+
* E.g. "/abc/public_html/wp-content/themes/foo/whatever.php" => "foo".
|
163 |
+
*
|
164 |
+
* Note that subdirectories are currently not supported. For example,
|
165 |
+
* "/xyz/wp-content/themes/my-theme/includes/whatever.php" => NULL.
|
166 |
+
*
|
167 |
+
* @param string $absolutePath Normalized path.
|
168 |
+
* @return string|null Directory name, or NULL if the path doesn't point to a theme.
|
169 |
+
*/
|
170 |
+
protected static function getThemeDirectoryName($absolutePath) {
|
171 |
+
if ( is_file($absolutePath) ) {
|
172 |
+
$absolutePath = dirname($absolutePath);
|
173 |
+
}
|
174 |
+
|
175 |
+
if ( file_exists($absolutePath . '/style.css') ) {
|
176 |
+
return basename($absolutePath);
|
177 |
+
}
|
178 |
+
return null;
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* Get the name of the hosting service that the URL points to.
|
183 |
+
*
|
184 |
+
* @param string $metadataUrl
|
185 |
+
* @return string|null
|
186 |
+
*/
|
187 |
+
private static function getVcsService($metadataUrl) {
|
188 |
+
$service = null;
|
189 |
+
|
190 |
+
//Which hosting service does the URL point to?
|
191 |
+
$host = @parse_url($metadataUrl, PHP_URL_HOST);
|
192 |
+
$path = @parse_url($metadataUrl, PHP_URL_PATH);
|
193 |
+
//Check if the path looks like "/user-name/repository".
|
194 |
+
$usernameRepoRegex = '@^/?([^/]+?)/([^/#?&]+?)/?$@';
|
195 |
+
if ( preg_match($usernameRepoRegex, $path) ) {
|
196 |
+
$knownServices = array(
|
197 |
+
'github.com' => 'GitHub',
|
198 |
+
'bitbucket.org' => 'BitBucket',
|
199 |
+
'gitlab.com' => 'GitLab',
|
200 |
+
);
|
201 |
+
if ( isset($knownServices[$host]) ) {
|
202 |
+
$service = $knownServices[$host];
|
203 |
+
}
|
204 |
+
}
|
205 |
+
|
206 |
+
return $service;
|
207 |
+
}
|
208 |
+
|
209 |
+
/**
|
210 |
+
* Get the latest version of the specified class that has the same major version number
|
211 |
+
* as this factory class.
|
212 |
+
*
|
213 |
+
* @param string $class Partial class name.
|
214 |
+
* @return string|null Full class name.
|
215 |
+
*/
|
216 |
+
protected static function getCompatibleClassVersion($class) {
|
217 |
+
if ( isset(self::$classVersions[$class][self::$latestCompatibleVersion]) ) {
|
218 |
+
return self::$classVersions[$class][self::$latestCompatibleVersion];
|
219 |
+
}
|
220 |
+
return null;
|
221 |
+
}
|
222 |
+
|
223 |
+
/**
|
224 |
+
* Get the specific class name for the latest available version of a class.
|
225 |
+
*
|
226 |
+
* @param string $class
|
227 |
+
* @return null|string
|
228 |
+
*/
|
229 |
+
public static function getLatestClassVersion($class) {
|
230 |
+
if ( !self::$sorted ) {
|
231 |
+
self::sortVersions();
|
232 |
+
}
|
233 |
+
|
234 |
+
if ( isset(self::$classVersions[$class]) ) {
|
235 |
+
return reset(self::$classVersions[$class]);
|
236 |
+
} else {
|
237 |
+
return null;
|
238 |
+
}
|
239 |
+
}
|
240 |
+
|
241 |
+
/**
|
242 |
+
* Sort available class versions in descending order (i.e. newest first).
|
243 |
+
*/
|
244 |
+
protected static function sortVersions() {
|
245 |
+
foreach ( self::$classVersions as $class => $versions ) {
|
246 |
+
uksort($versions, array(__CLASS__, 'compareVersions'));
|
247 |
+
self::$classVersions[$class] = $versions;
|
248 |
+
}
|
249 |
+
self::$sorted = true;
|
250 |
+
}
|
251 |
+
|
252 |
+
protected static function compareVersions($a, $b) {
|
253 |
+
return -version_compare($a, $b);
|
254 |
+
}
|
255 |
+
|
256 |
+
/**
|
257 |
+
* Register a version of a class.
|
258 |
+
*
|
259 |
+
* @access private This method is only for internal use by the library.
|
260 |
+
*
|
261 |
+
* @param string $generalClass Class name without version numbers, e.g. 'PluginUpdateChecker'.
|
262 |
+
* @param string $versionedClass Actual class name, e.g. 'PluginUpdateChecker_1_2'.
|
263 |
+
* @param string $version Version number, e.g. '1.2'.
|
264 |
+
*/
|
265 |
+
public static function addVersion($generalClass, $versionedClass, $version) {
|
266 |
+
if ( empty(self::$myMajorVersion) ) {
|
267 |
+
$nameParts = explode('_', __CLASS__, 3);
|
268 |
+
self::$myMajorVersion = substr(ltrim($nameParts[1], 'v'), 0, 1);
|
269 |
+
}
|
270 |
+
|
271 |
+
//Store the greatest version number that matches our major version.
|
272 |
+
$components = explode('.', $version);
|
273 |
+
if ( $components[0] === self::$myMajorVersion ) {
|
274 |
+
|
275 |
+
if (
|
276 |
+
empty(self::$latestCompatibleVersion)
|
277 |
+
|| version_compare($version, self::$latestCompatibleVersion, '>')
|
278 |
+
) {
|
279 |
+
self::$latestCompatibleVersion = $version;
|
280 |
+
}
|
281 |
+
|
282 |
+
}
|
283 |
+
|
284 |
+
if ( !isset(self::$classVersions[$generalClass]) ) {
|
285 |
+
self::$classVersions[$generalClass] = array();
|
286 |
+
}
|
287 |
+
self::$classVersions[$generalClass][$version] = $versionedClass;
|
288 |
+
self::$sorted = false;
|
289 |
+
}
|
290 |
+
}
|
291 |
+
|
292 |
+
endif;
|
admin/update-checker/Puc/{v4p2 → v4p4}/Metadata.php
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
/**
|
5 |
* A base container for holding information about updates and plugin metadata.
|
@@ -8,7 +8,7 @@ if ( !class_exists('Puc_v4p2_Metadata', false) ):
|
|
8 |
* @copyright 2016
|
9 |
* @access public
|
10 |
*/
|
11 |
-
abstract class
|
12 |
|
13 |
/**
|
14 |
* Create an instance of this class from a JSON document.
|
@@ -30,15 +30,15 @@ if ( !class_exists('Puc_v4p2_Metadata', false) ):
|
|
30 |
/** @var StdClass $apiResponse */
|
31 |
$apiResponse = json_decode($json);
|
32 |
if ( empty($apiResponse) || !is_object($apiResponse) ){
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
);
|
37 |
return false;
|
38 |
}
|
39 |
|
40 |
$valid = $target->validateMetadata($apiResponse);
|
41 |
if ( is_wp_error($valid) ){
|
|
|
42 |
trigger_error($valid->get_error_message(), E_USER_NOTICE);
|
43 |
return false;
|
44 |
}
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Metadata', false) ):
|
3 |
|
4 |
/**
|
5 |
* A base container for holding information about updates and plugin metadata.
|
8 |
* @copyright 2016
|
9 |
* @access public
|
10 |
*/
|
11 |
+
abstract class Puc_v4p4_Metadata {
|
12 |
|
13 |
/**
|
14 |
* Create an instance of this class from a JSON document.
|
30 |
/** @var StdClass $apiResponse */
|
31 |
$apiResponse = json_decode($json);
|
32 |
if ( empty($apiResponse) || !is_object($apiResponse) ){
|
33 |
+
$errorMessage = "Failed to parse update metadata. Try validating your .json file with http://jsonlint.com/";
|
34 |
+
do_action('puc_api_error', new WP_Error('puc-invalid-json', $errorMessage));
|
35 |
+
trigger_error($errorMessage, E_USER_NOTICE);
|
|
|
36 |
return false;
|
37 |
}
|
38 |
|
39 |
$valid = $target->validateMetadata($apiResponse);
|
40 |
if ( is_wp_error($valid) ){
|
41 |
+
do_action('puc_api_error', $valid);
|
42 |
trigger_error($valid->get_error_message(), E_USER_NOTICE);
|
43 |
return false;
|
44 |
}
|
admin/update-checker/Puc/v4p4/OAuthSignature.php
ADDED
@@ -0,0 +1,88 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p4_OAuthSignature', false) ):
|
4 |
+
|
5 |
+
/**
|
6 |
+
* A basic signature generator for zero-legged OAuth 1.0.
|
7 |
+
*/
|
8 |
+
class Puc_v4p4_OAuthSignature {
|
9 |
+
private $consumerKey = '';
|
10 |
+
private $consumerSecret = '';
|
11 |
+
|
12 |
+
public function __construct($consumerKey, $consumerSecret) {
|
13 |
+
$this->consumerKey = $consumerKey;
|
14 |
+
$this->consumerSecret = $consumerSecret;
|
15 |
+
}
|
16 |
+
|
17 |
+
/**
|
18 |
+
* Sign a URL using OAuth 1.0.
|
19 |
+
*
|
20 |
+
* @param string $url The URL to be signed. It may contain query parameters.
|
21 |
+
* @param string $method HTTP method such as "GET", "POST" and so on.
|
22 |
+
* @return string The signed URL.
|
23 |
+
*/
|
24 |
+
public function sign($url, $method = 'GET') {
|
25 |
+
$parameters = array();
|
26 |
+
|
27 |
+
//Parse query parameters.
|
28 |
+
$query = @parse_url($url, PHP_URL_QUERY);
|
29 |
+
if ( !empty($query) ) {
|
30 |
+
parse_str($query, $parsedParams);
|
31 |
+
if ( is_array($parameters) ) {
|
32 |
+
$parameters = $parsedParams;
|
33 |
+
}
|
34 |
+
//Remove the query string from the URL. We'll replace it later.
|
35 |
+
$url = substr($url, 0, strpos($url, '?'));
|
36 |
+
}
|
37 |
+
|
38 |
+
$parameters = array_merge(
|
39 |
+
$parameters,
|
40 |
+
array(
|
41 |
+
'oauth_consumer_key' => $this->consumerKey,
|
42 |
+
'oauth_nonce' => $this->nonce(),
|
43 |
+
'oauth_signature_method' => 'HMAC-SHA1',
|
44 |
+
'oauth_timestamp' => time(),
|
45 |
+
'oauth_version' => '1.0',
|
46 |
+
)
|
47 |
+
);
|
48 |
+
unset($parameters['oauth_signature']);
|
49 |
+
|
50 |
+
//Parameters must be sorted alphabetically before signing.
|
51 |
+
ksort($parameters);
|
52 |
+
|
53 |
+
//The most complicated part of the request - generating the signature.
|
54 |
+
//The string to sign contains the HTTP method, the URL path, and all of
|
55 |
+
//our query parameters. Everything is URL encoded. Then we concatenate
|
56 |
+
//them with ampersands into a single string to hash.
|
57 |
+
$encodedVerb = urlencode($method);
|
58 |
+
$encodedUrl = urlencode($url);
|
59 |
+
$encodedParams = urlencode(http_build_query($parameters, '', '&'));
|
60 |
+
|
61 |
+
$stringToSign = $encodedVerb . '&' . $encodedUrl . '&' . $encodedParams;
|
62 |
+
|
63 |
+
//Since we only have one OAuth token (the consumer secret) we only have
|
64 |
+
//to use it as our HMAC key. However, we still have to append an & to it
|
65 |
+
//as if we were using it with additional tokens.
|
66 |
+
$secret = urlencode($this->consumerSecret) . '&';
|
67 |
+
|
68 |
+
//The signature is a hash of the consumer key and the base string. Note
|
69 |
+
//that we have to get the raw output from hash_hmac and base64 encode
|
70 |
+
//the binary data result.
|
71 |
+
$parameters['oauth_signature'] = base64_encode(hash_hmac('sha1', $stringToSign, $secret, true));
|
72 |
+
|
73 |
+
return ($url . '?' . http_build_query($parameters));
|
74 |
+
}
|
75 |
+
|
76 |
+
/**
|
77 |
+
* Generate a random nonce.
|
78 |
+
*
|
79 |
+
* @return string
|
80 |
+
*/
|
81 |
+
private function nonce() {
|
82 |
+
$mt = microtime();
|
83 |
+
$rand = mt_rand();
|
84 |
+
return md5($mt . '_' . $rand);
|
85 |
+
}
|
86 |
+
}
|
87 |
+
|
88 |
+
endif;
|
admin/update-checker/Puc/{v4p2 → v4p4}/Plugin/Info.php
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
/**
|
5 |
* A container class for holding and transforming various plugin metadata.
|
@@ -8,7 +8,7 @@ if ( !class_exists('Puc_v4p2_Plugin_Info', false) ):
|
|
8 |
* @copyright 2016
|
9 |
* @access public
|
10 |
*/
|
11 |
-
class
|
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;
|
@@ -16,9 +16,11 @@ if ( !class_exists('Puc_v4p2_Plugin_Info', false) ):
|
|
16 |
public $version;
|
17 |
public $homepage;
|
18 |
public $sections = array();
|
|
|
|
|
19 |
public $banners;
|
|
|
20 |
public $translations = array();
|
21 |
-
public $download_url;
|
22 |
|
23 |
public $author;
|
24 |
public $author_homepage;
|
@@ -51,8 +53,9 @@ if ( !class_exists('Puc_v4p2_Plugin_Info', false) ):
|
|
51 |
return null;
|
52 |
}
|
53 |
|
54 |
-
//json_decode decodes assoc. arrays as objects. We want
|
55 |
$instance->sections = (array)$instance->sections;
|
|
|
56 |
|
57 |
return $instance;
|
58 |
}
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Plugin_Info', false) ):
|
3 |
|
4 |
/**
|
5 |
* A container class for holding and transforming various plugin metadata.
|
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;
|
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;
|
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 |
}
|
admin/update-checker/Puc/{v4p2 → v4p4}/Plugin/Update.php
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
/**
|
5 |
* A simple container class for holding information about an available update.
|
@@ -8,28 +8,29 @@ if ( !class_exists('Puc_v4p2_Plugin_Update', false) ):
|
|
8 |
* @copyright 2016
|
9 |
* @access public
|
10 |
*/
|
11 |
-
class
|
12 |
public $id = 0;
|
13 |
public $homepage;
|
14 |
public $upgrade_notice;
|
15 |
public $tested;
|
|
|
16 |
public $filename; //Plugin filename relative to the plugins directory.
|
17 |
|
18 |
protected static $extraFields = array(
|
19 |
-
'id', 'homepage', 'tested', 'upgrade_notice', 'filename',
|
20 |
);
|
21 |
|
22 |
/**
|
23 |
* Create a new instance of PluginUpdate from its JSON-encoded representation.
|
24 |
*
|
25 |
* @param string $json
|
26 |
-
* @return
|
27 |
*/
|
28 |
public static function fromJson($json){
|
29 |
//Since update-related information is simply a subset of the full plugin info,
|
30 |
//we can parse the update JSON as if it was a plugin info string, then copy over
|
31 |
//the parts that we care about.
|
32 |
-
$pluginInfo =
|
33 |
if ( $pluginInfo !== null ) {
|
34 |
return self::fromPluginInfo($pluginInfo);
|
35 |
} else {
|
@@ -41,8 +42,8 @@ if ( !class_exists('Puc_v4p2_Plugin_Update', false) ):
|
|
41 |
* Create a new instance of PluginUpdate based on an instance of PluginInfo.
|
42 |
* Basically, this just copies a subset of fields from one object to another.
|
43 |
*
|
44 |
-
* @param
|
45 |
-
* @return
|
46 |
*/
|
47 |
public static function fromPluginInfo($info){
|
48 |
return self::fromObject($info);
|
@@ -51,8 +52,8 @@ if ( !class_exists('Puc_v4p2_Plugin_Update', false) ):
|
|
51 |
/**
|
52 |
* Create a new instance by copying the necessary fields from another object.
|
53 |
*
|
54 |
-
* @param StdClass|
|
55 |
-
* @return
|
56 |
*/
|
57 |
public static function fromObject($object) {
|
58 |
$update = new self();
|
@@ -72,7 +73,7 @@ if ( !class_exists('Puc_v4p2_Plugin_Update', false) ):
|
|
72 |
*
|
73 |
* @return object
|
74 |
*/
|
75 |
-
public function toWpFormat(){
|
76 |
$update = parent::toWpFormat();
|
77 |
|
78 |
$update->id = $this->id;
|
@@ -80,10 +81,28 @@ if ( !class_exists('Puc_v4p2_Plugin_Update', false) ):
|
|
80 |
$update->tested = $this->tested;
|
81 |
$update->plugin = $this->filename;
|
82 |
|
83 |
-
if ( !empty($this->upgrade_notice) ){
|
84 |
$update->upgrade_notice = $this->upgrade_notice;
|
85 |
}
|
86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
87 |
return $update;
|
88 |
}
|
89 |
}
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Plugin_Update', false) ):
|
3 |
|
4 |
/**
|
5 |
* A simple container class for holding information about an available update.
|
8 |
* @copyright 2016
|
9 |
* @access public
|
10 |
*/
|
11 |
+
class Puc_v4p4_Plugin_Update extends Puc_v4p4_Update {
|
12 |
public $id = 0;
|
13 |
public $homepage;
|
14 |
public $upgrade_notice;
|
15 |
public $tested;
|
16 |
+
public $icons = array();
|
17 |
public $filename; //Plugin filename relative to the plugins directory.
|
18 |
|
19 |
protected static $extraFields = array(
|
20 |
+
'id', 'homepage', 'tested', 'upgrade_notice', 'icons', 'filename',
|
21 |
);
|
22 |
|
23 |
/**
|
24 |
* Create a new instance of PluginUpdate from its JSON-encoded representation.
|
25 |
*
|
26 |
* @param string $json
|
27 |
+
* @return Puc_v4p4_Plugin_Update|null
|
28 |
*/
|
29 |
public static function fromJson($json){
|
30 |
//Since update-related information is simply a subset of the full plugin info,
|
31 |
//we can parse the update JSON as if it was a plugin info string, then copy over
|
32 |
//the parts that we care about.
|
33 |
+
$pluginInfo = Puc_v4p4_Plugin_Info::fromJson($json);
|
34 |
if ( $pluginInfo !== null ) {
|
35 |
return self::fromPluginInfo($pluginInfo);
|
36 |
} else {
|
42 |
* Create a new instance of PluginUpdate based on an instance of PluginInfo.
|
43 |
* Basically, this just copies a subset of fields from one object to another.
|
44 |
*
|
45 |
+
* @param Puc_v4p4_Plugin_Info $info
|
46 |
+
* @return Puc_v4p4_Plugin_Update
|
47 |
*/
|
48 |
public static function fromPluginInfo($info){
|
49 |
return self::fromObject($info);
|
52 |
/**
|
53 |
* Create a new instance by copying the necessary fields from another object.
|
54 |
*
|
55 |
+
* @param StdClass|Puc_v4p4_Plugin_Info|Puc_v4p4_Plugin_Update $object The source object.
|
56 |
+
* @return Puc_v4p4_Plugin_Update The new copy.
|
57 |
*/
|
58 |
public static function fromObject($object) {
|
59 |
$update = new self();
|
73 |
*
|
74 |
* @return object
|
75 |
*/
|
76 |
+
public function toWpFormat() {
|
77 |
$update = parent::toWpFormat();
|
78 |
|
79 |
$update->id = $this->id;
|
81 |
$update->tested = $this->tested;
|
82 |
$update->plugin = $this->filename;
|
83 |
|
84 |
+
if ( !empty($this->upgrade_notice) ) {
|
85 |
$update->upgrade_notice = $this->upgrade_notice;
|
86 |
}
|
87 |
|
88 |
+
if ( !empty($this->icons) && is_array($this->icons) ) {
|
89 |
+
//This should be an array with up to 4 keys: 'svg', '1x', '2x' and 'default'.
|
90 |
+
//Docs: https://developer.wordpress.org/plugins/wordpress-org/plugin-assets/#plugin-icons
|
91 |
+
$icons = array_intersect_key(
|
92 |
+
$this->icons,
|
93 |
+
array('svg' => true, '1x' => true, '2x' => true, 'default' => true)
|
94 |
+
);
|
95 |
+
if ( !empty($icons) ) {
|
96 |
+
$update->icons = $icons;
|
97 |
+
|
98 |
+
//It appears that the 'default' icon isn't used anywhere in WordPress 4.9,
|
99 |
+
//but lets set it just in case a future release needs it.
|
100 |
+
if ( !isset($update->icons['default']) ) {
|
101 |
+
$update->icons['default'] = current($update->icons);
|
102 |
+
}
|
103 |
+
}
|
104 |
+
}
|
105 |
+
|
106 |
return $update;
|
107 |
}
|
108 |
}
|
admin/update-checker/Puc/{v4p2 → v4p4}/Plugin/UpdateChecker.php
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
/**
|
5 |
* A custom plugin update checker.
|
@@ -8,7 +8,7 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
8 |
* @copyright 2016
|
9 |
* @access public
|
10 |
*/
|
11 |
-
class
|
12 |
protected $updateTransient = 'update_plugins';
|
13 |
protected $translationType = 'plugin';
|
14 |
|
@@ -17,6 +17,7 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
17 |
public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory.
|
18 |
|
19 |
private $cachedInstalledVersion = null;
|
|
|
20 |
|
21 |
/**
|
22 |
* Class constructor.
|
@@ -57,6 +58,12 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
57 |
$this->muPluginFile = $this->pluginFile;
|
58 |
}
|
59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
60 |
parent::__construct($metadataUrl, dirname($this->pluginFile), $slug, $checkPeriod, $optionName);
|
61 |
}
|
62 |
|
@@ -64,10 +71,10 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
64 |
* Create an instance of the scheduler.
|
65 |
*
|
66 |
* @param int $checkPeriod
|
67 |
-
* @return
|
68 |
*/
|
69 |
protected function createScheduler($checkPeriod) {
|
70 |
-
$scheduler = new
|
71 |
register_deactivation_hook($this->pluginFile, array($scheduler, 'removeUpdaterCron'));
|
72 |
return $scheduler;
|
73 |
}
|
@@ -82,6 +89,7 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
82 |
//Override requests for plugin information
|
83 |
add_filter('plugins_api', array($this, 'injectInfo'), 20, 3);
|
84 |
|
|
|
85 |
add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2);
|
86 |
add_action('admin_init', array($this, 'handleManualCheck'));
|
87 |
add_action('all_admin_notices', array($this, 'displayManualCheckResult'));
|
@@ -93,19 +101,47 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
93 |
parent::installHooks();
|
94 |
}
|
95 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
96 |
/**
|
97 |
* Retrieve plugin info from the configured API endpoint.
|
98 |
*
|
99 |
* @uses wp_remote_get()
|
100 |
*
|
101 |
* @param array $queryArgs Additional query arguments to append to the request. Optional.
|
102 |
-
* @return
|
103 |
*/
|
104 |
public function requestInfo($queryArgs = array()) {
|
105 |
-
list($pluginInfo, $result) = $this->requestMetadata('
|
106 |
|
107 |
if ( $pluginInfo !== null ) {
|
108 |
-
/** @var
|
109 |
$pluginInfo->filename = $this->pluginFile;
|
110 |
$pluginInfo->slug = $this->slug;
|
111 |
}
|
@@ -119,7 +155,7 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
119 |
*
|
120 |
* @uses PluginUpdateChecker::requestInfo()
|
121 |
*
|
122 |
-
* @return
|
123 |
*/
|
124 |
public function requestUpdate() {
|
125 |
//For the sake of simplicity, this function just calls requestInfo()
|
@@ -128,7 +164,7 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
128 |
if ( $pluginInfo === null ){
|
129 |
return null;
|
130 |
}
|
131 |
-
$update =
|
132 |
|
133 |
$update = $this->filterUpdateResult($update);
|
134 |
|
@@ -315,12 +351,12 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
315 |
* Uses cached update data. To retrieve update information straight from
|
316 |
* the metadata URL, call requestUpdate() instead.
|
317 |
*
|
318 |
-
* @return
|
319 |
*/
|
320 |
public function getUpdate() {
|
321 |
$update = parent::getUpdate();
|
322 |
if ( isset($update) ) {
|
323 |
-
/** @var
|
324 |
$update->filename = $this->pluginFile;
|
325 |
}
|
326 |
return $update;
|
@@ -328,7 +364,8 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
328 |
|
329 |
/**
|
330 |
* Add a "Check for updates" link to the plugin row in the "Plugins" page. By default,
|
331 |
-
* the new link will appear after the "Visit plugin site" link
|
|
|
332 |
*
|
333 |
* You can change the link text by using the "puc_manual_check_link-$slug" filter.
|
334 |
* Returning an empty string from the filter will disable the link.
|
@@ -365,6 +402,81 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
365 |
return $pluginMeta;
|
366 |
}
|
367 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
368 |
/**
|
369 |
* Check for updates when the user clicks the "Check for updates" link.
|
370 |
* @see self::addCheckForUpdatesLink()
|
@@ -381,6 +493,34 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
381 |
if ( $shouldCheck ) {
|
382 |
$update = $this->checkForUpdates();
|
383 |
$status = ($update === null) ? 'no_update' : 'update_available';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
384 |
wp_redirect(add_query_arg(
|
385 |
array(
|
386 |
'puc_update_check_result' => $status,
|
@@ -399,22 +539,69 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
399 |
*/
|
400 |
public function displayManualCheckResult() {
|
401 |
if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->slug) ) {
|
402 |
-
$status
|
403 |
-
$title
|
|
|
|
|
|
|
404 |
if ( $status == 'no_update' ) {
|
405 |
$message = sprintf(_x('The %s plugin is up to date.', 'the plugin title', 'plugin-update-checker'), $title);
|
406 |
} else if ( $status == 'update_available' ) {
|
407 |
$message = sprintf(_x('A new version of the %s plugin is available.', 'the plugin title', 'plugin-update-checker'), $title);
|
|
|
|
|
|
|
|
|
|
|
|
|
408 |
} else {
|
409 |
$message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), htmlentities($status));
|
|
|
410 |
}
|
411 |
printf(
|
412 |
-
'<div class="
|
413 |
-
|
|
|
|
|
414 |
);
|
415 |
}
|
416 |
}
|
417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
418 |
/**
|
419 |
* Get the translated plugin title.
|
420 |
*
|
@@ -488,6 +675,13 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
488 |
return $this->pluginAbsolutePath;
|
489 |
}
|
490 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
491 |
/**
|
492 |
* Register a callback for filtering query arguments.
|
493 |
*
|
@@ -537,6 +731,10 @@ if ( !class_exists('Puc_v4p2_Plugin_UpdateChecker', false) ):
|
|
537 |
public function addResultFilter($callback) {
|
538 |
$this->addFilter('request_info_result', $callback, 10, 2);
|
539 |
}
|
|
|
|
|
|
|
|
|
540 |
}
|
541 |
|
542 |
endif;
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Plugin_UpdateChecker', false) ):
|
3 |
|
4 |
/**
|
5 |
* A custom plugin update checker.
|
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 |
|
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.
|
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 |
|
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 |
}
|
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'));
|
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 |
}
|
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()
|
164 |
if ( $pluginInfo === null ){
|
165 |
return null;
|
166 |
}
|
167 |
+
$update = Puc_v4p4_Plugin_Update::fromPluginInfo($pluginInfo);
|
168 |
|
169 |
$update = $this->filterUpdateResult($update);
|
170 |
|
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;
|
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.
|
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()
|
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,
|
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 |
*
|
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 |
*
|
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 |
|
740 |
endif;
|
admin/update-checker/Puc/{v4p2 → v4p4}/Scheduler.php
RENAMED
@@ -1,11 +1,11 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
/**
|
5 |
* The scheduler decides when and how often to check for updates.
|
6 |
-
* It calls @see
|
7 |
*/
|
8 |
-
class
|
9 |
public $checkPeriod = 12; //How often to check for updates (in hours).
|
10 |
public $throttleRedundantChecks = false; //Check less often if we already know that an update is available.
|
11 |
public $throttledCheckPeriod = 72;
|
@@ -13,7 +13,7 @@ if ( !class_exists('Puc_v4p2_Scheduler', false) ):
|
|
13 |
protected $hourlyCheckHooks = array('load-update.php');
|
14 |
|
15 |
/**
|
16 |
-
* @var
|
17 |
*/
|
18 |
protected $updateChecker;
|
19 |
|
@@ -22,7 +22,7 @@ if ( !class_exists('Puc_v4p2_Scheduler', false) ):
|
|
22 |
/**
|
23 |
* Scheduler constructor.
|
24 |
*
|
25 |
-
* @param
|
26 |
* @param int $checkPeriod How often to check for updates (in hours).
|
27 |
* @param array $hourlyHooks
|
28 |
*/
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Scheduler', false) ):
|
3 |
|
4 |
/**
|
5 |
* The scheduler decides when and how often to check for updates.
|
6 |
+
* It calls @see Puc_v4p4_UpdateChecker::checkForUpdates() to perform the actual checks.
|
7 |
*/
|
8 |
+
class Puc_v4p4_Scheduler {
|
9 |
public $checkPeriod = 12; //How often to check for updates (in hours).
|
10 |
public $throttleRedundantChecks = false; //Check less often if we already know that an update is available.
|
11 |
public $throttledCheckPeriod = 72;
|
13 |
protected $hourlyCheckHooks = array('load-update.php');
|
14 |
|
15 |
/**
|
16 |
+
* @var Puc_v4p4_UpdateChecker
|
17 |
*/
|
18 |
protected $updateChecker;
|
19 |
|
22 |
/**
|
23 |
* Scheduler constructor.
|
24 |
*
|
25 |
+
* @param Puc_v4p4_UpdateChecker $updateChecker
|
26 |
* @param int $checkPeriod How often to check for updates (in hours).
|
27 |
* @param array $hourlyHooks
|
28 |
*/
|
admin/update-checker/Puc/{v4p2 → v4p4}/StateStore.php
RENAMED
@@ -1,8 +1,8 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
if ( !class_exists('
|
4 |
|
5 |
-
class
|
6 |
/**
|
7 |
* @var int Last update check timestamp.
|
8 |
*/
|
@@ -14,7 +14,7 @@ if ( !class_exists('Puc_v4p2_StateStore', false) ):
|
|
14 |
protected $checkedVersion = '';
|
15 |
|
16 |
/**
|
17 |
-
* @var
|
18 |
*/
|
19 |
protected $update = null;
|
20 |
|
@@ -65,7 +65,7 @@ if ( !class_exists('Puc_v4p2_StateStore', false) ):
|
|
65 |
}
|
66 |
|
67 |
/**
|
68 |
-
* @return null|
|
69 |
*/
|
70 |
public function getUpdate() {
|
71 |
$this->lazyLoad();
|
@@ -73,10 +73,10 @@ if ( !class_exists('Puc_v4p2_StateStore', false) ):
|
|
73 |
}
|
74 |
|
75 |
/**
|
76 |
-
* @param
|
77 |
* @return $this
|
78 |
*/
|
79 |
-
public function setUpdate(
|
80 |
$this->lazyLoad();
|
81 |
$this->update = $update;
|
82 |
return $this;
|
@@ -138,7 +138,7 @@ if ( !class_exists('Puc_v4p2_StateStore', false) ):
|
|
138 |
$updateClass = get_class($this->update);
|
139 |
$state->updateClass = $updateClass;
|
140 |
$prefix = $this->getLibPrefix();
|
141 |
-
if (
|
142 |
$state->updateBaseClass = substr($updateClass, strlen($prefix));
|
143 |
}
|
144 |
}
|
@@ -169,8 +169,8 @@ if ( !class_exists('Puc_v4p2_StateStore', false) ):
|
|
169 |
return;
|
170 |
}
|
171 |
|
172 |
-
$this->lastCheck = intval(
|
173 |
-
$this->checkedVersion =
|
174 |
$this->update = null;
|
175 |
|
176 |
if ( isset($state->update) ) {
|
1 |
<?php
|
2 |
|
3 |
+
if ( !class_exists('Puc_v4p4_StateStore', false) ):
|
4 |
|
5 |
+
class Puc_v4p4_StateStore {
|
6 |
/**
|
7 |
* @var int Last update check timestamp.
|
8 |
*/
|
14 |
protected $checkedVersion = '';
|
15 |
|
16 |
/**
|
17 |
+
* @var Puc_v4p4_Update|null Cached update.
|
18 |
*/
|
19 |
protected $update = null;
|
20 |
|
65 |
}
|
66 |
|
67 |
/**
|
68 |
+
* @return null|Puc_v4p4_Update
|
69 |
*/
|
70 |
public function getUpdate() {
|
71 |
$this->lazyLoad();
|
73 |
}
|
74 |
|
75 |
/**
|
76 |
+
* @param Puc_v4p4_Update|null $update
|
77 |
* @return $this
|
78 |
*/
|
79 |
+
public function setUpdate(Puc_v4p4_Update $update = null) {
|
80 |
$this->lazyLoad();
|
81 |
$this->update = $update;
|
82 |
return $this;
|
138 |
$updateClass = get_class($this->update);
|
139 |
$state->updateClass = $updateClass;
|
140 |
$prefix = $this->getLibPrefix();
|
141 |
+
if ( Puc_v4p4_Utils::startsWith($updateClass, $prefix) ) {
|
142 |
$state->updateBaseClass = substr($updateClass, strlen($prefix));
|
143 |
}
|
144 |
}
|
169 |
return;
|
170 |
}
|
171 |
|
172 |
+
$this->lastCheck = intval(Puc_v4p4_Utils::get($state, 'lastCheck', 0));
|
173 |
+
$this->checkedVersion = Puc_v4p4_Utils::get($state, 'checkedVersion', '');
|
174 |
$this->update = null;
|
175 |
|
176 |
if ( isset($state->update) ) {
|
admin/update-checker/Puc/{v4p2 → v4p4}/Theme/Update.php
RENAMED
@@ -1,8 +1,8 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
if ( !class_exists('
|
4 |
|
5 |
-
class
|
6 |
public $details_url = '';
|
7 |
|
8 |
protected static $extraFields = array('details_url');
|
@@ -44,8 +44,8 @@ if ( !class_exists('Puc_v4p2_Theme_Update', false) ):
|
|
44 |
/**
|
45 |
* Create a new instance by copying the necessary fields from another object.
|
46 |
*
|
47 |
-
* @param StdClass|
|
48 |
-
* @return
|
49 |
*/
|
50 |
public static function fromObject($object) {
|
51 |
$update = new self();
|
1 |
<?php
|
2 |
|
3 |
+
if ( !class_exists('Puc_v4p4_Theme_Update', false) ):
|
4 |
|
5 |
+
class Puc_v4p4_Theme_Update extends Puc_v4p4_Update {
|
6 |
public $details_url = '';
|
7 |
|
8 |
protected static $extraFields = array('details_url');
|
44 |
/**
|
45 |
* Create a new instance by copying the necessary fields from another object.
|
46 |
*
|
47 |
+
* @param StdClass|Puc_v4p4_Theme_Update $object The source object.
|
48 |
+
* @return Puc_v4p4_Theme_Update The new copy.
|
49 |
*/
|
50 |
public static function fromObject($object) {
|
51 |
$update = new self();
|
admin/update-checker/Puc/{v4p2 → v4p4}/Theme/UpdateChecker.php
RENAMED
@@ -1,8 +1,8 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
if ( !class_exists('
|
4 |
|
5 |
-
class
|
6 |
protected $filterSuffix = 'theme';
|
7 |
protected $updateTransient = 'update_themes';
|
8 |
protected $translationType = 'theme';
|
@@ -45,13 +45,13 @@ if ( !class_exists('Puc_v4p2_Theme_UpdateChecker', false) ):
|
|
45 |
/**
|
46 |
* Retrieve the latest update (if any) from the configured API endpoint.
|
47 |
*
|
48 |
-
* @return
|
49 |
*/
|
50 |
public function requestUpdate() {
|
51 |
-
list($themeUpdate, $result) = $this->requestMetadata('
|
52 |
|
53 |
if ( $themeUpdate !== null ) {
|
54 |
-
/** @var
|
55 |
$themeUpdate->slug = $this->slug;
|
56 |
}
|
57 |
|
@@ -72,14 +72,24 @@ if ( !class_exists('Puc_v4p2_Theme_UpdateChecker', false) ):
|
|
72 |
return $this->theme->get('Version');
|
73 |
}
|
74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
/**
|
76 |
* Create an instance of the scheduler.
|
77 |
*
|
78 |
* @param int $checkPeriod
|
79 |
-
* @return
|
80 |
*/
|
81 |
protected function createScheduler($checkPeriod) {
|
82 |
-
return new
|
83 |
}
|
84 |
|
85 |
/**
|
@@ -92,6 +102,10 @@ if ( !class_exists('Puc_v4p2_Theme_UpdateChecker', false) ):
|
|
92 |
return $this->upgraderStatus->isThemeBeingUpgraded($this->stylesheet, $upgrader);
|
93 |
}
|
94 |
|
|
|
|
|
|
|
|
|
95 |
/**
|
96 |
* Register a callback for filtering query arguments.
|
97 |
*
|
1 |
<?php
|
2 |
|
3 |
+
if ( !class_exists('Puc_v4p4_Theme_UpdateChecker', false) ):
|
4 |
|
5 |
+
class Puc_v4p4_Theme_UpdateChecker extends Puc_v4p4_UpdateChecker {
|
6 |
protected $filterSuffix = 'theme';
|
7 |
protected $updateTransient = 'update_themes';
|
8 |
protected $translationType = 'theme';
|
45 |
/**
|
46 |
* Retrieve the latest update (if any) from the configured API endpoint.
|
47 |
*
|
48 |
+
* @return Puc_v4p4_Update|null An instance of Update, or NULL when no updates are available.
|
49 |
*/
|
50 |
public function requestUpdate() {
|
51 |
+
list($themeUpdate, $result) = $this->requestMetadata('Puc_v4p4_Theme_Update', 'request_update');
|
52 |
|
53 |
if ( $themeUpdate !== null ) {
|
54 |
+
/** @var Puc_v4p4_Theme_Update $themeUpdate */
|
55 |
$themeUpdate->slug = $this->slug;
|
56 |
}
|
57 |
|
72 |
return $this->theme->get('Version');
|
73 |
}
|
74 |
|
75 |
+
/**
|
76 |
+
* @return string
|
77 |
+
*/
|
78 |
+
public function getAbsoluteDirectoryPath() {
|
79 |
+
if ( method_exists($this->theme, 'get_stylesheet_directory') ) {
|
80 |
+
return $this->theme->get_stylesheet_directory(); //Available since WP 3.4.
|
81 |
+
}
|
82 |
+
return get_theme_root($this->stylesheet) . '/' . $this->stylesheet;
|
83 |
+
}
|
84 |
+
|
85 |
/**
|
86 |
* Create an instance of the scheduler.
|
87 |
*
|
88 |
* @param int $checkPeriod
|
89 |
+
* @return Puc_v4p4_Scheduler
|
90 |
*/
|
91 |
protected function createScheduler($checkPeriod) {
|
92 |
+
return new Puc_v4p4_Scheduler($this, $checkPeriod, array('load-themes.php'));
|
93 |
}
|
94 |
|
95 |
/**
|
102 |
return $this->upgraderStatus->isThemeBeingUpgraded($this->stylesheet, $upgrader);
|
103 |
}
|
104 |
|
105 |
+
protected function createDebugBarExtension() {
|
106 |
+
return new Puc_v4p4_DebugBar_Extension($this, 'Puc_v4p4_DebugBar_ThemePanel');
|
107 |
+
}
|
108 |
+
|
109 |
/**
|
110 |
* Register a callback for filtering query arguments.
|
111 |
*
|
admin/update-checker/Puc/{v4p2 → v4p4}/Update.php
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
/**
|
5 |
* A simple container class for holding information about an available update.
|
@@ -7,7 +7,7 @@ if ( !class_exists('Puc_v4p2_Update', false) ):
|
|
7 |
* @author Janis Elsts
|
8 |
* @access public
|
9 |
*/
|
10 |
-
abstract class
|
11 |
public $slug;
|
12 |
public $version;
|
13 |
public $download_url;
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Update', false) ):
|
3 |
|
4 |
/**
|
5 |
* A simple container class for holding information about an available update.
|
7 |
* @author Janis Elsts
|
8 |
* @access public
|
9 |
*/
|
10 |
+
abstract class Puc_v4p4_Update extends Puc_v4p4_Metadata {
|
11 |
public $slug;
|
12 |
public $version;
|
13 |
public $download_url;
|
admin/update-checker/Puc/{v4p2 → v4p4}/UpdateChecker.php
RENAMED
@@ -1,8 +1,8 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
if ( !class_exists('
|
4 |
|
5 |
-
abstract class
|
6 |
protected $filterSuffix = '';
|
7 |
protected $updateTransient = '';
|
8 |
protected $translationType = ''; //"plugin" or "theme".
|
@@ -36,20 +36,25 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
36 |
public $slug = '';
|
37 |
|
38 |
/**
|
39 |
-
* @var
|
40 |
*/
|
41 |
public $scheduler;
|
42 |
|
43 |
/**
|
44 |
-
* @var
|
45 |
*/
|
46 |
protected $upgraderStatus;
|
47 |
|
48 |
/**
|
49 |
-
* @var
|
50 |
*/
|
51 |
protected $updateState;
|
52 |
|
|
|
|
|
|
|
|
|
|
|
53 |
public function __construct($metadataUrl, $directoryName, $slug = null, $checkPeriod = 12, $optionName = '') {
|
54 |
$this->debugMode = (bool)(constant('WP_DEBUG'));
|
55 |
$this->metadataUrl = $metadataUrl;
|
@@ -68,8 +73,8 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
68 |
}
|
69 |
|
70 |
$this->scheduler = $this->createScheduler($checkPeriod);
|
71 |
-
$this->upgraderStatus = new
|
72 |
-
$this->updateState = new
|
73 |
|
74 |
if ( did_action('init') ) {
|
75 |
$this->loadTextDomain();
|
@@ -123,6 +128,31 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
123 |
|
124 |
//Allow HTTP requests to the metadata URL even if it's on a local host.
|
125 |
add_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10, 2);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
}
|
127 |
|
128 |
/**
|
@@ -169,14 +199,14 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
169 |
* and substitute their own scheduler.
|
170 |
*
|
171 |
* @param int $checkPeriod
|
172 |
-
* @return
|
173 |
*/
|
174 |
abstract protected function createScheduler($checkPeriod);
|
175 |
|
176 |
/**
|
177 |
* Check for updates. The results are stored in the DB option specified in $optionName.
|
178 |
*
|
179 |
-
* @return
|
180 |
*/
|
181 |
public function checkForUpdates() {
|
182 |
$installedVersion = $this->getInstalledVersion();
|
@@ -189,6 +219,10 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
189 |
return null;
|
190 |
}
|
191 |
|
|
|
|
|
|
|
|
|
192 |
$state = $this->updateState;
|
193 |
$state->setLastCheckToNow()
|
194 |
->setCheckedVersion($installedVersion)
|
@@ -197,13 +231,16 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
197 |
$state->setUpdate($this->requestUpdate());
|
198 |
$state->save();
|
199 |
|
|
|
|
|
|
|
200 |
return $this->getUpdate();
|
201 |
}
|
202 |
|
203 |
/**
|
204 |
* Load the update checker state from the DB.
|
205 |
*
|
206 |
-
* @return
|
207 |
*/
|
208 |
public function getUpdateState() {
|
209 |
return $this->updateState->lazyLoad();
|
@@ -228,7 +265,7 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
228 |
* Uses cached update data. To retrieve update information straight from
|
229 |
* the metadata URL, call requestUpdate() instead.
|
230 |
*
|
231 |
-
* @return
|
232 |
*/
|
233 |
public function getUpdate() {
|
234 |
$update = $this->updateState->getUpdate();
|
@@ -249,16 +286,16 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
249 |
*
|
250 |
* Subclasses should run the update through filterUpdateResult before returning it.
|
251 |
*
|
252 |
-
* @return
|
253 |
*/
|
254 |
abstract public function requestUpdate();
|
255 |
|
256 |
/**
|
257 |
* Filter the result of a requestUpdate() call.
|
258 |
*
|
259 |
-
* @param
|
260 |
* @param array|WP_Error|null $httpResult The value returned by wp_remote_get(), if any.
|
261 |
-
* @return
|
262 |
*/
|
263 |
protected function filterUpdateResult($update, $httpResult = null) {
|
264 |
//Let plugins/themes modify the update.
|
@@ -279,6 +316,13 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
279 |
*/
|
280 |
abstract public function getInstalledVersion();
|
281 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
/**
|
283 |
* Trigger a PHP error, but only when $debugMode is enabled.
|
284 |
*
|
@@ -308,6 +352,34 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
308 |
return $name . '-' . $this->slug;
|
309 |
}
|
310 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
311 |
/* -------------------------------------------------------------------
|
312 |
* PUC filters and filter utilities
|
313 |
* -------------------------------------------------------------------
|
@@ -422,7 +494,7 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
422 |
* @param string $metaClass Parse the JSON as an instance of this class. It must have a static fromJson method.
|
423 |
* @param string $filterRoot
|
424 |
* @param array $queryArgs Additional query arguments.
|
425 |
-
* @return array [
|
426 |
*/
|
427 |
protected function requestMetadata($metaClass, $filterRoot, $queryArgs = array()) {
|
428 |
//Query args to append to the URL. Plugins can add their own by using a filter callback (see addQueryArgFilter()).
|
@@ -461,6 +533,7 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
461 |
if ( !is_wp_error($status) ){
|
462 |
$metadata = call_user_func(array($metaClass, 'fromJson'), $result['body']);
|
463 |
} else {
|
|
|
464 |
$this->triggerError(
|
465 |
sprintf('The URL %s does not point to a valid metadata file. ', $url)
|
466 |
. $status->get_error_message(),
|
@@ -544,6 +617,9 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
544 |
* @return array
|
545 |
*/
|
546 |
protected function getInstalledTranslations() {
|
|
|
|
|
|
|
547 |
$installedTranslations = wp_get_installed_translations($this->translationType . 's');
|
548 |
if ( isset($installedTranslations[$this->directoryName]) ) {
|
549 |
$installedTranslations = $installedTranslations[$this->directoryName];
|
@@ -787,6 +863,34 @@ if ( !class_exists('Puc_v4p2_UpdateChecker', false) ):
|
|
787 |
* @return array Format: ['HeaderKey' => 'Header Name']
|
788 |
*/
|
789 |
abstract protected function getHeaderNames();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
790 |
}
|
791 |
|
792 |
endif;
|
1 |
<?php
|
2 |
|
3 |
+
if ( !class_exists('Puc_v4p4_UpdateChecker', false) ):
|
4 |
|
5 |
+
abstract class Puc_v4p4_UpdateChecker {
|
6 |
protected $filterSuffix = '';
|
7 |
protected $updateTransient = '';
|
8 |
protected $translationType = ''; //"plugin" or "theme".
|
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;
|
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();
|
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 |
/**
|
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();
|
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)
|
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();
|
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();
|
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.
|
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 |
*
|
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 |
* PUC filters and filter utilities
|
385 |
* -------------------------------------------------------------------
|
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()).
|
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(),
|
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];
|
863 |
* @return array Format: ['HeaderKey' => 'Header Name']
|
864 |
*/
|
865 |
abstract protected function getHeaderNames();
|
866 |
+
|
867 |
+
/* -------------------------------------------------------------------
|
868 |
+
* DebugBar integration
|
869 |
+
* -------------------------------------------------------------------
|
870 |
+
*/
|
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 |
}
|
895 |
|
896 |
endif;
|
admin/update-checker/Puc/{v4p2 → v4p4}/UpgraderStatus.php
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
/**
|
5 |
* A utility class that helps figure out which plugin or theme WordPress is upgrading.
|
@@ -8,7 +8,7 @@ if ( !class_exists('Puc_v4p2_UpgraderStatus', false) ):
|
|
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
|
12 |
private $currentType = null; //"plugin" or "theme".
|
13 |
private $currentId = null; //Plugin basename or theme directory name.
|
14 |
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_UpgraderStatus', false) ):
|
3 |
|
4 |
/**
|
5 |
* A utility class that helps figure out which plugin or theme WordPress is upgrading.
|
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 |
|
admin/update-checker/Puc/{v4p2 → v4p4}/Utils.php
RENAMED
@@ -1,8 +1,8 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
if ( !class_exists('
|
4 |
|
5 |
-
class
|
6 |
/**
|
7 |
* Get a value from a nested array or object based on a path.
|
8 |
*
|
1 |
<?php
|
2 |
|
3 |
+
if ( !class_exists('Puc_v4p4_Utils', false) ):
|
4 |
|
5 |
+
class Puc_v4p4_Utils {
|
6 |
/**
|
7 |
* Get a value from a nested array or object based on a path.
|
8 |
*
|
admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/Api.php
RENAMED
@@ -1,8 +1,9 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
-
abstract class
|
5 |
protected $tagNameProperty = 'name';
|
|
|
6 |
|
7 |
/**
|
8 |
* @var string
|
@@ -21,7 +22,12 @@ if ( !class_exists('Puc_v4p2_Vcs_Api') ):
|
|
21 |
protected $httpFilterName = '';
|
22 |
|
23 |
/**
|
24 |
-
*
|
|
|
|
|
|
|
|
|
|
|
25 |
*
|
26 |
* @param string $repositoryUrl
|
27 |
* @param array|string|null $credentials
|
@@ -42,7 +48,7 @@ if ( !class_exists('Puc_v4p2_Vcs_Api') ):
|
|
42 |
* Figure out which reference (i.e tag or branch) contains the latest version.
|
43 |
*
|
44 |
* @param string $configBranch Start looking in this branch.
|
45 |
-
* @return null|
|
46 |
*/
|
47 |
abstract public function chooseReference($configBranch);
|
48 |
|
@@ -54,7 +60,7 @@ if ( !class_exists('Puc_v4p2_Vcs_Api') ):
|
|
54 |
* @return array Parsed readme.
|
55 |
*/
|
56 |
public function getRemoteReadme($ref = 'master') {
|
57 |
-
$fileContents = $this->getRemoteFile(
|
58 |
if ( empty($fileContents) ) {
|
59 |
return array();
|
60 |
}
|
@@ -63,11 +69,43 @@ if ( !class_exists('Puc_v4p2_Vcs_Api') ):
|
|
63 |
return $parser->parse_readme_contents($fileContents);
|
64 |
}
|
65 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
66 |
/**
|
67 |
* Get a branch.
|
68 |
*
|
69 |
* @param string $branchName
|
70 |
-
* @return
|
71 |
*/
|
72 |
abstract public function getBranch($branchName);
|
73 |
|
@@ -75,7 +113,7 @@ if ( !class_exists('Puc_v4p2_Vcs_Api') ):
|
|
75 |
* Get a specific tag.
|
76 |
*
|
77 |
* @param string $tagName
|
78 |
-
* @return
|
79 |
*/
|
80 |
abstract public function getTag($tagName);
|
81 |
|
@@ -83,7 +121,7 @@ if ( !class_exists('Puc_v4p2_Vcs_Api') ):
|
|
83 |
* Get the tag that looks like the highest version number.
|
84 |
* (Implementations should skip pre-release versions if possible.)
|
85 |
*
|
86 |
-
* @return
|
87 |
*/
|
88 |
abstract public function getLatestTag();
|
89 |
|
@@ -196,7 +234,10 @@ if ( !class_exists('Puc_v4p2_Vcs_Api') ):
|
|
196 |
* @param string $directory
|
197 |
* @return string|null
|
198 |
*/
|
199 |
-
protected function findChangelogName($directory) {
|
|
|
|
|
|
|
200 |
if ( empty($directory) || !is_dir($directory) || ($directory === '.') ) {
|
201 |
return null;
|
202 |
}
|
@@ -238,6 +279,24 @@ if ( !class_exists('Puc_v4p2_Vcs_Api') ):
|
|
238 |
public function setHttpFilterName($filterName) {
|
239 |
$this->httpFilterName = $filterName;
|
240 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
241 |
}
|
242 |
|
243 |
endif;
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Vcs_Api') ):
|
3 |
|
4 |
+
abstract class Puc_v4p4_Vcs_Api {
|
5 |
protected $tagNameProperty = 'name';
|
6 |
+
protected $slug = '';
|
7 |
|
8 |
/**
|
9 |
* @var string
|
22 |
protected $httpFilterName = '';
|
23 |
|
24 |
/**
|
25 |
+
* @var string|null
|
26 |
+
*/
|
27 |
+
protected $localDirectory = null;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Puc_v4p4_Vcs_Api constructor.
|
31 |
*
|
32 |
* @param string $repositoryUrl
|
33 |
* @param array|string|null $credentials
|
48 |
* Figure out which reference (i.e tag or branch) contains the latest version.
|
49 |
*
|
50 |
* @param string $configBranch Start looking in this branch.
|
51 |
+
* @return null|Puc_v4p4_Vcs_Reference
|
52 |
*/
|
53 |
abstract public function chooseReference($configBranch);
|
54 |
|
60 |
* @return array Parsed readme.
|
61 |
*/
|
62 |
public function getRemoteReadme($ref = 'master') {
|
63 |
+
$fileContents = $this->getRemoteFile($this->getLocalReadmeName(), $ref);
|
64 |
if ( empty($fileContents) ) {
|
65 |
return array();
|
66 |
}
|
69 |
return $parser->parse_readme_contents($fileContents);
|
70 |
}
|
71 |
|
72 |
+
/**
|
73 |
+
* Get the case-sensitive name of the local readme.txt file.
|
74 |
+
*
|
75 |
+
* In most cases it should just be called "readme.txt", but some plugins call it "README.txt",
|
76 |
+
* "README.TXT", or even "Readme.txt". Most VCS are case-sensitive so we need to know the correct
|
77 |
+
* capitalization.
|
78 |
+
*
|
79 |
+
* Defaults to "readme.txt" (all lowercase).
|
80 |
+
*
|
81 |
+
* @return string
|
82 |
+
*/
|
83 |
+
public function getLocalReadmeName() {
|
84 |
+
static $fileName = null;
|
85 |
+
if ( $fileName !== null ) {
|
86 |
+
return $fileName;
|
87 |
+
}
|
88 |
+
|
89 |
+
$fileName = 'readme.txt';
|
90 |
+
if ( isset($this->localDirectory) ) {
|
91 |
+
$files = scandir($this->localDirectory);
|
92 |
+
if ( !empty($files) ) {
|
93 |
+
foreach ($files as $possibleFileName) {
|
94 |
+
if ( strcasecmp($possibleFileName, 'readme.txt') === 0 ) {
|
95 |
+
$fileName = $possibleFileName;
|
96 |
+
break;
|
97 |
+
}
|
98 |
+
}
|
99 |
+
}
|
100 |
+
}
|
101 |
+
return $fileName;
|
102 |
+
}
|
103 |
+
|
104 |
/**
|
105 |
* Get a branch.
|
106 |
*
|
107 |
* @param string $branchName
|
108 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
109 |
*/
|
110 |
abstract public function getBranch($branchName);
|
111 |
|
113 |
* Get a specific tag.
|
114 |
*
|
115 |
* @param string $tagName
|
116 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
117 |
*/
|
118 |
abstract public function getTag($tagName);
|
119 |
|
121 |
* Get the tag that looks like the highest version number.
|
122 |
* (Implementations should skip pre-release versions if possible.)
|
123 |
*
|
124 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
125 |
*/
|
126 |
abstract public function getLatestTag();
|
127 |
|
234 |
* @param string $directory
|
235 |
* @return string|null
|
236 |
*/
|
237 |
+
protected function findChangelogName($directory = null) {
|
238 |
+
if ( !isset($directory) ) {
|
239 |
+
$directory = $this->localDirectory;
|
240 |
+
}
|
241 |
if ( empty($directory) || !is_dir($directory) || ($directory === '.') ) {
|
242 |
return null;
|
243 |
}
|
279 |
public function setHttpFilterName($filterName) {
|
280 |
$this->httpFilterName = $filterName;
|
281 |
}
|
282 |
+
|
283 |
+
/**
|
284 |
+
* @param string $directory
|
285 |
+
*/
|
286 |
+
public function setLocalDirectory($directory) {
|
287 |
+
if ( empty($directory) || !is_dir($directory) || ($directory === '.') ) {
|
288 |
+
$this->localDirectory = null;
|
289 |
+
} else {
|
290 |
+
$this->localDirectory = $directory;
|
291 |
+
}
|
292 |
+
}
|
293 |
+
|
294 |
+
/**
|
295 |
+
* @param string $slug
|
296 |
+
*/
|
297 |
+
public function setSlug($slug) {
|
298 |
+
$this->slug = $slug;
|
299 |
+
}
|
300 |
}
|
301 |
|
302 |
endif;
|
admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/BaseChecker.php
RENAMED
@@ -1,7 +1,7 @@
|
|
1 |
<?php
|
2 |
-
if ( !interface_exists('
|
3 |
|
4 |
-
interface
|
5 |
/**
|
6 |
* Set the repository branch to use for updates. Defaults to 'master'.
|
7 |
*
|
@@ -17,6 +17,11 @@ if ( !interface_exists('Puc_v4p2_Vcs_BaseChecker', false) ):
|
|
17 |
* @return $this
|
18 |
*/
|
19 |
public function setAuthentication($credentials);
|
|
|
|
|
|
|
|
|
|
|
20 |
}
|
21 |
|
22 |
endif;
|
1 |
<?php
|
2 |
+
if ( !interface_exists('Puc_v4p4_Vcs_BaseChecker', false) ):
|
3 |
|
4 |
+
interface Puc_v4p4_Vcs_BaseChecker {
|
5 |
/**
|
6 |
* Set the repository branch to use for updates. Defaults to 'master'.
|
7 |
*
|
17 |
* @return $this
|
18 |
*/
|
19 |
public function setAuthentication($credentials);
|
20 |
+
|
21 |
+
/**
|
22 |
+
* @return Puc_v4p4_Vcs_Api
|
23 |
+
*/
|
24 |
+
public function getVcsApi();
|
25 |
}
|
26 |
|
27 |
endif;
|
admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/BitBucketApi.php
RENAMED
@@ -1,9 +1,9 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
-
class
|
5 |
/**
|
6 |
-
* @var
|
7 |
*/
|
8 |
private $oauth = null;
|
9 |
|
@@ -33,7 +33,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
33 |
* Figure out which reference (i.e tag or branch) contains the latest version.
|
34 |
*
|
35 |
* @param string $configBranch Start looking in this branch.
|
36 |
-
* @return null|
|
37 |
*/
|
38 |
public function chooseReference($configBranch) {
|
39 |
$updateSource = null;
|
@@ -59,7 +59,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
59 |
return null;
|
60 |
}
|
61 |
|
62 |
-
return new
|
63 |
'name' => $branch->name,
|
64 |
'updated' => $branch->target->date,
|
65 |
'downloadUrl' => $this->getDownloadUrl($branch->name),
|
@@ -70,7 +70,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
70 |
* Get a specific tag.
|
71 |
*
|
72 |
* @param string $tagName
|
73 |
-
* @return
|
74 |
*/
|
75 |
public function getTag($tagName) {
|
76 |
$tag = $this->api('/refs/tags/' . $tagName);
|
@@ -78,7 +78,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
78 |
return null;
|
79 |
}
|
80 |
|
81 |
-
return new
|
82 |
'name' => $tag->name,
|
83 |
'version' => ltrim($tag->name, 'v'),
|
84 |
'updated' => $tag->target->date,
|
@@ -89,7 +89,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
89 |
/**
|
90 |
* Get the tag that looks like the highest version number.
|
91 |
*
|
92 |
-
* @return
|
93 |
*/
|
94 |
public function getLatestTag() {
|
95 |
$tags = $this->api('/refs/tags?sort=-target.date');
|
@@ -103,7 +103,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
103 |
//Return the first result.
|
104 |
if ( !empty($versionTags) ) {
|
105 |
$tag = $versionTags[0];
|
106 |
-
return new
|
107 |
'name' => $tag->name,
|
108 |
'version' => ltrim($tag->name, 'v'),
|
109 |
'updated' => $tag->target->date,
|
@@ -117,7 +117,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
117 |
* Get the tag/ref specified by the "Stable tag" header in the readme.txt of a given branch.
|
118 |
*
|
119 |
* @param string $branch
|
120 |
-
* @return null|
|
121 |
*/
|
122 |
protected function getStableTag($branch) {
|
123 |
$remoteReadme = $this->getRemoteReadme($branch);
|
@@ -194,6 +194,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
194 |
$this->repository,
|
195 |
ltrim($url, '/')
|
196 |
));
|
|
|
197 |
|
198 |
if ( $this->oauth ) {
|
199 |
$url = $this->oauth->sign($url,'GET');
|
@@ -205,6 +206,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
205 |
}
|
206 |
$response = wp_remote_get($url, $options);
|
207 |
if ( is_wp_error($response) ) {
|
|
|
208 |
return $response;
|
209 |
}
|
210 |
|
@@ -215,10 +217,13 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
215 |
return $document;
|
216 |
}
|
217 |
|
218 |
-
|
219 |
'puc-bitbucket-http-error',
|
220 |
-
'BitBucket API error. HTTP status: '
|
221 |
);
|
|
|
|
|
|
|
222 |
}
|
223 |
|
224 |
/**
|
@@ -228,7 +233,7 @@ if ( !class_exists('Puc_v4p2_Vcs_BitBucketApi', false) ):
|
|
228 |
parent::setAuthentication($credentials);
|
229 |
|
230 |
if ( !empty($credentials) && !empty($credentials['consumer_key']) ) {
|
231 |
-
$this->oauth = new
|
232 |
$credentials['consumer_key'],
|
233 |
$credentials['consumer_secret']
|
234 |
);
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Vcs_BitBucketApi', false) ):
|
3 |
|
4 |
+
class Puc_v4p4_Vcs_BitBucketApi extends Puc_v4p4_Vcs_Api {
|
5 |
/**
|
6 |
+
* @var Puc_v4p4_OAuthSignature
|
7 |
*/
|
8 |
private $oauth = null;
|
9 |
|
33 |
* Figure out which reference (i.e tag or branch) contains the latest version.
|
34 |
*
|
35 |
* @param string $configBranch Start looking in this branch.
|
36 |
+
* @return null|Puc_v4p4_Vcs_Reference
|
37 |
*/
|
38 |
public function chooseReference($configBranch) {
|
39 |
$updateSource = null;
|
59 |
return null;
|
60 |
}
|
61 |
|
62 |
+
return new Puc_v4p4_Vcs_Reference(array(
|
63 |
'name' => $branch->name,
|
64 |
'updated' => $branch->target->date,
|
65 |
'downloadUrl' => $this->getDownloadUrl($branch->name),
|
70 |
* Get a specific tag.
|
71 |
*
|
72 |
* @param string $tagName
|
73 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
74 |
*/
|
75 |
public function getTag($tagName) {
|
76 |
$tag = $this->api('/refs/tags/' . $tagName);
|
78 |
return null;
|
79 |
}
|
80 |
|
81 |
+
return new Puc_v4p4_Vcs_Reference(array(
|
82 |
'name' => $tag->name,
|
83 |
'version' => ltrim($tag->name, 'v'),
|
84 |
'updated' => $tag->target->date,
|
89 |
/**
|
90 |
* Get the tag that looks like the highest version number.
|
91 |
*
|
92 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
93 |
*/
|
94 |
public function getLatestTag() {
|
95 |
$tags = $this->api('/refs/tags?sort=-target.date');
|
103 |
//Return the first result.
|
104 |
if ( !empty($versionTags) ) {
|
105 |
$tag = $versionTags[0];
|
106 |
+
return new Puc_v4p4_Vcs_Reference(array(
|
107 |
'name' => $tag->name,
|
108 |
'version' => ltrim($tag->name, 'v'),
|
109 |
'updated' => $tag->target->date,
|
117 |
* Get the tag/ref specified by the "Stable tag" header in the readme.txt of a given branch.
|
118 |
*
|
119 |
* @param string $branch
|
120 |
+
* @return null|Puc_v4p4_Vcs_Reference
|
121 |
*/
|
122 |
protected function getStableTag($branch) {
|
123 |
$remoteReadme = $this->getRemoteReadme($branch);
|
194 |
$this->repository,
|
195 |
ltrim($url, '/')
|
196 |
));
|
197 |
+
$baseUrl = $url;
|
198 |
|
199 |
if ( $this->oauth ) {
|
200 |
$url = $this->oauth->sign($url,'GET');
|
206 |
}
|
207 |
$response = wp_remote_get($url, $options);
|
208 |
if ( is_wp_error($response) ) {
|
209 |
+
do_action('puc_api_error', $response, null, $url, $this->slug);
|
210 |
return $response;
|
211 |
}
|
212 |
|
217 |
return $document;
|
218 |
}
|
219 |
|
220 |
+
$error = new WP_Error(
|
221 |
'puc-bitbucket-http-error',
|
222 |
+
sprintf('BitBucket API error. Base URL: "%s", HTTP status code: %d.', $baseUrl, $code)
|
223 |
);
|
224 |
+
do_action('puc_api_error', $error, $response, $url, $this->slug);
|
225 |
+
|
226 |
+
return $error;
|
227 |
}
|
228 |
|
229 |
/**
|
233 |
parent::setAuthentication($credentials);
|
234 |
|
235 |
if ( !empty($credentials) && !empty($credentials['consumer_key']) ) {
|
236 |
+
$this->oauth = new Puc_v4p4_OAuthSignature(
|
237 |
$credentials['consumer_key'],
|
238 |
$credentials['consumer_secret']
|
239 |
);
|
admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/GitHubApi.php
RENAMED
@@ -1,8 +1,8 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
if ( !class_exists('
|
4 |
|
5 |
-
class
|
6 |
/**
|
7 |
* @var string GitHub username.
|
8 |
*/
|
@@ -22,6 +22,21 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
22 |
*/
|
23 |
protected $accessToken;
|
24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
public function __construct($repositoryUrl, $accessToken = null) {
|
26 |
$path = @parse_url($repositoryUrl, PHP_URL_PATH);
|
27 |
if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) {
|
@@ -37,7 +52,7 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
37 |
/**
|
38 |
* Get the latest release from GitHub.
|
39 |
*
|
40 |
-
* @return
|
41 |
*/
|
42 |
public function getLatestRelease() {
|
43 |
$release = $this->api('/repos/:user/:repo/releases/latest');
|
@@ -45,21 +60,42 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
45 |
return null;
|
46 |
}
|
47 |
|
48 |
-
$reference = new
|
49 |
-
'name'
|
50 |
-
'version'
|
51 |
'downloadUrl' => $this->signDownloadUrl($release->zipball_url),
|
52 |
-
'updated'
|
53 |
'apiResponse' => $release,
|
54 |
));
|
55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
if ( !empty($release->body) ) {
|
57 |
/** @noinspection PhpUndefinedClassInspection */
|
58 |
$reference->changelog = Parsedown::instance()->text($release->body);
|
59 |
}
|
60 |
-
if ( isset($release->assets[0]) ) {
|
61 |
-
$reference->downloadCount = $release->assets[0]->download_count;
|
62 |
-
}
|
63 |
|
64 |
return $reference;
|
65 |
}
|
@@ -67,7 +103,7 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
67 |
/**
|
68 |
* Get the tag that looks like the highest version number.
|
69 |
*
|
70 |
-
* @return
|
71 |
*/
|
72 |
public function getLatestTag() {
|
73 |
$tags = $this->api('/repos/:user/:repo/tags');
|
@@ -82,9 +118,9 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
82 |
}
|
83 |
|
84 |
$tag = $versionTags[0];
|
85 |
-
return new
|
86 |
-
'name'
|
87 |
-
'version'
|
88 |
'downloadUrl' => $this->signDownloadUrl($tag->zipball_url),
|
89 |
'apiResponse' => $tag,
|
90 |
));
|
@@ -94,7 +130,7 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
94 |
* Get a branch by name.
|
95 |
*
|
96 |
* @param string $branchName
|
97 |
-
* @return null|
|
98 |
*/
|
99 |
public function getBranch($branchName) {
|
100 |
$branch = $this->api('/repos/:user/:repo/branches/' . $branchName);
|
@@ -102,8 +138,8 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
102 |
return null;
|
103 |
}
|
104 |
|
105 |
-
$reference = new
|
106 |
-
'name'
|
107 |
'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name),
|
108 |
'apiResponse' => $branch,
|
109 |
));
|
@@ -127,7 +163,7 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
127 |
'/repos/:user/:repo/commits',
|
128 |
array(
|
129 |
'path' => $filename,
|
130 |
-
'sha'
|
131 |
)
|
132 |
);
|
133 |
if ( !is_wp_error($commits) && is_array($commits) && isset($commits[0]) ) {
|
@@ -158,6 +194,7 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
158 |
* @return mixed|WP_Error
|
159 |
*/
|
160 |
protected function api($url, $queryParams = array()) {
|
|
|
161 |
$url = $this->buildApiUrl($url, $queryParams);
|
162 |
|
163 |
$options = array('timeout' => 10);
|
@@ -166,6 +203,7 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
166 |
}
|
167 |
$response = wp_remote_get($url, $options);
|
168 |
if ( is_wp_error($response) ) {
|
|
|
169 |
return $response;
|
170 |
}
|
171 |
|
@@ -176,10 +214,13 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
176 |
return $document;
|
177 |
}
|
178 |
|
179 |
-
|
180 |
'puc-github-http-error',
|
181 |
-
'GitHub API error. HTTP status: '
|
182 |
);
|
|
|
|
|
|
|
183 |
}
|
184 |
|
185 |
/**
|
@@ -249,7 +290,7 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
249 |
* Get a specific tag.
|
250 |
*
|
251 |
* @param string $tagName
|
252 |
-
* @return
|
253 |
*/
|
254 |
public function getTag($tagName) {
|
255 |
//The current GitHub update checker doesn't use getTag, so I didn't bother to implement it.
|
@@ -265,7 +306,7 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
265 |
* Figure out which reference (i.e tag or branch) contains the latest version.
|
266 |
*
|
267 |
* @param string $configBranch Start looking in this branch.
|
268 |
-
* @return null|
|
269 |
*/
|
270 |
public function chooseReference($configBranch) {
|
271 |
$updateSource = null;
|
@@ -297,6 +338,76 @@ if ( !class_exists('Puc_v4p2_Vcs_GitHubApi', false) ):
|
|
297 |
return add_query_arg('access_token', $this->credentials, $url);
|
298 |
}
|
299 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
300 |
}
|
301 |
|
302 |
endif;
|
1 |
<?php
|
2 |
|
3 |
+
if ( !class_exists('Puc_v4p4_Vcs_GitHubApi', false) ):
|
4 |
|
5 |
+
class Puc_v4p4_Vcs_GitHubApi extends Puc_v4p4_Vcs_Api {
|
6 |
/**
|
7 |
* @var string GitHub username.
|
8 |
*/
|
22 |
*/
|
23 |
protected $accessToken;
|
24 |
|
25 |
+
/**
|
26 |
+
* @var bool Whether to download release assets instead of the auto-generated source code archives.
|
27 |
+
*/
|
28 |
+
protected $releaseAssetsEnabled = false;
|
29 |
+
|
30 |
+
/**
|
31 |
+
* @var string|null Regular expression that's used to filter release assets by name. Optional.
|
32 |
+
*/
|
33 |
+
protected $assetFilterRegex = null;
|
34 |
+
|
35 |
+
/**
|
36 |
+
* @var string|null The unchanging part of a release asset URL. Used to identify download attempts.
|
37 |
+
*/
|
38 |
+
protected $assetApiBaseUrl = null;
|
39 |
+
|
40 |
public function __construct($repositoryUrl, $accessToken = null) {
|
41 |
$path = @parse_url($repositoryUrl, PHP_URL_PATH);
|
42 |
if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) {
|
52 |
/**
|
53 |
* Get the latest release from GitHub.
|
54 |
*
|
55 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
56 |
*/
|
57 |
public function getLatestRelease() {
|
58 |
$release = $this->api('/repos/:user/:repo/releases/latest');
|
60 |
return null;
|
61 |
}
|
62 |
|
63 |
+
$reference = new Puc_v4p4_Vcs_Reference(array(
|
64 |
+
'name' => $release->tag_name,
|
65 |
+
'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3".
|
66 |
'downloadUrl' => $this->signDownloadUrl($release->zipball_url),
|
67 |
+
'updated' => $release->created_at,
|
68 |
'apiResponse' => $release,
|
69 |
));
|
70 |
|
71 |
+
if ( isset($release->assets[0]) ) {
|
72 |
+
$reference->downloadCount = $release->assets[0]->download_count;
|
73 |
+
}
|
74 |
+
|
75 |
+
if ( $this->releaseAssetsEnabled && isset($release->assets, $release->assets[0]) ) {
|
76 |
+
//Use the first release asset that matches the specified regular expression.
|
77 |
+
$matchingAssets = array_filter($release->assets, array($this, 'matchesAssetFilter'));
|
78 |
+
if ( !empty($matchingAssets) ) {
|
79 |
+
if ( $this->isAuthenticationEnabled() ) {
|
80 |
+
/**
|
81 |
+
* Keep in mind that we'll need to add an "Accept" header to download this asset.
|
82 |
+
* @see setReleaseDownloadHeader()
|
83 |
+
*/
|
84 |
+
$reference->downloadUrl = $this->signDownloadUrl($matchingAssets[0]->url);
|
85 |
+
} else {
|
86 |
+
//It seems that browser_download_url only works for public repositories.
|
87 |
+
//Using an access_token doesn't help. Maybe OAuth would work?
|
88 |
+
$reference->downloadUrl = $matchingAssets[0]->browser_download_url;
|
89 |
+
}
|
90 |
+
|
91 |
+
$reference->downloadCount = $matchingAssets[0]->download_count;
|
92 |
+
}
|
93 |
+
}
|
94 |
+
|
95 |
if ( !empty($release->body) ) {
|
96 |
/** @noinspection PhpUndefinedClassInspection */
|
97 |
$reference->changelog = Parsedown::instance()->text($release->body);
|
98 |
}
|
|
|
|
|
|
|
99 |
|
100 |
return $reference;
|
101 |
}
|
103 |
/**
|
104 |
* Get the tag that looks like the highest version number.
|
105 |
*
|
106 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
107 |
*/
|
108 |
public function getLatestTag() {
|
109 |
$tags = $this->api('/repos/:user/:repo/tags');
|
118 |
}
|
119 |
|
120 |
$tag = $versionTags[0];
|
121 |
+
return new Puc_v4p4_Vcs_Reference(array(
|
122 |
+
'name' => $tag->name,
|
123 |
+
'version' => ltrim($tag->name, 'v'),
|
124 |
'downloadUrl' => $this->signDownloadUrl($tag->zipball_url),
|
125 |
'apiResponse' => $tag,
|
126 |
));
|
130 |
* Get a branch by name.
|
131 |
*
|
132 |
* @param string $branchName
|
133 |
+
* @return null|Puc_v4p4_Vcs_Reference
|
134 |
*/
|
135 |
public function getBranch($branchName) {
|
136 |
$branch = $this->api('/repos/:user/:repo/branches/' . $branchName);
|
138 |
return null;
|
139 |
}
|
140 |
|
141 |
+
$reference = new Puc_v4p4_Vcs_Reference(array(
|
142 |
+
'name' => $branch->name,
|
143 |
'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name),
|
144 |
'apiResponse' => $branch,
|
145 |
));
|
163 |
'/repos/:user/:repo/commits',
|
164 |
array(
|
165 |
'path' => $filename,
|
166 |
+
'sha' => $ref,
|
167 |
)
|
168 |
);
|
169 |
if ( !is_wp_error($commits) && is_array($commits) && isset($commits[0]) ) {
|
194 |
* @return mixed|WP_Error
|
195 |
*/
|
196 |
protected function api($url, $queryParams = array()) {
|
197 |
+
$baseUrl = $url;
|
198 |
$url = $this->buildApiUrl($url, $queryParams);
|
199 |
|
200 |
$options = array('timeout' => 10);
|
203 |
}
|
204 |
$response = wp_remote_get($url, $options);
|
205 |
if ( is_wp_error($response) ) {
|
206 |
+
do_action('puc_api_error', $response, null, $url, $this->slug);
|
207 |
return $response;
|
208 |
}
|
209 |
|
214 |
return $document;
|
215 |
}
|
216 |
|
217 |
+
$error = new WP_Error(
|
218 |
'puc-github-http-error',
|
219 |
+
sprintf('GitHub API error. Base URL: "%s", HTTP status code: %d.', $baseUrl, $code)
|
220 |
);
|
221 |
+
do_action('puc_api_error', $error, $response, $url, $this->slug);
|
222 |
+
|
223 |
+
return $error;
|
224 |
}
|
225 |
|
226 |
/**
|
290 |
* Get a specific tag.
|
291 |
*
|
292 |
* @param string $tagName
|
293 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
294 |
*/
|
295 |
public function getTag($tagName) {
|
296 |
//The current GitHub update checker doesn't use getTag, so I didn't bother to implement it.
|
306 |
* Figure out which reference (i.e tag or branch) contains the latest version.
|
307 |
*
|
308 |
* @param string $configBranch Start looking in this branch.
|
309 |
+
* @return null|Puc_v4p4_Vcs_Reference
|
310 |
*/
|
311 |
public function chooseReference($configBranch) {
|
312 |
$updateSource = null;
|
338 |
return add_query_arg('access_token', $this->credentials, $url);
|
339 |
}
|
340 |
|
341 |
+
/**
|
342 |
+
* Enable updating via release assets.
|
343 |
+
*
|
344 |
+
* If the latest release contains no usable assets, the update checker
|
345 |
+
* will fall back to using the automatically generated ZIP archive.
|
346 |
+
*
|
347 |
+
* Private repositories will only work with WordPress 3.7 or later.
|
348 |
+
*
|
349 |
+
* @param string|null $fileNameRegex Optional. Use only those assets where the file name matches this regex.
|
350 |
+
*/
|
351 |
+
public function enableReleaseAssets($fileNameRegex = null) {
|
352 |
+
$this->releaseAssetsEnabled = true;
|
353 |
+
$this->assetFilterRegex = $fileNameRegex;
|
354 |
+
$this->assetApiBaseUrl = sprintf(
|
355 |
+
'//api.github.com/repos/%1$s/%2$s/releases/assets/',
|
356 |
+
$this->userName,
|
357 |
+
$this->repositoryName
|
358 |
+
);
|
359 |
+
|
360 |
+
//Optimization: Instead of filtering all HTTP requests, let's do it only when
|
361 |
+
//WordPress is about to download an update.
|
362 |
+
add_filter('upgrader_pre_download', array($this, 'addHttpRequestFilter'), 10, 1); //WP 3.7+
|
363 |
+
}
|
364 |
+
|
365 |
+
/**
|
366 |
+
* Does this asset match the file name regex?
|
367 |
+
*
|
368 |
+
* @param stdClass $releaseAsset
|
369 |
+
* @return bool
|
370 |
+
*/
|
371 |
+
protected function matchesAssetFilter($releaseAsset) {
|
372 |
+
if ( $this->assetFilterRegex === null ) {
|
373 |
+
//The default is to accept all assets.
|
374 |
+
return true;
|
375 |
+
}
|
376 |
+
return isset($releaseAsset->name) && preg_match($this->assetFilterRegex, $releaseAsset->name);
|
377 |
+
}
|
378 |
+
|
379 |
+
/**
|
380 |
+
* @internal
|
381 |
+
* @param bool $result
|
382 |
+
* @return bool
|
383 |
+
*/
|
384 |
+
public function addHttpRequestFilter($result) {
|
385 |
+
static $filterAdded = false;
|
386 |
+
if ( $this->releaseAssetsEnabled && !$filterAdded && $this->isAuthenticationEnabled() ) {
|
387 |
+
add_filter('http_request_args', array($this, 'setReleaseDownloadHeader'), 10, 2);
|
388 |
+
$filterAdded = true;
|
389 |
+
}
|
390 |
+
return $result;
|
391 |
+
}
|
392 |
+
|
393 |
+
/**
|
394 |
+
* Set the HTTP header that's necessary to download private release assets.
|
395 |
+
*
|
396 |
+
* See GitHub docs:
|
397 |
+
* @link https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
|
398 |
+
*
|
399 |
+
* @internal
|
400 |
+
* @param array $requestArgs
|
401 |
+
* @param string $url
|
402 |
+
* @return array
|
403 |
+
*/
|
404 |
+
public function setReleaseDownloadHeader($requestArgs, $url = '') {
|
405 |
+
//Is WordPress trying to download one of our assets?
|
406 |
+
if ( strpos($url, $this->assetApiBaseUrl) !== false ) {
|
407 |
+
$requestArgs['headers']['accept'] = 'application/octet-stream';
|
408 |
+
}
|
409 |
+
return $requestArgs;
|
410 |
+
}
|
411 |
}
|
412 |
|
413 |
endif;
|
admin/update-checker/Puc/v4p4/Vcs/GitLabApi.php
ADDED
@@ -0,0 +1,274 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p4_Vcs_GitLabApi', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p4_Vcs_GitLabApi extends Puc_v4p4_Vcs_Api {
|
6 |
+
/**
|
7 |
+
* @var string GitLab username.
|
8 |
+
*/
|
9 |
+
protected $userName;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* @var string GitLab server host.
|
13 |
+
*/
|
14 |
+
private $repositoryHost;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* @var string GitLab repository name.
|
18 |
+
*/
|
19 |
+
protected $repositoryName;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* @var string GitLab authentication token. Optional.
|
23 |
+
*/
|
24 |
+
protected $accessToken;
|
25 |
+
|
26 |
+
public function __construct($repositoryUrl, $accessToken = null) {
|
27 |
+
//Parse the repository host to support custom hosts.
|
28 |
+
$this->repositoryHost = @parse_url($repositoryUrl, PHP_URL_HOST);
|
29 |
+
|
30 |
+
//Find the repository information
|
31 |
+
$path = @parse_url($repositoryUrl, PHP_URL_PATH);
|
32 |
+
if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) {
|
33 |
+
$this->userName = $matches['username'];
|
34 |
+
$this->repositoryName = $matches['repository'];
|
35 |
+
} else {
|
36 |
+
//This is not a traditional url, it could be gitlab is in a deeper subdirectory.
|
37 |
+
//Get the path segments.
|
38 |
+
$segments = explode('/', untrailingslashit(ltrim($path, '/')));
|
39 |
+
|
40 |
+
//We need at least /user-name/repository-name/
|
41 |
+
if ( count($segments) < 2 ) {
|
42 |
+
throw new InvalidArgumentException('Invalid GitLab repository URL: "' . $repositoryUrl . '"');
|
43 |
+
}
|
44 |
+
|
45 |
+
//Get the username and repository name.
|
46 |
+
$usernameRepo = array_splice($segments, -2, 2);
|
47 |
+
$this->userName = $usernameRepo[0];
|
48 |
+
$this->repositoryName = $usernameRepo[1];
|
49 |
+
|
50 |
+
//Append the remaining segments to the host.
|
51 |
+
$this->repositoryHost = trailingslashit($this->repositoryHost) . implode('/', $segments);
|
52 |
+
}
|
53 |
+
|
54 |
+
parent::__construct($repositoryUrl, $accessToken);
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Get the latest release from GitLab.
|
59 |
+
*
|
60 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
61 |
+
*/
|
62 |
+
public function getLatestRelease() {
|
63 |
+
return $this->getLatestTag();
|
64 |
+
}
|
65 |
+
|
66 |
+
/**
|
67 |
+
* Get the tag that looks like the highest version number.
|
68 |
+
*
|
69 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
70 |
+
*/
|
71 |
+
public function getLatestTag() {
|
72 |
+
$tags = $this->api('/:user/:repo/repository/tags');
|
73 |
+
if ( is_wp_error($tags) || empty($tags) || !is_array($tags) ) {
|
74 |
+
return null;
|
75 |
+
}
|
76 |
+
|
77 |
+
$versionTags = $this->sortTagsByVersion($tags);
|
78 |
+
if ( empty($versionTags) ) {
|
79 |
+
return null;
|
80 |
+
}
|
81 |
+
|
82 |
+
$tag = $versionTags[0];
|
83 |
+
return new Puc_v4p4_Vcs_Reference(array(
|
84 |
+
'name' => $tag->name,
|
85 |
+
'version' => ltrim($tag->name, 'v'),
|
86 |
+
'downloadUrl' => $this->buildArchiveDownloadUrl($tag->name),
|
87 |
+
'apiResponse' => $tag
|
88 |
+
));
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Get a branch by name.
|
93 |
+
*
|
94 |
+
* @param string $branchName
|
95 |
+
* @return null|Puc_v4p4_Vcs_Reference
|
96 |
+
*/
|
97 |
+
public function getBranch($branchName) {
|
98 |
+
$branch = $this->api('/:user/:repo/repository/branches/' . $branchName);
|
99 |
+
if ( is_wp_error($branch) || empty($branch) ) {
|
100 |
+
return null;
|
101 |
+
}
|
102 |
+
|
103 |
+
$reference = new Puc_v4p4_Vcs_Reference(array(
|
104 |
+
'name' => $branch->name,
|
105 |
+
'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name),
|
106 |
+
'apiResponse' => $branch,
|
107 |
+
));
|
108 |
+
|
109 |
+
if ( isset($branch->commit, $branch->commit->committed_date) ) {
|
110 |
+
$reference->updated = $branch->commit->committed_date;
|
111 |
+
}
|
112 |
+
|
113 |
+
return $reference;
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Get the timestamp of the latest commit that changed the specified branch or tag.
|
118 |
+
*
|
119 |
+
* @param string $ref Reference name (e.g. branch or tag).
|
120 |
+
* @return string|null
|
121 |
+
*/
|
122 |
+
public function getLatestCommitTime($ref) {
|
123 |
+
$commits = $this->api('/:user/:repo/repository/commits/', array('ref_name' => $ref));
|
124 |
+
if ( is_wp_error($commits) || !is_array($commits) || !isset($commits[0]) ) {
|
125 |
+
return null;
|
126 |
+
}
|
127 |
+
|
128 |
+
return $commits[0]->committed_date;
|
129 |
+
}
|
130 |
+
|
131 |
+
/**
|
132 |
+
* Perform a GitLab API request.
|
133 |
+
*
|
134 |
+
* @param string $url
|
135 |
+
* @param array $queryParams
|
136 |
+
* @return mixed|WP_Error
|
137 |
+
*/
|
138 |
+
protected function api($url, $queryParams = array()) {
|
139 |
+
$baseUrl = $url;
|
140 |
+
$url = $this->buildApiUrl($url, $queryParams);
|
141 |
+
|
142 |
+
$options = array('timeout' => 10);
|
143 |
+
if ( !empty($this->httpFilterName) ) {
|
144 |
+
$options = apply_filters($this->httpFilterName, $options);
|
145 |
+
}
|
146 |
+
|
147 |
+
$response = wp_remote_get($url, $options);
|
148 |
+
if ( is_wp_error($response) ) {
|
149 |
+
do_action('puc_api_error', $response, null, $url, $this->slug);
|
150 |
+
return $response;
|
151 |
+
}
|
152 |
+
|
153 |
+
$code = wp_remote_retrieve_response_code($response);
|
154 |
+
$body = wp_remote_retrieve_body($response);
|
155 |
+
if ( $code === 200 ) {
|
156 |
+
return json_decode($body);
|
157 |
+
}
|
158 |
+
|
159 |
+
$error = new WP_Error(
|
160 |
+
'puc-gitlab-http-error',
|
161 |
+
sprintf('GitLab API error. URL: "%s", HTTP status code: %d.', $baseUrl, $code)
|
162 |
+
);
|
163 |
+
do_action('puc_api_error', $error, $response, $url, $this->slug);
|
164 |
+
|
165 |
+
return $error;
|
166 |
+
}
|
167 |
+
|
168 |
+
/**
|
169 |
+
* Build a fully qualified URL for an API request.
|
170 |
+
*
|
171 |
+
* @param string $url
|
172 |
+
* @param array $queryParams
|
173 |
+
* @return string
|
174 |
+
*/
|
175 |
+
protected function buildApiUrl($url, $queryParams) {
|
176 |
+
$variables = array(
|
177 |
+
'user' => $this->userName,
|
178 |
+
'repo' => $this->repositoryName
|
179 |
+
);
|
180 |
+
|
181 |
+
foreach ($variables as $name => $value) {
|
182 |
+
$url = str_replace("/:{$name}", urlencode('/' . $value), $url);
|
183 |
+
}
|
184 |
+
|
185 |
+
$url = substr($url, 3);
|
186 |
+
$url = sprintf('https://%1$s/api/v4/projects/%2$s', $this->repositoryHost, $url);
|
187 |
+
|
188 |
+
if ( !empty($this->accessToken) ) {
|
189 |
+
$queryParams['private_token'] = $this->accessToken;
|
190 |
+
}
|
191 |
+
|
192 |
+
if ( !empty($queryParams) ) {
|
193 |
+
$url = add_query_arg($queryParams, $url);
|
194 |
+
}
|
195 |
+
|
196 |
+
return $url;
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* Get the contents of a file from a specific branch or tag.
|
201 |
+
*
|
202 |
+
* @param string $path File name.
|
203 |
+
* @param string $ref
|
204 |
+
* @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
|
205 |
+
*/
|
206 |
+
public function getRemoteFile($path, $ref = 'master') {
|
207 |
+
$response = $this->api('/:user/:repo/repository/files/' . $path, array('ref' => $ref));
|
208 |
+
if ( is_wp_error($response) || !isset($response->content) || $response->encoding !== 'base64' ) {
|
209 |
+
return null;
|
210 |
+
}
|
211 |
+
|
212 |
+
return base64_decode($response->content);
|
213 |
+
}
|
214 |
+
|
215 |
+
/**
|
216 |
+
* Generate a URL to download a ZIP archive of the specified branch/tag/etc.
|
217 |
+
*
|
218 |
+
* @param string $ref
|
219 |
+
* @return string
|
220 |
+
*/
|
221 |
+
public function buildArchiveDownloadUrl($ref = 'master') {
|
222 |
+
$url = sprintf(
|
223 |
+
'https://%1$s/%2$s/%3$s/repository/%4$s/archive.zip',
|
224 |
+
$this->repositoryHost,
|
225 |
+
urlencode($this->userName),
|
226 |
+
urlencode($this->repositoryName),
|
227 |
+
urlencode($ref)
|
228 |
+
);
|
229 |
+
|
230 |
+
if ( !empty($this->accessToken) ) {
|
231 |
+
$url = add_query_arg('private_token', $this->accessToken, $url);
|
232 |
+
}
|
233 |
+
|
234 |
+
return $url;
|
235 |
+
}
|
236 |
+
|
237 |
+
/**
|
238 |
+
* Get a specific tag.
|
239 |
+
*
|
240 |
+
* @param string $tagName
|
241 |
+
* @return Puc_v4p4_Vcs_Reference|null
|
242 |
+
*/
|
243 |
+
public function getTag($tagName) {
|
244 |
+
throw new LogicException('The ' . __METHOD__ . ' method is not implemented and should not be used.');
|
245 |
+
}
|
246 |
+
|
247 |
+
/**
|
248 |
+
* Figure out which reference (i.e tag or branch) contains the latest version.
|
249 |
+
*
|
250 |
+
* @param string $configBranch Start looking in this branch.
|
251 |
+
* @return null|Puc_v4p4_Vcs_Reference
|
252 |
+
*/
|
253 |
+
public function chooseReference($configBranch) {
|
254 |
+
$updateSource = null;
|
255 |
+
|
256 |
+
// GitLab doesn't handle releases the same as GitHub so just use the latest tag
|
257 |
+
if ( $configBranch === 'master' ) {
|
258 |
+
$updateSource = $this->getLatestTag();
|
259 |
+
}
|
260 |
+
|
261 |
+
if ( empty($updateSource) ) {
|
262 |
+
$updateSource = $this->getBranch($configBranch);
|
263 |
+
}
|
264 |
+
|
265 |
+
return $updateSource;
|
266 |
+
}
|
267 |
+
|
268 |
+
public function setAuthentication($credentials) {
|
269 |
+
parent::setAuthentication($credentials);
|
270 |
+
$this->accessToken = is_string($credentials) ? $credentials : null;
|
271 |
+
}
|
272 |
+
}
|
273 |
+
|
274 |
+
endif;
|
admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/PluginUpdateChecker.php
RENAMED
@@ -1,21 +1,21 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
-
class
|
5 |
/**
|
6 |
* @var string The branch where to look for updates. Defaults to "master".
|
7 |
*/
|
8 |
protected $branch = 'master';
|
9 |
|
10 |
/**
|
11 |
-
* @var
|
12 |
*/
|
13 |
protected $api = null;
|
14 |
|
15 |
/**
|
16 |
-
*
|
17 |
*
|
18 |
-
* @param
|
19 |
* @param string $pluginFile
|
20 |
* @param string $slug
|
21 |
* @param int $checkPeriod
|
@@ -27,6 +27,8 @@ if ( !class_exists('Puc_v4p2_Vcs_PluginUpdateChecker') ):
|
|
27 |
$this->api->setHttpFilterName($this->getUniqueName('request_info_options'));
|
28 |
|
29 |
parent::__construct($api->getRepositoryUrl(), $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile);
|
|
|
|
|
30 |
}
|
31 |
|
32 |
public function requestInfo($unusedParameter = null) {
|
@@ -37,8 +39,9 @@ if ( !class_exists('Puc_v4p2_Vcs_PluginUpdateChecker') ):
|
|
37 |
}
|
38 |
|
39 |
$api = $this->api;
|
|
|
40 |
|
41 |
-
$info = new
|
42 |
$info->filename = $this->pluginFile;
|
43 |
$info->slug = $this->slug;
|
44 |
|
@@ -60,6 +63,16 @@ if ( !class_exists('Puc_v4p2_Vcs_PluginUpdateChecker') ):
|
|
60 |
}
|
61 |
} else {
|
62 |
//There's probably a network problem or an authentication error.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
return null;
|
64 |
}
|
65 |
|
@@ -104,18 +117,18 @@ if ( !class_exists('Puc_v4p2_Vcs_PluginUpdateChecker') ):
|
|
104 |
* @return bool
|
105 |
*/
|
106 |
protected function readmeTxtExistsLocally() {
|
107 |
-
$pluginDirectory =
|
108 |
-
if ( empty($
|
109 |
return false;
|
110 |
}
|
111 |
-
return is_file($pluginDirectory . '/
|
112 |
}
|
113 |
|
114 |
/**
|
115 |
* Copy plugin metadata from a file header to a Plugin Info object.
|
116 |
*
|
117 |
* @param array $fileHeader
|
118 |
-
* @param
|
119 |
*/
|
120 |
protected function setInfoFromHeader($fileHeader, $pluginInfo) {
|
121 |
$headerToPropertyMap = array(
|
@@ -146,7 +159,7 @@ if ( !class_exists('Puc_v4p2_Vcs_PluginUpdateChecker') ):
|
|
146 |
* Copy plugin metadata from the remote readme.txt file.
|
147 |
*
|
148 |
* @param string $ref GitHub tag or branch where to look for the readme.
|
149 |
-
* @param
|
150 |
*/
|
151 |
protected function setInfoFromRemoteReadme($ref, $pluginInfo) {
|
152 |
$readme = $this->api->getRemoteReadme($ref);
|
@@ -179,6 +192,10 @@ if ( !class_exists('Puc_v4p2_Vcs_PluginUpdateChecker') ):
|
|
179 |
return $this;
|
180 |
}
|
181 |
|
|
|
|
|
|
|
|
|
182 |
public function getUpdate() {
|
183 |
$update = parent::getUpdate();
|
184 |
|
@@ -188,6 +205,13 @@ if ( !class_exists('Puc_v4p2_Vcs_PluginUpdateChecker') ):
|
|
188 |
|
189 |
return $update;
|
190 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
191 |
}
|
192 |
|
193 |
endif;
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Vcs_PluginUpdateChecker') ):
|
3 |
|
4 |
+
class Puc_v4p4_Vcs_PluginUpdateChecker extends Puc_v4p4_Plugin_UpdateChecker implements Puc_v4p4_Vcs_BaseChecker {
|
5 |
/**
|
6 |
* @var string The branch where to look for updates. Defaults to "master".
|
7 |
*/
|
8 |
protected $branch = 'master';
|
9 |
|
10 |
/**
|
11 |
+
* @var Puc_v4p4_Vcs_Api Repository API client.
|
12 |
*/
|
13 |
protected $api = null;
|
14 |
|
15 |
/**
|
16 |
+
* Puc_v4p4_Vcs_PluginUpdateChecker constructor.
|
17 |
*
|
18 |
+
* @param Puc_v4p4_Vcs_Api $api
|
19 |
* @param string $pluginFile
|
20 |
* @param string $slug
|
21 |
* @param int $checkPeriod
|
27 |
$this->api->setHttpFilterName($this->getUniqueName('request_info_options'));
|
28 |
|
29 |
parent::__construct($api->getRepositoryUrl(), $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile);
|
30 |
+
|
31 |
+
$this->api->setSlug($this->slug);
|
32 |
}
|
33 |
|
34 |
public function requestInfo($unusedParameter = null) {
|
39 |
}
|
40 |
|
41 |
$api = $this->api;
|
42 |
+
$api->setLocalDirectory($this->getAbsoluteDirectoryPath());
|
43 |
|
44 |
+
$info = new Puc_v4p4_Plugin_Info();
|
45 |
$info->filename = $this->pluginFile;
|
46 |
$info->slug = $this->slug;
|
47 |
|
63 |
}
|
64 |
} else {
|
65 |
//There's probably a network problem or an authentication error.
|
66 |
+
do_action(
|
67 |
+
'puc_api_error',
|
68 |
+
new WP_Error(
|
69 |
+
'puc-no-update-source',
|
70 |
+
'Could not retrieve version information from the repository. '
|
71 |
+
. 'This usually means that the update checker either can\'t connect '
|
72 |
+
. 'to the repository or it\'s configured incorrectly.'
|
73 |
+
),
|
74 |
+
null, null, $this->slug
|
75 |
+
);
|
76 |
return null;
|
77 |
}
|
78 |
|
117 |
* @return bool
|
118 |
*/
|
119 |
protected function readmeTxtExistsLocally() {
|
120 |
+
$pluginDirectory = $this->getAbsoluteDirectoryPath();
|
121 |
+
if ( empty($pluginDirectory) || !is_dir($pluginDirectory) || ($pluginDirectory === '.') ) {
|
122 |
return false;
|
123 |
}
|
124 |
+
return is_file($pluginDirectory . '/' . $this->api->getLocalReadmeName());
|
125 |
}
|
126 |
|
127 |
/**
|
128 |
* Copy plugin metadata from a file header to a Plugin Info object.
|
129 |
*
|
130 |
* @param array $fileHeader
|
131 |
+
* @param Puc_v4p4_Plugin_Info $pluginInfo
|
132 |
*/
|
133 |
protected function setInfoFromHeader($fileHeader, $pluginInfo) {
|
134 |
$headerToPropertyMap = array(
|
159 |
* Copy plugin metadata from the remote readme.txt file.
|
160 |
*
|
161 |
* @param string $ref GitHub tag or branch where to look for the readme.
|
162 |
+
* @param Puc_v4p4_Plugin_Info $pluginInfo
|
163 |
*/
|
164 |
protected function setInfoFromRemoteReadme($ref, $pluginInfo) {
|
165 |
$readme = $this->api->getRemoteReadme($ref);
|
192 |
return $this;
|
193 |
}
|
194 |
|
195 |
+
public function getVcsApi() {
|
196 |
+
return $this->api;
|
197 |
+
}
|
198 |
+
|
199 |
public function getUpdate() {
|
200 |
$update = parent::getUpdate();
|
201 |
|
205 |
|
206 |
return $update;
|
207 |
}
|
208 |
+
|
209 |
+
public function onDisplayConfiguration($panel) {
|
210 |
+
parent::onDisplayConfiguration($panel);
|
211 |
+
$panel->row('Branch', $this->branch);
|
212 |
+
$panel->row('Authentication enabled', $this->api->isAuthenticationEnabled() ? 'Yes' : 'No');
|
213 |
+
$panel->row('API client', get_class($this->api));
|
214 |
+
}
|
215 |
}
|
216 |
|
217 |
endif;
|
admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/Reference.php
RENAMED
@@ -1,5 +1,5 @@
|
|
1 |
<?php
|
2 |
-
if ( !class_exists('
|
3 |
|
4 |
/**
|
5 |
* This class represents a VCS branch or tag. It's intended as a read only, short-lived container
|
@@ -13,7 +13,7 @@ if ( !class_exists('Puc_v4p2_Vcs_Reference', false) ):
|
|
13 |
* @property string|null $changelog
|
14 |
* @property int|null $downloadCount
|
15 |
*/
|
16 |
-
class
|
17 |
private $properties = array();
|
18 |
|
19 |
public function __construct($properties = array()) {
|
1 |
<?php
|
2 |
+
if ( !class_exists('Puc_v4p4_Vcs_Reference', false) ):
|
3 |
|
4 |
/**
|
5 |
* This class represents a VCS branch or tag. It's intended as a read only, short-lived container
|
13 |
* @property string|null $changelog
|
14 |
* @property int|null $downloadCount
|
15 |
*/
|
16 |
+
class Puc_v4p4_Vcs_Reference {
|
17 |
private $properties = array();
|
18 |
|
19 |
public function __construct($properties = array()) {
|
admin/update-checker/Puc/{v4p2 → v4p4}/Vcs/ThemeUpdateChecker.php
RENAMED
@@ -1,22 +1,22 @@
|
|
1 |
<?php
|
2 |
|
3 |
-
if ( !class_exists('
|
4 |
|
5 |
-
class
|
6 |
/**
|
7 |
* @var string The branch where to look for updates. Defaults to "master".
|
8 |
*/
|
9 |
protected $branch = 'master';
|
10 |
|
11 |
/**
|
12 |
-
* @var
|
13 |
*/
|
14 |
protected $api = null;
|
15 |
|
16 |
/**
|
17 |
-
*
|
18 |
*
|
19 |
-
* @param
|
20 |
* @param null $stylesheet
|
21 |
* @param null $customSlug
|
22 |
* @param int $checkPeriod
|
@@ -27,12 +27,15 @@ if ( !class_exists('Puc_v4p2_Vcs_ThemeUpdateChecker', false) ):
|
|
27 |
$this->api->setHttpFilterName($this->getUniqueName('request_update_options'));
|
28 |
|
29 |
parent::__construct($api->getRepositoryUrl(), $stylesheet, $customSlug, $checkPeriod, $optionName);
|
|
|
|
|
30 |
}
|
31 |
|
32 |
public function requestUpdate() {
|
33 |
$api = $this->api;
|
|
|
34 |
|
35 |
-
$update = new
|
36 |
$update->slug = $this->slug;
|
37 |
|
38 |
//Figure out which reference (tag or branch) we'll use to get the latest version of the theme.
|
@@ -41,19 +44,29 @@ if ( !class_exists('Puc_v4p2_Vcs_ThemeUpdateChecker', false) ):
|
|
41 |
$ref = $updateSource->name;
|
42 |
$update->download_url = $updateSource->downloadUrl;
|
43 |
} else {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
44 |
$ref = $this->branch;
|
45 |
}
|
46 |
|
47 |
//Get headers from the main stylesheet in this branch/tag. Its "Version" header and other metadata
|
48 |
//are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags.
|
49 |
$remoteHeader = $this->getFileHeader($api->getRemoteFile('style.css', $ref));
|
50 |
-
$update->version =
|
51 |
$remoteHeader['Version'],
|
52 |
-
|
53 |
));
|
54 |
|
55 |
//The details URL defaults to the Theme URI header or the repository URL.
|
56 |
-
$update->details_url =
|
57 |
$remoteHeader['ThemeURI'],
|
58 |
$this->theme->get('ThemeURI'),
|
59 |
$this->metadataUrl,
|
@@ -80,6 +93,10 @@ if ( !class_exists('Puc_v4p2_Vcs_ThemeUpdateChecker', false) ):
|
|
80 |
return $this;
|
81 |
}
|
82 |
|
|
|
|
|
|
|
|
|
83 |
public function getUpdate() {
|
84 |
$update = parent::getUpdate();
|
85 |
|
@@ -89,6 +106,13 @@ if ( !class_exists('Puc_v4p2_Vcs_ThemeUpdateChecker', false) ):
|
|
89 |
|
90 |
return $update;
|
91 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
}
|
93 |
|
94 |
endif;
|
1 |
<?php
|
2 |
|
3 |
+
if ( !class_exists('Puc_v4p4_Vcs_ThemeUpdateChecker', false) ):
|
4 |
|
5 |
+
class Puc_v4p4_Vcs_ThemeUpdateChecker extends Puc_v4p4_Theme_UpdateChecker implements Puc_v4p4_Vcs_BaseChecker {
|
6 |
/**
|
7 |
* @var string The branch where to look for updates. Defaults to "master".
|
8 |
*/
|
9 |
protected $branch = 'master';
|
10 |
|
11 |
/**
|
12 |
+
* @var Puc_v4p4_Vcs_Api Repository API client.
|
13 |
*/
|
14 |
protected $api = null;
|
15 |
|
16 |
/**
|
17 |
+
* Puc_v4p4_Vcs_ThemeUpdateChecker constructor.
|
18 |
*
|
19 |
+
* @param Puc_v4p4_Vcs_Api $api
|
20 |
* @param null $stylesheet
|
21 |
* @param null $customSlug
|
22 |
* @param int $checkPeriod
|
27 |
$this->api->setHttpFilterName($this->getUniqueName('request_update_options'));
|
28 |
|
29 |
parent::__construct($api->getRepositoryUrl(), $stylesheet, $customSlug, $checkPeriod, $optionName);
|
30 |
+
|
31 |
+
$this->api->setSlug($this->slug);
|
32 |
}
|
33 |
|
34 |
public function requestUpdate() {
|
35 |
$api = $this->api;
|
36 |
+
$api->setLocalDirectory($this->getAbsoluteDirectoryPath());
|
37 |
|
38 |
+
$update = new Puc_v4p4_Theme_Update();
|
39 |
$update->slug = $this->slug;
|
40 |
|
41 |
//Figure out which reference (tag or branch) we'll use to get the latest version of the theme.
|
44 |
$ref = $updateSource->name;
|
45 |
$update->download_url = $updateSource->downloadUrl;
|
46 |
} else {
|
47 |
+
do_action(
|
48 |
+
'puc_api_error',
|
49 |
+
new WP_Error(
|
50 |
+
'puc-no-update-source',
|
51 |
+
'Could not retrieve version information from the repository. '
|
52 |
+
. 'This usually means that the update checker either can\'t connect '
|
53 |
+
. 'to the repository or it\'s configured incorrectly.'
|
54 |
+
),
|
55 |
+
null, null, $this->slug
|
56 |
+
);
|
57 |
$ref = $this->branch;
|
58 |
}
|
59 |
|
60 |
//Get headers from the main stylesheet in this branch/tag. Its "Version" header and other metadata
|
61 |
//are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags.
|
62 |
$remoteHeader = $this->getFileHeader($api->getRemoteFile('style.css', $ref));
|
63 |
+
$update->version = Puc_v4p4_Utils::findNotEmpty(array(
|
64 |
$remoteHeader['Version'],
|
65 |
+
Puc_v4p4_Utils::get($updateSource, 'version'),
|
66 |
));
|
67 |
|
68 |
//The details URL defaults to the Theme URI header or the repository URL.
|
69 |
+
$update->details_url = Puc_v4p4_Utils::findNotEmpty(array(
|
70 |
$remoteHeader['ThemeURI'],
|
71 |
$this->theme->get('ThemeURI'),
|
72 |
$this->metadataUrl,
|
93 |
return $this;
|
94 |
}
|
95 |
|
96 |
+
public function getVcsApi() {
|
97 |
+
return $this->api;
|
98 |
+
}
|
99 |
+
|
100 |
public function getUpdate() {
|
101 |
$update = parent::getUpdate();
|
102 |
|
106 |
|
107 |
return $update;
|
108 |
}
|
109 |
+
|
110 |
+
public function onDisplayConfiguration($panel) {
|
111 |
+
parent::onDisplayConfiguration($panel);
|
112 |
+
$panel->row('Branch', $this->branch);
|
113 |
+
$panel->row('Authentication enabled', $this->api->isAuthenticationEnabled() ? 'Yes' : 'No');
|
114 |
+
$panel->row('API client', get_class($this->api));
|
115 |
+
}
|
116 |
}
|
117 |
|
118 |
endif;
|
admin/update-checker/license.txt
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
-
Copyright (c)
|
2 |
|
3 |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4 |
|
5 |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6 |
|
7 |
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
1 |
+
Copyright (c) 2017 Jānis Elsts
|
2 |
|
3 |
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
4 |
|
5 |
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
6 |
|
7 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
admin/update-checker/plugin-update-checker.php
CHANGED
@@ -1,22 +1,24 @@
|
|
1 |
<?php
|
2 |
/**
|
3 |
-
* Plugin Update Checker Library 4.
|
4 |
* http://w-shadow.com/
|
5 |
*
|
6 |
* Copyright 2017 Janis Elsts
|
7 |
* Released under the MIT license. See license.txt for details.
|
8 |
*/
|
9 |
|
|
|
10 |
require dirname(__FILE__) . '/Puc/v4/Factory.php';
|
11 |
-
require dirname(__FILE__) . '/Puc/
|
12 |
-
new
|
13 |
|
14 |
//Register classes defined in this file with the factory.
|
15 |
-
Puc_v4_Factory::addVersion('Plugin_UpdateChecker', '
|
16 |
-
Puc_v4_Factory::addVersion('Theme_UpdateChecker', '
|
17 |
|
18 |
-
Puc_v4_Factory::addVersion('Vcs_PluginUpdateChecker', '
|
19 |
-
Puc_v4_Factory::addVersion('Vcs_ThemeUpdateChecker', '
|
20 |
|
21 |
-
Puc_v4_Factory::addVersion('GitHubApi', '
|
22 |
-
Puc_v4_Factory::addVersion('BitBucketApi', '
|
|
1 |
<?php
|
2 |
/**
|
3 |
+
* Plugin Update Checker Library 4.4
|
4 |
* http://w-shadow.com/
|
5 |
*
|
6 |
* Copyright 2017 Janis Elsts
|
7 |
* Released under the MIT license. See license.txt for details.
|
8 |
*/
|
9 |
|
10 |
+
require dirname(__FILE__) . '/Puc/v4p4/Factory.php';
|
11 |
require dirname(__FILE__) . '/Puc/v4/Factory.php';
|
12 |
+
require dirname(__FILE__) . '/Puc/v4p4/Autoloader.php';
|
13 |
+
new Puc_v4p4_Autoloader();
|
14 |
|
15 |
//Register classes defined in this file with the factory.
|
16 |
+
Puc_v4_Factory::addVersion('Plugin_UpdateChecker', 'Puc_v4p4_Plugin_UpdateChecker', '4.4');
|
17 |
+
Puc_v4_Factory::addVersion('Theme_UpdateChecker', 'Puc_v4p4_Theme_UpdateChecker', '4.4');
|
18 |
|
19 |
+
Puc_v4_Factory::addVersion('Vcs_PluginUpdateChecker', 'Puc_v4p4_Vcs_PluginUpdateChecker', '4.4');
|
20 |
+
Puc_v4_Factory::addVersion('Vcs_ThemeUpdateChecker', 'Puc_v4p4_Vcs_ThemeUpdateChecker', '4.4');
|
21 |
|
22 |
+
Puc_v4_Factory::addVersion('GitHubApi', 'Puc_v4p4_Vcs_GitHubApi', '4.4');
|
23 |
+
Puc_v4_Factory::addVersion('BitBucketApi', 'Puc_v4p4_Vcs_BitBucketApi', '4.4');
|
24 |
+
Puc_v4_Factory::addVersion('GitLabApi', 'Puc_v4p4_Vcs_GitLabApi', '4.4');
|
admin/update-checker/vendor/ParsedownLegacy.php
ADDED
@@ -0,0 +1,1535 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
#
|
4 |
+
#
|
5 |
+
# Parsedown
|
6 |
+
# http://parsedown.org
|
7 |
+
#
|
8 |
+
# (c) Emanuil Rusev
|
9 |
+
# http://erusev.com
|
10 |
+
#
|
11 |
+
# For the full license information, view the LICENSE file that was distributed
|
12 |
+
# with this source code.
|
13 |
+
#
|
14 |
+
#
|
15 |
+
|
16 |
+
class Parsedown
|
17 |
+
{
|
18 |
+
# ~
|
19 |
+
|
20 |
+
const version = '1.5.0';
|
21 |
+
|
22 |
+
# ~
|
23 |
+
|
24 |
+
function text($text)
|
25 |
+
{
|
26 |
+
# make sure no definitions are set
|
27 |
+
$this->DefinitionData = array();
|
28 |
+
|
29 |
+
# standardize line breaks
|
30 |
+
$text = str_replace(array("\r\n", "\r"), "\n", $text);
|
31 |
+
|
32 |
+
# remove surrounding line breaks
|
33 |
+
$text = trim($text, "\n");
|
34 |
+
|
35 |
+
# split text into lines
|
36 |
+
$lines = explode("\n", $text);
|
37 |
+
|
38 |
+
# iterate through lines to identify blocks
|
39 |
+
$markup = $this->lines($lines);
|
40 |
+
|
41 |
+
# trim line breaks
|
42 |
+
$markup = trim($markup, "\n");
|
43 |
+
|
44 |
+
return $markup;
|
45 |
+
}
|
46 |
+
|
47 |
+
#
|
48 |
+
# Setters
|
49 |
+
#
|
50 |
+
|
51 |
+
function setBreaksEnabled($breaksEnabled)
|
52 |
+
{
|
53 |
+
$this->breaksEnabled = $breaksEnabled;
|
54 |
+
|
55 |
+
return $this;
|
56 |
+
}
|
57 |
+
|
58 |
+
protected $breaksEnabled;
|
59 |
+
|
60 |
+
function setMarkupEscaped($markupEscaped)
|
61 |
+
{
|
62 |
+
$this->markupEscaped = $markupEscaped;
|
63 |
+
|
64 |
+
return $this;
|
65 |
+
}
|
66 |
+
|
67 |
+
protected $markupEscaped;
|
68 |
+
|
69 |
+
function setUrlsLinked($urlsLinked)
|
70 |
+
{
|
71 |
+
$this->urlsLinked = $urlsLinked;
|
72 |
+
|
73 |
+
return $this;
|
74 |
+
}
|
75 |
+
|
76 |
+
protected $urlsLinked = true;
|
77 |
+
|
78 |
+
#
|
79 |
+
# Lines
|
80 |
+
#
|
81 |
+
|
82 |
+
protected $BlockTypes = array(
|
83 |
+
'#' => array('Header'),
|
84 |
+
'*' => array('Rule', 'List'),
|
85 |
+
'+' => array('List'),
|
86 |
+
'-' => array('SetextHeader', 'Table', 'Rule', 'List'),
|
87 |
+
'0' => array('List'),
|
88 |
+
'1' => array('List'),
|
89 |
+
'2' => array('List'),
|
90 |
+
'3' => array('List'),
|
91 |
+
'4' => array('List'),
|
92 |
+
'5' => array('List'),
|
93 |
+
'6' => array('List'),
|
94 |
+
'7' => array('List'),
|
95 |
+
'8' => array('List'),
|
96 |
+
'9' => array('List'),
|
97 |
+
':' => array('Table'),
|
98 |
+
'<' => array('Comment', 'Markup'),
|
99 |
+
'=' => array('SetextHeader'),
|
100 |
+
'>' => array('Quote'),
|
101 |
+
'[' => array('Reference'),
|
102 |
+
'_' => array('Rule'),
|
103 |
+
'`' => array('FencedCode'),
|
104 |
+
'|' => array('Table'),
|
105 |
+
'~' => array('FencedCode'),
|
106 |
+
);
|
107 |
+
|
108 |
+
# ~
|
109 |
+
|
110 |
+
protected $DefinitionTypes = array(
|
111 |
+
'[' => array('Reference'),
|
112 |
+
);
|
113 |
+
|
114 |
+
# ~
|
115 |
+
|
116 |
+
protected $unmarkedBlockTypes = array(
|
117 |
+
'Code',
|
118 |
+
);
|
119 |
+
|
120 |
+
#
|
121 |
+
# Blocks
|
122 |
+
#
|
123 |
+
|
124 |
+
private function lines(array $lines)
|
125 |
+
{
|
126 |
+
$CurrentBlock = null;
|
127 |
+
|
128 |
+
foreach ($lines as $line)
|
129 |
+
{
|
130 |
+
if (chop($line) === '')
|
131 |
+
{
|
132 |
+
if (isset($CurrentBlock))
|
133 |
+
{
|
134 |
+
$CurrentBlock['interrupted'] = true;
|
135 |
+
}
|
136 |
+
|
137 |
+
continue;
|
138 |
+
}
|
139 |
+
|
140 |
+
if (strpos($line, "\t") !== false)
|
141 |
+
{
|
142 |
+
$parts = explode("\t", $line);
|
143 |
+
|
144 |
+
$line = $parts[0];
|
145 |
+
|
146 |
+
unset($parts[0]);
|
147 |
+
|
148 |
+
foreach ($parts as $part)
|
149 |
+
{
|
150 |
+
$shortage = 4 - mb_strlen($line, 'utf-8') % 4;
|
151 |
+
|
152 |
+
$line .= str_repeat(' ', $shortage);
|
153 |
+
$line .= $part;
|
154 |
+
}
|
155 |
+
}
|
156 |
+
|
157 |
+
$indent = 0;
|
158 |
+
|
159 |
+
while (isset($line[$indent]) and $line[$indent] === ' ')
|
160 |
+
{
|
161 |
+
$indent ++;
|
162 |
+
}
|
163 |
+
|
164 |
+
$text = $indent > 0 ? substr($line, $indent) : $line;
|
165 |
+
|
166 |
+
# ~
|
167 |
+
|
168 |
+
$Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
|
169 |
+
|
170 |
+
# ~
|
171 |
+
|
172 |
+
if (isset($CurrentBlock['incomplete']))
|
173 |
+
{
|
174 |
+
$Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
|
175 |
+
|
176 |
+
if (isset($Block))
|
177 |
+
{
|
178 |
+
$CurrentBlock = $Block;
|
179 |
+
|
180 |
+
continue;
|
181 |
+
}
|
182 |
+
else
|
183 |
+
{
|
184 |
+
if (method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
|
185 |
+
{
|
186 |
+
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
|
187 |
+
}
|
188 |
+
|
189 |
+
unset($CurrentBlock['incomplete']);
|
190 |
+
}
|
191 |
+
}
|
192 |
+
|
193 |
+
# ~
|
194 |
+
|
195 |
+
$marker = $text[0];
|
196 |
+
|
197 |
+
# ~
|
198 |
+
|
199 |
+
$blockTypes = $this->unmarkedBlockTypes;
|
200 |
+
|
201 |
+
if (isset($this->BlockTypes[$marker]))
|
202 |
+
{
|
203 |
+
foreach ($this->BlockTypes[$marker] as $blockType)
|
204 |
+
{
|
205 |
+
$blockTypes []= $blockType;
|
206 |
+
}
|
207 |
+
}
|
208 |
+
|
209 |
+
#
|
210 |
+
# ~
|
211 |
+
|
212 |
+
foreach ($blockTypes as $blockType)
|
213 |
+
{
|
214 |
+
$Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
|
215 |
+
|
216 |
+
if (isset($Block))
|
217 |
+
{
|
218 |
+
$Block['type'] = $blockType;
|
219 |
+
|
220 |
+
if ( ! isset($Block['identified']))
|
221 |
+
{
|
222 |
+
$Blocks []= $CurrentBlock;
|
223 |
+
|
224 |
+
$Block['identified'] = true;
|
225 |
+
}
|
226 |
+
|
227 |
+
if (method_exists($this, 'block'.$blockType.'Continue'))
|
228 |
+
{
|
229 |
+
$Block['incomplete'] = true;
|
230 |
+
}
|
231 |
+
|
232 |
+
$CurrentBlock = $Block;
|
233 |
+
|
234 |
+
continue 2;
|
235 |
+
}
|
236 |
+
}
|
237 |
+
|
238 |
+
# ~
|
239 |
+
|
240 |
+
if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
|
241 |
+
{
|
242 |
+
$CurrentBlock['element']['text'] .= "\n".$text;
|
243 |
+
}
|
244 |
+
else
|
245 |
+
{
|
246 |
+
$Blocks []= $CurrentBlock;
|
247 |
+
|
248 |
+
$CurrentBlock = $this->paragraph($Line);
|
249 |
+
|
250 |
+
$CurrentBlock['identified'] = true;
|
251 |
+
}
|
252 |
+
}
|
253 |
+
|
254 |
+
# ~
|
255 |
+
|
256 |
+
if (isset($CurrentBlock['incomplete']) and method_exists($this, 'block'.$CurrentBlock['type'].'Complete'))
|
257 |
+
{
|
258 |
+
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
|
259 |
+
}
|
260 |
+
|
261 |
+
# ~
|
262 |
+
|
263 |
+
$Blocks []= $CurrentBlock;
|
264 |
+
|
265 |
+
unset($Blocks[0]);
|
266 |
+
|
267 |
+
# ~
|
268 |
+
|
269 |
+
$markup = '';
|
270 |
+
|
271 |
+
foreach ($Blocks as $Block)
|
272 |
+
{
|
273 |
+
if (isset($Block['hidden']))
|
274 |
+
{
|
275 |
+
continue;
|
276 |
+
}
|
277 |
+
|
278 |
+
$markup .= "\n";
|
279 |
+
$markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
|
280 |
+
}
|
281 |
+
|
282 |
+
$markup .= "\n";
|
283 |
+
|
284 |
+
# ~
|
285 |
+
|
286 |
+
return $markup;
|
287 |
+
}
|
288 |
+
|
289 |
+
#
|
290 |
+
# Code
|
291 |
+
|
292 |
+
protected function blockCode($Line, $Block = null)
|
293 |
+
{
|
294 |
+
if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
|
295 |
+
{
|
296 |
+
return;
|
297 |
+
}
|
298 |
+
|
299 |
+
if ($Line['indent'] >= 4)
|
300 |
+
{
|
301 |
+
$text = substr($Line['body'], 4);
|
302 |
+
|
303 |
+
$Block = array(
|
304 |
+
'element' => array(
|
305 |
+
'name' => 'pre',
|
306 |
+
'handler' => 'element',
|
307 |
+
'text' => array(
|
308 |
+
'name' => 'code',
|
309 |
+
'text' => $text,
|
310 |
+
),
|
311 |
+
),
|
312 |
+
);
|
313 |
+
|
314 |
+
return $Block;
|
315 |
+
}
|
316 |
+
}
|
317 |
+
|
318 |
+
protected function blockCodeContinue($Line, $Block)
|
319 |
+
{
|
320 |
+
if ($Line['indent'] >= 4)
|
321 |
+
{
|
322 |
+
if (isset($Block['interrupted']))
|
323 |
+
{
|
324 |
+
$Block['element']['text']['text'] .= "\n";
|
325 |
+
|
326 |
+
unset($Block['interrupted']);
|
327 |
+
}
|
328 |
+
|
329 |
+
$Block['element']['text']['text'] .= "\n";
|
330 |
+
|
331 |
+
$text = substr($Line['body'], 4);
|
332 |
+
|
333 |
+
$Block['element']['text']['text'] .= $text;
|
334 |
+
|
335 |
+
return $Block;
|
336 |
+
}
|
337 |
+
}
|
338 |
+
|
339 |
+
protected function blockCodeComplete($Block)
|
340 |
+
{
|
341 |
+
$text = $Block['element']['text']['text'];
|
342 |
+
|
343 |
+
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
|
344 |
+
|
345 |
+
$Block['element']['text']['text'] = $text;
|
346 |
+
|
347 |
+
return $Block;
|
348 |
+
}
|
349 |
+
|
350 |
+
#
|
351 |
+
# Comment
|
352 |
+
|
353 |
+
protected function blockComment($Line)
|
354 |
+
{
|
355 |
+
if ($this->markupEscaped)
|
356 |
+
{
|
357 |
+
return;
|
358 |
+
}
|
359 |
+
|
360 |
+
if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
|
361 |
+
{
|
362 |
+
$Block = array(
|
363 |
+
'markup' => $Line['body'],
|
364 |
+
);
|
365 |
+
|
366 |
+
if (preg_match('/-->$/', $Line['text']))
|
367 |
+
{
|
368 |
+
$Block['closed'] = true;
|
369 |
+
}
|
370 |
+
|
371 |
+
return $Block;
|
372 |
+
}
|
373 |
+
}
|
374 |
+
|
375 |
+
protected function blockCommentContinue($Line, array $Block)
|
376 |
+
{
|
377 |
+
if (isset($Block['closed']))
|
378 |
+
{
|
379 |
+
return;
|
380 |
+
}
|
381 |
+
|
382 |
+
$Block['markup'] .= "\n" . $Line['body'];
|
383 |
+
|
384 |
+
if (preg_match('/-->$/', $Line['text']))
|
385 |
+
{
|
386 |
+
$Block['closed'] = true;
|
387 |
+
}
|
388 |
+
|
389 |
+
return $Block;
|
390 |
+
}
|
391 |
+
|
392 |
+
#
|
393 |
+
# Fenced Code
|
394 |
+
|
395 |
+
protected function blockFencedCode($Line)
|
396 |
+
{
|
397 |
+
if (preg_match('/^(['.$Line['text'][0].']{3,})[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
|
398 |
+
{
|
399 |
+
$Element = array(
|
400 |
+
'name' => 'code',
|
401 |
+
'text' => '',
|
402 |
+
);
|
403 |
+
|
404 |
+
if (isset($matches[2]))
|
405 |
+
{
|
406 |
+
$class = 'language-'.$matches[2];
|
407 |
+
|
408 |
+
$Element['attributes'] = array(
|
409 |
+
'class' => $class,
|
410 |
+
);
|
411 |
+
}
|
412 |
+
|
413 |
+
$Block = array(
|
414 |
+
'char' => $Line['text'][0],
|
415 |
+
'element' => array(
|
416 |
+
'name' => 'pre',
|
417 |
+
'handler' => 'element',
|
418 |
+
'text' => $Element,
|
419 |
+
),
|
420 |
+
);
|
421 |
+
|
422 |
+
return $Block;
|
423 |
+
}
|
424 |
+
}
|
425 |
+
|
426 |
+
protected function blockFencedCodeContinue($Line, $Block)
|
427 |
+
{
|
428 |
+
if (isset($Block['complete']))
|
429 |
+
{
|
430 |
+
return;
|
431 |
+
}
|
432 |
+
|
433 |
+
if (isset($Block['interrupted']))
|
434 |
+
{
|
435 |
+
$Block['element']['text']['text'] .= "\n";
|
436 |
+
|
437 |
+
unset($Block['interrupted']);
|
438 |
+
}
|
439 |
+
|
440 |
+
if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
|
441 |
+
{
|
442 |
+
$Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
|
443 |
+
|
444 |
+
$Block['complete'] = true;
|
445 |
+
|
446 |
+
return $Block;
|
447 |
+
}
|
448 |
+
|
449 |
+
$Block['element']['text']['text'] .= "\n".$Line['body'];;
|
450 |
+
|
451 |
+
return $Block;
|
452 |
+
}
|
453 |
+
|
454 |
+
protected function blockFencedCodeComplete($Block)
|
455 |
+
{
|
456 |
+
$text = $Block['element']['text']['text'];
|
457 |
+
|
458 |
+
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
|
459 |
+
|
460 |
+
$Block['element']['text']['text'] = $text;
|
461 |
+
|
462 |
+
return $Block;
|
463 |
+
}
|
464 |
+
|
465 |
+
#
|
466 |
+
# Header
|
467 |
+
|
468 |
+
protected function blockHeader($Line)
|
469 |
+
{
|
470 |
+
if (isset($Line['text'][1]))
|
471 |
+
{
|
472 |
+
$level = 1;
|
473 |
+
|
474 |
+
while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
|
475 |
+
{
|
476 |
+
$level ++;
|
477 |
+
}
|
478 |
+
|
479 |
+
if ($level > 6)
|
480 |
+
{
|
481 |
+
return;
|
482 |
+
}
|
483 |
+
|
484 |
+
$text = trim($Line['text'], '# ');
|
485 |
+
|
486 |
+
$Block = array(
|
487 |
+
'element' => array(
|
488 |
+
'name' => 'h' . min(6, $level),
|
489 |
+
'text' => $text,
|
490 |
+
'handler' => 'line',
|
491 |
+
),
|
492 |
+
);
|
493 |
+
|
494 |
+
return $Block;
|
495 |
+
}
|
496 |
+
}
|
497 |
+
|
498 |
+
#
|
499 |
+
# List
|
500 |
+
|
501 |
+
protected function blockList($Line)
|
502 |
+
{
|
503 |
+
list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
|
504 |
+
|
505 |
+
if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
|
506 |
+
{
|
507 |
+
$Block = array(
|
508 |
+
'indent' => $Line['indent'],
|
509 |
+
'pattern' => $pattern,
|
510 |
+
'element' => array(
|
511 |
+
'name' => $name,
|
512 |
+
'handler' => 'elements',
|
513 |
+
),
|
514 |
+
);
|
515 |
+
|
516 |
+
$Block['li'] = array(
|
517 |
+
'name' => 'li',
|
518 |
+
'handler' => 'li',
|
519 |
+
'text' => array(
|
520 |
+
$matches[2],
|
521 |
+
),
|
522 |
+
);
|
523 |
+
|
524 |
+
$Block['element']['text'] []= & $Block['li'];
|
525 |
+
|
526 |
+
return $Block;
|
527 |
+
}
|
528 |
+
}
|
529 |
+
|
530 |
+
protected function blockListContinue($Line, array $Block)
|
531 |
+
{
|
532 |
+
if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
|
533 |
+
{
|
534 |
+
if (isset($Block['interrupted']))
|
535 |
+
{
|
536 |
+
$Block['li']['text'] []= '';
|
537 |
+
|
538 |
+
unset($Block['interrupted']);
|
539 |
+
}
|
540 |
+
|
541 |
+
unset($Block['li']);
|
542 |
+
|
543 |
+
$text = isset($matches[1]) ? $matches[1] : '';
|
544 |
+
|
545 |
+
$Block['li'] = array(
|
546 |
+
'name' => 'li',
|
547 |
+
'handler' => 'li',
|
548 |
+
'text' => array(
|
549 |
+
$text,
|
550 |
+
),
|
551 |
+
);
|
552 |
+
|
553 |
+
$Block['element']['text'] []= & $Block['li'];
|
554 |
+
|
555 |
+
return $Block;
|
556 |
+
}
|
557 |
+
|
558 |
+
if ($Line['text'][0] === '[' and $this->blockReference($Line))
|
559 |
+
{
|
560 |
+
return $Block;
|
561 |
+
}
|
562 |
+
|
563 |
+
if ( ! isset($Block['interrupted']))
|
564 |
+
{
|
565 |
+
$text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
|
566 |
+
|
567 |
+
$Block['li']['text'] []= $text;
|
568 |
+
|
569 |
+
return $Block;
|
570 |
+
}
|
571 |
+
|
572 |
+
if ($Line['indent'] > 0)
|
573 |
+
{
|
574 |
+
$Block['li']['text'] []= '';
|
575 |
+
|
576 |
+
$text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
|
577 |
+
|
578 |
+
$Block['li']['text'] []= $text;
|
579 |
+
|
580 |
+
unset($Block['interrupted']);
|
581 |
+
|
582 |
+
return $Block;
|
583 |
+
}
|
584 |
+
}
|
585 |
+
|
586 |
+
#
|
587 |
+
# Quote
|
588 |
+
|
589 |
+
protected function blockQuote($Line)
|
590 |
+
{
|
591 |
+
if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
|
592 |
+
{
|
593 |
+
$Block = array(
|
594 |
+
'element' => array(
|
595 |
+
'name' => 'blockquote',
|
596 |
+
'handler' => 'lines',
|
597 |
+
'text' => (array) $matches[1],
|
598 |
+
),
|
599 |
+
);
|
600 |
+
|
601 |
+
return $Block;
|
602 |
+
}
|
603 |
+
}
|
604 |
+
|
605 |
+
protected function blockQuoteContinue($Line, array $Block)
|
606 |
+
{
|
607 |
+
if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
|
608 |
+
{
|
609 |
+
if (isset($Block['interrupted']))
|
610 |
+
{
|
611 |
+
$Block['element']['text'] []= '';
|
612 |
+
|
613 |
+
unset($Block['interrupted']);
|
614 |
+
}
|
615 |
+
|
616 |
+
$Block['element']['text'] []= $matches[1];
|
617 |
+
|
618 |
+
return $Block;
|
619 |
+
}
|
620 |
+
|
621 |
+
if ( ! isset($Block['interrupted']))
|
622 |
+
{
|
623 |
+
$Block['element']['text'] []= $Line['text'];
|
624 |
+
|
625 |
+
return $Block;
|
626 |
+
}
|
627 |
+
}
|
628 |
+
|
629 |
+
#
|
630 |
+
# Rule
|
631 |
+
|
632 |
+
protected function blockRule($Line)
|
633 |
+
{
|
634 |
+
if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
|
635 |
+
{
|
636 |
+
$Block = array(
|
637 |
+
'element' => array(
|
638 |
+
'name' => 'hr'
|
639 |
+
),
|
640 |
+
);
|
641 |
+
|
642 |
+
return $Block;
|
643 |
+
}
|
644 |
+
}
|
645 |
+
|
646 |
+
#
|
647 |
+
# Setext
|
648 |
+
|
649 |
+
protected function blockSetextHeader($Line, array $Block = null)
|
650 |
+
{
|
651 |
+
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
|
652 |
+
{
|
653 |
+
return;
|
654 |
+
}
|
655 |
+
|
656 |
+
if (chop($Line['text'], $Line['text'][0]) === '')
|
657 |
+
{
|
658 |
+
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
|
659 |
+
|
660 |
+
return $Block;
|
661 |
+
}
|
662 |
+
}
|
663 |
+
|
664 |
+
#
|
665 |
+
# Markup
|
666 |
+
|
667 |
+
protected function blockMarkup($Line)
|
668 |
+
{
|
669 |
+
if ($this->markupEscaped)
|
670 |
+
{
|
671 |
+
return;
|
672 |
+
}
|
673 |
+
|
674 |
+
if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
|
675 |
+
{
|
676 |
+
if (in_array($matches[1], $this->textLevelElements))
|
677 |
+
{
|
678 |
+
return;
|
679 |
+
}
|
680 |
+
|
681 |
+
$Block = array(
|
682 |
+
'name' => $matches[1],
|
683 |
+
'depth' => 0,
|
684 |
+
'markup' => $Line['text'],
|
685 |
+
);
|
686 |
+
|
687 |
+
$length = strlen($matches[0]);
|
688 |
+
|
689 |
+
$remainder = substr($Line['text'], $length);
|
690 |
+
|
691 |
+
if (trim($remainder) === '')
|
692 |
+
{
|
693 |
+
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
|
694 |
+
{
|
695 |
+
$Block['closed'] = true;
|
696 |
+
|
697 |
+
$Block['void'] = true;
|
698 |
+
}
|
699 |
+
}
|
700 |
+
else
|
701 |
+
{
|
702 |
+
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
|
703 |
+
{
|
704 |
+
return;
|
705 |
+
}
|
706 |
+
|
707 |
+
if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
|
708 |
+
{
|
709 |
+
$Block['closed'] = true;
|
710 |
+
}
|
711 |
+
}
|
712 |
+
|
713 |
+
return $Block;
|
714 |
+
}
|
715 |
+
}
|
716 |
+
|
717 |
+
protected function blockMarkupContinue($Line, array $Block)
|
718 |
+
{
|
719 |
+
if (isset($Block['closed']))
|
720 |
+
{
|
721 |
+
return;
|
722 |
+
}
|
723 |
+
|
724 |
+
if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
|
725 |
+
{
|
726 |
+
$Block['depth'] ++;
|
727 |
+
}
|
728 |
+
|
729 |
+
if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
|
730 |
+
{
|
731 |
+
if ($Block['depth'] > 0)
|
732 |
+
{
|
733 |
+
$Block['depth'] --;
|
734 |
+
}
|
735 |
+
else
|
736 |
+
{
|
737 |
+
$Block['closed'] = true;
|
738 |
+
}
|
739 |
+
|
740 |
+
$Block['markup'] .= $matches[1];
|
741 |
+
}
|
742 |
+
|
743 |
+
if (isset($Block['interrupted']))
|
744 |
+
{
|
745 |
+
$Block['markup'] .= "\n";
|
746 |
+
|
747 |
+
unset($Block['interrupted']);
|
748 |
+
}
|
749 |
+
|
750 |
+
$Block['markup'] .= "\n".$Line['body'];
|
751 |
+
|
752 |
+
return $Block;
|
753 |
+
}
|
754 |
+
|
755 |
+
#
|
756 |
+
# Reference
|
757 |
+
|
758 |
+
protected function blockReference($Line)
|
759 |
+
{
|
760 |
+
if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
|
761 |
+
{
|
762 |
+
$id = strtolower($matches[1]);
|
763 |
+
|
764 |
+
$Data = array(
|
765 |
+
'url' => $matches[2],
|
766 |
+
'title' => null,
|
767 |
+
);
|
768 |
+
|
769 |
+
if (isset($matches[3]))
|
770 |
+
{
|
771 |
+
$Data['title'] = $matches[3];
|
772 |
+
}
|
773 |
+
|
774 |
+
$this->DefinitionData['Reference'][$id] = $Data;
|
775 |
+
|
776 |
+
$Block = array(
|
777 |
+
'hidden' => true,
|
778 |
+
);
|
779 |
+
|
780 |
+
return $Block;
|
781 |
+
}
|
782 |
+
}
|
783 |
+
|
784 |
+
#
|
785 |
+
# Table
|
786 |
+
|
787 |
+
protected function blockTable($Line, array $Block = null)
|
788 |
+
{
|
789 |
+
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
|
790 |
+
{
|
791 |
+
return;
|
792 |
+
}
|
793 |
+
|
794 |
+
if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
|
795 |
+
{
|
796 |
+
$alignments = array();
|
797 |
+
|
798 |
+
$divider = $Line['text'];
|
799 |
+
|
800 |
+
$divider = trim($divider);
|
801 |
+
$divider = trim($divider, '|');
|
802 |
+
|
803 |
+
$dividerCells = explode('|', $divider);
|
804 |
+
|
805 |
+
foreach ($dividerCells as $dividerCell)
|
806 |
+
{
|
807 |
+
$dividerCell = trim($dividerCell);
|
808 |
+
|
809 |
+
if ($dividerCell === '')
|
810 |
+
{
|
811 |
+
continue;
|
812 |
+
}
|
813 |
+
|
814 |
+
$alignment = null;
|
815 |
+
|
816 |
+
if ($dividerCell[0] === ':')
|
817 |
+
{
|
818 |
+
$alignment = 'left';
|
819 |
+
}
|
820 |
+
|
821 |
+
if (substr($dividerCell, - 1) === ':')
|
822 |
+
{
|
823 |
+
$alignment = $alignment === 'left' ? 'center' : 'right';
|
824 |
+
}
|
825 |
+
|
826 |
+
$alignments []= $alignment;
|
827 |
+
}
|
828 |
+
|
829 |
+
# ~
|
830 |
+
|
831 |
+
$HeaderElements = array();
|
832 |
+
|
833 |
+
$header = $Block['element']['text'];
|
834 |
+
|
835 |
+
$header = trim($header);
|
836 |
+
$header = trim($header, '|');
|
837 |
+
|
838 |
+
$headerCells = explode('|', $header);
|
839 |
+
|
840 |
+
foreach ($headerCells as $index => $headerCell)
|
841 |
+
{
|
842 |
+
$headerCell = trim($headerCell);
|
843 |
+
|
844 |
+
$HeaderElement = array(
|
845 |
+
'name' => 'th',
|
846 |
+
'text' => $headerCell,
|
847 |
+
'handler' => 'line',
|
848 |
+
);
|
849 |
+
|
850 |
+
if (isset($alignments[$index]))
|
851 |
+
{
|
852 |
+
$alignment = $alignments[$index];
|
853 |
+
|
854 |
+
$HeaderElement['attributes'] = array(
|
855 |
+
'style' => 'text-align: '.$alignment.';',
|
856 |
+
);
|
857 |
+
}
|
858 |
+
|
859 |
+
$HeaderElements []= $HeaderElement;
|
860 |
+
}
|
861 |
+
|
862 |
+
# ~
|
863 |
+
|
864 |
+
$Block = array(
|
865 |
+
'alignments' => $alignments,
|
866 |
+
'identified' => true,
|
867 |
+
'element' => array(
|
868 |
+
'name' => 'table',
|
869 |
+
'handler' => 'elements',
|
870 |
+
),
|
871 |
+
);
|
872 |
+
|
873 |
+
$Block['element']['text'] []= array(
|
874 |
+
'name' => 'thead',
|
875 |
+
'handler' => 'elements',
|
876 |
+
);
|
877 |
+
|
878 |
+
$Block['element']['text'] []= array(
|
879 |
+
'name' => 'tbody',
|
880 |
+
'handler' => 'elements',
|
881 |
+
'text' => array(),
|
882 |
+
);
|
883 |
+
|
884 |
+
$Block['element']['text'][0]['text'] []= array(
|
885 |
+
'name' => 'tr',
|
886 |
+
'handler' => 'elements',
|
887 |
+
'text' => $HeaderElements,
|
888 |
+
);
|
889 |
+
|
890 |
+
return $Block;
|
891 |
+
}
|
892 |
+
}
|
893 |
+
|
894 |
+
protected function blockTableContinue($Line, array $Block)
|
895 |
+
{
|
896 |
+
if (isset($Block['interrupted']))
|
897 |
+
{
|
898 |
+
return;
|
899 |
+
}
|
900 |
+
|
901 |
+
if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
|
902 |
+
{
|
903 |
+
$Elements = array();
|
904 |
+
|
905 |
+
$row = $Line['text'];
|
906 |
+
|
907 |
+
$row = trim($row);
|
908 |
+
$row = trim($row, '|');
|
909 |
+
|
910 |
+
preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
|
911 |
+
|
912 |
+
foreach ($matches[0] as $index => $cell)
|
913 |
+
{
|
914 |
+
$cell = trim($cell);
|
915 |
+
|
916 |
+
$Element = array(
|
917 |
+
'name' => 'td',
|
918 |
+
'handler' => 'line',
|
919 |
+
'text' => $cell,
|
920 |
+
);
|
921 |
+
|
922 |
+
if (isset($Block['alignments'][$index]))
|
923 |
+
{
|
924 |
+
$Element['attributes'] = array(
|
925 |
+
'style' => 'text-align: '.$Block['alignments'][$index].';',
|
926 |
+
);
|
927 |
+
}
|
928 |
+
|
929 |
+
$Elements []= $Element;
|
930 |
+
}
|
931 |
+
|
932 |
+
$Element = array(
|
933 |
+
'name' => 'tr',
|
934 |
+
'handler' => 'elements',
|
935 |
+
'text' => $Elements,
|
936 |
+
);
|
937 |
+
|
938 |
+
$Block['element']['text'][1]['text'] []= $Element;
|
939 |
+
|
940 |
+
return $Block;
|
941 |
+
}
|
942 |
+
}
|
943 |
+
|
944 |
+
#
|
945 |
+
# ~
|
946 |
+
#
|
947 |
+
|
948 |
+
protected function paragraph($Line)
|
949 |
+
{
|
950 |
+
$Block = array(
|
951 |
+
'element' => array(
|
952 |
+
'name' => 'p',
|
953 |
+
'text' => $Line['text'],
|
954 |
+
'handler' => 'line',
|
955 |
+
),
|
956 |
+
);
|
957 |
+
|
958 |
+
return $Block;
|
959 |
+
}
|
960 |
+
|
961 |
+
#
|
962 |
+
# Inline Elements
|
963 |
+
#
|
964 |
+
|
965 |
+
protected $InlineTypes = array(
|
966 |
+
'"' => array('SpecialCharacter'),
|
967 |
+
'!' => array('Image'),
|
968 |
+
'&' => array('SpecialCharacter'),
|
969 |
+
'*' => array('Emphasis'),
|
970 |
+
':' => array('Url'),
|
971 |
+
'<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
|
972 |
+
'>' => array('SpecialCharacter'),
|
973 |
+
'[' => array('Link'),
|
974 |
+
'_' => array('Emphasis'),
|
975 |
+
'`' => array('Code'),
|
976 |
+
'~' => array('Strikethrough'),
|
977 |
+
'\\' => array('EscapeSequence'),
|
978 |
+
);
|
979 |
+
|
980 |
+
# ~
|
981 |
+
|
982 |
+
protected $inlineMarkerList = '!"*_&[:<>`~\\';
|
983 |
+
|
984 |
+
#
|
985 |
+
# ~
|
986 |
+
#
|
987 |
+
|
988 |
+
public function line($text)
|
989 |
+
{
|
990 |
+
$markup = '';
|
991 |
+
|
992 |
+
$unexaminedText = $text;
|
993 |
+
|
994 |
+
$markerPosition = 0;
|
995 |
+
|
996 |
+
while ($excerpt = strpbrk($unexaminedText, $this->inlineMarkerList))
|
997 |
+
{
|
998 |
+
$marker = $excerpt[0];
|
999 |
+
|
1000 |
+
$markerPosition += strpos($unexaminedText, $marker);
|
1001 |
+
|
1002 |
+
$Excerpt = array('text' => $excerpt, 'context' => $text);
|
1003 |
+
|
1004 |
+
foreach ($this->InlineTypes[$marker] as $inlineType)
|
1005 |
+
{
|
1006 |
+
$Inline = $this->{'inline'.$inlineType}($Excerpt);
|
1007 |
+
|
1008 |
+
if ( ! isset($Inline))
|
1009 |
+
{
|
1010 |
+
continue;
|
1011 |
+
}
|
1012 |
+
|
1013 |
+
if (isset($Inline['position']) and $Inline['position'] > $markerPosition) # position is ahead of marker
|
1014 |
+
{
|
1015 |
+
continue;
|
1016 |
+
}
|
1017 |
+
|
1018 |
+
if ( ! isset($Inline['position']))
|
1019 |
+
{
|
1020 |
+
$Inline['position'] = $markerPosition;
|
1021 |
+
}
|
1022 |
+
|
1023 |
+
$unmarkedText = substr($text, 0, $Inline['position']);
|
1024 |
+
|
1025 |
+
$markup .= $this->unmarkedText($unmarkedText);
|
1026 |
+
|
1027 |
+
$markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
|
1028 |
+
|
1029 |
+
$text = substr($text, $Inline['position'] + $Inline['extent']);
|
1030 |
+
|
1031 |
+
$unexaminedText = $text;
|
1032 |
+
|
1033 |
+
$markerPosition = 0;
|
1034 |
+
|
1035 |
+
continue 2;
|
1036 |
+
}
|
1037 |
+
|
1038 |
+
$unexaminedText = substr($excerpt, 1);
|
1039 |
+
|
1040 |
+
$markerPosition ++;
|
1041 |
+
}
|
1042 |
+
|
1043 |
+
$markup .= $this->unmarkedText($text);
|
1044 |
+
|
1045 |
+
return $markup;
|
1046 |
+
}
|
1047 |
+
|
1048 |
+
#
|
1049 |
+
# ~
|
1050 |
+
#
|
1051 |
+
|
1052 |
+
protected function inlineCode($Excerpt)
|
1053 |
+
{
|
1054 |
+
$marker = $Excerpt['text'][0];
|
1055 |
+
|
1056 |
+
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
|
1057 |
+
{
|
1058 |
+
$text = $matches[2];
|
1059 |
+
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
|
1060 |
+
$text = preg_replace("/[ ]*\n/", ' ', $text);
|
1061 |
+
|
1062 |
+
return array(
|
1063 |
+
'extent' => strlen($matches[0]),
|
1064 |
+
'element' => array(
|
1065 |
+
'name' => 'code',
|
1066 |
+
'text' => $text,
|
1067 |
+
),
|
1068 |
+
);
|
1069 |
+
}
|
1070 |
+
}
|
1071 |
+
|
1072 |
+
protected function inlineEmailTag($Excerpt)
|
1073 |
+
{
|
1074 |
+
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
|
1075 |
+
{
|
1076 |
+
$url = $matches[1];
|
1077 |
+
|
1078 |
+
if ( ! isset($matches[2]))
|
1079 |
+
{
|
1080 |
+
$url = 'mailto:' . $url;
|
1081 |
+
}
|
1082 |
+
|
1083 |
+
return array(
|
1084 |
+
'extent' => strlen($matches[0]),
|
1085 |
+
'element' => array(
|
1086 |
+
'name' => 'a',
|
1087 |
+
'text' => $matches[1],
|
1088 |
+
'attributes' => array(
|
1089 |
+
'href' => $url,
|
1090 |
+
),
|
1091 |
+
),
|
1092 |
+
);
|
1093 |
+
}
|
1094 |
+
}
|
1095 |
+
|
1096 |
+
protected function inlineEmphasis($Excerpt)
|
1097 |
+
{
|
1098 |
+
if ( ! isset($Excerpt['text'][1]))
|
1099 |
+
{
|
1100 |
+
return;
|
1101 |
+
}
|
1102 |
+
|
1103 |
+
$marker = $Excerpt['text'][0];
|
1104 |
+
|
1105 |
+
if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
|
1106 |
+
{
|
1107 |
+
$emphasis = 'strong';
|
1108 |
+
}
|
1109 |
+
elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
|
1110 |
+
{
|
1111 |
+
$emphasis = 'em';
|
1112 |
+
}
|
1113 |
+
else
|
1114 |
+
{
|
1115 |
+
return;
|
1116 |
+
}
|
1117 |
+
|
1118 |
+
return array(
|
1119 |
+
'extent' => strlen($matches[0]),
|
1120 |
+
'element' => array(
|
1121 |
+
'name' => $emphasis,
|
1122 |
+
'handler' => 'line',
|
1123 |
+
'text' => $matches[1],
|
1124 |
+
),
|
1125 |
+
);
|
1126 |
+
}
|
1127 |
+
|
1128 |
+
protected function inlineEscapeSequence($Excerpt)
|
1129 |
+
{
|
1130 |
+
if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
|
1131 |
+
{
|
1132 |
+
return array(
|
1133 |
+
'markup' => $Excerpt['text'][1],
|
1134 |
+
'extent' => 2,
|
1135 |
+
);
|
1136 |
+
}
|
1137 |
+
}
|
1138 |
+
|
1139 |
+
protected function inlineImage($Excerpt)
|
1140 |
+
{
|
1141 |
+
if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
|
1142 |
+
{
|
1143 |
+
return;
|
1144 |
+
}
|
1145 |
+
|
1146 |
+
$Excerpt['text']= substr($Excerpt['text'], 1);
|
1147 |
+
|
1148 |
+
$Link = $this->inlineLink($Excerpt);
|
1149 |
+
|
1150 |
+
if ($Link === null)
|
1151 |
+
{
|
1152 |
+
return;
|
1153 |
+
}
|
1154 |
+
|
1155 |
+
$Inline = array(
|
1156 |
+
'extent' => $Link['extent'] + 1,
|
1157 |
+
'element' => array(
|
1158 |
+
'name' => 'img',
|
1159 |
+
'attributes' => array(
|
1160 |
+
'src' => $Link['element']['attributes']['href'],
|
1161 |
+
'alt' => $Link['element']['text'],
|
1162 |
+
),
|
1163 |
+
),
|
1164 |
+
);
|
1165 |
+
|
1166 |
+
$Inline['element']['attributes'] += $Link['element']['attributes'];
|
1167 |
+
|
1168 |
+
unset($Inline['element']['attributes']['href']);
|
1169 |
+
|
1170 |
+
return $Inline;
|
1171 |
+
}
|
1172 |
+
|
1173 |
+
protected function inlineLink($Excerpt)
|
1174 |
+
{
|
1175 |
+
$Element = array(
|
1176 |
+
'name' => 'a',
|
1177 |
+
'handler' => 'line',
|
1178 |
+
'text' => null,
|
1179 |
+
'attributes' => array(
|
1180 |
+
'href' => null,
|
1181 |
+
'title' => null,
|
1182 |
+
),
|
1183 |
+
);
|
1184 |
+
|
1185 |
+
$extent = 0;
|
1186 |
+
|
1187 |
+
$remainder = $Excerpt['text'];
|
1188 |
+
|
1189 |
+
if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
|
1190 |
+
{
|
1191 |
+
$Element['text'] = $matches[1];
|
1192 |
+
|
1193 |
+
$extent += strlen($matches[0]);
|
1194 |
+
|
1195 |
+
$remainder = substr($remainder, $extent);
|
1196 |
+
}
|
1197 |
+
else
|
1198 |
+
{
|
1199 |
+
return;
|
1200 |
+
}
|
1201 |
+
|
1202 |
+
if (preg_match('/^[(]((?:[^ (]|[(][^ )]+[)])+)(?:[ ]+("[^"]+"|\'[^\']+\'))?[)]/', $remainder, $matches))
|
1203 |
+
{
|
1204 |
+
$Element['attributes']['href'] = $matches[1];
|
1205 |
+
|
1206 |
+
if (isset($matches[2]))
|
1207 |
+
{
|
1208 |
+
$Element['attributes']['title'] = substr($matches[2], 1, - 1);
|
1209 |
+
}
|
1210 |
+
|
1211 |
+
$extent += strlen($matches[0]);
|
1212 |
+
}
|
1213 |
+
else
|
1214 |
+
{
|
1215 |
+
if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
|
1216 |
+
{
|
1217 |
+
$definition = $matches[1] ? $matches[1] : $Element['text'];
|
1218 |
+
$definition = strtolower($definition);
|
1219 |
+
|
1220 |
+
$extent += strlen($matches[0]);
|
1221 |
+
}
|
1222 |
+
else
|
1223 |
+
{
|
1224 |
+
$definition = strtolower($Element['text']);
|
1225 |
+
}
|
1226 |
+
|
1227 |
+
if ( ! isset($this->DefinitionData['Reference'][$definition]))
|
1228 |
+
{
|
1229 |
+
return;
|
1230 |
+
}
|
1231 |
+
|
1232 |
+
$Definition = $this->DefinitionData['Reference'][$definition];
|
1233 |
+
|
1234 |
+
$Element['attributes']['href'] = $Definition['url'];
|
1235 |
+
$Element['attributes']['title'] = $Definition['title'];
|
1236 |
+
}
|
1237 |
+
|
1238 |
+
$Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']);
|
1239 |
+
|
1240 |
+
return array(
|
1241 |
+
'extent' => $extent,
|
1242 |
+
'element' => $Element,
|
1243 |
+
);
|
1244 |
+
}
|
1245 |
+
|
1246 |
+
protected function inlineMarkup($Excerpt)
|
1247 |
+
{
|
1248 |
+
if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
|
1249 |
+
{
|
1250 |
+
return;
|
1251 |
+
}
|
1252 |
+
|
1253 |
+
if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/s', $Excerpt['text'], $matches))
|
1254 |
+
{
|
1255 |
+
return array(
|
1256 |
+
'markup' => $matches[0],
|
1257 |
+
'extent' => strlen($matches[0]),
|
1258 |
+
);
|
1259 |
+
}
|
1260 |
+
|
1261 |
+
if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
|
1262 |
+
{
|
1263 |
+
return array(
|
1264 |
+
'markup' => $matches[0],
|
1265 |
+
'extent' => strlen($matches[0]),
|
1266 |
+
);
|
1267 |
+
}
|
1268 |
+
|
1269 |
+
if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
|
1270 |
+
{
|
1271 |
+
return array(
|
1272 |
+
'markup' => $matches[0],
|
1273 |
+
'extent' => strlen($matches[0]),
|
1274 |
+
);
|
1275 |
+
}
|
1276 |
+
}
|
1277 |
+
|
1278 |
+
protected function inlineSpecialCharacter($Excerpt)
|
1279 |
+
{
|
1280 |
+
if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
|
1281 |
+
{
|
1282 |
+
return array(
|
1283 |
+
'markup' => '&',
|
1284 |
+
'extent' => 1,
|
1285 |
+
);
|
1286 |
+
}
|
1287 |
+
|
1288 |
+
$SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
|
1289 |
+
|
1290 |
+
if (isset($SpecialCharacter[$Excerpt['text'][0]]))
|
1291 |
+
{
|
1292 |
+
return array(
|
1293 |
+
'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
|
1294 |
+
'extent' => 1,
|
1295 |
+
);
|
1296 |
+
}
|
1297 |
+
}
|
1298 |
+
|
1299 |
+
protected function inlineStrikethrough($Excerpt)
|
1300 |
+
{
|
1301 |
+
if ( ! isset($Excerpt['text'][1]))
|
1302 |
+
{
|
1303 |
+
return;
|
1304 |
+
}
|
1305 |
+
|
1306 |
+
if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
|
1307 |
+
{
|
1308 |
+
return array(
|
1309 |
+
'extent' => strlen($matches[0]),
|
1310 |
+
'element' => array(
|
1311 |
+
'name' => 'del',
|
1312 |
+
'text' => $matches[1],
|
1313 |
+
'handler' => 'line',
|
1314 |
+
),
|
1315 |
+
);
|
1316 |
+
}
|
1317 |
+
}
|
1318 |
+
|
1319 |
+
protected function inlineUrl($Excerpt)
|
1320 |
+
{
|
1321 |
+
if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
|
1322 |
+
{
|
1323 |
+
return;
|
1324 |
+
}
|
1325 |
+
|
1326 |
+
if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
|
1327 |
+
{
|
1328 |
+
$Inline = array(
|
1329 |
+
'extent' => strlen($matches[0][0]),
|
1330 |
+
'position' => $matches[0][1],
|
1331 |
+
'element' => array(
|
1332 |
+
'name' => 'a',
|
1333 |
+
'text' => $matches[0][0],
|
1334 |
+
'attributes' => array(
|
1335 |
+
'href' => $matches[0][0],
|
1336 |
+
),
|
1337 |
+
),
|
1338 |
+
);
|
1339 |
+
|
1340 |
+
return $Inline;
|
1341 |
+
}
|
1342 |
+
}
|
1343 |
+
|
1344 |
+
protected function inlineUrlTag($Excerpt)
|
1345 |
+
{
|
1346 |
+
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
|
1347 |
+
{
|
1348 |
+
$url = str_replace(array('&', '<'), array('&', '<'), $matches[1]);
|
1349 |
+
|
1350 |
+
return array(
|
1351 |
+
'extent' => strlen($matches[0]),
|
1352 |
+
'element' => array(
|
1353 |
+
'name' => 'a',
|
1354 |
+
'text' => $url,
|
1355 |
+
'attributes' => array(
|
1356 |
+
'href' => $url,
|
1357 |
+
),
|
1358 |
+
),
|
1359 |
+
);
|
1360 |
+
}
|
1361 |
+
}
|
1362 |
+
|
1363 |
+
#
|
1364 |
+
# ~
|
1365 |
+
|
1366 |
+
protected $unmarkedInlineTypes = array("\n" => 'Break', '://' => 'Url');
|
1367 |
+
|
1368 |
+
# ~
|
1369 |
+
|
1370 |
+
protected function unmarkedText($text)
|
1371 |
+
{
|
1372 |
+
if ($this->breaksEnabled)
|
1373 |
+
{
|
1374 |
+
$text = preg_replace('/[ ]*\n/', "<br />\n", $text);
|
1375 |
+
}
|
1376 |
+
else
|
1377 |
+
{
|
1378 |
+
$text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
|
1379 |
+
$text = str_replace(" \n", "\n", $text);
|
1380 |
+
}
|
1381 |
+
|
1382 |
+
return $text;
|
1383 |
+
}
|
1384 |
+
|
1385 |
+
#
|
1386 |
+
# Handlers
|
1387 |
+
#
|
1388 |
+
|
1389 |
+
protected function element(array $Element)
|
1390 |
+
{
|
1391 |
+
$markup = '<'.$Element['name'];
|
1392 |
+
|
1393 |
+
if (isset($Element['attributes']))
|
1394 |
+
{
|
1395 |
+
foreach ($Element['attributes'] as $name => $value)
|
1396 |
+
{
|
1397 |
+
if ($value === null)
|
1398 |
+
{
|
1399 |
+
continue;
|
1400 |
+
}
|
1401 |
+
|
1402 |
+
$markup .= ' '.$name.'="'.$value.'"';
|
1403 |
+
}
|
1404 |
+
}
|
1405 |
+
|
1406 |
+
if (isset($Element['text']))
|
1407 |
+
{
|
1408 |
+
$markup .= '>';
|
1409 |
+
|
1410 |
+
if (isset($Element['handler']))
|
1411 |
+
{
|
1412 |
+
$markup .= $this->{$Element['handler']}($Element['text']);
|
1413 |
+
}
|
1414 |
+
else
|
1415 |
+
{
|
1416 |
+
$markup .= $Element['text'];
|
1417 |
+
}
|
1418 |
+
|
1419 |
+
$markup .= '</'.$Element['name'].'>';
|
1420 |
+
}
|
1421 |
+
else
|
1422 |
+
{
|
1423 |
+
$markup .= ' />';
|
1424 |
+
}
|
1425 |
+
|
1426 |
+
return $markup;
|
1427 |
+
}
|
1428 |
+
|
1429 |
+
protected function elements(array $Elements)
|
1430 |
+
{
|
1431 |
+
$markup = '';
|
1432 |
+
|
1433 |
+
foreach ($Elements as $Element)
|
1434 |
+
{
|
1435 |
+
$markup .= "\n" . $this->element($Element);
|
1436 |
+
}
|
1437 |
+
|
1438 |
+
$markup .= "\n";
|
1439 |
+
|
1440 |
+
return $markup;
|
1441 |
+
}
|
1442 |
+
|
1443 |
+
# ~
|
1444 |
+
|
1445 |
+
protected function li($lines)
|
1446 |
+
{
|
1447 |
+
$markup = $this->lines($lines);
|
1448 |
+
|
1449 |
+
$trimmedMarkup = trim($markup);
|
1450 |
+
|
1451 |
+
if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
|
1452 |
+
{
|
1453 |
+
$markup = $trimmedMarkup;
|
1454 |
+
$markup = substr($markup, 3);
|
1455 |
+
|
1456 |
+
$position = strpos($markup, "</p>");
|
1457 |
+
|
1458 |
+
$markup = substr_replace($markup, '', $position, 4);
|
1459 |
+
}
|
1460 |
+
|
1461 |
+
return $markup;
|
1462 |
+
}
|
1463 |
+
|
1464 |
+
#
|
1465 |
+
# Deprecated Methods
|
1466 |
+
#
|
1467 |
+
|
1468 |
+
function parse($text)
|
1469 |
+
{
|
1470 |
+
$markup = $this->text($text);
|
1471 |
+
|
1472 |
+
return $markup;
|
1473 |
+
}
|
1474 |
+
|
1475 |
+
#
|
1476 |
+
# Static Methods
|
1477 |
+
#
|
1478 |
+
|
1479 |
+
static function instance($name = 'default')
|
1480 |
+
{
|
1481 |
+
if (isset(self::$instances[$name]))
|
1482 |
+
{
|
1483 |
+
return self::$instances[$name];
|
1484 |
+
}
|
1485 |
+
|
1486 |
+
$instance = new self();
|
1487 |
+
|
1488 |
+
self::$instances[$name] = $instance;
|
1489 |
+
|
1490 |
+
return $instance;
|
1491 |
+
}
|
1492 |
+
|
1493 |
+
private static $instances = array();
|
1494 |
+
|
1495 |
+
#
|
1496 |
+
# Fields
|
1497 |
+
#
|
1498 |
+
|
1499 |
+
protected $DefinitionData;
|
1500 |
+
|
1501 |
+
#
|
1502 |
+
# Read-Only
|
1503 |
+
|
1504 |
+
protected $specialCharacters = array(
|
1505 |
+
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
|
1506 |
+
);
|
1507 |
+
|
1508 |
+
protected $StrongRegex = array(
|
1509 |
+
'*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
|
1510 |
+
'_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
|
1511 |
+
);
|
1512 |
+
|
1513 |
+
protected $EmRegex = array(
|
1514 |
+
'*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
|
1515 |
+
'_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
|
1516 |
+
);
|
1517 |
+
|
1518 |
+
protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
|
1519 |
+
|
1520 |
+
protected $voidElements = array(
|
1521 |
+
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
|
1522 |
+
);
|
1523 |
+
|
1524 |
+
protected $textLevelElements = array(
|
1525 |
+
'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
|
1526 |
+
'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
|
1527 |
+
'i', 'rp', 'del', 'code', 'strike', 'marquee',
|
1528 |
+
'q', 'rt', 'ins', 'font', 'strong',
|
1529 |
+
's', 'tt', 'sub', 'mark',
|
1530 |
+
'u', 'xm', 'sup', 'nobr',
|
1531 |
+
'var', 'ruby',
|
1532 |
+
'wbr', 'span',
|
1533 |
+
'time',
|
1534 |
+
);
|
1535 |
+
}
|
admin/view/addons.php
CHANGED
@@ -90,7 +90,7 @@ if ( !is_array( $list_addons ) ) {
|
|
90 |
<div class="plugin-description"><p><?php echo $a_addon['description'] ?></p></div>
|
91 |
<?php if ( ( is_plugin_active( $a_addon[ 'slug' ] . '/index.php' ) || is_plugin_active( $a_addon[ 'slug' ] . '/' . $a_addon[ 'slug' ] . '.php' ) ) ): ?>
|
92 |
<div class="active second">
|
93 |
-
License Key
|
94 |
</div>
|
95 |
<?php endif; ?>
|
96 |
</td>
|
90 |
<div class="plugin-description"><p><?php echo $a_addon['description'] ?></p></div>
|
91 |
<?php if ( ( is_plugin_active( $a_addon[ 'slug' ] . '/index.php' ) || is_plugin_active( $a_addon[ 'slug' ] . '/' . $a_addon[ 'slug' ] . '.php' ) ) ): ?>
|
92 |
<div class="active second">
|
93 |
+
License Key <input type="text" name="licenses[<?php echo $a_addon['slug'] ?>]" value="<?php echo !empty( wp_slimstat::$settings[ 'addon_licenses' ][ $a_addon[ 'slug' ] ] ) ? wp_slimstat::$settings[ 'addon_licenses' ][ $a_addon[ 'slug' ] ] : '' ?>" size="50">
|
94 |
</div>
|
95 |
<?php endif; ?>
|
96 |
</td>
|
admin/view/wp-slimstat-reports.php
CHANGED
@@ -1871,8 +1871,8 @@ class wp_slimstat_reports {
|
|
1871 |
var dataProvider = {
|
1872 |
map: "worldLow",
|
1873 |
getAreasFromMap: true,
|
1874 |
-
areas:[ <?php echo implode(',', $data_areas) ?> ],
|
1875 |
-
images: [ <?php echo implode(',', $data_points) ?> ]
|
1876 |
};
|
1877 |
|
1878 |
// Create AmMap object
|
1871 |
var dataProvider = {
|
1872 |
map: "worldLow",
|
1873 |
getAreasFromMap: true,
|
1874 |
+
areas:[ <?php echo implode( ',', $data_areas ) ?> ],
|
1875 |
+
images: [ <?php if ( !empty( $data_points ) ) echo implode( ',', $data_points ) ?> ]
|
1876 |
};
|
1877 |
|
1878 |
// Create AmMap object
|
admin/wp-slimstat-admin.php
CHANGED
@@ -11,8 +11,8 @@ class wp_slimstat_admin {
|
|
11 |
* Init -- Sets things up.
|
12 |
*/
|
13 |
public static function init() {
|
14 |
-
|
15 |
-
|
16 |
|
17 |
// Load language files
|
18 |
load_plugin_textdomain( 'wp-slimstat', WP_PLUGIN_DIR .'/wp-slimstat/languages', '/wp-slimstat/languages' );
|
11 |
* Init -- Sets things up.
|
12 |
*/
|
13 |
public static function init() {
|
14 |
+
self::$admin_notice = "We just launched our <a href='https://www.wp-slimstat.com' target='_blank'>redesigned website</a>, to promote our plugin in a cleaner and simpler way. And we are also looking for <strong>sponsors</strong>: if you can donate your time, your talent or your money to support the development of Slimstat, please <a href='http://support.wp-slimstat.com/' target='_blank'>contact our support team</a> today to learn more.";
|
15 |
+
self::$admin_notice .= '<br/><br/><a id="slimstat-hide-admin-notice" href="#" class="button-secondary">Got it, thanks</a>';
|
16 |
|
17 |
// Load language files
|
18 |
load_plugin_textdomain( 'wp-slimstat', WP_PLUGIN_DIR .'/wp-slimstat/languages', '/wp-slimstat/languages' );
|
readme.txt
CHANGED
@@ -5,7 +5,7 @@ Tags: analytics, statistics, counter, tracking, reports, wassup, geolocation, on
|
|
5 |
Text Domain: wp-slimstat
|
6 |
Requires at least: 3.8
|
7 |
Tested up to: 4.9
|
8 |
-
Stable tag: 4.7.5.
|
9 |
|
10 |
== Description ==
|
11 |
The leading web analytics plugin for WordPress. Track returning customers and registered users, monitor Javascript events, detect intrusions, analyze email campaigns. Thousands of WordPress sites are already using it.
|
@@ -71,6 +71,11 @@ Our knowledge base is available on our [support center](http://docs.wp-slimstat.
|
|
71 |
5. **Responsive layout** - Keep an eye on your reports on the go
|
72 |
|
73 |
== Changelog ==
|
|
|
|
|
|
|
|
|
|
|
74 |
= 4.7.5.2 =
|
75 |
* [Update] You can now customize the amount of dots displayed on the World Map, under Slimstat > Settings > Reports > Access Log and World Map. Thank you, [service4](https://wordpress.org/support/topic/new-geolocation-map-with-cities/).
|
76 |
* [Fix] A dependency error was being highlighted for one of our premium add-ons under certain circumstances. Thank you, Peter.
|
5 |
Text Domain: wp-slimstat
|
6 |
Requires at least: 3.8
|
7 |
Tested up to: 4.9
|
8 |
+
Stable tag: 4.7.5.3
|
9 |
|
10 |
== Description ==
|
11 |
The leading web analytics plugin for WordPress. Track returning customers and registered users, monitor Javascript events, detect intrusions, analyze email campaigns. Thousands of WordPress sites are already using it.
|
71 |
5. **Responsive layout** - Keep an eye on your reports on the go
|
72 |
|
73 |
== Changelog ==
|
74 |
+
= 4.7.5.3 =
|
75 |
+
* [New] For those who can't manage to automatically update their local copy of our premium add-ons, we've added a link to manually download the zip file. You'll find it under the Plugins screen, once you've entered and saved the corresponding license key under Slimstat > Add-ons.
|
76 |
+
* [Update] The [Update Checker](https://github.com/YahnisElsts/plugin-update-checker) library has been updated to version 4.4.
|
77 |
+
* [Fix] The world map was not being displayed correctly if no data points were available for the selected time range (thank you, Michel).
|
78 |
+
|
79 |
= 4.7.5.2 =
|
80 |
* [Update] You can now customize the amount of dots displayed on the World Map, under Slimstat > Settings > Reports > Access Log and World Map. Thank you, [service4](https://wordpress.org/support/topic/new-geolocation-map-with-cities/).
|
81 |
* [Fix] A dependency error was being highlighted for one of our premium add-ons under certain circumstances. Thank you, Peter.
|
wp-slimstat.php
CHANGED
@@ -3,7 +3,7 @@
|
|
3 |
Plugin Name: Slimstat Analytics
|
4 |
Plugin URI: http://wordpress.org/plugins/wp-slimstat/
|
5 |
Description: The leading web analytics plugin for WordPress
|
6 |
-
Version: 4.7.5.
|
7 |
Author: Jason Crouse
|
8 |
Author URI: http://www.wp-slimstat.com/
|
9 |
Text Domain: wp-slimstat
|
@@ -15,7 +15,7 @@ if ( !empty( wp_slimstat::$settings ) ) {
|
|
15 |
}
|
16 |
|
17 |
class wp_slimstat {
|
18 |
-
public static $version = '4.7.5.
|
19 |
public static $settings = array();
|
20 |
|
21 |
public static $wpdb = '';
|
@@ -1789,16 +1789,42 @@ class wp_slimstat {
|
|
1789 |
}
|
1790 |
|
1791 |
$update_checker_objects = array();
|
|
|
|
|
|
|
|
|
1792 |
foreach ( self::$update_checker as $a_slug ) {
|
1793 |
$a_clean_slug = str_replace( array( 'wp_slimstat_', '_' ), array( '', '-' ), $a_slug );
|
|
|
1794 |
if ( !empty( self::$settings[ 'addon_licenses' ][ 'wp-slimstat-' . $a_clean_slug ] ) ) {
|
|
|
|
|
|
|
|
|
|
|
|
|
1795 |
|
1796 |
-
|
1797 |
-
|
1798 |
|
1799 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1800 |
}
|
1801 |
}
|
|
|
|
|
1802 |
}
|
1803 |
|
1804 |
public static function register_widget() {
|
3 |
Plugin Name: Slimstat Analytics
|
4 |
Plugin URI: http://wordpress.org/plugins/wp-slimstat/
|
5 |
Description: The leading web analytics plugin for WordPress
|
6 |
+
Version: 4.7.5.3
|
7 |
Author: Jason Crouse
|
8 |
Author URI: http://www.wp-slimstat.com/
|
9 |
Text Domain: wp-slimstat
|
15 |
}
|
16 |
|
17 |
class wp_slimstat {
|
18 |
+
public static $version = '4.7.5.3';
|
19 |
public static $settings = array();
|
20 |
|
21 |
public static $wpdb = '';
|
1789 |
}
|
1790 |
|
1791 |
$update_checker_objects = array();
|
1792 |
+
|
1793 |
+
// This is only included if add-ons are installed
|
1794 |
+
include_once( plugin_dir_path( __FILE__ ) . 'admin/update-checker/plugin-update-checker.php' );
|
1795 |
+
|
1796 |
foreach ( self::$update_checker as $a_slug ) {
|
1797 |
$a_clean_slug = str_replace( array( 'wp_slimstat_', '_' ), array( '', '-' ), $a_slug );
|
1798 |
+
|
1799 |
if ( !empty( self::$settings[ 'addon_licenses' ][ 'wp-slimstat-' . $a_clean_slug ] ) ) {
|
1800 |
+
$update_checker_objects[ $a_clean_slug ] = Puc_v4_Factory::buildUpdateChecker( 'http://www.wp-slimstat.com/update-checker/?slug=' . $a_clean_slug . '&key=' . urlencode( self::$settings[ 'addon_licenses' ][ 'wp-slimstat-' . $a_clean_slug ] ), dirname( dirname( __FILE__ ) ) . '/wp-slimstat-' . $a_clean_slug . '/index.php', 'wp-slimstat-' . $a_clean_slug );
|
1801 |
+
|
1802 |
+
add_filter( "plugin_action_links_wp-slimstat-{$a_clean_slug}/index.php", array( __CLASS__, 'add_plugin_manual_download_link' ), 10, 2 );
|
1803 |
+
}
|
1804 |
+
}
|
1805 |
+
}
|
1806 |
|
1807 |
+
public static function add_plugin_manual_download_link( $_links = array(), $_plugin_file = '' ) {
|
1808 |
+
$a_clean_slug = str_replace( array( 'wp-slimstat-', '/index.php' ), array( '', '' ), $_plugin_file );
|
1809 |
|
1810 |
+
if ( false !== ( $download_url = get_transient( 'wp-slimstat-download-link-' . $a_clean_slug ) ) ) {
|
1811 |
+
$_links[] = '<a href="' . $download_url . '">Download ZIP</a>';
|
1812 |
+
}
|
1813 |
+
else {
|
1814 |
+
$url = 'http://www.wp-slimstat.com/update-checker/?slug=' . $a_clean_slug . '&key=' . urlencode( self::$settings[ 'addon_licenses' ][ 'wp-slimstat-' . $a_clean_slug ] );
|
1815 |
+
$response = wp_safe_remote_get( $url, array( 'timeout' => 300, 'user-agent' => 'Slimstat Analytics/' . self::$version . '; ' . home_url() ) );
|
1816 |
+
|
1817 |
+
if ( !is_wp_error( $response ) && 200 == wp_remote_retrieve_response_code( $response ) ) {
|
1818 |
+
$data = @json_decode( $response[ 'body' ] );
|
1819 |
+
|
1820 |
+
if ( is_object( $data ) ) {
|
1821 |
+
$_links[] = '<a href="' . $data->download_url . '">Download ZIP</a>';
|
1822 |
+
set_transient( 'wp-slimstat-download-link-' . $a_clean_slug, $data->download_url, 172800 ); // 48 hours
|
1823 |
+
}
|
1824 |
}
|
1825 |
}
|
1826 |
+
|
1827 |
+
return $_links;
|
1828 |
}
|
1829 |
|
1830 |
public static function register_widget() {
|