Version Description
Download this release
Release Info
Developer | anadnet |
Plugin | Quick Page/Post Redirect Plugin |
Version | 5.2.1 |
Comparing to | |
See all releases |
Code changes from version 5.2.0 to 5.2.1
- page_post_redirect_plugin.php +11 -2
- readme.txt +2 -2
- updater/Puc/v4/Factory.php +6 -0
- updater/Puc/v4p10/Autoloader.php +63 -0
- updater/Puc/v4p10/DebugBar/Extension.php +186 -0
- updater/Puc/v4p10/DebugBar/Panel.php +165 -0
- updater/Puc/v4p10/DebugBar/PluginExtension.php +33 -0
- updater/Puc/v4p10/DebugBar/PluginPanel.php +38 -0
- updater/Puc/v4p10/DebugBar/ThemePanel.php +21 -0
- updater/Puc/v4p10/Factory.php +356 -0
- updater/Puc/v4p10/InstalledPackage.php +103 -0
- updater/Puc/v4p10/Metadata.php +132 -0
- updater/Puc/v4p10/OAuthSignature.php +100 -0
- updater/Puc/v4p10/Plugin/Info.php +132 -0
- updater/Puc/v4p10/Plugin/Package.php +184 -0
- updater/Puc/v4p10/Plugin/Ui.php +277 -0
- updater/Puc/v4p10/Plugin/Update.php +112 -0
- updater/Puc/v4p10/Plugin/UpdateChecker.php +414 -0
- updater/Puc/v4p10/Scheduler.php +266 -0
- updater/Puc/v4p10/StateStore.php +207 -0
- updater/Puc/v4p10/Theme/Package.php +65 -0
- updater/Puc/v4p10/Theme/Update.php +84 -0
- updater/Puc/v4p10/Theme/UpdateChecker.php +152 -0
- updater/Puc/v4p10/Update.php +34 -0
- updater/Puc/v4p10/UpdateChecker.php +994 -0
- updater/Puc/v4p10/UpgraderStatus.php +199 -0
- updater/Puc/v4p10/Utils.php +69 -0
- updater/Puc/v4p10/Vcs/Api.php +302 -0
- updater/Puc/v4p10/Vcs/BaseChecker.php +27 -0
- updater/Puc/v4p10/Vcs/BitBucketApi.php +265 -0
- updater/Puc/v4p10/Vcs/GitHubApi.php +441 -0
- updater/Puc/v4p10/Vcs/GitLabApi.php +309 -0
- updater/Puc/v4p10/Vcs/PluginUpdateChecker.php +218 -0
- updater/Puc/v4p10/Vcs/Reference.php +49 -0
- updater/Puc/v4p10/Vcs/ThemeUpdateChecker.php +118 -0
- updater/css/puc-debug-bar.css +70 -0
- updater/js/debug-bar.js +52 -0
- updater/languages/plugin-update-checker-ca.mo +0 -0
- updater/languages/plugin-update-checker-ca.po +48 -0
- updater/languages/plugin-update-checker-cs_CZ.mo +0 -0
- updater/languages/plugin-update-checker-cs_CZ.po +45 -0
- updater/languages/plugin-update-checker-da_DK.mo +0 -0
- updater/languages/plugin-update-checker-da_DK.po +42 -0
- updater/languages/plugin-update-checker-de_DE.mo +0 -0
- updater/languages/plugin-update-checker-de_DE.po +38 -0
- updater/languages/plugin-update-checker-es_AR.mo +0 -0
- updater/languages/plugin-update-checker-es_AR.po +48 -0
- updater/languages/plugin-update-checker-es_CL.mo +0 -0
- updater/languages/plugin-update-checker-es_CL.po +48 -0
- updater/languages/plugin-update-checker-es_CO.mo +0 -0
- updater/languages/plugin-update-checker-es_CO.po +48 -0
- updater/languages/plugin-update-checker-es_CR.mo +0 -0
- updater/languages/plugin-update-checker-es_CR.po +48 -0
- updater/languages/plugin-update-checker-es_DO.mo +0 -0
- updater/languages/plugin-update-checker-es_DO.po +48 -0
- updater/languages/plugin-update-checker-es_ES.mo +0 -0
- updater/languages/plugin-update-checker-es_ES.po +48 -0
- updater/languages/plugin-update-checker-es_GT.mo +0 -0
- updater/languages/plugin-update-checker-es_GT.po +48 -0
- updater/languages/plugin-update-checker-es_HN.mo +0 -0
- updater/languages/plugin-update-checker-es_HN.po +48 -0
- updater/languages/plugin-update-checker-es_MX.mo +0 -0
- updater/languages/plugin-update-checker-es_MX.po +48 -0
- updater/languages/plugin-update-checker-es_PE.mo +0 -0
- updater/languages/plugin-update-checker-es_PE.po +48 -0
- updater/languages/plugin-update-checker-es_PR.mo +0 -0
- updater/languages/plugin-update-checker-es_PR.po +48 -0
- updater/languages/plugin-update-checker-es_UY.mo +0 -0
- updater/languages/plugin-update-checker-es_UY.po +48 -0
- updater/languages/plugin-update-checker-es_VE.mo +0 -0
- updater/languages/plugin-update-checker-es_VE.po +48 -0
- updater/languages/plugin-update-checker-fa_IR.mo +0 -0
- updater/languages/plugin-update-checker-fa_IR.po +38 -0
- updater/languages/plugin-update-checker-fr_CA.mo +0 -0
- updater/languages/plugin-update-checker-fr_CA.po +48 -0
- updater/languages/plugin-update-checker-fr_FR.mo +0 -0
- updater/languages/plugin-update-checker-fr_FR.po +42 -0
- updater/languages/plugin-update-checker-hu_HU.mo +0 -0
- updater/languages/plugin-update-checker-hu_HU.po +41 -0
- updater/languages/plugin-update-checker-it_IT.mo +0 -0
- updater/languages/plugin-update-checker-it_IT.po +38 -0
- updater/languages/plugin-update-checker-ja.mo +0 -0
- updater/languages/plugin-update-checker-ja.po +57 -0
- updater/languages/plugin-update-checker-nl_BE.mo +0 -0
- updater/languages/plugin-update-checker-nl_BE.po +48 -0
- updater/languages/plugin-update-checker-nl_NL.mo +0 -0
- updater/languages/plugin-update-checker-nl_NL.po +48 -0
- updater/languages/plugin-update-checker-pt_BR.mo +0 -0
- updater/languages/plugin-update-checker-pt_BR.po +48 -0
- updater/languages/plugin-update-checker-sl_SI.mo +0 -0
- updater/languages/plugin-update-checker-sl_SI.po +48 -0
- updater/languages/plugin-update-checker-sv_SE.mo +0 -0
- updater/languages/plugin-update-checker-sv_SE.po +42 -0
- updater/languages/plugin-update-checker-zh_CN.mo +0 -0
- updater/languages/plugin-update-checker-zh_CN.po +48 -0
- updater/languages/plugin-update-checker.pot +49 -0
- updater/load-v4p10.php +28 -0
- updater/plugin-update-checker.php +10 -0
- updater/vendor/Parsedown.php +9 -0
- updater/vendor/ParsedownLegacy.php +1535 -0
- updater/vendor/ParsedownModern.php +1538 -0
- updater/vendor/PucReadmeParser.php +348 -0
page_post_redirect_plugin.php
CHANGED
@@ -6,7 +6,7 @@ Description: Redirect Pages, Posts or Custom Post Types to another location quic
|
|
6 |
Author: anadnet
|
7 |
Author URI: http://www.anadnet.com/
|
8 |
Donate link:
|
9 |
-
Version: 5.2.
|
10 |
Text Domain: quick-pagepost-redirect-plugin
|
11 |
Domain Path: /lang
|
12 |
License: GPLv2 or later
|
@@ -33,6 +33,15 @@ License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
|
33 |
===================
|
34 |
*/
|
35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
global $newqppr, $redirect_plugin, $qppr_setting_links;
|
37 |
$qppr_setting_links = false;
|
38 |
start_ppr_class();
|
@@ -71,7 +80,7 @@ class quick_page_post_reds {
|
|
71 |
public $pprptypes_ok;
|
72 |
|
73 |
function __construct() {
|
74 |
-
$this->ppr_curr_version = '5.2.
|
75 |
$this->ppr_nofollow = array();
|
76 |
$this->ppr_newindow = array();
|
77 |
$this->ppr_url = array();
|
6 |
Author: anadnet
|
7 |
Author URI: http://www.anadnet.com/
|
8 |
Donate link:
|
9 |
+
Version: 5.2.1
|
10 |
Text Domain: quick-pagepost-redirect-plugin
|
11 |
Domain Path: /lang
|
12 |
License: GPLv2 or later
|
33 |
===================
|
34 |
*/
|
35 |
|
36 |
+
// update functionality
|
37 |
+
require dirname(__FILE__).'/updater/plugin-update-checker.php';
|
38 |
+
$myUpdateChecker = Puc_v4_Factory::buildUpdateChecker(
|
39 |
+
//'https://anadnet.com/updates/plugin.json',
|
40 |
+
'https://anadnet.com/updates/?action=get_metadata&slug=quick-pagepost-redirect-plugin',
|
41 |
+
__FILE__, //Full path to the main plugin file or functions.php.
|
42 |
+
'quick-pagepost-redirect-plugin'
|
43 |
+
);
|
44 |
+
|
45 |
global $newqppr, $redirect_plugin, $qppr_setting_links;
|
46 |
$qppr_setting_links = false;
|
47 |
start_ppr_class();
|
80 |
public $pprptypes_ok;
|
81 |
|
82 |
function __construct() {
|
83 |
+
$this->ppr_curr_version = '5.2.1';
|
84 |
$this->ppr_nofollow = array();
|
85 |
$this->ppr_newindow = array();
|
86 |
$this->ppr_url = array();
|
readme.txt
CHANGED
@@ -6,12 +6,12 @@ Requires at least: 4.0
|
|
6 |
License: GPLv2 or later
|
7 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
8 |
Tested up to: 5.5.1
|
9 |
-
Stable tag: 5.2.
|
10 |
|
11 |
Easily redirect pages/posts or custom post types to another page/post or external URL by specifying the redirect URL and type (301, 302, 307, meta).
|
12 |
|
13 |
== Description ==
|
14 |
-
**Current Version 5.2.
|
15 |
|
16 |
This plugin has two redirect functionalities - **"Quick Redirects"** and **"Individual Redirects"**:
|
17 |
|
6 |
License: GPLv2 or later
|
7 |
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
8 |
Tested up to: 5.5.1
|
9 |
+
Stable tag: 5.2.1
|
10 |
|
11 |
Easily redirect pages/posts or custom post types to another page/post or external URL by specifying the redirect URL and type (301, 302, 307, meta).
|
12 |
|
13 |
== Description ==
|
14 |
+
**Current Version 5.2.1**
|
15 |
|
16 |
This plugin has two redirect functionalities - **"Quick Redirects"** and **"Individual Redirects"**:
|
17 |
|
updater/Puc/v4/Factory.php
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4_Factory', false) ):
|
3 |
+
|
4 |
+
class Puc_v4_Factory extends Puc_v4p10_Factory { }
|
5 |
+
|
6 |
+
endif;
|
updater/Puc/v4p10/Autoloader.php
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_Autoloader', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_Autoloader {
|
6 |
+
private $prefix = '';
|
7 |
+
private $rootDir = '';
|
8 |
+
private $libraryDir = '';
|
9 |
+
|
10 |
+
private $staticMap;
|
11 |
+
|
12 |
+
public function __construct() {
|
13 |
+
$this->rootDir = dirname(__FILE__) . '/';
|
14 |
+
$nameParts = explode('_', __CLASS__, 3);
|
15 |
+
$this->prefix = $nameParts[0] . '_' . $nameParts[1] . '_';
|
16 |
+
|
17 |
+
$this->libraryDir = $this->rootDir . '../..';
|
18 |
+
if ( !self::isPhar() ) {
|
19 |
+
$this->libraryDir = realpath($this->libraryDir);
|
20 |
+
}
|
21 |
+
$this->libraryDir = $this->libraryDir . '/';
|
22 |
+
|
23 |
+
$this->staticMap = array(
|
24 |
+
'PucReadmeParser' => 'vendor/PucReadmeParser.php',
|
25 |
+
'Parsedown' => 'vendor/Parsedown.php',
|
26 |
+
'Puc_v4_Factory' => 'Puc/v4/Factory.php',
|
27 |
+
);
|
28 |
+
|
29 |
+
spl_autoload_register(array($this, 'autoload'));
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
33 |
+
* Determine if this file is running as part of a Phar archive.
|
34 |
+
*
|
35 |
+
* @return bool
|
36 |
+
*/
|
37 |
+
private static function isPhar() {
|
38 |
+
//Check if the current file path starts with "phar://".
|
39 |
+
static $pharProtocol = 'phar://';
|
40 |
+
return (substr(__FILE__, 0, strlen($pharProtocol)) === $pharProtocol);
|
41 |
+
}
|
42 |
+
|
43 |
+
public function autoload($className) {
|
44 |
+
if ( isset($this->staticMap[$className]) && file_exists($this->libraryDir . $this->staticMap[$className]) ) {
|
45 |
+
/** @noinspection PhpIncludeInspection */
|
46 |
+
include ($this->libraryDir . $this->staticMap[$className]);
|
47 |
+
return;
|
48 |
+
}
|
49 |
+
|
50 |
+
if (strpos($className, $this->prefix) === 0) {
|
51 |
+
$path = substr($className, strlen($this->prefix));
|
52 |
+
$path = str_replace('_', '/', $path);
|
53 |
+
$path = $this->rootDir . $path . '.php';
|
54 |
+
|
55 |
+
if (file_exists($path)) {
|
56 |
+
/** @noinspection PhpIncludeInspection */
|
57 |
+
include $path;
|
58 |
+
}
|
59 |
+
}
|
60 |
+
}
|
61 |
+
}
|
62 |
+
|
63 |
+
endif;
|
updater/Puc/v4p10/DebugBar/Extension.php
ADDED
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_DebugBar_Extension', false) ):
|
3 |
+
|
4 |
+
class Puc_v4p10_DebugBar_Extension {
|
5 |
+
const RESPONSE_BODY_LENGTH_LIMIT = 4000;
|
6 |
+
|
7 |
+
/** @var Puc_v4p10_UpdateChecker */
|
8 |
+
protected $updateChecker;
|
9 |
+
protected $panelClass = 'Puc_v4p10_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 |
+
* Remove hooks that were added by this extension.
|
147 |
+
*/
|
148 |
+
public function removeHooks() {
|
149 |
+
remove_filter('debug_bar_panels', array($this, 'addDebugBarPanel'));
|
150 |
+
remove_action('debug_bar_enqueue_scripts', array($this, 'enqueuePanelDependencies'));
|
151 |
+
remove_action('wp_ajax_puc_v4_debug_check_now', array($this, 'ajaxCheckNow'));
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* @param string $filePath
|
156 |
+
* @return string
|
157 |
+
*/
|
158 |
+
private function getLibraryUrl($filePath) {
|
159 |
+
$absolutePath = realpath(dirname(__FILE__) . '/../../../' . ltrim($filePath, '/'));
|
160 |
+
|
161 |
+
//Where is the library located inside the WordPress directory structure?
|
162 |
+
$absolutePath = Puc_v4p10_Factory::normalizePath($absolutePath);
|
163 |
+
|
164 |
+
$pluginDir = Puc_v4p10_Factory::normalizePath(WP_PLUGIN_DIR);
|
165 |
+
$muPluginDir = Puc_v4p10_Factory::normalizePath(WPMU_PLUGIN_DIR);
|
166 |
+
$themeDir = Puc_v4p10_Factory::normalizePath(get_theme_root());
|
167 |
+
|
168 |
+
if ( (strpos($absolutePath, $pluginDir) === 0) || (strpos($absolutePath, $muPluginDir) === 0) ) {
|
169 |
+
//It's part of a plugin.
|
170 |
+
return plugins_url(basename($absolutePath), $absolutePath);
|
171 |
+
} else if ( strpos($absolutePath, $themeDir) === 0 ) {
|
172 |
+
//It's part of a theme.
|
173 |
+
$relativePath = substr($absolutePath, strlen($themeDir) + 1);
|
174 |
+
$template = substr($relativePath, 0, strpos($relativePath, '/'));
|
175 |
+
$baseUrl = get_theme_root_uri($template);
|
176 |
+
|
177 |
+
if ( !empty($baseUrl) && $relativePath ) {
|
178 |
+
return $baseUrl . '/' . $relativePath;
|
179 |
+
}
|
180 |
+
}
|
181 |
+
|
182 |
+
return '';
|
183 |
+
}
|
184 |
+
}
|
185 |
+
|
186 |
+
endif;
|
updater/Puc/v4p10/DebugBar/Panel.php
ADDED
@@ -0,0 +1,165 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_DebugBar_Panel', false) && class_exists('Debug_Bar_Panel', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_DebugBar_Panel extends Debug_Bar_Panel {
|
6 |
+
/** @var Puc_v4p10_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;
|
updater/Puc/v4p10/DebugBar/PluginExtension.php
ADDED
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_DebugBar_PluginExtension', false) ):
|
3 |
+
|
4 |
+
class Puc_v4p10_DebugBar_PluginExtension extends Puc_v4p10_DebugBar_Extension {
|
5 |
+
/** @var Puc_v4p10_Plugin_UpdateChecker */
|
6 |
+
protected $updateChecker;
|
7 |
+
|
8 |
+
public function __construct($updateChecker) {
|
9 |
+
parent::__construct($updateChecker, 'Puc_v4p10_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;
|
updater/Puc/v4p10/DebugBar/PluginPanel.php
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_DebugBar_PluginPanel', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_DebugBar_PluginPanel extends Puc_v4p10_DebugBar_Panel {
|
6 |
+
/**
|
7 |
+
* @var Puc_v4p10_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;
|
updater/Puc/v4p10/DebugBar/ThemePanel.php
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_DebugBar_ThemePanel', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_DebugBar_ThemePanel extends Puc_v4p10_DebugBar_Panel {
|
6 |
+
/**
|
7 |
+
* @var Puc_v4p10_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;
|
updater/Puc/v4p10/Factory.php
ADDED
@@ -0,0 +1,356 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_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_v4p10_Factory {
|
15 |
+
protected static $classVersions = array();
|
16 |
+
protected static $sorted = false;
|
17 |
+
|
18 |
+
protected static $myMajorVersion = '';
|
19 |
+
protected static $latestCompatibleVersion = '';
|
20 |
+
|
21 |
+
/**
|
22 |
+
* A wrapper method for buildUpdateChecker() that reads the metadata URL from the plugin or theme header.
|
23 |
+
*
|
24 |
+
* @param string $fullPath Full path to the main plugin file or the theme's style.css.
|
25 |
+
* @param array $args Optional arguments. Keys should match the argument names of the buildUpdateChecker() method.
|
26 |
+
* @return Puc_v4p10_Plugin_UpdateChecker|Puc_v4p10_Theme_UpdateChecker|Puc_v4p10_Vcs_BaseChecker
|
27 |
+
*/
|
28 |
+
public static function buildFromHeader($fullPath, $args = array()) {
|
29 |
+
$fullPath = self::normalizePath($fullPath);
|
30 |
+
|
31 |
+
//Set up defaults.
|
32 |
+
$defaults = array(
|
33 |
+
'metadataUrl' => '',
|
34 |
+
'slug' => '',
|
35 |
+
'checkPeriod' => 12,
|
36 |
+
'optionName' => '',
|
37 |
+
'muPluginFile' => '',
|
38 |
+
);
|
39 |
+
$args = array_merge($defaults, array_intersect_key($args, $defaults));
|
40 |
+
extract($args, EXTR_SKIP);
|
41 |
+
|
42 |
+
//Check for the service URI
|
43 |
+
if ( empty($metadataUrl) ) {
|
44 |
+
$metadataUrl = self::getServiceURI($fullPath);
|
45 |
+
}
|
46 |
+
|
47 |
+
/** @noinspection PhpUndefinedVariableInspection These variables are created by extract(), above. */
|
48 |
+
return self::buildUpdateChecker($metadataUrl, $fullPath, $slug, $checkPeriod, $optionName, $muPluginFile);
|
49 |
+
}
|
50 |
+
|
51 |
+
/**
|
52 |
+
* Create a new instance of the update checker.
|
53 |
+
*
|
54 |
+
* This method automatically detects if you're using it for a plugin or a theme and chooses
|
55 |
+
* the appropriate implementation for your update source (JSON file, GitHub, BitBucket, etc).
|
56 |
+
*
|
57 |
+
* @see Puc_v4p10_UpdateChecker::__construct
|
58 |
+
*
|
59 |
+
* @param string $metadataUrl The URL of the metadata file, a GitHub repository, or another supported update source.
|
60 |
+
* @param string $fullPath Full path to the main plugin file or to the theme directory.
|
61 |
+
* @param string $slug Custom slug. Defaults to the name of the main plugin file or the theme directory.
|
62 |
+
* @param int $checkPeriod How often to check for updates (in hours).
|
63 |
+
* @param string $optionName Where to store book-keeping info about update checks.
|
64 |
+
* @param string $muPluginFile The plugin filename relative to the mu-plugins directory.
|
65 |
+
* @return Puc_v4p10_Plugin_UpdateChecker|Puc_v4p10_Theme_UpdateChecker|Puc_v4p10_Vcs_BaseChecker
|
66 |
+
*/
|
67 |
+
public static function buildUpdateChecker($metadataUrl, $fullPath, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') {
|
68 |
+
$fullPath = self::normalizePath($fullPath);
|
69 |
+
$id = null;
|
70 |
+
|
71 |
+
//Plugin or theme?
|
72 |
+
$themeDirectory = self::getThemeDirectoryName($fullPath);
|
73 |
+
if ( self::isPluginFile($fullPath) ) {
|
74 |
+
$type = 'Plugin';
|
75 |
+
$id = $fullPath;
|
76 |
+
} else if ( $themeDirectory !== null ) {
|
77 |
+
$type = 'Theme';
|
78 |
+
$id = $themeDirectory;
|
79 |
+
} else {
|
80 |
+
throw new RuntimeException(sprintf(
|
81 |
+
'The update checker cannot determine if "%s" is a plugin or a theme. ' .
|
82 |
+
'This is a bug. Please contact the PUC developer.',
|
83 |
+
htmlentities($fullPath)
|
84 |
+
));
|
85 |
+
}
|
86 |
+
|
87 |
+
//Which hosting service does the URL point to?
|
88 |
+
$service = self::getVcsService($metadataUrl);
|
89 |
+
|
90 |
+
$apiClass = null;
|
91 |
+
if ( empty($service) ) {
|
92 |
+
//The default is to get update information from a remote JSON file.
|
93 |
+
$checkerClass = $type . '_UpdateChecker';
|
94 |
+
} else {
|
95 |
+
//You can also use a VCS repository like GitHub.
|
96 |
+
$checkerClass = 'Vcs_' . $type . 'UpdateChecker';
|
97 |
+
$apiClass = $service . 'Api';
|
98 |
+
}
|
99 |
+
|
100 |
+
$checkerClass = self::getCompatibleClassVersion($checkerClass);
|
101 |
+
if ( $checkerClass === null ) {
|
102 |
+
trigger_error(
|
103 |
+
sprintf(
|
104 |
+
'PUC %s does not support updates for %ss %s',
|
105 |
+
htmlentities(self::$latestCompatibleVersion),
|
106 |
+
strtolower($type),
|
107 |
+
$service ? ('hosted on ' . htmlentities($service)) : 'using JSON metadata'
|
108 |
+
),
|
109 |
+
E_USER_ERROR
|
110 |
+
);
|
111 |
+
return null;
|
112 |
+
}
|
113 |
+
|
114 |
+
if ( !isset($apiClass) ) {
|
115 |
+
//Plain old update checker.
|
116 |
+
return new $checkerClass($metadataUrl, $id, $slug, $checkPeriod, $optionName, $muPluginFile);
|
117 |
+
} else {
|
118 |
+
//VCS checker + an API client.
|
119 |
+
$apiClass = self::getCompatibleClassVersion($apiClass);
|
120 |
+
if ( $apiClass === null ) {
|
121 |
+
trigger_error(sprintf(
|
122 |
+
'PUC %s does not support %s',
|
123 |
+
htmlentities(self::$latestCompatibleVersion),
|
124 |
+
htmlentities($service)
|
125 |
+
), E_USER_ERROR);
|
126 |
+
return null;
|
127 |
+
}
|
128 |
+
|
129 |
+
return new $checkerClass(
|
130 |
+
new $apiClass($metadataUrl),
|
131 |
+
$id,
|
132 |
+
$slug,
|
133 |
+
$checkPeriod,
|
134 |
+
$optionName,
|
135 |
+
$muPluginFile
|
136 |
+
);
|
137 |
+
}
|
138 |
+
}
|
139 |
+
|
140 |
+
/**
|
141 |
+
*
|
142 |
+
* Normalize a filesystem path. Introduced in WP 3.9.
|
143 |
+
* Copying here allows use of the class on earlier versions.
|
144 |
+
* This version adapted from WP 4.8.2 (unchanged since 4.5.0)
|
145 |
+
*
|
146 |
+
* @param string $path Path to normalize.
|
147 |
+
* @return string Normalized path.
|
148 |
+
*/
|
149 |
+
public static function normalizePath($path) {
|
150 |
+
if ( function_exists('wp_normalize_path') ) {
|
151 |
+
return wp_normalize_path($path);
|
152 |
+
}
|
153 |
+
$path = str_replace('\\', '/', $path);
|
154 |
+
$path = preg_replace('|(?<=.)/+|', '/', $path);
|
155 |
+
if ( substr($path, 1, 1) === ':' ) {
|
156 |
+
$path = ucfirst($path);
|
157 |
+
}
|
158 |
+
return $path;
|
159 |
+
}
|
160 |
+
|
161 |
+
/**
|
162 |
+
* Check if the path points to a plugin file.
|
163 |
+
*
|
164 |
+
* @param string $absolutePath Normalized path.
|
165 |
+
* @return bool
|
166 |
+
*/
|
167 |
+
protected static function isPluginFile($absolutePath) {
|
168 |
+
//Is the file inside the "plugins" or "mu-plugins" directory?
|
169 |
+
$pluginDir = self::normalizePath(WP_PLUGIN_DIR);
|
170 |
+
$muPluginDir = self::normalizePath(WPMU_PLUGIN_DIR);
|
171 |
+
if ( (strpos($absolutePath, $pluginDir) === 0) || (strpos($absolutePath, $muPluginDir) === 0) ) {
|
172 |
+
return true;
|
173 |
+
}
|
174 |
+
|
175 |
+
//Is it a file at all? Caution: is_file() can fail if the parent dir. doesn't have the +x permission set.
|
176 |
+
if ( !is_file($absolutePath) ) {
|
177 |
+
return false;
|
178 |
+
}
|
179 |
+
|
180 |
+
//Does it have a valid plugin header?
|
181 |
+
//This is a last-ditch check for plugins symlinked from outside the WP root.
|
182 |
+
if ( function_exists('get_file_data') ) {
|
183 |
+
$headers = get_file_data($absolutePath, array('Name' => 'Plugin Name'), 'plugin');
|
184 |
+
return !empty($headers['Name']);
|
185 |
+
}
|
186 |
+
|
187 |
+
return false;
|
188 |
+
}
|
189 |
+
|
190 |
+
/**
|
191 |
+
* Get the name of the theme's directory from a full path to a file inside that directory.
|
192 |
+
* E.g. "/abc/public_html/wp-content/themes/foo/whatever.php" => "foo".
|
193 |
+
*
|
194 |
+
* Note that subdirectories are currently not supported. For example,
|
195 |
+
* "/xyz/wp-content/themes/my-theme/includes/whatever.php" => NULL.
|
196 |
+
*
|
197 |
+
* @param string $absolutePath Normalized path.
|
198 |
+
* @return string|null Directory name, or NULL if the path doesn't point to a theme.
|
199 |
+
*/
|
200 |
+
protected static function getThemeDirectoryName($absolutePath) {
|
201 |
+
if ( is_file($absolutePath) ) {
|
202 |
+
$absolutePath = dirname($absolutePath);
|
203 |
+
}
|
204 |
+
|
205 |
+
if ( file_exists($absolutePath . '/style.css') ) {
|
206 |
+
return basename($absolutePath);
|
207 |
+
}
|
208 |
+
return null;
|
209 |
+
}
|
210 |
+
|
211 |
+
/**
|
212 |
+
* Get the service URI from the file header.
|
213 |
+
*
|
214 |
+
* @param string $fullPath
|
215 |
+
* @return string
|
216 |
+
*/
|
217 |
+
private static function getServiceURI($fullPath) {
|
218 |
+
//Look for the URI
|
219 |
+
if ( is_readable($fullPath) ) {
|
220 |
+
$seek = array(
|
221 |
+
'github' => 'GitHub URI',
|
222 |
+
'gitlab' => 'GitLab URI',
|
223 |
+
'bucket' => 'BitBucket URI',
|
224 |
+
);
|
225 |
+
$seek = apply_filters('puc_get_source_uri', $seek);
|
226 |
+
$data = get_file_data($fullPath, $seek);
|
227 |
+
foreach ($data as $key => $uri) {
|
228 |
+
if ( $uri ) {
|
229 |
+
return $uri;
|
230 |
+
}
|
231 |
+
}
|
232 |
+
}
|
233 |
+
|
234 |
+
//URI was not found so throw an error.
|
235 |
+
throw new RuntimeException(
|
236 |
+
sprintf('Unable to locate URI in header of "%s"', htmlentities($fullPath))
|
237 |
+
);
|
238 |
+
}
|
239 |
+
|
240 |
+
/**
|
241 |
+
* Get the name of the hosting service that the URL points to.
|
242 |
+
*
|
243 |
+
* @param string $metadataUrl
|
244 |
+
* @return string|null
|
245 |
+
*/
|
246 |
+
private static function getVcsService($metadataUrl) {
|
247 |
+
$service = null;
|
248 |
+
|
249 |
+
//Which hosting service does the URL point to?
|
250 |
+
$host = parse_url($metadataUrl, PHP_URL_HOST);
|
251 |
+
$path = parse_url($metadataUrl, PHP_URL_PATH);
|
252 |
+
|
253 |
+
//Check if the path looks like "/user-name/repository".
|
254 |
+
//For GitLab.com it can also be "/user/group1/group2/.../repository".
|
255 |
+
$repoRegex = '@^/?([^/]+?)/([^/#?&]+?)/?$@';
|
256 |
+
if ( $host === 'gitlab.com' ) {
|
257 |
+
$repoRegex = '@^/?(?:[^/#?&]++/){1,20}(?:[^/#?&]++)/?$@';
|
258 |
+
}
|
259 |
+
if ( preg_match($repoRegex, $path) ) {
|
260 |
+
$knownServices = array(
|
261 |
+
'github.com' => 'GitHub',
|
262 |
+
'bitbucket.org' => 'BitBucket',
|
263 |
+
'gitlab.com' => 'GitLab',
|
264 |
+
);
|
265 |
+
if ( isset($knownServices[$host]) ) {
|
266 |
+
$service = $knownServices[$host];
|
267 |
+
}
|
268 |
+
}
|
269 |
+
|
270 |
+
return apply_filters('puc_get_vcs_service', $service, $host, $path, $metadataUrl);
|
271 |
+
}
|
272 |
+
|
273 |
+
/**
|
274 |
+
* Get the latest version of the specified class that has the same major version number
|
275 |
+
* as this factory class.
|
276 |
+
*
|
277 |
+
* @param string $class Partial class name.
|
278 |
+
* @return string|null Full class name.
|
279 |
+
*/
|
280 |
+
protected static function getCompatibleClassVersion($class) {
|
281 |
+
if ( isset(self::$classVersions[$class][self::$latestCompatibleVersion]) ) {
|
282 |
+
return self::$classVersions[$class][self::$latestCompatibleVersion];
|
283 |
+
}
|
284 |
+
return null;
|
285 |
+
}
|
286 |
+
|
287 |
+
/**
|
288 |
+
* Get the specific class name for the latest available version of a class.
|
289 |
+
*
|
290 |
+
* @param string $class
|
291 |
+
* @return null|string
|
292 |
+
*/
|
293 |
+
public static function getLatestClassVersion($class) {
|
294 |
+
if ( !self::$sorted ) {
|
295 |
+
self::sortVersions();
|
296 |
+
}
|
297 |
+
|
298 |
+
if ( isset(self::$classVersions[$class]) ) {
|
299 |
+
return reset(self::$classVersions[$class]);
|
300 |
+
} else {
|
301 |
+
return null;
|
302 |
+
}
|
303 |
+
}
|
304 |
+
|
305 |
+
/**
|
306 |
+
* Sort available class versions in descending order (i.e. newest first).
|
307 |
+
*/
|
308 |
+
protected static function sortVersions() {
|
309 |
+
foreach ( self::$classVersions as $class => $versions ) {
|
310 |
+
uksort($versions, array(__CLASS__, 'compareVersions'));
|
311 |
+
self::$classVersions[$class] = $versions;
|
312 |
+
}
|
313 |
+
self::$sorted = true;
|
314 |
+
}
|
315 |
+
|
316 |
+
protected static function compareVersions($a, $b) {
|
317 |
+
return -version_compare($a, $b);
|
318 |
+
}
|
319 |
+
|
320 |
+
/**
|
321 |
+
* Register a version of a class.
|
322 |
+
*
|
323 |
+
* @access private This method is only for internal use by the library.
|
324 |
+
*
|
325 |
+
* @param string $generalClass Class name without version numbers, e.g. 'PluginUpdateChecker'.
|
326 |
+
* @param string $versionedClass Actual class name, e.g. 'PluginUpdateChecker_1_2'.
|
327 |
+
* @param string $version Version number, e.g. '1.2'.
|
328 |
+
*/
|
329 |
+
public static function addVersion($generalClass, $versionedClass, $version) {
|
330 |
+
if ( empty(self::$myMajorVersion) ) {
|
331 |
+
$nameParts = explode('_', __CLASS__, 3);
|
332 |
+
self::$myMajorVersion = substr(ltrim($nameParts[1], 'v'), 0, 1);
|
333 |
+
}
|
334 |
+
|
335 |
+
//Store the greatest version number that matches our major version.
|
336 |
+
$components = explode('.', $version);
|
337 |
+
if ( $components[0] === self::$myMajorVersion ) {
|
338 |
+
|
339 |
+
if (
|
340 |
+
empty(self::$latestCompatibleVersion)
|
341 |
+
|| version_compare($version, self::$latestCompatibleVersion, '>')
|
342 |
+
) {
|
343 |
+
self::$latestCompatibleVersion = $version;
|
344 |
+
}
|
345 |
+
|
346 |
+
}
|
347 |
+
|
348 |
+
if ( !isset(self::$classVersions[$generalClass]) ) {
|
349 |
+
self::$classVersions[$generalClass] = array();
|
350 |
+
}
|
351 |
+
self::$classVersions[$generalClass][$version] = $versionedClass;
|
352 |
+
self::$sorted = false;
|
353 |
+
}
|
354 |
+
}
|
355 |
+
|
356 |
+
endif;
|
updater/Puc/v4p10/InstalledPackage.php
ADDED
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_InstalledPackage', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* This class represents a currently installed plugin or theme.
|
6 |
+
*
|
7 |
+
* Not to be confused with the "package" field in WP update API responses that contains
|
8 |
+
* the download URL of a the new version.
|
9 |
+
*/
|
10 |
+
abstract class Puc_v4p10_InstalledPackage {
|
11 |
+
/**
|
12 |
+
* @var Puc_v4p10_UpdateChecker
|
13 |
+
*/
|
14 |
+
protected $updateChecker;
|
15 |
+
|
16 |
+
public function __construct($updateChecker) {
|
17 |
+
$this->updateChecker = $updateChecker;
|
18 |
+
}
|
19 |
+
|
20 |
+
/**
|
21 |
+
* Get the currently installed version of the plugin or theme.
|
22 |
+
*
|
23 |
+
* @return string|null Version number.
|
24 |
+
*/
|
25 |
+
abstract public function getInstalledVersion();
|
26 |
+
|
27 |
+
/**
|
28 |
+
* Get the full path of the plugin or theme directory (without a trailing slash).
|
29 |
+
*
|
30 |
+
* @return string
|
31 |
+
*/
|
32 |
+
abstract public function getAbsoluteDirectoryPath();
|
33 |
+
|
34 |
+
/**
|
35 |
+
* Check whether a regular file exists in the package's directory.
|
36 |
+
*
|
37 |
+
* @param string $relativeFileName File name relative to the package directory.
|
38 |
+
* @return bool
|
39 |
+
*/
|
40 |
+
public function fileExists($relativeFileName) {
|
41 |
+
return is_file(
|
42 |
+
$this->getAbsoluteDirectoryPath()
|
43 |
+
. DIRECTORY_SEPARATOR
|
44 |
+
. ltrim($relativeFileName, '/\\')
|
45 |
+
);
|
46 |
+
}
|
47 |
+
|
48 |
+
/* -------------------------------------------------------------------
|
49 |
+
* File header parsing
|
50 |
+
* -------------------------------------------------------------------
|
51 |
+
*/
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Parse plugin or theme metadata from the header comment.
|
55 |
+
*
|
56 |
+
* This is basically a simplified version of the get_file_data() function from /wp-includes/functions.php.
|
57 |
+
* It's intended as a utility for subclasses that detect updates by parsing files in a VCS.
|
58 |
+
*
|
59 |
+
* @param string|null $content File contents.
|
60 |
+
* @return string[]
|
61 |
+
*/
|
62 |
+
public function getFileHeader($content) {
|
63 |
+
$content = (string)$content;
|
64 |
+
|
65 |
+
//WordPress only looks at the first 8 KiB of the file, so we do the same.
|
66 |
+
$content = substr($content, 0, 8192);
|
67 |
+
//Normalize line endings.
|
68 |
+
$content = str_replace("\r", "\n", $content);
|
69 |
+
|
70 |
+
$headers = $this->getHeaderNames();
|
71 |
+
$results = array();
|
72 |
+
foreach ($headers as $field => $name) {
|
73 |
+
$success = preg_match('/^[ \t\/*#@]*' . preg_quote($name, '/') . ':(.*)$/mi', $content, $matches);
|
74 |
+
|
75 |
+
if ( ($success === 1) && $matches[1] ) {
|
76 |
+
$value = $matches[1];
|
77 |
+
if ( function_exists('_cleanup_header_comment') ) {
|
78 |
+
$value = _cleanup_header_comment($value);
|
79 |
+
}
|
80 |
+
$results[$field] = $value;
|
81 |
+
} else {
|
82 |
+
$results[$field] = '';
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
return $results;
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* @return array Format: ['HeaderKey' => 'Header Name']
|
91 |
+
*/
|
92 |
+
abstract protected function getHeaderNames();
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Get the value of a specific plugin or theme header.
|
96 |
+
*
|
97 |
+
* @param string $headerName
|
98 |
+
* @return string Either the value of the header, or an empty string if the header doesn't exist.
|
99 |
+
*/
|
100 |
+
abstract public function getHeaderValue($headerName);
|
101 |
+
|
102 |
+
}
|
103 |
+
endif;
|
updater/Puc/v4p10/Metadata.php
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Metadata', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* A base container for holding information about updates and plugin metadata.
|
6 |
+
*
|
7 |
+
* @author Janis Elsts
|
8 |
+
* @copyright 2016
|
9 |
+
* @access public
|
10 |
+
*/
|
11 |
+
abstract class Puc_v4p10_Metadata {
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Create an instance of this class from a JSON document.
|
15 |
+
*
|
16 |
+
* @abstract
|
17 |
+
* @param string $json
|
18 |
+
* @return self
|
19 |
+
*/
|
20 |
+
public static function fromJson(/** @noinspection PhpUnusedParameterInspection */ $json) {
|
21 |
+
throw new LogicException('The ' . __METHOD__ . ' method must be implemented by subclasses');
|
22 |
+
}
|
23 |
+
|
24 |
+
/**
|
25 |
+
* @param string $json
|
26 |
+
* @param self $target
|
27 |
+
* @return bool
|
28 |
+
*/
|
29 |
+
protected static function createFromJson($json, $target) {
|
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 |
+
}
|
45 |
+
|
46 |
+
foreach(get_object_vars($apiResponse) as $key => $value){
|
47 |
+
$target->$key = $value;
|
48 |
+
}
|
49 |
+
|
50 |
+
return true;
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* No validation by default! Subclasses should check that the required fields are present.
|
55 |
+
*
|
56 |
+
* @param StdClass $apiResponse
|
57 |
+
* @return bool|WP_Error
|
58 |
+
*/
|
59 |
+
protected function validateMetadata(/** @noinspection PhpUnusedParameterInspection */ $apiResponse) {
|
60 |
+
return true;
|
61 |
+
}
|
62 |
+
|
63 |
+
/**
|
64 |
+
* Create a new instance by copying the necessary fields from another object.
|
65 |
+
*
|
66 |
+
* @abstract
|
67 |
+
* @param StdClass|self $object The source object.
|
68 |
+
* @return self The new copy.
|
69 |
+
*/
|
70 |
+
public static function fromObject(/** @noinspection PhpUnusedParameterInspection */ $object) {
|
71 |
+
throw new LogicException('The ' . __METHOD__ . ' method must be implemented by subclasses');
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Create an instance of StdClass that can later be converted back to an
|
76 |
+
* update or info container. Useful for serialization and caching, as it
|
77 |
+
* avoids the "incomplete object" problem if the cached value is loaded
|
78 |
+
* before this class.
|
79 |
+
*
|
80 |
+
* @return StdClass
|
81 |
+
*/
|
82 |
+
public function toStdClass() {
|
83 |
+
$object = new stdClass();
|
84 |
+
$this->copyFields($this, $object);
|
85 |
+
return $object;
|
86 |
+
}
|
87 |
+
|
88 |
+
/**
|
89 |
+
* Transform the metadata into the format used by WordPress core.
|
90 |
+
*
|
91 |
+
* @return object
|
92 |
+
*/
|
93 |
+
abstract public function toWpFormat();
|
94 |
+
|
95 |
+
/**
|
96 |
+
* Copy known fields from one object to another.
|
97 |
+
*
|
98 |
+
* @param StdClass|self $from
|
99 |
+
* @param StdClass|self $to
|
100 |
+
*/
|
101 |
+
protected function copyFields($from, $to) {
|
102 |
+
$fields = $this->getFieldNames();
|
103 |
+
|
104 |
+
if ( property_exists($from, 'slug') && !empty($from->slug) ) {
|
105 |
+
//Let plugins add extra fields without having to create subclasses.
|
106 |
+
$fields = apply_filters($this->getPrefixedFilter('retain_fields') . '-' . $from->slug, $fields);
|
107 |
+
}
|
108 |
+
|
109 |
+
foreach ($fields as $field) {
|
110 |
+
if ( property_exists($from, $field) ) {
|
111 |
+
$to->$field = $from->$field;
|
112 |
+
}
|
113 |
+
}
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* @return string[]
|
118 |
+
*/
|
119 |
+
protected function getFieldNames() {
|
120 |
+
return array();
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* @param string $tag
|
125 |
+
* @return string
|
126 |
+
*/
|
127 |
+
protected function getPrefixedFilter($tag) {
|
128 |
+
return 'puc_' . $tag;
|
129 |
+
}
|
130 |
+
}
|
131 |
+
|
132 |
+
endif;
|
updater/Puc/v4p10/OAuthSignature.php
ADDED
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_OAuthSignature', false) ):
|
4 |
+
|
5 |
+
/**
|
6 |
+
* A basic signature generator for zero-legged OAuth 1.0.
|
7 |
+
*/
|
8 |
+
class Puc_v4p10_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 |
+
|
84 |
+
$rand = null;
|
85 |
+
if ( is_callable('random_bytes') ) {
|
86 |
+
try {
|
87 |
+
$rand = random_bytes(16);
|
88 |
+
} catch (Exception $ex) {
|
89 |
+
//Fall back to mt_rand (below).
|
90 |
+
}
|
91 |
+
}
|
92 |
+
if ( $rand === null ) {
|
93 |
+
$rand = mt_rand();
|
94 |
+
}
|
95 |
+
|
96 |
+
return md5($mt . '_' . $rand);
|
97 |
+
}
|
98 |
+
}
|
99 |
+
|
100 |
+
endif;
|
updater/Puc/v4p10/Plugin/Info.php
ADDED
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Plugin_Info', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* A container class for holding and transforming various plugin metadata.
|
6 |
+
*
|
7 |
+
* @author Janis Elsts
|
8 |
+
* @copyright 2016
|
9 |
+
* @access public
|
10 |
+
*/
|
11 |
+
class Puc_v4p10_Plugin_Info extends Puc_v4p10_Metadata {
|
12 |
+
//Most fields map directly to the contents of the plugin's info.json file.
|
13 |
+
//See the relevant docs for a description of their meaning.
|
14 |
+
public $name;
|
15 |
+
public $slug;
|
16 |
+
public $version;
|
17 |
+
public $homepage;
|
18 |
+
public $sections = array();
|
19 |
+
public $download_url;
|
20 |
+
|
21 |
+
public $banners;
|
22 |
+
public $icons = array();
|
23 |
+
public $translations = array();
|
24 |
+
|
25 |
+
public $author;
|
26 |
+
public $author_homepage;
|
27 |
+
|
28 |
+
public $requires;
|
29 |
+
public $tested;
|
30 |
+
public $requires_php;
|
31 |
+
public $upgrade_notice;
|
32 |
+
|
33 |
+
public $rating;
|
34 |
+
public $num_ratings;
|
35 |
+
public $downloaded;
|
36 |
+
public $active_installs;
|
37 |
+
public $last_updated;
|
38 |
+
|
39 |
+
public $id = 0; //The native WP.org API returns numeric plugin IDs, but they're not used for anything.
|
40 |
+
|
41 |
+
public $filename; //Plugin filename relative to the plugins directory.
|
42 |
+
|
43 |
+
/**
|
44 |
+
* Create a new instance of Plugin Info from JSON-encoded plugin info
|
45 |
+
* returned by an external update API.
|
46 |
+
*
|
47 |
+
* @param string $json Valid JSON string representing plugin info.
|
48 |
+
* @return self|null New instance of Plugin Info, or NULL on error.
|
49 |
+
*/
|
50 |
+
public static function fromJson($json){
|
51 |
+
$instance = new self();
|
52 |
+
|
53 |
+
if ( !parent::createFromJson($json, $instance) ) {
|
54 |
+
return null;
|
55 |
+
}
|
56 |
+
|
57 |
+
//json_decode decodes assoc. arrays as objects. We want them as arrays.
|
58 |
+
$instance->sections = (array)$instance->sections;
|
59 |
+
$instance->icons = (array)$instance->icons;
|
60 |
+
|
61 |
+
return $instance;
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Very, very basic validation.
|
66 |
+
*
|
67 |
+
* @param StdClass $apiResponse
|
68 |
+
* @return bool|WP_Error
|
69 |
+
*/
|
70 |
+
protected function validateMetadata($apiResponse) {
|
71 |
+
if (
|
72 |
+
!isset($apiResponse->name, $apiResponse->version)
|
73 |
+
|| empty($apiResponse->name)
|
74 |
+
|| empty($apiResponse->version)
|
75 |
+
) {
|
76 |
+
return new WP_Error(
|
77 |
+
'puc-invalid-metadata',
|
78 |
+
"The plugin metadata file does not contain the required 'name' and/or 'version' keys."
|
79 |
+
);
|
80 |
+
}
|
81 |
+
return true;
|
82 |
+
}
|
83 |
+
|
84 |
+
|
85 |
+
/**
|
86 |
+
* Transform plugin info into the format used by the native WordPress.org API
|
87 |
+
*
|
88 |
+
* @return object
|
89 |
+
*/
|
90 |
+
public function toWpFormat(){
|
91 |
+
$info = new stdClass;
|
92 |
+
|
93 |
+
//The custom update API is built so that many fields have the same name and format
|
94 |
+
//as those returned by the native WordPress.org API. These can be assigned directly.
|
95 |
+
$sameFormat = array(
|
96 |
+
'name', 'slug', 'version', 'requires', 'tested', 'rating', 'upgrade_notice',
|
97 |
+
'num_ratings', 'downloaded', 'active_installs', 'homepage', 'last_updated',
|
98 |
+
'requires_php',
|
99 |
+
);
|
100 |
+
foreach($sameFormat as $field){
|
101 |
+
if ( isset($this->$field) ) {
|
102 |
+
$info->$field = $this->$field;
|
103 |
+
} else {
|
104 |
+
$info->$field = null;
|
105 |
+
}
|
106 |
+
}
|
107 |
+
|
108 |
+
//Other fields need to be renamed and/or transformed.
|
109 |
+
$info->download_link = $this->download_url;
|
110 |
+
$info->author = $this->getFormattedAuthor();
|
111 |
+
$info->sections = array_merge(array('description' => ''), $this->sections);
|
112 |
+
|
113 |
+
if ( !empty($this->banners) ) {
|
114 |
+
//WP expects an array with two keys: "high" and "low". Both are optional.
|
115 |
+
//Docs: https://wordpress.org/plugins/about/faq/#banners
|
116 |
+
$info->banners = is_object($this->banners) ? get_object_vars($this->banners) : $this->banners;
|
117 |
+
$info->banners = array_intersect_key($info->banners, array('high' => true, 'low' => true));
|
118 |
+
}
|
119 |
+
|
120 |
+
return $info;
|
121 |
+
}
|
122 |
+
|
123 |
+
protected function getFormattedAuthor() {
|
124 |
+
if ( !empty($this->author_homepage) ){
|
125 |
+
/** @noinspection HtmlUnknownTarget */
|
126 |
+
return sprintf('<a href="%s">%s</a>', $this->author_homepage, $this->author);
|
127 |
+
}
|
128 |
+
return $this->author;
|
129 |
+
}
|
130 |
+
}
|
131 |
+
|
132 |
+
endif;
|
updater/Puc/v4p10/Plugin/Package.php
ADDED
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Plugin_Package', false) ):
|
3 |
+
|
4 |
+
class Puc_v4p10_Plugin_Package extends Puc_v4p10_InstalledPackage {
|
5 |
+
/**
|
6 |
+
* @var Puc_v4p10_Plugin_UpdateChecker
|
7 |
+
*/
|
8 |
+
protected $updateChecker;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* @var string Full path of the main plugin file.
|
12 |
+
*/
|
13 |
+
protected $pluginAbsolutePath = '';
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @var string Plugin basename.
|
17 |
+
*/
|
18 |
+
private $pluginFile;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* @var string|null
|
22 |
+
*/
|
23 |
+
private $cachedInstalledVersion = null;
|
24 |
+
|
25 |
+
public function __construct($pluginAbsolutePath, $updateChecker) {
|
26 |
+
$this->pluginAbsolutePath = $pluginAbsolutePath;
|
27 |
+
$this->pluginFile = plugin_basename($this->pluginAbsolutePath);
|
28 |
+
|
29 |
+
parent::__construct($updateChecker);
|
30 |
+
|
31 |
+
//Clear the version number cache when something - anything - is upgraded or WP clears the update cache.
|
32 |
+
add_filter('upgrader_post_install', array($this, 'clearCachedVersion'));
|
33 |
+
add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));
|
34 |
+
}
|
35 |
+
|
36 |
+
public function getInstalledVersion() {
|
37 |
+
if ( isset($this->cachedInstalledVersion) ) {
|
38 |
+
return $this->cachedInstalledVersion;
|
39 |
+
}
|
40 |
+
|
41 |
+
$pluginHeader = $this->getPluginHeader();
|
42 |
+
if ( isset($pluginHeader['Version']) ) {
|
43 |
+
$this->cachedInstalledVersion = $pluginHeader['Version'];
|
44 |
+
return $pluginHeader['Version'];
|
45 |
+
} else {
|
46 |
+
//This can happen if the filename points to something that is not a plugin.
|
47 |
+
$this->updateChecker->triggerError(
|
48 |
+
sprintf(
|
49 |
+
"Can't to read the Version header for '%s'. The filename is incorrect or is not a plugin.",
|
50 |
+
$this->updateChecker->pluginFile
|
51 |
+
),
|
52 |
+
E_USER_WARNING
|
53 |
+
);
|
54 |
+
return null;
|
55 |
+
}
|
56 |
+
}
|
57 |
+
|
58 |
+
/**
|
59 |
+
* Clear the cached plugin version. This method can be set up as a filter (hook) and will
|
60 |
+
* return the filter argument unmodified.
|
61 |
+
*
|
62 |
+
* @param mixed $filterArgument
|
63 |
+
* @return mixed
|
64 |
+
*/
|
65 |
+
public function clearCachedVersion($filterArgument = null) {
|
66 |
+
$this->cachedInstalledVersion = null;
|
67 |
+
return $filterArgument;
|
68 |
+
}
|
69 |
+
|
70 |
+
public function getAbsoluteDirectoryPath() {
|
71 |
+
return dirname($this->pluginAbsolutePath);
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Get the value of a specific plugin or theme header.
|
76 |
+
*
|
77 |
+
* @param string $headerName
|
78 |
+
* @param string $defaultValue
|
79 |
+
* @return string Either the value of the header, or $defaultValue if the header doesn't exist or is empty.
|
80 |
+
*/
|
81 |
+
public function getHeaderValue($headerName, $defaultValue = '') {
|
82 |
+
$headers = $this->getPluginHeader();
|
83 |
+
if ( isset($headers[$headerName]) && ($headers[$headerName] !== '') ) {
|
84 |
+
return $headers[$headerName];
|
85 |
+
}
|
86 |
+
return $defaultValue;
|
87 |
+
}
|
88 |
+
|
89 |
+
protected function getHeaderNames() {
|
90 |
+
return array(
|
91 |
+
'Name' => 'Plugin Name',
|
92 |
+
'PluginURI' => 'Plugin URI',
|
93 |
+
'Version' => 'Version',
|
94 |
+
'Description' => 'Description',
|
95 |
+
'Author' => 'Author',
|
96 |
+
'AuthorURI' => 'Author URI',
|
97 |
+
'TextDomain' => 'Text Domain',
|
98 |
+
'DomainPath' => 'Domain Path',
|
99 |
+
'Network' => 'Network',
|
100 |
+
|
101 |
+
//The newest WordPress version that this plugin requires or has been tested with.
|
102 |
+
//We support several different formats for compatibility with other libraries.
|
103 |
+
'Tested WP' => 'Tested WP',
|
104 |
+
'Requires WP' => 'Requires WP',
|
105 |
+
'Tested up to' => 'Tested up to',
|
106 |
+
'Requires at least' => 'Requires at least',
|
107 |
+
);
|
108 |
+
}
|
109 |
+
|
110 |
+
/**
|
111 |
+
* Get the translated plugin title.
|
112 |
+
*
|
113 |
+
* @return string
|
114 |
+
*/
|
115 |
+
public function getPluginTitle() {
|
116 |
+
$title = '';
|
117 |
+
$header = $this->getPluginHeader();
|
118 |
+
if ( $header && !empty($header['Name']) && isset($header['TextDomain']) ) {
|
119 |
+
$title = translate($header['Name'], $header['TextDomain']);
|
120 |
+
}
|
121 |
+
return $title;
|
122 |
+
}
|
123 |
+
|
124 |
+
/**
|
125 |
+
* Get plugin's metadata from its file header.
|
126 |
+
*
|
127 |
+
* @return array
|
128 |
+
*/
|
129 |
+
public function getPluginHeader() {
|
130 |
+
if ( !is_file($this->pluginAbsolutePath) ) {
|
131 |
+
//This can happen if the plugin filename is wrong.
|
132 |
+
$this->updateChecker->triggerError(
|
133 |
+
sprintf(
|
134 |
+
"Can't to read the plugin header for '%s'. The file does not exist.",
|
135 |
+
$this->updateChecker->pluginFile
|
136 |
+
),
|
137 |
+
E_USER_WARNING
|
138 |
+
);
|
139 |
+
return array();
|
140 |
+
}
|
141 |
+
|
142 |
+
if ( !function_exists('get_plugin_data') ) {
|
143 |
+
/** @noinspection PhpIncludeInspection */
|
144 |
+
require_once(ABSPATH . '/wp-admin/includes/plugin.php');
|
145 |
+
}
|
146 |
+
return get_plugin_data($this->pluginAbsolutePath, false, false);
|
147 |
+
}
|
148 |
+
|
149 |
+
public function removeHooks() {
|
150 |
+
remove_filter('upgrader_post_install', array($this, 'clearCachedVersion'));
|
151 |
+
remove_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));
|
152 |
+
}
|
153 |
+
|
154 |
+
/**
|
155 |
+
* Check if the plugin file is inside the mu-plugins directory.
|
156 |
+
*
|
157 |
+
* @return bool
|
158 |
+
*/
|
159 |
+
public function isMuPlugin() {
|
160 |
+
static $cachedResult = null;
|
161 |
+
|
162 |
+
if ( $cachedResult === null ) {
|
163 |
+
if ( !defined('WPMU_PLUGIN_DIR') || !is_string(WPMU_PLUGIN_DIR) ) {
|
164 |
+
$cachedResult = false;
|
165 |
+
return $cachedResult;
|
166 |
+
}
|
167 |
+
|
168 |
+
//Convert both paths to the canonical form before comparison.
|
169 |
+
$muPluginDir = realpath(WPMU_PLUGIN_DIR);
|
170 |
+
$pluginPath = realpath($this->pluginAbsolutePath);
|
171 |
+
//If realpath() fails, just normalize the syntax instead.
|
172 |
+
if (($muPluginDir === false) || ($pluginPath === false)) {
|
173 |
+
$muPluginDir = Puc_v4p10_Factory::normalizePath(WPMU_PLUGIN_DIR);
|
174 |
+
$pluginPath = Puc_v4p10_Factory::normalizePath($this->pluginAbsolutePath);
|
175 |
+
}
|
176 |
+
|
177 |
+
$cachedResult = (strpos($pluginPath, $muPluginDir) === 0);
|
178 |
+
}
|
179 |
+
|
180 |
+
return $cachedResult;
|
181 |
+
}
|
182 |
+
}
|
183 |
+
|
184 |
+
endif;
|
updater/Puc/v4p10/Plugin/Ui.php
ADDED
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Plugin_Ui', false) ):
|
3 |
+
/**
|
4 |
+
* Additional UI elements for plugins.
|
5 |
+
*/
|
6 |
+
class Puc_v4p10_Plugin_Ui {
|
7 |
+
private $updateChecker;
|
8 |
+
private $manualCheckErrorTransient = '';
|
9 |
+
|
10 |
+
/**
|
11 |
+
* @param Puc_v4p10_Plugin_UpdateChecker $updateChecker
|
12 |
+
*/
|
13 |
+
public function __construct($updateChecker) {
|
14 |
+
$this->updateChecker = $updateChecker;
|
15 |
+
$this->manualCheckErrorTransient = $this->updateChecker->getUniqueName('manual_check_errors');
|
16 |
+
|
17 |
+
add_action('admin_init', array($this, 'onAdminInit'));
|
18 |
+
}
|
19 |
+
|
20 |
+
public function onAdminInit() {
|
21 |
+
if ( $this->updateChecker->userCanInstallUpdates() ) {
|
22 |
+
$this->handleManualCheck();
|
23 |
+
|
24 |
+
add_filter('plugin_row_meta', array($this, 'addViewDetailsLink'), 10, 3);
|
25 |
+
add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2);
|
26 |
+
add_action('all_admin_notices', array($this, 'displayManualCheckResult'));
|
27 |
+
}
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Add a "View Details" link to the plugin row in the "Plugins" page. By default,
|
32 |
+
* the new link will appear before the "Visit plugin site" link (if present).
|
33 |
+
*
|
34 |
+
* You can change the link text by using the "puc_view_details_link-$slug" filter.
|
35 |
+
* Returning an empty string from the filter will disable the link.
|
36 |
+
*
|
37 |
+
* You can change the position of the link using the
|
38 |
+
* "puc_view_details_link_position-$slug" filter.
|
39 |
+
* Returning 'before' or 'after' will place the link immediately before/after
|
40 |
+
* the "Visit plugin site" link.
|
41 |
+
* Returning 'append' places the link after any existing links at the time of the hook.
|
42 |
+
* Returning 'replace' replaces the "Visit plugin site" link.
|
43 |
+
* Returning anything else disables the link when there is a "Visit plugin site" link.
|
44 |
+
*
|
45 |
+
* If there is no "Visit plugin site" link 'append' is always used!
|
46 |
+
*
|
47 |
+
* @param array $pluginMeta Array of meta links.
|
48 |
+
* @param string $pluginFile
|
49 |
+
* @param array $pluginData Array of plugin header data.
|
50 |
+
* @return array
|
51 |
+
*/
|
52 |
+
public function addViewDetailsLink($pluginMeta, $pluginFile, $pluginData = array()) {
|
53 |
+
if ( $this->isMyPluginFile($pluginFile) && !isset($pluginData['slug']) ) {
|
54 |
+
$linkText = apply_filters($this->updateChecker->getUniqueName('view_details_link'), __('View details'));
|
55 |
+
if ( !empty($linkText) ) {
|
56 |
+
$viewDetailsLinkPosition = 'append';
|
57 |
+
|
58 |
+
//Find the "Visit plugin site" link (if present).
|
59 |
+
$visitPluginSiteLinkIndex = count($pluginMeta) - 1;
|
60 |
+
if ( $pluginData['PluginURI'] ) {
|
61 |
+
$escapedPluginUri = esc_url($pluginData['PluginURI']);
|
62 |
+
foreach ($pluginMeta as $linkIndex => $existingLink) {
|
63 |
+
if ( strpos($existingLink, $escapedPluginUri) !== false ) {
|
64 |
+
$visitPluginSiteLinkIndex = $linkIndex;
|
65 |
+
$viewDetailsLinkPosition = apply_filters(
|
66 |
+
$this->updateChecker->getUniqueName('view_details_link_position'),
|
67 |
+
'before'
|
68 |
+
);
|
69 |
+
break;
|
70 |
+
}
|
71 |
+
}
|
72 |
+
}
|
73 |
+
|
74 |
+
$viewDetailsLink = sprintf('<a href="%s" class="thickbox open-plugin-details-modal" aria-label="%s" data-title="%s">%s</a>',
|
75 |
+
esc_url(network_admin_url('plugin-install.php?tab=plugin-information&plugin=' . urlencode($this->updateChecker->slug) .
|
76 |
+
'&TB_iframe=true&width=600&height=550')),
|
77 |
+
esc_attr(sprintf(__('More information about %s'), $pluginData['Name'])),
|
78 |
+
esc_attr($pluginData['Name']),
|
79 |
+
$linkText
|
80 |
+
);
|
81 |
+
switch ($viewDetailsLinkPosition) {
|
82 |
+
case 'before':
|
83 |
+
array_splice($pluginMeta, $visitPluginSiteLinkIndex, 0, $viewDetailsLink);
|
84 |
+
break;
|
85 |
+
case 'after':
|
86 |
+
array_splice($pluginMeta, $visitPluginSiteLinkIndex + 1, 0, $viewDetailsLink);
|
87 |
+
break;
|
88 |
+
case 'replace':
|
89 |
+
$pluginMeta[$visitPluginSiteLinkIndex] = $viewDetailsLink;
|
90 |
+
break;
|
91 |
+
case 'append':
|
92 |
+
default:
|
93 |
+
$pluginMeta[] = $viewDetailsLink;
|
94 |
+
break;
|
95 |
+
}
|
96 |
+
}
|
97 |
+
}
|
98 |
+
return $pluginMeta;
|
99 |
+
}
|
100 |
+
|
101 |
+
/**
|
102 |
+
* Add a "Check for updates" link to the plugin row in the "Plugins" page. By default,
|
103 |
+
* the new link will appear after the "Visit plugin site" link if present, otherwise
|
104 |
+
* after the "View plugin details" link.
|
105 |
+
*
|
106 |
+
* You can change the link text by using the "puc_manual_check_link-$slug" filter.
|
107 |
+
* Returning an empty string from the filter will disable the link.
|
108 |
+
*
|
109 |
+
* @param array $pluginMeta Array of meta links.
|
110 |
+
* @param string $pluginFile
|
111 |
+
* @return array
|
112 |
+
*/
|
113 |
+
public function addCheckForUpdatesLink($pluginMeta, $pluginFile) {
|
114 |
+
if ( $this->isMyPluginFile($pluginFile) ) {
|
115 |
+
$linkUrl = wp_nonce_url(
|
116 |
+
add_query_arg(
|
117 |
+
array(
|
118 |
+
'puc_check_for_updates' => 1,
|
119 |
+
'puc_slug' => $this->updateChecker->slug,
|
120 |
+
),
|
121 |
+
self_admin_url('plugins.php')
|
122 |
+
),
|
123 |
+
'puc_check_for_updates'
|
124 |
+
);
|
125 |
+
|
126 |
+
$linkText = apply_filters(
|
127 |
+
$this->updateChecker->getUniqueName('manual_check_link'),
|
128 |
+
__('Check for updates', 'plugin-update-checker')
|
129 |
+
);
|
130 |
+
if ( !empty($linkText) ) {
|
131 |
+
/** @noinspection HtmlUnknownTarget */
|
132 |
+
$pluginMeta[] = sprintf('<a href="%s">%s</a>', esc_attr($linkUrl), $linkText);
|
133 |
+
}
|
134 |
+
}
|
135 |
+
return $pluginMeta;
|
136 |
+
}
|
137 |
+
|
138 |
+
protected function isMyPluginFile($pluginFile) {
|
139 |
+
return ($pluginFile == $this->updateChecker->pluginFile)
|
140 |
+
|| (!empty($this->updateChecker->muPluginFile) && ($pluginFile == $this->updateChecker->muPluginFile));
|
141 |
+
}
|
142 |
+
|
143 |
+
/**
|
144 |
+
* Check for updates when the user clicks the "Check for updates" link.
|
145 |
+
*
|
146 |
+
* @see self::addCheckForUpdatesLink()
|
147 |
+
*
|
148 |
+
* @return void
|
149 |
+
*/
|
150 |
+
public function handleManualCheck() {
|
151 |
+
$shouldCheck =
|
152 |
+
isset($_GET['puc_check_for_updates'], $_GET['puc_slug'])
|
153 |
+
&& $_GET['puc_slug'] == $this->updateChecker->slug
|
154 |
+
&& check_admin_referer('puc_check_for_updates');
|
155 |
+
|
156 |
+
if ( $shouldCheck ) {
|
157 |
+
$update = $this->updateChecker->checkForUpdates();
|
158 |
+
$status = ($update === null) ? 'no_update' : 'update_available';
|
159 |
+
|
160 |
+
if ( ($update === null) && !empty($this->lastRequestApiErrors) ) {
|
161 |
+
//Some errors are not critical. For example, if PUC tries to retrieve the readme.txt
|
162 |
+
//file from GitHub and gets a 404, that's an API error, but it doesn't prevent updates
|
163 |
+
//from working. Maybe the plugin simply doesn't have a readme.
|
164 |
+
//Let's only show important errors.
|
165 |
+
$foundCriticalErrors = false;
|
166 |
+
$questionableErrorCodes = array(
|
167 |
+
'puc-github-http-error',
|
168 |
+
'puc-gitlab-http-error',
|
169 |
+
'puc-bitbucket-http-error',
|
170 |
+
);
|
171 |
+
|
172 |
+
foreach ($this->lastRequestApiErrors as $item) {
|
173 |
+
$wpError = $item['error'];
|
174 |
+
/** @var WP_Error $wpError */
|
175 |
+
if ( !in_array($wpError->get_error_code(), $questionableErrorCodes) ) {
|
176 |
+
$foundCriticalErrors = true;
|
177 |
+
break;
|
178 |
+
}
|
179 |
+
}
|
180 |
+
|
181 |
+
if ( $foundCriticalErrors ) {
|
182 |
+
$status = 'error';
|
183 |
+
set_site_transient($this->manualCheckErrorTransient, $this->lastRequestApiErrors, 60);
|
184 |
+
}
|
185 |
+
}
|
186 |
+
|
187 |
+
wp_redirect(add_query_arg(
|
188 |
+
array(
|
189 |
+
'puc_update_check_result' => $status,
|
190 |
+
'puc_slug' => $this->updateChecker->slug,
|
191 |
+
),
|
192 |
+
self_admin_url('plugins.php')
|
193 |
+
));
|
194 |
+
exit;
|
195 |
+
}
|
196 |
+
}
|
197 |
+
|
198 |
+
/**
|
199 |
+
* Display the results of a manual update check.
|
200 |
+
*
|
201 |
+
* @see self::handleManualCheck()
|
202 |
+
*
|
203 |
+
* You can change the result message by using the "puc_manual_check_message-$slug" filter.
|
204 |
+
*/
|
205 |
+
public function displayManualCheckResult() {
|
206 |
+
if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->updateChecker->slug) ) {
|
207 |
+
$status = strval($_GET['puc_update_check_result']);
|
208 |
+
$title = $this->updateChecker->getInstalledPackage()->getPluginTitle();
|
209 |
+
$noticeClass = 'updated notice-success';
|
210 |
+
$details = '';
|
211 |
+
|
212 |
+
if ( $status == 'no_update' ) {
|
213 |
+
$message = sprintf(_x('The %s plugin is up to date.', 'the plugin title', 'plugin-update-checker'), $title);
|
214 |
+
} else if ( $status == 'update_available' ) {
|
215 |
+
$message = sprintf(_x('A new version of the %s plugin is available.', 'the plugin title', 'plugin-update-checker'), $title);
|
216 |
+
} else if ( $status === 'error' ) {
|
217 |
+
$message = sprintf(_x('Could not determine if updates are available for %s.', 'the plugin title', 'plugin-update-checker'), $title);
|
218 |
+
$noticeClass = 'error notice-error';
|
219 |
+
|
220 |
+
$details = $this->formatManualCheckErrors(get_site_transient($this->manualCheckErrorTransient));
|
221 |
+
delete_site_transient($this->manualCheckErrorTransient);
|
222 |
+
} else {
|
223 |
+
$message = sprintf(__('Unknown update checker status "%s"', 'plugin-update-checker'), htmlentities($status));
|
224 |
+
$noticeClass = 'error notice-error';
|
225 |
+
}
|
226 |
+
printf(
|
227 |
+
'<div class="notice %s is-dismissible"><p>%s</p>%s</div>',
|
228 |
+
$noticeClass,
|
229 |
+
apply_filters($this->updateChecker->getUniqueName('manual_check_message'), $message, $status),
|
230 |
+
$details
|
231 |
+
);
|
232 |
+
}
|
233 |
+
}
|
234 |
+
|
235 |
+
/**
|
236 |
+
* Format the list of errors that were thrown during an update check.
|
237 |
+
*
|
238 |
+
* @param array $errors
|
239 |
+
* @return string
|
240 |
+
*/
|
241 |
+
protected function formatManualCheckErrors($errors) {
|
242 |
+
if ( empty($errors) ) {
|
243 |
+
return '';
|
244 |
+
}
|
245 |
+
$output = '';
|
246 |
+
|
247 |
+
$showAsList = count($errors) > 1;
|
248 |
+
if ( $showAsList ) {
|
249 |
+
$output .= '<ol>';
|
250 |
+
$formatString = '<li>%1$s <code>%2$s</code></li>';
|
251 |
+
} else {
|
252 |
+
$formatString = '<p>%1$s <code>%2$s</code></p>';
|
253 |
+
}
|
254 |
+
foreach ($errors as $item) {
|
255 |
+
$wpError = $item['error'];
|
256 |
+
/** @var WP_Error $wpError */
|
257 |
+
$output .= sprintf(
|
258 |
+
$formatString,
|
259 |
+
$wpError->get_error_message(),
|
260 |
+
$wpError->get_error_code()
|
261 |
+
);
|
262 |
+
}
|
263 |
+
if ( $showAsList ) {
|
264 |
+
$output .= '</ol>';
|
265 |
+
}
|
266 |
+
|
267 |
+
return $output;
|
268 |
+
}
|
269 |
+
|
270 |
+
public function removeHooks() {
|
271 |
+
remove_action('admin_init', array($this, 'onAdminInit'));
|
272 |
+
remove_filter('plugin_row_meta', array($this, 'addViewDetailsLink'), 10);
|
273 |
+
remove_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10);
|
274 |
+
remove_action('all_admin_notices', array($this, 'displayManualCheckResult'));
|
275 |
+
}
|
276 |
+
}
|
277 |
+
endif;
|
updater/Puc/v4p10/Plugin/Update.php
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Plugin_Update', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* A simple container class for holding information about an available update.
|
6 |
+
*
|
7 |
+
* @author Janis Elsts
|
8 |
+
* @copyright 2016
|
9 |
+
* @access public
|
10 |
+
*/
|
11 |
+
class Puc_v4p10_Plugin_Update extends Puc_v4p10_Update {
|
12 |
+
public $id = 0;
|
13 |
+
public $homepage;
|
14 |
+
public $upgrade_notice;
|
15 |
+
public $tested;
|
16 |
+
public $requires_php = false;
|
17 |
+
public $icons = array();
|
18 |
+
public $filename; //Plugin filename relative to the plugins directory.
|
19 |
+
|
20 |
+
protected static $extraFields = array(
|
21 |
+
'id', 'homepage', 'tested', 'requires_php', 'upgrade_notice', 'icons', 'filename',
|
22 |
+
);
|
23 |
+
|
24 |
+
/**
|
25 |
+
* Create a new instance of PluginUpdate from its JSON-encoded representation.
|
26 |
+
*
|
27 |
+
* @param string $json
|
28 |
+
* @return Puc_v4p10_Plugin_Update|null
|
29 |
+
*/
|
30 |
+
public static function fromJson($json){
|
31 |
+
//Since update-related information is simply a subset of the full plugin info,
|
32 |
+
//we can parse the update JSON as if it was a plugin info string, then copy over
|
33 |
+
//the parts that we care about.
|
34 |
+
$pluginInfo = Puc_v4p10_Plugin_Info::fromJson($json);
|
35 |
+
if ( $pluginInfo !== null ) {
|
36 |
+
return self::fromPluginInfo($pluginInfo);
|
37 |
+
} else {
|
38 |
+
return null;
|
39 |
+
}
|
40 |
+
}
|
41 |
+
|
42 |
+
/**
|
43 |
+
* Create a new instance of PluginUpdate based on an instance of PluginInfo.
|
44 |
+
* Basically, this just copies a subset of fields from one object to another.
|
45 |
+
*
|
46 |
+
* @param Puc_v4p10_Plugin_Info $info
|
47 |
+
* @return Puc_v4p10_Plugin_Update
|
48 |
+
*/
|
49 |
+
public static function fromPluginInfo($info){
|
50 |
+
return self::fromObject($info);
|
51 |
+
}
|
52 |
+
|
53 |
+
/**
|
54 |
+
* Create a new instance by copying the necessary fields from another object.
|
55 |
+
*
|
56 |
+
* @param StdClass|Puc_v4p10_Plugin_Info|Puc_v4p10_Plugin_Update $object The source object.
|
57 |
+
* @return Puc_v4p10_Plugin_Update The new copy.
|
58 |
+
*/
|
59 |
+
public static function fromObject($object) {
|
60 |
+
$update = new self();
|
61 |
+
$update->copyFields($object, $update);
|
62 |
+
return $update;
|
63 |
+
}
|
64 |
+
|
65 |
+
/**
|
66 |
+
* @return string[]
|
67 |
+
*/
|
68 |
+
protected function getFieldNames() {
|
69 |
+
return array_merge(parent::getFieldNames(), self::$extraFields);
|
70 |
+
}
|
71 |
+
|
72 |
+
/**
|
73 |
+
* Transform the update into the format used by WordPress native plugin API.
|
74 |
+
*
|
75 |
+
* @return object
|
76 |
+
*/
|
77 |
+
public function toWpFormat() {
|
78 |
+
$update = parent::toWpFormat();
|
79 |
+
|
80 |
+
$update->id = $this->id;
|
81 |
+
$update->url = $this->homepage;
|
82 |
+
$update->tested = $this->tested;
|
83 |
+
$update->requires_php = $this->requires_php;
|
84 |
+
$update->plugin = $this->filename;
|
85 |
+
|
86 |
+
if ( !empty($this->upgrade_notice) ) {
|
87 |
+
$update->upgrade_notice = $this->upgrade_notice;
|
88 |
+
}
|
89 |
+
|
90 |
+
if ( !empty($this->icons) && is_array($this->icons) ) {
|
91 |
+
//This should be an array with up to 4 keys: 'svg', '1x', '2x' and 'default'.
|
92 |
+
//Docs: https://developer.wordpress.org/plugins/wordpress-org/plugin-assets/#plugin-icons
|
93 |
+
$icons = array_intersect_key(
|
94 |
+
$this->icons,
|
95 |
+
array('svg' => true, '1x' => true, '2x' => true, 'default' => true)
|
96 |
+
);
|
97 |
+
if ( !empty($icons) ) {
|
98 |
+
$update->icons = $icons;
|
99 |
+
|
100 |
+
//It appears that the 'default' icon isn't used anywhere in WordPress 4.9,
|
101 |
+
//but lets set it just in case a future release needs it.
|
102 |
+
if ( !isset($update->icons['default']) ) {
|
103 |
+
$update->icons['default'] = current($update->icons);
|
104 |
+
}
|
105 |
+
}
|
106 |
+
}
|
107 |
+
|
108 |
+
return $update;
|
109 |
+
}
|
110 |
+
}
|
111 |
+
|
112 |
+
endif;
|
updater/Puc/v4p10/Plugin/UpdateChecker.php
ADDED
@@ -0,0 +1,414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Plugin_UpdateChecker', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* A custom plugin update checker.
|
6 |
+
*
|
7 |
+
* @author Janis Elsts
|
8 |
+
* @copyright 2018
|
9 |
+
* @access public
|
10 |
+
*/
|
11 |
+
class Puc_v4p10_Plugin_UpdateChecker extends Puc_v4p10_UpdateChecker {
|
12 |
+
protected $updateTransient = 'update_plugins';
|
13 |
+
protected $translationType = 'plugin';
|
14 |
+
|
15 |
+
public $pluginAbsolutePath = ''; //Full path of the main plugin file.
|
16 |
+
public $pluginFile = ''; //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins.
|
17 |
+
public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory.
|
18 |
+
|
19 |
+
/**
|
20 |
+
* @var Puc_v4p10_Plugin_Package
|
21 |
+
*/
|
22 |
+
protected $package;
|
23 |
+
|
24 |
+
private $extraUi = null;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* Class constructor.
|
28 |
+
*
|
29 |
+
* @param string $metadataUrl The URL of the plugin's metadata file.
|
30 |
+
* @param string $pluginFile Fully qualified path to the main plugin file.
|
31 |
+
* @param string $slug The plugin's 'slug'. If not specified, the filename part of $pluginFile sans '.php' will be used as the slug.
|
32 |
+
* @param integer $checkPeriod How often to check for updates (in hours). Defaults to checking every 12 hours. Set to 0 to disable automatic update checks.
|
33 |
+
* @param string $optionName Where to store book-keeping info about update checks. Defaults to 'external_updates-$slug'.
|
34 |
+
* @param string $muPluginFile Optional. The plugin filename relative to the mu-plugins directory.
|
35 |
+
*/
|
36 |
+
public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = ''){
|
37 |
+
$this->pluginAbsolutePath = $pluginFile;
|
38 |
+
$this->pluginFile = plugin_basename($this->pluginAbsolutePath);
|
39 |
+
$this->muPluginFile = $muPluginFile;
|
40 |
+
|
41 |
+
//If no slug is specified, use the name of the main plugin file as the slug.
|
42 |
+
//For example, 'my-cool-plugin/cool-plugin.php' becomes 'cool-plugin'.
|
43 |
+
if ( empty($slug) ){
|
44 |
+
$slug = basename($this->pluginFile, '.php');
|
45 |
+
}
|
46 |
+
|
47 |
+
//Plugin slugs must be unique.
|
48 |
+
$slugCheckFilter = 'puc_is_slug_in_use-' . $slug;
|
49 |
+
$slugUsedBy = apply_filters($slugCheckFilter, false);
|
50 |
+
if ( $slugUsedBy ) {
|
51 |
+
$this->triggerError(sprintf(
|
52 |
+
'Plugin slug "%s" is already in use by %s. Slugs must be unique.',
|
53 |
+
htmlentities($slug),
|
54 |
+
htmlentities($slugUsedBy)
|
55 |
+
), E_USER_ERROR);
|
56 |
+
}
|
57 |
+
add_filter($slugCheckFilter, array($this, 'getAbsolutePath'));
|
58 |
+
|
59 |
+
parent::__construct($metadataUrl, dirname($this->pluginFile), $slug, $checkPeriod, $optionName);
|
60 |
+
|
61 |
+
//Backwards compatibility: If the plugin is a mu-plugin but no $muPluginFile is specified, assume
|
62 |
+
//it's the same as $pluginFile given that it's not in a subdirectory (WP only looks in the base dir).
|
63 |
+
if ( (strpbrk($this->pluginFile, '/\\') === false) && $this->isUnknownMuPlugin() ) {
|
64 |
+
$this->muPluginFile = $this->pluginFile;
|
65 |
+
}
|
66 |
+
|
67 |
+
//To prevent a crash during plugin uninstallation, remove updater hooks when the user removes the plugin.
|
68 |
+
//Details: https://github.com/YahnisElsts/plugin-update-checker/issues/138#issuecomment-335590964
|
69 |
+
add_action('uninstall_' . $this->pluginFile, array($this, 'removeHooks'));
|
70 |
+
|
71 |
+
$this->extraUi = new Puc_v4p10_Plugin_Ui($this);
|
72 |
+
}
|
73 |
+
|
74 |
+
/**
|
75 |
+
* Create an instance of the scheduler.
|
76 |
+
*
|
77 |
+
* @param int $checkPeriod
|
78 |
+
* @return Puc_v4p10_Scheduler
|
79 |
+
*/
|
80 |
+
protected function createScheduler($checkPeriod) {
|
81 |
+
$scheduler = new Puc_v4p10_Scheduler($this, $checkPeriod, array('load-plugins.php'));
|
82 |
+
register_deactivation_hook($this->pluginFile, array($scheduler, 'removeUpdaterCron'));
|
83 |
+
return $scheduler;
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Install the hooks required to run periodic update checks and inject update info
|
88 |
+
* into WP data structures.
|
89 |
+
*
|
90 |
+
* @return void
|
91 |
+
*/
|
92 |
+
protected function installHooks(){
|
93 |
+
//Override requests for plugin information
|
94 |
+
add_filter('plugins_api', array($this, 'injectInfo'), 20, 3);
|
95 |
+
|
96 |
+
parent::installHooks();
|
97 |
+
}
|
98 |
+
|
99 |
+
/**
|
100 |
+
* Remove update checker hooks.
|
101 |
+
*
|
102 |
+
* The intent is to prevent a fatal error that can happen if the plugin has an uninstall
|
103 |
+
* hook. During uninstallation, WP includes the main plugin file (which creates a PUC instance),
|
104 |
+
* the uninstall hook runs, WP deletes the plugin files and then updates some transients.
|
105 |
+
* If PUC hooks are still around at this time, they could throw an error while trying to
|
106 |
+
* autoload classes from files that no longer exist.
|
107 |
+
*
|
108 |
+
* The "site_transient_{$transient}" filter is the main problem here, but let's also remove
|
109 |
+
* most other PUC hooks to be safe.
|
110 |
+
*
|
111 |
+
* @internal
|
112 |
+
*/
|
113 |
+
public function removeHooks() {
|
114 |
+
parent::removeHooks();
|
115 |
+
$this->extraUi->removeHooks();
|
116 |
+
$this->package->removeHooks();
|
117 |
+
|
118 |
+
remove_filter('plugins_api', array($this, 'injectInfo'), 20);
|
119 |
+
}
|
120 |
+
|
121 |
+
/**
|
122 |
+
* Retrieve plugin info from the configured API endpoint.
|
123 |
+
*
|
124 |
+
* @uses wp_remote_get()
|
125 |
+
*
|
126 |
+
* @param array $queryArgs Additional query arguments to append to the request. Optional.
|
127 |
+
* @return Puc_v4p10_Plugin_Info
|
128 |
+
*/
|
129 |
+
public function requestInfo($queryArgs = array()) {
|
130 |
+
list($pluginInfo, $result) = $this->requestMetadata('Puc_v4p10_Plugin_Info', 'request_info', $queryArgs);
|
131 |
+
|
132 |
+
if ( $pluginInfo !== null ) {
|
133 |
+
/** @var Puc_v4p10_Plugin_Info $pluginInfo */
|
134 |
+
$pluginInfo->filename = $this->pluginFile;
|
135 |
+
$pluginInfo->slug = $this->slug;
|
136 |
+
}
|
137 |
+
|
138 |
+
$pluginInfo = apply_filters($this->getUniqueName('request_info_result'), $pluginInfo, $result);
|
139 |
+
return $pluginInfo;
|
140 |
+
}
|
141 |
+
|
142 |
+
/**
|
143 |
+
* Retrieve the latest update (if any) from the configured API endpoint.
|
144 |
+
*
|
145 |
+
* @uses PluginUpdateChecker::requestInfo()
|
146 |
+
*
|
147 |
+
* @return Puc_v4p10_Update|null An instance of Plugin_Update, or NULL when no updates are available.
|
148 |
+
*/
|
149 |
+
public function requestUpdate() {
|
150 |
+
//For the sake of simplicity, this function just calls requestInfo()
|
151 |
+
//and transforms the result accordingly.
|
152 |
+
$pluginInfo = $this->requestInfo(array('checking_for_updates' => '1'));
|
153 |
+
if ( $pluginInfo === null ){
|
154 |
+
return null;
|
155 |
+
}
|
156 |
+
$update = Puc_v4p10_Plugin_Update::fromPluginInfo($pluginInfo);
|
157 |
+
|
158 |
+
$update = $this->filterUpdateResult($update);
|
159 |
+
|
160 |
+
return $update;
|
161 |
+
}
|
162 |
+
|
163 |
+
/**
|
164 |
+
* Intercept plugins_api() calls that request information about our plugin and
|
165 |
+
* use the configured API endpoint to satisfy them.
|
166 |
+
*
|
167 |
+
* @see plugins_api()
|
168 |
+
*
|
169 |
+
* @param mixed $result
|
170 |
+
* @param string $action
|
171 |
+
* @param array|object $args
|
172 |
+
* @return mixed
|
173 |
+
*/
|
174 |
+
public function injectInfo($result, $action = null, $args = null){
|
175 |
+
$relevant = ($action == 'plugin_information') && isset($args->slug) && (
|
176 |
+
($args->slug == $this->slug) || ($args->slug == dirname($this->pluginFile))
|
177 |
+
);
|
178 |
+
if ( !$relevant ) {
|
179 |
+
return $result;
|
180 |
+
}
|
181 |
+
|
182 |
+
$pluginInfo = $this->requestInfo();
|
183 |
+
$this->fixSupportedWordpressVersion($pluginInfo);
|
184 |
+
|
185 |
+
$pluginInfo = apply_filters($this->getUniqueName('pre_inject_info'), $pluginInfo);
|
186 |
+
if ( $pluginInfo ) {
|
187 |
+
return $pluginInfo->toWpFormat();
|
188 |
+
}
|
189 |
+
|
190 |
+
return $result;
|
191 |
+
}
|
192 |
+
|
193 |
+
protected function shouldShowUpdates() {
|
194 |
+
//No update notifications for mu-plugins unless explicitly enabled. The MU plugin file
|
195 |
+
//is usually different from the main plugin file so the update wouldn't show up properly anyway.
|
196 |
+
return !$this->isUnknownMuPlugin();
|
197 |
+
}
|
198 |
+
|
199 |
+
/**
|
200 |
+
* @param stdClass|null $updates
|
201 |
+
* @param stdClass $updateToAdd
|
202 |
+
* @return stdClass
|
203 |
+
*/
|
204 |
+
protected function addUpdateToList($updates, $updateToAdd) {
|
205 |
+
if ( $this->package->isMuPlugin() ) {
|
206 |
+
//WP does not support automatic update installation for mu-plugins, but we can
|
207 |
+
//still display a notice.
|
208 |
+
$updateToAdd->package = null;
|
209 |
+
}
|
210 |
+
return parent::addUpdateToList($updates, $updateToAdd);
|
211 |
+
}
|
212 |
+
|
213 |
+
/**
|
214 |
+
* @param stdClass|null $updates
|
215 |
+
* @return stdClass|null
|
216 |
+
*/
|
217 |
+
protected function removeUpdateFromList($updates) {
|
218 |
+
$updates = parent::removeUpdateFromList($updates);
|
219 |
+
if ( !empty($this->muPluginFile) && isset($updates, $updates->response) ) {
|
220 |
+
unset($updates->response[$this->muPluginFile]);
|
221 |
+
}
|
222 |
+
return $updates;
|
223 |
+
}
|
224 |
+
|
225 |
+
/**
|
226 |
+
* For plugins, the update array is indexed by the plugin filename relative to the "plugins"
|
227 |
+
* directory. Example: "plugin-name/plugin.php".
|
228 |
+
*
|
229 |
+
* @return string
|
230 |
+
*/
|
231 |
+
protected function getUpdateListKey() {
|
232 |
+
if ( $this->package->isMuPlugin() ) {
|
233 |
+
return $this->muPluginFile;
|
234 |
+
}
|
235 |
+
return $this->pluginFile;
|
236 |
+
}
|
237 |
+
|
238 |
+
protected function getNoUpdateItemFields() {
|
239 |
+
return array_merge(
|
240 |
+
parent::getNoUpdateItemFields(),
|
241 |
+
array(
|
242 |
+
'id' => $this->pluginFile,
|
243 |
+
'slug' => $this->slug,
|
244 |
+
'plugin' => $this->pluginFile,
|
245 |
+
'icons' => array(),
|
246 |
+
'banners' => array(),
|
247 |
+
'banners_rtl' => array(),
|
248 |
+
'tested' => '',
|
249 |
+
'compatibility' => new stdClass(),
|
250 |
+
)
|
251 |
+
);
|
252 |
+
}
|
253 |
+
|
254 |
+
/**
|
255 |
+
* Alias for isBeingUpgraded().
|
256 |
+
*
|
257 |
+
* @deprecated
|
258 |
+
* @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
|
259 |
+
* @return bool
|
260 |
+
*/
|
261 |
+
public function isPluginBeingUpgraded($upgrader = null) {
|
262 |
+
return $this->isBeingUpgraded($upgrader);
|
263 |
+
}
|
264 |
+
|
265 |
+
/**
|
266 |
+
* Is there an update being installed for this plugin, right now?
|
267 |
+
*
|
268 |
+
* @param WP_Upgrader|null $upgrader
|
269 |
+
* @return bool
|
270 |
+
*/
|
271 |
+
public function isBeingUpgraded($upgrader = null) {
|
272 |
+
return $this->upgraderStatus->isPluginBeingUpgraded($this->pluginFile, $upgrader);
|
273 |
+
}
|
274 |
+
|
275 |
+
/**
|
276 |
+
* Get the details of the currently available update, if any.
|
277 |
+
*
|
278 |
+
* If no updates are available, or if the last known update version is below or equal
|
279 |
+
* to the currently installed version, this method will return NULL.
|
280 |
+
*
|
281 |
+
* Uses cached update data. To retrieve update information straight from
|
282 |
+
* the metadata URL, call requestUpdate() instead.
|
283 |
+
*
|
284 |
+
* @return Puc_v4p10_Plugin_Update|null
|
285 |
+
*/
|
286 |
+
public function getUpdate() {
|
287 |
+
$update = parent::getUpdate();
|
288 |
+
if ( isset($update) ) {
|
289 |
+
/** @var Puc_v4p10_Plugin_Update $update */
|
290 |
+
$update->filename = $this->pluginFile;
|
291 |
+
}
|
292 |
+
return $update;
|
293 |
+
}
|
294 |
+
|
295 |
+
/**
|
296 |
+
* Get the translated plugin title.
|
297 |
+
*
|
298 |
+
* @deprecated
|
299 |
+
* @return string
|
300 |
+
*/
|
301 |
+
public function getPluginTitle() {
|
302 |
+
return $this->package->getPluginTitle();
|
303 |
+
}
|
304 |
+
|
305 |
+
/**
|
306 |
+
* Check if the current user has the required permissions to install updates.
|
307 |
+
*
|
308 |
+
* @return bool
|
309 |
+
*/
|
310 |
+
public function userCanInstallUpdates() {
|
311 |
+
return current_user_can('update_plugins');
|
312 |
+
}
|
313 |
+
|
314 |
+
/**
|
315 |
+
* Check if the plugin file is inside the mu-plugins directory.
|
316 |
+
*
|
317 |
+
* @deprecated
|
318 |
+
* @return bool
|
319 |
+
*/
|
320 |
+
protected function isMuPlugin() {
|
321 |
+
return $this->package->isMuPlugin();
|
322 |
+
}
|
323 |
+
|
324 |
+
/**
|
325 |
+
* MU plugins are partially supported, but only when we know which file in mu-plugins
|
326 |
+
* corresponds to this plugin.
|
327 |
+
*
|
328 |
+
* @return bool
|
329 |
+
*/
|
330 |
+
protected function isUnknownMuPlugin() {
|
331 |
+
return empty($this->muPluginFile) && $this->package->isMuPlugin();
|
332 |
+
}
|
333 |
+
|
334 |
+
/**
|
335 |
+
* Get absolute path to the main plugin file.
|
336 |
+
*
|
337 |
+
* @return string
|
338 |
+
*/
|
339 |
+
public function getAbsolutePath() {
|
340 |
+
return $this->pluginAbsolutePath;
|
341 |
+
}
|
342 |
+
|
343 |
+
/**
|
344 |
+
* Register a callback for filtering query arguments.
|
345 |
+
*
|
346 |
+
* The callback function should take one argument - an associative array of query arguments.
|
347 |
+
* It should return a modified array of query arguments.
|
348 |
+
*
|
349 |
+
* @uses add_filter() This method is a convenience wrapper for add_filter().
|
350 |
+
*
|
351 |
+
* @param callable $callback
|
352 |
+
* @return void
|
353 |
+
*/
|
354 |
+
public function addQueryArgFilter($callback){
|
355 |
+
$this->addFilter('request_info_query_args', $callback);
|
356 |
+
}
|
357 |
+
|
358 |
+
/**
|
359 |
+
* Register a callback for filtering arguments passed to wp_remote_get().
|
360 |
+
*
|
361 |
+
* The callback function should take one argument - an associative array of arguments -
|
362 |
+
* and return a modified array or arguments. See the WP documentation on wp_remote_get()
|
363 |
+
* for details on what arguments are available and how they work.
|
364 |
+
*
|
365 |
+
* @uses add_filter() This method is a convenience wrapper for add_filter().
|
366 |
+
*
|
367 |
+
* @param callable $callback
|
368 |
+
* @return void
|
369 |
+
*/
|
370 |
+
public function addHttpRequestArgFilter($callback) {
|
371 |
+
$this->addFilter('request_info_options', $callback);
|
372 |
+
}
|
373 |
+
|
374 |
+
/**
|
375 |
+
* Register a callback for filtering the plugin info retrieved from the external API.
|
376 |
+
*
|
377 |
+
* The callback function should take two arguments. If the plugin info was retrieved
|
378 |
+
* successfully, the first argument passed will be an instance of PluginInfo. Otherwise,
|
379 |
+
* it will be NULL. The second argument will be the corresponding return value of
|
380 |
+
* wp_remote_get (see WP docs for details).
|
381 |
+
*
|
382 |
+
* The callback function should return a new or modified instance of PluginInfo or NULL.
|
383 |
+
*
|
384 |
+
* @uses add_filter() This method is a convenience wrapper for add_filter().
|
385 |
+
*
|
386 |
+
* @param callable $callback
|
387 |
+
* @return void
|
388 |
+
*/
|
389 |
+
public function addResultFilter($callback) {
|
390 |
+
$this->addFilter('request_info_result', $callback, 10, 2);
|
391 |
+
}
|
392 |
+
|
393 |
+
protected function createDebugBarExtension() {
|
394 |
+
return new Puc_v4p10_DebugBar_PluginExtension($this);
|
395 |
+
}
|
396 |
+
|
397 |
+
/**
|
398 |
+
* Create a package instance that represents this plugin or theme.
|
399 |
+
*
|
400 |
+
* @return Puc_v4p10_InstalledPackage
|
401 |
+
*/
|
402 |
+
protected function createInstalledPackage() {
|
403 |
+
return new Puc_v4p10_Plugin_Package($this->pluginAbsolutePath, $this);
|
404 |
+
}
|
405 |
+
|
406 |
+
/**
|
407 |
+
* @return Puc_v4p10_Plugin_Package
|
408 |
+
*/
|
409 |
+
public function getInstalledPackage() {
|
410 |
+
return $this->package;
|
411 |
+
}
|
412 |
+
}
|
413 |
+
|
414 |
+
endif;
|
updater/Puc/v4p10/Scheduler.php
ADDED
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Scheduler', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* The scheduler decides when and how often to check for updates.
|
6 |
+
* It calls @see Puc_v4p10_UpdateChecker::checkForUpdates() to perform the actual checks.
|
7 |
+
*/
|
8 |
+
class Puc_v4p10_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;
|
12 |
+
|
13 |
+
protected $hourlyCheckHooks = array('load-update.php');
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @var Puc_v4p10_UpdateChecker
|
17 |
+
*/
|
18 |
+
protected $updateChecker;
|
19 |
+
|
20 |
+
private $cronHook = null;
|
21 |
+
|
22 |
+
/**
|
23 |
+
* Scheduler constructor.
|
24 |
+
*
|
25 |
+
* @param Puc_v4p10_UpdateChecker $updateChecker
|
26 |
+
* @param int $checkPeriod How often to check for updates (in hours).
|
27 |
+
* @param array $hourlyHooks
|
28 |
+
*/
|
29 |
+
public function __construct($updateChecker, $checkPeriod, $hourlyHooks = array('load-plugins.php')) {
|
30 |
+
$this->updateChecker = $updateChecker;
|
31 |
+
$this->checkPeriod = $checkPeriod;
|
32 |
+
|
33 |
+
//Set up the periodic update checks
|
34 |
+
$this->cronHook = $this->updateChecker->getUniqueName('cron_check_updates');
|
35 |
+
if ( $this->checkPeriod > 0 ){
|
36 |
+
|
37 |
+
//Trigger the check via Cron.
|
38 |
+
//Try to use one of the default schedules if possible as it's less likely to conflict
|
39 |
+
//with other plugins and their custom schedules.
|
40 |
+
$defaultSchedules = array(
|
41 |
+
1 => 'hourly',
|
42 |
+
12 => 'twicedaily',
|
43 |
+
24 => 'daily',
|
44 |
+
);
|
45 |
+
if ( array_key_exists($this->checkPeriod, $defaultSchedules) ) {
|
46 |
+
$scheduleName = $defaultSchedules[$this->checkPeriod];
|
47 |
+
} else {
|
48 |
+
//Use a custom cron schedule.
|
49 |
+
$scheduleName = 'every' . $this->checkPeriod . 'hours';
|
50 |
+
add_filter('cron_schedules', array($this, '_addCustomSchedule'));
|
51 |
+
}
|
52 |
+
|
53 |
+
if ( !wp_next_scheduled($this->cronHook) && !defined('WP_INSTALLING') ) {
|
54 |
+
//Randomly offset the schedule to help prevent update server traffic spikes. Without this
|
55 |
+
//most checks may happen during times of day when people are most likely to install new plugins.
|
56 |
+
$firstCheckTime = time() - rand(0, max($this->checkPeriod * 3600 - 15 * 60, 1));
|
57 |
+
$firstCheckTime = apply_filters(
|
58 |
+
$this->updateChecker->getUniqueName('first_check_time'),
|
59 |
+
$firstCheckTime
|
60 |
+
);
|
61 |
+
wp_schedule_event($firstCheckTime, $scheduleName, $this->cronHook);
|
62 |
+
}
|
63 |
+
add_action($this->cronHook, array($this, 'maybeCheckForUpdates'));
|
64 |
+
|
65 |
+
//In case Cron is disabled or unreliable, we also manually trigger
|
66 |
+
//the periodic checks while the user is browsing the Dashboard.
|
67 |
+
add_action( 'admin_init', array($this, 'maybeCheckForUpdates') );
|
68 |
+
|
69 |
+
//Like WordPress itself, we check more often on certain pages.
|
70 |
+
/** @see wp_update_plugins */
|
71 |
+
add_action('load-update-core.php', array($this, 'maybeCheckForUpdates'));
|
72 |
+
//"load-update.php" and "load-plugins.php" or "load-themes.php".
|
73 |
+
$this->hourlyCheckHooks = array_merge($this->hourlyCheckHooks, $hourlyHooks);
|
74 |
+
foreach($this->hourlyCheckHooks as $hook) {
|
75 |
+
add_action($hook, array($this, 'maybeCheckForUpdates'));
|
76 |
+
}
|
77 |
+
//This hook fires after a bulk update is complete.
|
78 |
+
add_action('upgrader_process_complete', array($this, 'upgraderProcessComplete'), 11, 2);
|
79 |
+
|
80 |
+
} else {
|
81 |
+
//Periodic checks are disabled.
|
82 |
+
wp_clear_scheduled_hook($this->cronHook);
|
83 |
+
}
|
84 |
+
}
|
85 |
+
|
86 |
+
/**
|
87 |
+
* Runs upon the WP action upgrader_process_complete.
|
88 |
+
*
|
89 |
+
* We look at the parameters to decide whether to call maybeCheckForUpdates() or not.
|
90 |
+
* We also check if the update checker has been removed by the update.
|
91 |
+
*
|
92 |
+
* @param WP_Upgrader $upgrader WP_Upgrader instance
|
93 |
+
* @param array $upgradeInfo extra information about the upgrade
|
94 |
+
*/
|
95 |
+
public function upgraderProcessComplete(
|
96 |
+
/** @noinspection PhpUnusedParameterInspection */
|
97 |
+
$upgrader, $upgradeInfo
|
98 |
+
) {
|
99 |
+
//Cancel all further actions if the current version of PUC has been deleted or overwritten
|
100 |
+
//by a different version during the upgrade. If we try to do anything more in that situation,
|
101 |
+
//we could trigger a fatal error by trying to autoload a deleted class.
|
102 |
+
clearstatcache();
|
103 |
+
if ( !file_exists(__FILE__) ) {
|
104 |
+
$this->removeHooks();
|
105 |
+
$this->updateChecker->removeHooks();
|
106 |
+
return;
|
107 |
+
}
|
108 |
+
|
109 |
+
//Sanity check and limitation to relevant types.
|
110 |
+
if (
|
111 |
+
!is_array($upgradeInfo) || !isset($upgradeInfo['type'], $upgradeInfo['action'])
|
112 |
+
|| 'update' !== $upgradeInfo['action'] || !in_array($upgradeInfo['type'], array('plugin', 'theme'))
|
113 |
+
) {
|
114 |
+
return;
|
115 |
+
}
|
116 |
+
|
117 |
+
//Filter out notifications of upgrades that should have no bearing upon whether or not our
|
118 |
+
//current info is up-to-date.
|
119 |
+
if ( is_a($this->updateChecker, 'Puc_v4p10_Theme_UpdateChecker') ) {
|
120 |
+
if ( 'theme' !== $upgradeInfo['type'] || !isset($upgradeInfo['themes']) ) {
|
121 |
+
return;
|
122 |
+
}
|
123 |
+
|
124 |
+
//Letting too many things going through for checks is not a real problem, so we compare widely.
|
125 |
+
if ( !in_array(
|
126 |
+
strtolower($this->updateChecker->directoryName),
|
127 |
+
array_map('strtolower', $upgradeInfo['themes'])
|
128 |
+
) ) {
|
129 |
+
return;
|
130 |
+
}
|
131 |
+
}
|
132 |
+
|
133 |
+
if ( is_a($this->updateChecker, 'Puc_v4p10_Plugin_UpdateChecker') ) {
|
134 |
+
if ( 'plugin' !== $upgradeInfo['type'] || !isset($upgradeInfo['plugins']) ) {
|
135 |
+
return;
|
136 |
+
}
|
137 |
+
|
138 |
+
//Themes pass in directory names in the information array, but plugins use the relative plugin path.
|
139 |
+
if ( !in_array(
|
140 |
+
strtolower($this->updateChecker->directoryName),
|
141 |
+
array_map('dirname', array_map('strtolower', $upgradeInfo['plugins']))
|
142 |
+
) ) {
|
143 |
+
return;
|
144 |
+
}
|
145 |
+
}
|
146 |
+
|
147 |
+
$this->maybeCheckForUpdates();
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Check for updates if the configured check interval has already elapsed.
|
152 |
+
* Will use a shorter check interval on certain admin pages like "Dashboard -> Updates" or when doing cron.
|
153 |
+
*
|
154 |
+
* You can override the default behaviour by using the "puc_check_now-$slug" filter.
|
155 |
+
* The filter callback will be passed three parameters:
|
156 |
+
* - Current decision. TRUE = check updates now, FALSE = don't check now.
|
157 |
+
* - Last check time as a Unix timestamp.
|
158 |
+
* - Configured check period in hours.
|
159 |
+
* Return TRUE to check for updates immediately, or FALSE to cancel.
|
160 |
+
*
|
161 |
+
* This method is declared public because it's a hook callback. Calling it directly is not recommended.
|
162 |
+
*/
|
163 |
+
public function maybeCheckForUpdates() {
|
164 |
+
if ( empty($this->checkPeriod) ){
|
165 |
+
return;
|
166 |
+
}
|
167 |
+
|
168 |
+
$state = $this->updateChecker->getUpdateState();
|
169 |
+
$shouldCheck = ($state->timeSinceLastCheck() >= $this->getEffectiveCheckPeriod());
|
170 |
+
|
171 |
+
//Let plugin authors substitute their own algorithm.
|
172 |
+
$shouldCheck = apply_filters(
|
173 |
+
$this->updateChecker->getUniqueName('check_now'),
|
174 |
+
$shouldCheck,
|
175 |
+
$state->getLastCheck(),
|
176 |
+
$this->checkPeriod
|
177 |
+
);
|
178 |
+
|
179 |
+
if ( $shouldCheck ) {
|
180 |
+
$this->updateChecker->checkForUpdates();
|
181 |
+
}
|
182 |
+
}
|
183 |
+
|
184 |
+
/**
|
185 |
+
* Calculate the actual check period based on the current status and environment.
|
186 |
+
*
|
187 |
+
* @return int Check period in seconds.
|
188 |
+
*/
|
189 |
+
protected function getEffectiveCheckPeriod() {
|
190 |
+
$currentFilter = current_filter();
|
191 |
+
if ( in_array($currentFilter, array('load-update-core.php', 'upgrader_process_complete')) ) {
|
192 |
+
//Check more often when the user visits "Dashboard -> Updates" or does a bulk update.
|
193 |
+
$period = 60;
|
194 |
+
} else if ( in_array($currentFilter, $this->hourlyCheckHooks) ) {
|
195 |
+
//Also check more often on /wp-admin/update.php and the "Plugins" or "Themes" page.
|
196 |
+
$period = 3600;
|
197 |
+
} else if ( $this->throttleRedundantChecks && ($this->updateChecker->getUpdate() !== null) ) {
|
198 |
+
//Check less frequently if it's already known that an update is available.
|
199 |
+
$period = $this->throttledCheckPeriod * 3600;
|
200 |
+
} else if ( defined('DOING_CRON') && constant('DOING_CRON') ) {
|
201 |
+
//WordPress cron schedules are not exact, so lets do an update check even
|
202 |
+
//if slightly less than $checkPeriod hours have elapsed since the last check.
|
203 |
+
$cronFuzziness = 20 * 60;
|
204 |
+
$period = $this->checkPeriod * 3600 - $cronFuzziness;
|
205 |
+
} else {
|
206 |
+
$period = $this->checkPeriod * 3600;
|
207 |
+
}
|
208 |
+
|
209 |
+
return $period;
|
210 |
+
}
|
211 |
+
|
212 |
+
/**
|
213 |
+
* Add our custom schedule to the array of Cron schedules used by WP.
|
214 |
+
*
|
215 |
+
* @param array $schedules
|
216 |
+
* @return array
|
217 |
+
*/
|
218 |
+
public function _addCustomSchedule($schedules) {
|
219 |
+
if ( $this->checkPeriod && ($this->checkPeriod > 0) ){
|
220 |
+
$scheduleName = 'every' . $this->checkPeriod . 'hours';
|
221 |
+
$schedules[$scheduleName] = array(
|
222 |
+
'interval' => $this->checkPeriod * 3600,
|
223 |
+
'display' => sprintf('Every %d hours', $this->checkPeriod),
|
224 |
+
);
|
225 |
+
}
|
226 |
+
return $schedules;
|
227 |
+
}
|
228 |
+
|
229 |
+
/**
|
230 |
+
* Remove the scheduled cron event that the library uses to check for updates.
|
231 |
+
*
|
232 |
+
* @return void
|
233 |
+
*/
|
234 |
+
public function removeUpdaterCron() {
|
235 |
+
wp_clear_scheduled_hook($this->cronHook);
|
236 |
+
}
|
237 |
+
|
238 |
+
/**
|
239 |
+
* Get the name of the update checker's WP-cron hook. Mostly useful for debugging.
|
240 |
+
*
|
241 |
+
* @return string
|
242 |
+
*/
|
243 |
+
public function getCronHookName() {
|
244 |
+
return $this->cronHook;
|
245 |
+
}
|
246 |
+
|
247 |
+
/**
|
248 |
+
* Remove most hooks added by the scheduler.
|
249 |
+
*/
|
250 |
+
public function removeHooks() {
|
251 |
+
remove_filter('cron_schedules', array($this, '_addCustomSchedule'));
|
252 |
+
remove_action('admin_init', array($this, 'maybeCheckForUpdates'));
|
253 |
+
remove_action('load-update-core.php', array($this, 'maybeCheckForUpdates'));
|
254 |
+
|
255 |
+
if ( $this->cronHook !== null ) {
|
256 |
+
remove_action($this->cronHook, array($this, 'maybeCheckForUpdates'));
|
257 |
+
}
|
258 |
+
if ( !empty($this->hourlyCheckHooks) ) {
|
259 |
+
foreach ($this->hourlyCheckHooks as $hook) {
|
260 |
+
remove_action($hook, array($this, 'maybeCheckForUpdates'));
|
261 |
+
}
|
262 |
+
}
|
263 |
+
}
|
264 |
+
}
|
265 |
+
|
266 |
+
endif;
|
updater/Puc/v4p10/StateStore.php
ADDED
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_StateStore', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_StateStore {
|
6 |
+
/**
|
7 |
+
* @var int Last update check timestamp.
|
8 |
+
*/
|
9 |
+
protected $lastCheck = 0;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* @var string Version number.
|
13 |
+
*/
|
14 |
+
protected $checkedVersion = '';
|
15 |
+
|
16 |
+
/**
|
17 |
+
* @var Puc_v4p10_Update|null Cached update.
|
18 |
+
*/
|
19 |
+
protected $update = null;
|
20 |
+
|
21 |
+
/**
|
22 |
+
* @var string Site option name.
|
23 |
+
*/
|
24 |
+
private $optionName = '';
|
25 |
+
|
26 |
+
/**
|
27 |
+
* @var bool Whether we've already tried to load the state from the database.
|
28 |
+
*/
|
29 |
+
private $isLoaded = false;
|
30 |
+
|
31 |
+
public function __construct($optionName) {
|
32 |
+
$this->optionName = $optionName;
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Get time elapsed since the last update check.
|
37 |
+
*
|
38 |
+
* If there are no recorded update checks, this method returns a large arbitrary number
|
39 |
+
* (i.e. time since the Unix epoch).
|
40 |
+
*
|
41 |
+
* @return int Elapsed time in seconds.
|
42 |
+
*/
|
43 |
+
public function timeSinceLastCheck() {
|
44 |
+
$this->lazyLoad();
|
45 |
+
return time() - $this->lastCheck;
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* @return int
|
50 |
+
*/
|
51 |
+
public function getLastCheck() {
|
52 |
+
$this->lazyLoad();
|
53 |
+
return $this->lastCheck;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Set the time of the last update check to the current timestamp.
|
58 |
+
*
|
59 |
+
* @return $this
|
60 |
+
*/
|
61 |
+
public function setLastCheckToNow() {
|
62 |
+
$this->lazyLoad();
|
63 |
+
$this->lastCheck = time();
|
64 |
+
return $this;
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* @return null|Puc_v4p10_Update
|
69 |
+
*/
|
70 |
+
public function getUpdate() {
|
71 |
+
$this->lazyLoad();
|
72 |
+
return $this->update;
|
73 |
+
}
|
74 |
+
|
75 |
+
/**
|
76 |
+
* @param Puc_v4p10_Update|null $update
|
77 |
+
* @return $this
|
78 |
+
*/
|
79 |
+
public function setUpdate(Puc_v4p10_Update $update = null) {
|
80 |
+
$this->lazyLoad();
|
81 |
+
$this->update = $update;
|
82 |
+
return $this;
|
83 |
+
}
|
84 |
+
|
85 |
+
/**
|
86 |
+
* @return string
|
87 |
+
*/
|
88 |
+
public function getCheckedVersion() {
|
89 |
+
$this->lazyLoad();
|
90 |
+
return $this->checkedVersion;
|
91 |
+
}
|
92 |
+
|
93 |
+
/**
|
94 |
+
* @param string $version
|
95 |
+
* @return $this
|
96 |
+
*/
|
97 |
+
public function setCheckedVersion($version) {
|
98 |
+
$this->lazyLoad();
|
99 |
+
$this->checkedVersion = strval($version);
|
100 |
+
return $this;
|
101 |
+
}
|
102 |
+
|
103 |
+
/**
|
104 |
+
* Get translation updates.
|
105 |
+
*
|
106 |
+
* @return array
|
107 |
+
*/
|
108 |
+
public function getTranslations() {
|
109 |
+
$this->lazyLoad();
|
110 |
+
if ( isset($this->update, $this->update->translations) ) {
|
111 |
+
return $this->update->translations;
|
112 |
+
}
|
113 |
+
return array();
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
117 |
+
* Set translation updates.
|
118 |
+
*
|
119 |
+
* @param array $translationUpdates
|
120 |
+
*/
|
121 |
+
public function setTranslations($translationUpdates) {
|
122 |
+
$this->lazyLoad();
|
123 |
+
if ( isset($this->update) ) {
|
124 |
+
$this->update->translations = $translationUpdates;
|
125 |
+
$this->save();
|
126 |
+
}
|
127 |
+
}
|
128 |
+
|
129 |
+
public function save() {
|
130 |
+
$state = new stdClass();
|
131 |
+
|
132 |
+
$state->lastCheck = $this->lastCheck;
|
133 |
+
$state->checkedVersion = $this->checkedVersion;
|
134 |
+
|
135 |
+
if ( isset($this->update)) {
|
136 |
+
$state->update = $this->update->toStdClass();
|
137 |
+
|
138 |
+
$updateClass = get_class($this->update);
|
139 |
+
$state->updateClass = $updateClass;
|
140 |
+
$prefix = $this->getLibPrefix();
|
141 |
+
if ( Puc_v4p10_Utils::startsWith($updateClass, $prefix) ) {
|
142 |
+
$state->updateBaseClass = substr($updateClass, strlen($prefix));
|
143 |
+
}
|
144 |
+
}
|
145 |
+
|
146 |
+
update_site_option($this->optionName, $state);
|
147 |
+
$this->isLoaded = true;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* @return $this
|
152 |
+
*/
|
153 |
+
public function lazyLoad() {
|
154 |
+
if ( !$this->isLoaded ) {
|
155 |
+
$this->load();
|
156 |
+
}
|
157 |
+
return $this;
|
158 |
+
}
|
159 |
+
|
160 |
+
protected function load() {
|
161 |
+
$this->isLoaded = true;
|
162 |
+
|
163 |
+
$state = get_site_option($this->optionName, null);
|
164 |
+
|
165 |
+
if ( !is_object($state) ) {
|
166 |
+
$this->lastCheck = 0;
|
167 |
+
$this->checkedVersion = '';
|
168 |
+
$this->update = null;
|
169 |
+
return;
|
170 |
+
}
|
171 |
+
|
172 |
+
$this->lastCheck = intval(Puc_v4p10_Utils::get($state, 'lastCheck', 0));
|
173 |
+
$this->checkedVersion = Puc_v4p10_Utils::get($state, 'checkedVersion', '');
|
174 |
+
$this->update = null;
|
175 |
+
|
176 |
+
if ( isset($state->update) ) {
|
177 |
+
//This mess is due to the fact that the want the update class from this version
|
178 |
+
//of the library, not the version that saved the update.
|
179 |
+
|
180 |
+
$updateClass = null;
|
181 |
+
if ( isset($state->updateBaseClass) ) {
|
182 |
+
$updateClass = $this->getLibPrefix() . $state->updateBaseClass;
|
183 |
+
} else if ( isset($state->updateClass) && class_exists($state->updateClass) ) {
|
184 |
+
$updateClass = $state->updateClass;
|
185 |
+
}
|
186 |
+
|
187 |
+
if ( $updateClass !== null ) {
|
188 |
+
$this->update = call_user_func(array($updateClass, 'fromObject'), $state->update);
|
189 |
+
}
|
190 |
+
}
|
191 |
+
}
|
192 |
+
|
193 |
+
public function delete() {
|
194 |
+
delete_site_option($this->optionName);
|
195 |
+
|
196 |
+
$this->lastCheck = 0;
|
197 |
+
$this->checkedVersion = '';
|
198 |
+
$this->update = null;
|
199 |
+
}
|
200 |
+
|
201 |
+
private function getLibPrefix() {
|
202 |
+
$parts = explode('_', __CLASS__, 3);
|
203 |
+
return $parts[0] . '_' . $parts[1] . '_';
|
204 |
+
}
|
205 |
+
}
|
206 |
+
|
207 |
+
endif;
|
updater/Puc/v4p10/Theme/Package.php
ADDED
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Theme_Package', false) ):
|
3 |
+
|
4 |
+
class Puc_v4p10_Theme_Package extends Puc_v4p10_InstalledPackage {
|
5 |
+
/**
|
6 |
+
* @var string Theme directory name.
|
7 |
+
*/
|
8 |
+
protected $stylesheet;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* @var WP_Theme Theme object.
|
12 |
+
*/
|
13 |
+
protected $theme;
|
14 |
+
|
15 |
+
public function __construct($stylesheet, $updateChecker) {
|
16 |
+
$this->stylesheet = $stylesheet;
|
17 |
+
$this->theme = wp_get_theme($this->stylesheet);
|
18 |
+
|
19 |
+
parent::__construct($updateChecker);
|
20 |
+
}
|
21 |
+
|
22 |
+
public function getInstalledVersion() {
|
23 |
+
return $this->theme->get('Version');
|
24 |
+
}
|
25 |
+
|
26 |
+
public function getAbsoluteDirectoryPath() {
|
27 |
+
if ( method_exists($this->theme, 'get_stylesheet_directory') ) {
|
28 |
+
return $this->theme->get_stylesheet_directory(); //Available since WP 3.4.
|
29 |
+
}
|
30 |
+
return get_theme_root($this->stylesheet) . '/' . $this->stylesheet;
|
31 |
+
}
|
32 |
+
|
33 |
+
/**
|
34 |
+
* Get the value of a specific plugin or theme header.
|
35 |
+
*
|
36 |
+
* @param string $headerName
|
37 |
+
* @param string $defaultValue
|
38 |
+
* @return string Either the value of the header, or $defaultValue if the header doesn't exist or is empty.
|
39 |
+
*/
|
40 |
+
public function getHeaderValue($headerName, $defaultValue = '') {
|
41 |
+
$value = $this->theme->get($headerName);
|
42 |
+
if ( ($headerName === false) || ($headerName === '') ) {
|
43 |
+
return $defaultValue;
|
44 |
+
}
|
45 |
+
return $value;
|
46 |
+
}
|
47 |
+
|
48 |
+
protected function getHeaderNames() {
|
49 |
+
return array(
|
50 |
+
'Name' => 'Theme Name',
|
51 |
+
'ThemeURI' => 'Theme URI',
|
52 |
+
'Description' => 'Description',
|
53 |
+
'Author' => 'Author',
|
54 |
+
'AuthorURI' => 'Author URI',
|
55 |
+
'Version' => 'Version',
|
56 |
+
'Template' => 'Template',
|
57 |
+
'Status' => 'Status',
|
58 |
+
'Tags' => 'Tags',
|
59 |
+
'TextDomain' => 'Text Domain',
|
60 |
+
'DomainPath' => 'Domain Path',
|
61 |
+
);
|
62 |
+
}
|
63 |
+
}
|
64 |
+
|
65 |
+
endif;
|
updater/Puc/v4p10/Theme/Update.php
ADDED
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_Theme_Update', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_Theme_Update extends Puc_v4p10_Update {
|
6 |
+
public $details_url = '';
|
7 |
+
|
8 |
+
protected static $extraFields = array('details_url');
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Transform the metadata into the format used by WordPress core.
|
12 |
+
* Note the inconsistency: WP stores plugin updates as objects and theme updates as arrays.
|
13 |
+
*
|
14 |
+
* @return array
|
15 |
+
*/
|
16 |
+
public function toWpFormat() {
|
17 |
+
$update = array(
|
18 |
+
'theme' => $this->slug,
|
19 |
+
'new_version' => $this->version,
|
20 |
+
'url' => $this->details_url,
|
21 |
+
);
|
22 |
+
|
23 |
+
if ( !empty($this->download_url) ) {
|
24 |
+
$update['package'] = $this->download_url;
|
25 |
+
}
|
26 |
+
|
27 |
+
return $update;
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* Create a new instance of Theme_Update from its JSON-encoded representation.
|
32 |
+
*
|
33 |
+
* @param string $json Valid JSON string representing a theme information object.
|
34 |
+
* @return self New instance of ThemeUpdate, or NULL on error.
|
35 |
+
*/
|
36 |
+
public static function fromJson($json) {
|
37 |
+
$instance = new self();
|
38 |
+
if ( !parent::createFromJson($json, $instance) ) {
|
39 |
+
return null;
|
40 |
+
}
|
41 |
+
return $instance;
|
42 |
+
}
|
43 |
+
|
44 |
+
/**
|
45 |
+
* Create a new instance by copying the necessary fields from another object.
|
46 |
+
*
|
47 |
+
* @param StdClass|Puc_v4p10_Theme_Update $object The source object.
|
48 |
+
* @return Puc_v4p10_Theme_Update The new copy.
|
49 |
+
*/
|
50 |
+
public static function fromObject($object) {
|
51 |
+
$update = new self();
|
52 |
+
$update->copyFields($object, $update);
|
53 |
+
return $update;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Basic validation.
|
58 |
+
*
|
59 |
+
* @param StdClass $apiResponse
|
60 |
+
* @return bool|WP_Error
|
61 |
+
*/
|
62 |
+
protected function validateMetadata($apiResponse) {
|
63 |
+
$required = array('version', 'details_url');
|
64 |
+
foreach($required as $key) {
|
65 |
+
if ( !isset($apiResponse->$key) || empty($apiResponse->$key) ) {
|
66 |
+
return new WP_Error(
|
67 |
+
'tuc-invalid-metadata',
|
68 |
+
sprintf('The theme metadata is missing the required "%s" key.', $key)
|
69 |
+
);
|
70 |
+
}
|
71 |
+
}
|
72 |
+
return true;
|
73 |
+
}
|
74 |
+
|
75 |
+
protected function getFieldNames() {
|
76 |
+
return array_merge(parent::getFieldNames(), self::$extraFields);
|
77 |
+
}
|
78 |
+
|
79 |
+
protected function getPrefixedFilter($tag) {
|
80 |
+
return parent::getPrefixedFilter($tag) . '_theme';
|
81 |
+
}
|
82 |
+
}
|
83 |
+
|
84 |
+
endif;
|
updater/Puc/v4p10/Theme/UpdateChecker.php
ADDED
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_Theme_UpdateChecker', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_Theme_UpdateChecker extends Puc_v4p10_UpdateChecker {
|
6 |
+
protected $filterSuffix = 'theme';
|
7 |
+
protected $updateTransient = 'update_themes';
|
8 |
+
protected $translationType = 'theme';
|
9 |
+
|
10 |
+
/**
|
11 |
+
* @var string Theme directory name.
|
12 |
+
*/
|
13 |
+
protected $stylesheet;
|
14 |
+
|
15 |
+
public function __construct($metadataUrl, $stylesheet = null, $customSlug = null, $checkPeriod = 12, $optionName = '') {
|
16 |
+
if ( $stylesheet === null ) {
|
17 |
+
$stylesheet = get_stylesheet();
|
18 |
+
}
|
19 |
+
$this->stylesheet = $stylesheet;
|
20 |
+
|
21 |
+
parent::__construct(
|
22 |
+
$metadataUrl,
|
23 |
+
$stylesheet,
|
24 |
+
$customSlug ? $customSlug : $stylesheet,
|
25 |
+
$checkPeriod,
|
26 |
+
$optionName
|
27 |
+
);
|
28 |
+
}
|
29 |
+
|
30 |
+
/**
|
31 |
+
* For themes, the update array is indexed by theme directory name.
|
32 |
+
*
|
33 |
+
* @return string
|
34 |
+
*/
|
35 |
+
protected function getUpdateListKey() {
|
36 |
+
return $this->directoryName;
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* Retrieve the latest update (if any) from the configured API endpoint.
|
41 |
+
*
|
42 |
+
* @return Puc_v4p10_Update|null An instance of Update, or NULL when no updates are available.
|
43 |
+
*/
|
44 |
+
public function requestUpdate() {
|
45 |
+
list($themeUpdate, $result) = $this->requestMetadata('Puc_v4p10_Theme_Update', 'request_update');
|
46 |
+
|
47 |
+
if ( $themeUpdate !== null ) {
|
48 |
+
/** @var Puc_v4p10_Theme_Update $themeUpdate */
|
49 |
+
$themeUpdate->slug = $this->slug;
|
50 |
+
}
|
51 |
+
|
52 |
+
$themeUpdate = $this->filterUpdateResult($themeUpdate, $result);
|
53 |
+
return $themeUpdate;
|
54 |
+
}
|
55 |
+
|
56 |
+
protected function getNoUpdateItemFields() {
|
57 |
+
return array_merge(
|
58 |
+
parent::getNoUpdateItemFields(),
|
59 |
+
array(
|
60 |
+
'theme' => $this->directoryName,
|
61 |
+
'requires' => '',
|
62 |
+
)
|
63 |
+
);
|
64 |
+
}
|
65 |
+
|
66 |
+
public function userCanInstallUpdates() {
|
67 |
+
return current_user_can('update_themes');
|
68 |
+
}
|
69 |
+
|
70 |
+
/**
|
71 |
+
* Create an instance of the scheduler.
|
72 |
+
*
|
73 |
+
* @param int $checkPeriod
|
74 |
+
* @return Puc_v4p10_Scheduler
|
75 |
+
*/
|
76 |
+
protected function createScheduler($checkPeriod) {
|
77 |
+
return new Puc_v4p10_Scheduler($this, $checkPeriod, array('load-themes.php'));
|
78 |
+
}
|
79 |
+
|
80 |
+
/**
|
81 |
+
* Is there an update being installed right now for this theme?
|
82 |
+
*
|
83 |
+
* @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
|
84 |
+
* @return bool
|
85 |
+
*/
|
86 |
+
public function isBeingUpgraded($upgrader = null) {
|
87 |
+
return $this->upgraderStatus->isThemeBeingUpgraded($this->stylesheet, $upgrader);
|
88 |
+
}
|
89 |
+
|
90 |
+
protected function createDebugBarExtension() {
|
91 |
+
return new Puc_v4p10_DebugBar_Extension($this, 'Puc_v4p10_DebugBar_ThemePanel');
|
92 |
+
}
|
93 |
+
|
94 |
+
/**
|
95 |
+
* Register a callback for filtering query arguments.
|
96 |
+
*
|
97 |
+
* The callback function should take one argument - an associative array of query arguments.
|
98 |
+
* It should return a modified array of query arguments.
|
99 |
+
*
|
100 |
+
* @param callable $callback
|
101 |
+
* @return void
|
102 |
+
*/
|
103 |
+
public function addQueryArgFilter($callback){
|
104 |
+
$this->addFilter('request_update_query_args', $callback);
|
105 |
+
}
|
106 |
+
|
107 |
+
/**
|
108 |
+
* Register a callback for filtering arguments passed to wp_remote_get().
|
109 |
+
*
|
110 |
+
* The callback function should take one argument - an associative array of arguments -
|
111 |
+
* and return a modified array or arguments. See the WP documentation on wp_remote_get()
|
112 |
+
* for details on what arguments are available and how they work.
|
113 |
+
*
|
114 |
+
* @uses add_filter() This method is a convenience wrapper for add_filter().
|
115 |
+
*
|
116 |
+
* @param callable $callback
|
117 |
+
* @return void
|
118 |
+
*/
|
119 |
+
public function addHttpRequestArgFilter($callback) {
|
120 |
+
$this->addFilter('request_update_options', $callback);
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Register a callback for filtering theme updates retrieved from the external API.
|
125 |
+
*
|
126 |
+
* The callback function should take two arguments. If the theme update was retrieved
|
127 |
+
* successfully, the first argument passed will be an instance of Theme_Update. Otherwise,
|
128 |
+
* it will be NULL. The second argument will be the corresponding return value of
|
129 |
+
* wp_remote_get (see WP docs for details).
|
130 |
+
*
|
131 |
+
* The callback function should return a new or modified instance of Theme_Update or NULL.
|
132 |
+
*
|
133 |
+
* @uses add_filter() This method is a convenience wrapper for add_filter().
|
134 |
+
*
|
135 |
+
* @param callable $callback
|
136 |
+
* @return void
|
137 |
+
*/
|
138 |
+
public function addResultFilter($callback) {
|
139 |
+
$this->addFilter('request_update_result', $callback, 10, 2);
|
140 |
+
}
|
141 |
+
|
142 |
+
/**
|
143 |
+
* Create a package instance that represents this plugin or theme.
|
144 |
+
*
|
145 |
+
* @return Puc_v4p10_InstalledPackage
|
146 |
+
*/
|
147 |
+
protected function createInstalledPackage() {
|
148 |
+
return new Puc_v4p10_Theme_Package($this->stylesheet, $this);
|
149 |
+
}
|
150 |
+
}
|
151 |
+
|
152 |
+
endif;
|
updater/Puc/v4p10/Update.php
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Update', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* A simple container class for holding information about an available update.
|
6 |
+
*
|
7 |
+
* @author Janis Elsts
|
8 |
+
* @access public
|
9 |
+
*/
|
10 |
+
abstract class Puc_v4p10_Update extends Puc_v4p10_Metadata {
|
11 |
+
public $slug;
|
12 |
+
public $version;
|
13 |
+
public $download_url;
|
14 |
+
public $translations = array();
|
15 |
+
|
16 |
+
/**
|
17 |
+
* @return string[]
|
18 |
+
*/
|
19 |
+
protected function getFieldNames() {
|
20 |
+
return array('slug', 'version', 'download_url', 'translations');
|
21 |
+
}
|
22 |
+
|
23 |
+
public function toWpFormat() {
|
24 |
+
$update = new stdClass();
|
25 |
+
|
26 |
+
$update->slug = $this->slug;
|
27 |
+
$update->new_version = $this->version;
|
28 |
+
$update->package = $this->download_url;
|
29 |
+
|
30 |
+
return $update;
|
31 |
+
}
|
32 |
+
}
|
33 |
+
|
34 |
+
endif;
|
updater/Puc/v4p10/UpdateChecker.php
ADDED
@@ -0,0 +1,994 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_UpdateChecker', false) ):
|
4 |
+
|
5 |
+
abstract class Puc_v4p10_UpdateChecker {
|
6 |
+
protected $filterSuffix = '';
|
7 |
+
protected $updateTransient = '';
|
8 |
+
protected $translationType = ''; //"plugin" or "theme".
|
9 |
+
|
10 |
+
/**
|
11 |
+
* Set to TRUE to enable error reporting. Errors are raised using trigger_error()
|
12 |
+
* and should be logged to the standard PHP error log.
|
13 |
+
* @var bool
|
14 |
+
*/
|
15 |
+
public $debugMode = null;
|
16 |
+
|
17 |
+
/**
|
18 |
+
* @var string Where to store the update info.
|
19 |
+
*/
|
20 |
+
public $optionName = '';
|
21 |
+
|
22 |
+
/**
|
23 |
+
* @var string The URL of the metadata file.
|
24 |
+
*/
|
25 |
+
public $metadataUrl = '';
|
26 |
+
|
27 |
+
/**
|
28 |
+
* @var string Plugin or theme directory name.
|
29 |
+
*/
|
30 |
+
public $directoryName = '';
|
31 |
+
|
32 |
+
/**
|
33 |
+
* @var string The slug that will be used in update checker hooks and remote API requests.
|
34 |
+
* Usually matches the directory name unless the plugin/theme directory has been renamed.
|
35 |
+
*/
|
36 |
+
public $slug = '';
|
37 |
+
|
38 |
+
/**
|
39 |
+
* @var Puc_v4p10_InstalledPackage
|
40 |
+
*/
|
41 |
+
protected $package;
|
42 |
+
|
43 |
+
/**
|
44 |
+
* @var Puc_v4p10_Scheduler
|
45 |
+
*/
|
46 |
+
public $scheduler;
|
47 |
+
|
48 |
+
/**
|
49 |
+
* @var Puc_v4p10_UpgraderStatus
|
50 |
+
*/
|
51 |
+
protected $upgraderStatus;
|
52 |
+
|
53 |
+
/**
|
54 |
+
* @var Puc_v4p10_StateStore
|
55 |
+
*/
|
56 |
+
protected $updateState;
|
57 |
+
|
58 |
+
/**
|
59 |
+
* @var array List of API errors triggered during the last checkForUpdates() call.
|
60 |
+
*/
|
61 |
+
protected $lastRequestApiErrors = array();
|
62 |
+
|
63 |
+
/**
|
64 |
+
* @var string|mixed The default is 0 because parse_url() can return NULL or FALSE.
|
65 |
+
*/
|
66 |
+
protected $cachedMetadataHost = 0;
|
67 |
+
|
68 |
+
/**
|
69 |
+
* @var Puc_v4p10_DebugBar_Extension|null
|
70 |
+
*/
|
71 |
+
protected $debugBarExtension = null;
|
72 |
+
|
73 |
+
public function __construct($metadataUrl, $directoryName, $slug = null, $checkPeriod = 12, $optionName = '') {
|
74 |
+
$this->debugMode = (bool)(constant('WP_DEBUG'));
|
75 |
+
$this->metadataUrl = $metadataUrl;
|
76 |
+
$this->directoryName = $directoryName;
|
77 |
+
$this->slug = !empty($slug) ? $slug : $this->directoryName;
|
78 |
+
|
79 |
+
$this->optionName = $optionName;
|
80 |
+
if ( empty($this->optionName) ) {
|
81 |
+
//BC: Initially the library only supported plugin updates and didn't use type prefixes
|
82 |
+
//in the option name. Lets use the same prefix-less name when possible.
|
83 |
+
if ( $this->filterSuffix === '' ) {
|
84 |
+
$this->optionName = 'external_updates-' . $this->slug;
|
85 |
+
} else {
|
86 |
+
$this->optionName = $this->getUniqueName('external_updates');
|
87 |
+
}
|
88 |
+
}
|
89 |
+
|
90 |
+
$this->package = $this->createInstalledPackage();
|
91 |
+
$this->scheduler = $this->createScheduler($checkPeriod);
|
92 |
+
$this->upgraderStatus = new Puc_v4p10_UpgraderStatus();
|
93 |
+
$this->updateState = new Puc_v4p10_StateStore($this->optionName);
|
94 |
+
|
95 |
+
if ( did_action('init') ) {
|
96 |
+
$this->loadTextDomain();
|
97 |
+
} else {
|
98 |
+
add_action('init', array($this, 'loadTextDomain'));
|
99 |
+
}
|
100 |
+
|
101 |
+
$this->installHooks();
|
102 |
+
}
|
103 |
+
|
104 |
+
/**
|
105 |
+
* @internal
|
106 |
+
*/
|
107 |
+
public function loadTextDomain() {
|
108 |
+
//We're not using load_plugin_textdomain() or its siblings because figuring out where
|
109 |
+
//the library is located (plugin, mu-plugin, theme, custom wp-content paths) is messy.
|
110 |
+
$domain = 'plugin-update-checker';
|
111 |
+
$locale = apply_filters(
|
112 |
+
'plugin_locale',
|
113 |
+
(is_admin() && function_exists('get_user_locale')) ? get_user_locale() : get_locale(),
|
114 |
+
$domain
|
115 |
+
);
|
116 |
+
|
117 |
+
$moFile = $domain . '-' . $locale . '.mo';
|
118 |
+
$path = realpath(dirname(__FILE__) . '/../../languages');
|
119 |
+
|
120 |
+
if ($path && file_exists($path)) {
|
121 |
+
load_textdomain($domain, $path . '/' . $moFile);
|
122 |
+
}
|
123 |
+
}
|
124 |
+
|
125 |
+
protected function installHooks() {
|
126 |
+
//Insert our update info into the update array maintained by WP.
|
127 |
+
add_filter('site_transient_' . $this->updateTransient, array($this,'injectUpdate'));
|
128 |
+
|
129 |
+
//Insert translation updates into the update list.
|
130 |
+
add_filter('site_transient_' . $this->updateTransient, array($this, 'injectTranslationUpdates'));
|
131 |
+
|
132 |
+
//Clear translation updates when WP clears the update cache.
|
133 |
+
//This needs to be done directly because the library doesn't actually remove obsolete plugin updates,
|
134 |
+
//it just hides them (see getUpdate()). We can't do that with translations - too much disk I/O.
|
135 |
+
add_action(
|
136 |
+
'delete_site_transient_' . $this->updateTransient,
|
137 |
+
array($this, 'clearCachedTranslationUpdates')
|
138 |
+
);
|
139 |
+
|
140 |
+
//Rename the update directory to be the same as the existing directory.
|
141 |
+
if ( $this->directoryName !== '.' ) {
|
142 |
+
add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3);
|
143 |
+
}
|
144 |
+
|
145 |
+
//Allow HTTP requests to the metadata URL even if it's on a local host.
|
146 |
+
add_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10, 2);
|
147 |
+
|
148 |
+
//DebugBar integration.
|
149 |
+
if ( did_action('plugins_loaded') ) {
|
150 |
+
$this->maybeInitDebugBar();
|
151 |
+
} else {
|
152 |
+
add_action('plugins_loaded', array($this, 'maybeInitDebugBar'));
|
153 |
+
}
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Remove hooks that were added by this update checker instance.
|
158 |
+
*/
|
159 |
+
public function removeHooks() {
|
160 |
+
remove_filter('site_transient_' . $this->updateTransient, array($this,'injectUpdate'));
|
161 |
+
remove_filter('site_transient_' . $this->updateTransient, array($this, 'injectTranslationUpdates'));
|
162 |
+
remove_action(
|
163 |
+
'delete_site_transient_' . $this->updateTransient,
|
164 |
+
array($this, 'clearCachedTranslationUpdates')
|
165 |
+
);
|
166 |
+
|
167 |
+
remove_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10);
|
168 |
+
remove_filter('http_request_host_is_external', array($this, 'allowMetadataHost'), 10);
|
169 |
+
remove_action('plugins_loaded', array($this, 'maybeInitDebugBar'));
|
170 |
+
|
171 |
+
remove_action('init', array($this, 'loadTextDomain'));
|
172 |
+
|
173 |
+
if ( $this->scheduler ) {
|
174 |
+
$this->scheduler->removeHooks();
|
175 |
+
}
|
176 |
+
|
177 |
+
if ( $this->debugBarExtension ) {
|
178 |
+
$this->debugBarExtension->removeHooks();
|
179 |
+
}
|
180 |
+
}
|
181 |
+
|
182 |
+
/**
|
183 |
+
* Check if the current user has the required permissions to install updates.
|
184 |
+
*
|
185 |
+
* @return bool
|
186 |
+
*/
|
187 |
+
abstract public function userCanInstallUpdates();
|
188 |
+
|
189 |
+
/**
|
190 |
+
* Explicitly allow HTTP requests to the metadata URL.
|
191 |
+
*
|
192 |
+
* WordPress has a security feature where the HTTP API will reject all requests that are sent to
|
193 |
+
* another site hosted on the same server as the current site (IP match), a local host, or a local
|
194 |
+
* IP, unless the host exactly matches the current site.
|
195 |
+
*
|
196 |
+
* This feature is opt-in (at least in WP 4.4). Apparently some people enable it.
|
197 |
+
*
|
198 |
+
* That can be a problem when you're developing your plugin and you decide to host the update information
|
199 |
+
* on the same server as your test site. Update requests will mysteriously fail.
|
200 |
+
*
|
201 |
+
* We fix that by adding an exception for the metadata host.
|
202 |
+
*
|
203 |
+
* @param bool $allow
|
204 |
+
* @param string $host
|
205 |
+
* @return bool
|
206 |
+
*/
|
207 |
+
public function allowMetadataHost($allow, $host) {
|
208 |
+
if ( $this->cachedMetadataHost === 0 ) {
|
209 |
+
$this->cachedMetadataHost = parse_url($this->metadataUrl, PHP_URL_HOST);
|
210 |
+
}
|
211 |
+
|
212 |
+
if ( is_string($this->cachedMetadataHost) && (strtolower($host) === strtolower($this->cachedMetadataHost)) ) {
|
213 |
+
return true;
|
214 |
+
}
|
215 |
+
return $allow;
|
216 |
+
}
|
217 |
+
|
218 |
+
/**
|
219 |
+
* Create a package instance that represents this plugin or theme.
|
220 |
+
*
|
221 |
+
* @return Puc_v4p10_InstalledPackage
|
222 |
+
*/
|
223 |
+
abstract protected function createInstalledPackage();
|
224 |
+
|
225 |
+
/**
|
226 |
+
* @return Puc_v4p10_InstalledPackage
|
227 |
+
*/
|
228 |
+
public function getInstalledPackage() {
|
229 |
+
return $this->package;
|
230 |
+
}
|
231 |
+
|
232 |
+
/**
|
233 |
+
* Create an instance of the scheduler.
|
234 |
+
*
|
235 |
+
* This is implemented as a method to make it possible for plugins to subclass the update checker
|
236 |
+
* and substitute their own scheduler.
|
237 |
+
*
|
238 |
+
* @param int $checkPeriod
|
239 |
+
* @return Puc_v4p10_Scheduler
|
240 |
+
*/
|
241 |
+
abstract protected function createScheduler($checkPeriod);
|
242 |
+
|
243 |
+
/**
|
244 |
+
* Check for updates. The results are stored in the DB option specified in $optionName.
|
245 |
+
*
|
246 |
+
* @return Puc_v4p10_Update|null
|
247 |
+
*/
|
248 |
+
public function checkForUpdates() {
|
249 |
+
$installedVersion = $this->getInstalledVersion();
|
250 |
+
//Fail silently if we can't find the plugin/theme or read its header.
|
251 |
+
if ( $installedVersion === null ) {
|
252 |
+
$this->triggerError(
|
253 |
+
sprintf('Skipping update check for %s - installed version unknown.', $this->slug),
|
254 |
+
E_USER_WARNING
|
255 |
+
);
|
256 |
+
return null;
|
257 |
+
}
|
258 |
+
|
259 |
+
//Start collecting API errors.
|
260 |
+
$this->lastRequestApiErrors = array();
|
261 |
+
add_action('puc_api_error', array($this, 'collectApiErrors'), 10, 4);
|
262 |
+
|
263 |
+
$state = $this->updateState;
|
264 |
+
$state->setLastCheckToNow()
|
265 |
+
->setCheckedVersion($installedVersion)
|
266 |
+
->save(); //Save before checking in case something goes wrong
|
267 |
+
|
268 |
+
$state->setUpdate($this->requestUpdate());
|
269 |
+
$state->save();
|
270 |
+
|
271 |
+
//Stop collecting API errors.
|
272 |
+
remove_action('puc_api_error', array($this, 'collectApiErrors'), 10);
|
273 |
+
|
274 |
+
return $this->getUpdate();
|
275 |
+
}
|
276 |
+
|
277 |
+
/**
|
278 |
+
* Load the update checker state from the DB.
|
279 |
+
*
|
280 |
+
* @return Puc_v4p10_StateStore
|
281 |
+
*/
|
282 |
+
public function getUpdateState() {
|
283 |
+
return $this->updateState->lazyLoad();
|
284 |
+
}
|
285 |
+
|
286 |
+
/**
|
287 |
+
* Reset update checker state - i.e. last check time, cached update data and so on.
|
288 |
+
*
|
289 |
+
* Call this when your plugin is being uninstalled, or if you want to
|
290 |
+
* clear the update cache.
|
291 |
+
*/
|
292 |
+
public function resetUpdateState() {
|
293 |
+
$this->updateState->delete();
|
294 |
+
}
|
295 |
+
|
296 |
+
/**
|
297 |
+
* Get the details of the currently available update, if any.
|
298 |
+
*
|
299 |
+
* If no updates are available, or if the last known update version is below or equal
|
300 |
+
* to the currently installed version, this method will return NULL.
|
301 |
+
*
|
302 |
+
* Uses cached update data. To retrieve update information straight from
|
303 |
+
* the metadata URL, call requestUpdate() instead.
|
304 |
+
*
|
305 |
+
* @return Puc_v4p10_Update|null
|
306 |
+
*/
|
307 |
+
public function getUpdate() {
|
308 |
+
$update = $this->updateState->getUpdate();
|
309 |
+
|
310 |
+
//Is there an update available?
|
311 |
+
if ( isset($update) ) {
|
312 |
+
//Check if the update is actually newer than the currently installed version.
|
313 |
+
$installedVersion = $this->getInstalledVersion();
|
314 |
+
if ( ($installedVersion !== null) && version_compare($update->version, $installedVersion, '>') ){
|
315 |
+
return $update;
|
316 |
+
}
|
317 |
+
}
|
318 |
+
return null;
|
319 |
+
}
|
320 |
+
|
321 |
+
/**
|
322 |
+
* Retrieve the latest update (if any) from the configured API endpoint.
|
323 |
+
*
|
324 |
+
* Subclasses should run the update through filterUpdateResult before returning it.
|
325 |
+
*
|
326 |
+
* @return Puc_v4p10_Update An instance of Update, or NULL when no updates are available.
|
327 |
+
*/
|
328 |
+
abstract public function requestUpdate();
|
329 |
+
|
330 |
+
/**
|
331 |
+
* Filter the result of a requestUpdate() call.
|
332 |
+
*
|
333 |
+
* @param Puc_v4p10_Update|null $update
|
334 |
+
* @param array|WP_Error|null $httpResult The value returned by wp_remote_get(), if any.
|
335 |
+
* @return Puc_v4p10_Update
|
336 |
+
*/
|
337 |
+
protected function filterUpdateResult($update, $httpResult = null) {
|
338 |
+
//Let plugins/themes modify the update.
|
339 |
+
$update = apply_filters($this->getUniqueName('request_update_result'), $update, $httpResult);
|
340 |
+
|
341 |
+
$this->fixSupportedWordpressVersion($update);
|
342 |
+
|
343 |
+
if ( isset($update, $update->translations) ) {
|
344 |
+
//Keep only those translation updates that apply to this site.
|
345 |
+
$update->translations = $this->filterApplicableTranslations($update->translations);
|
346 |
+
}
|
347 |
+
|
348 |
+
return $update;
|
349 |
+
}
|
350 |
+
|
351 |
+
/**
|
352 |
+
* The "Tested up to" field in the plugin metadata is supposed to be in the form of "major.minor",
|
353 |
+
* while WordPress core's list_plugin_updates() expects the $update->tested field to be an exact
|
354 |
+
* version, e.g. "major.minor.patch", to say it's compatible. In other case it shows
|
355 |
+
* "Compatibility: Unknown".
|
356 |
+
* The function mimics how wordpress.org API crafts the "tested" field out of "Tested up to".
|
357 |
+
*
|
358 |
+
* @param Puc_v4p10_Metadata|null $update
|
359 |
+
*/
|
360 |
+
protected function fixSupportedWordpressVersion(Puc_v4p10_Metadata $update = null) {
|
361 |
+
if ( !isset($update->tested) || !preg_match('/^\d++\.\d++$/', $update->tested) ) {
|
362 |
+
return;
|
363 |
+
}
|
364 |
+
|
365 |
+
$actualWpVersions = array();
|
366 |
+
|
367 |
+
$wpVersion = $GLOBALS['wp_version'];
|
368 |
+
|
369 |
+
if ( function_exists('get_core_updates') ) {
|
370 |
+
$coreUpdates = get_core_updates();
|
371 |
+
if ( is_array($coreUpdates) ) {
|
372 |
+
foreach ($coreUpdates as $coreUpdate) {
|
373 |
+
if ( isset($coreUpdate->current) ) {
|
374 |
+
$actualWpVersions[] = $coreUpdate->current;
|
375 |
+
}
|
376 |
+
}
|
377 |
+
}
|
378 |
+
}
|
379 |
+
|
380 |
+
$actualWpVersions[] = $wpVersion;
|
381 |
+
|
382 |
+
$actualWpPatchNumber = null;
|
383 |
+
foreach ($actualWpVersions as $version) {
|
384 |
+
if ( preg_match('/^(?P<majorMinor>\d++\.\d++)(?:\.(?P<patch>\d++))?/', $version, $versionParts) ) {
|
385 |
+
if ( $versionParts['majorMinor'] === $update->tested ) {
|
386 |
+
$patch = isset($versionParts['patch']) ? intval($versionParts['patch']) : 0;
|
387 |
+
if ( $actualWpPatchNumber === null ) {
|
388 |
+
$actualWpPatchNumber = $patch;
|
389 |
+
} else {
|
390 |
+
$actualWpPatchNumber = max($actualWpPatchNumber, $patch);
|
391 |
+
}
|
392 |
+
}
|
393 |
+
}
|
394 |
+
}
|
395 |
+
if ( $actualWpPatchNumber === null ) {
|
396 |
+
$actualWpPatchNumber = 999;
|
397 |
+
}
|
398 |
+
|
399 |
+
if ( $actualWpPatchNumber > 0 ) {
|
400 |
+
$update->tested .= '.' . $actualWpPatchNumber;
|
401 |
+
}
|
402 |
+
}
|
403 |
+
|
404 |
+
/**
|
405 |
+
* Get the currently installed version of the plugin or theme.
|
406 |
+
*
|
407 |
+
* @return string|null Version number.
|
408 |
+
*/
|
409 |
+
public function getInstalledVersion() {
|
410 |
+
return $this->package->getInstalledVersion();
|
411 |
+
}
|
412 |
+
|
413 |
+
/**
|
414 |
+
* Get the full path of the plugin or theme directory.
|
415 |
+
*
|
416 |
+
* @return string
|
417 |
+
*/
|
418 |
+
public function getAbsoluteDirectoryPath() {
|
419 |
+
return $this->package->getAbsoluteDirectoryPath();
|
420 |
+
}
|
421 |
+
|
422 |
+
/**
|
423 |
+
* Trigger a PHP error, but only when $debugMode is enabled.
|
424 |
+
*
|
425 |
+
* @param string $message
|
426 |
+
* @param int $errorType
|
427 |
+
*/
|
428 |
+
public function triggerError($message, $errorType) {
|
429 |
+
if ( $this->isDebugModeEnabled() ) {
|
430 |
+
trigger_error($message, $errorType);
|
431 |
+
}
|
432 |
+
}
|
433 |
+
|
434 |
+
/**
|
435 |
+
* @return bool
|
436 |
+
*/
|
437 |
+
protected function isDebugModeEnabled() {
|
438 |
+
if ( $this->debugMode === null ) {
|
439 |
+
$this->debugMode = (bool)(constant('WP_DEBUG'));
|
440 |
+
}
|
441 |
+
return $this->debugMode;
|
442 |
+
}
|
443 |
+
|
444 |
+
/**
|
445 |
+
* Get the full name of an update checker filter, action or DB entry.
|
446 |
+
*
|
447 |
+
* This method adds the "puc_" prefix and the "-$slug" suffix to the filter name.
|
448 |
+
* For example, "pre_inject_update" becomes "puc_pre_inject_update-plugin-slug".
|
449 |
+
*
|
450 |
+
* @param string $baseTag
|
451 |
+
* @return string
|
452 |
+
*/
|
453 |
+
public function getUniqueName($baseTag) {
|
454 |
+
$name = 'puc_' . $baseTag;
|
455 |
+
if ( $this->filterSuffix !== '' ) {
|
456 |
+
$name .= '_' . $this->filterSuffix;
|
457 |
+
}
|
458 |
+
return $name . '-' . $this->slug;
|
459 |
+
}
|
460 |
+
|
461 |
+
/**
|
462 |
+
* Store API errors that are generated when checking for updates.
|
463 |
+
*
|
464 |
+
* @internal
|
465 |
+
* @param WP_Error $error
|
466 |
+
* @param array|null $httpResponse
|
467 |
+
* @param string|null $url
|
468 |
+
* @param string|null $slug
|
469 |
+
*/
|
470 |
+
public function collectApiErrors($error, $httpResponse = null, $url = null, $slug = null) {
|
471 |
+
if ( isset($slug) && ($slug !== $this->slug) ) {
|
472 |
+
return;
|
473 |
+
}
|
474 |
+
|
475 |
+
$this->lastRequestApiErrors[] = array(
|
476 |
+
'error' => $error,
|
477 |
+
'httpResponse' => $httpResponse,
|
478 |
+
'url' => $url,
|
479 |
+
);
|
480 |
+
}
|
481 |
+
|
482 |
+
/**
|
483 |
+
* @return array
|
484 |
+
*/
|
485 |
+
public function getLastRequestApiErrors() {
|
486 |
+
return $this->lastRequestApiErrors;
|
487 |
+
}
|
488 |
+
|
489 |
+
/* -------------------------------------------------------------------
|
490 |
+
* PUC filters and filter utilities
|
491 |
+
* -------------------------------------------------------------------
|
492 |
+
*/
|
493 |
+
|
494 |
+
/**
|
495 |
+
* Register a callback for one of the update checker filters.
|
496 |
+
*
|
497 |
+
* Identical to add_filter(), except it automatically adds the "puc_" prefix
|
498 |
+
* and the "-$slug" suffix to the filter name. For example, "request_info_result"
|
499 |
+
* becomes "puc_request_info_result-your_plugin_slug".
|
500 |
+
*
|
501 |
+
* @param string $tag
|
502 |
+
* @param callable $callback
|
503 |
+
* @param int $priority
|
504 |
+
* @param int $acceptedArgs
|
505 |
+
*/
|
506 |
+
public function addFilter($tag, $callback, $priority = 10, $acceptedArgs = 1) {
|
507 |
+
add_filter($this->getUniqueName($tag), $callback, $priority, $acceptedArgs);
|
508 |
+
}
|
509 |
+
|
510 |
+
/* -------------------------------------------------------------------
|
511 |
+
* Inject updates
|
512 |
+
* -------------------------------------------------------------------
|
513 |
+
*/
|
514 |
+
|
515 |
+
/**
|
516 |
+
* Insert the latest update (if any) into the update list maintained by WP.
|
517 |
+
*
|
518 |
+
* @param stdClass $updates Update list.
|
519 |
+
* @return stdClass Modified update list.
|
520 |
+
*/
|
521 |
+
public function injectUpdate($updates) {
|
522 |
+
//Is there an update to insert?
|
523 |
+
$update = $this->getUpdate();
|
524 |
+
|
525 |
+
if ( !$this->shouldShowUpdates() ) {
|
526 |
+
$update = null;
|
527 |
+
}
|
528 |
+
|
529 |
+
if ( !empty($update) ) {
|
530 |
+
//Let plugins filter the update info before it's passed on to WordPress.
|
531 |
+
$update = apply_filters($this->getUniqueName('pre_inject_update'), $update);
|
532 |
+
$updates = $this->addUpdateToList($updates, $update->toWpFormat());
|
533 |
+
} else {
|
534 |
+
//Clean up any stale update info.
|
535 |
+
$updates = $this->removeUpdateFromList($updates);
|
536 |
+
//Add a placeholder item to the "no_update" list to enable auto-update support.
|
537 |
+
//If we don't do this, the option to enable automatic updates will only show up
|
538 |
+
//when an update is available.
|
539 |
+
$updates = $this->addNoUpdateItem($updates);
|
540 |
+
}
|
541 |
+
|
542 |
+
return $updates;
|
543 |
+
}
|
544 |
+
|
545 |
+
/**
|
546 |
+
* @param stdClass|null $updates
|
547 |
+
* @param stdClass|array $updateToAdd
|
548 |
+
* @return stdClass
|
549 |
+
*/
|
550 |
+
protected function addUpdateToList($updates, $updateToAdd) {
|
551 |
+
if ( !is_object($updates) ) {
|
552 |
+
$updates = new stdClass();
|
553 |
+
$updates->response = array();
|
554 |
+
}
|
555 |
+
|
556 |
+
$updates->response[$this->getUpdateListKey()] = $updateToAdd;
|
557 |
+
return $updates;
|
558 |
+
}
|
559 |
+
|
560 |
+
/**
|
561 |
+
* @param stdClass|null $updates
|
562 |
+
* @return stdClass|null
|
563 |
+
*/
|
564 |
+
protected function removeUpdateFromList($updates) {
|
565 |
+
if ( isset($updates, $updates->response) ) {
|
566 |
+
unset($updates->response[$this->getUpdateListKey()]);
|
567 |
+
}
|
568 |
+
return $updates;
|
569 |
+
}
|
570 |
+
|
571 |
+
/**
|
572 |
+
* See this post for more information:
|
573 |
+
* @link https://make.wordpress.org/core/2020/07/30/recommended-usage-of-the-updates-api-to-support-the-auto-updates-ui-for-plugins-and-themes-in-wordpress-5-5/
|
574 |
+
*
|
575 |
+
* @param stdClass|null $updates
|
576 |
+
* @return stdClass
|
577 |
+
*/
|
578 |
+
protected function addNoUpdateItem($updates) {
|
579 |
+
if ( !is_object($updates) ) {
|
580 |
+
$updates = new stdClass();
|
581 |
+
$updates->response = array();
|
582 |
+
$updates->no_update = array();
|
583 |
+
} else if ( !isset($updates->no_update) ) {
|
584 |
+
$updates->no_update = array();
|
585 |
+
}
|
586 |
+
|
587 |
+
$updates->no_update[$this->getUpdateListKey()] = (object) $this->getNoUpdateItemFields();
|
588 |
+
|
589 |
+
return $updates;
|
590 |
+
}
|
591 |
+
|
592 |
+
/**
|
593 |
+
* Subclasses should override this method to add fields that are specific to plugins or themes.
|
594 |
+
* @return array
|
595 |
+
*/
|
596 |
+
protected function getNoUpdateItemFields() {
|
597 |
+
return array(
|
598 |
+
'new_version' => $this->getInstalledVersion(),
|
599 |
+
'url' => '',
|
600 |
+
'package' => '',
|
601 |
+
'requires_php' => '',
|
602 |
+
);
|
603 |
+
}
|
604 |
+
|
605 |
+
/**
|
606 |
+
* Get the key that will be used when adding updates to the update list that's maintained
|
607 |
+
* by the WordPress core. The list is always an associative array, but the key is different
|
608 |
+
* for plugins and themes.
|
609 |
+
*
|
610 |
+
* @return string
|
611 |
+
*/
|
612 |
+
abstract protected function getUpdateListKey();
|
613 |
+
|
614 |
+
/**
|
615 |
+
* Should we show available updates?
|
616 |
+
*
|
617 |
+
* Usually the answer is "yes", but there are exceptions. For example, WordPress doesn't
|
618 |
+
* support automatic updates installation for mu-plugins, so PUC usually won't show update
|
619 |
+
* notifications in that case. See the plugin-specific subclass for details.
|
620 |
+
*
|
621 |
+
* Note: This method only applies to updates that are displayed (or not) in the WordPress
|
622 |
+
* admin. It doesn't affect APIs like requestUpdate and getUpdate.
|
623 |
+
*
|
624 |
+
* @return bool
|
625 |
+
*/
|
626 |
+
protected function shouldShowUpdates() {
|
627 |
+
return true;
|
628 |
+
}
|
629 |
+
|
630 |
+
/* -------------------------------------------------------------------
|
631 |
+
* JSON-based update API
|
632 |
+
* -------------------------------------------------------------------
|
633 |
+
*/
|
634 |
+
|
635 |
+
/**
|
636 |
+
* Retrieve plugin or theme metadata from the JSON document at $this->metadataUrl.
|
637 |
+
*
|
638 |
+
* @param string $metaClass Parse the JSON as an instance of this class. It must have a static fromJson method.
|
639 |
+
* @param string $filterRoot
|
640 |
+
* @param array $queryArgs Additional query arguments.
|
641 |
+
* @return array [Puc_v4p10_Metadata|null, array|WP_Error] A metadata instance and the value returned by wp_remote_get().
|
642 |
+
*/
|
643 |
+
protected function requestMetadata($metaClass, $filterRoot, $queryArgs = array()) {
|
644 |
+
//Query args to append to the URL. Plugins can add their own by using a filter callback (see addQueryArgFilter()).
|
645 |
+
$queryArgs = array_merge(
|
646 |
+
array(
|
647 |
+
'installed_version' => strval($this->getInstalledVersion()),
|
648 |
+
'php' => phpversion(),
|
649 |
+
'locale' => get_locale(),
|
650 |
+
),
|
651 |
+
$queryArgs
|
652 |
+
);
|
653 |
+
$queryArgs = apply_filters($this->getUniqueName($filterRoot . '_query_args'), $queryArgs);
|
654 |
+
|
655 |
+
//Various options for the wp_remote_get() call. Plugins can filter these, too.
|
656 |
+
$options = array(
|
657 |
+
'timeout' => 10, //seconds
|
658 |
+
'headers' => array(
|
659 |
+
'Accept' => 'application/json',
|
660 |
+
),
|
661 |
+
);
|
662 |
+
$options = apply_filters($this->getUniqueName($filterRoot . '_options'), $options);
|
663 |
+
|
664 |
+
//The metadata file should be at 'http://your-api.com/url/here/$slug/info.json'
|
665 |
+
$url = $this->metadataUrl;
|
666 |
+
if ( !empty($queryArgs) ){
|
667 |
+
$url = add_query_arg($queryArgs, $url);
|
668 |
+
}
|
669 |
+
|
670 |
+
$result = wp_remote_get($url, $options);
|
671 |
+
|
672 |
+
$result = apply_filters($this->getUniqueName('request_metadata_http_result'), $result, $url, $options);
|
673 |
+
|
674 |
+
//Try to parse the response
|
675 |
+
$status = $this->validateApiResponse($result);
|
676 |
+
$metadata = null;
|
677 |
+
if ( !is_wp_error($status) ){
|
678 |
+
$metadata = call_user_func(array($metaClass, 'fromJson'), $result['body']);
|
679 |
+
} else {
|
680 |
+
do_action('puc_api_error', $status, $result, $url, $this->slug);
|
681 |
+
$this->triggerError(
|
682 |
+
sprintf('The URL %s does not point to a valid metadata file. ', $url)
|
683 |
+
. $status->get_error_message(),
|
684 |
+
E_USER_WARNING
|
685 |
+
);
|
686 |
+
}
|
687 |
+
|
688 |
+
return array($metadata, $result);
|
689 |
+
}
|
690 |
+
|
691 |
+
/**
|
692 |
+
* Check if $result is a successful update API response.
|
693 |
+
*
|
694 |
+
* @param array|WP_Error $result
|
695 |
+
* @return true|WP_Error
|
696 |
+
*/
|
697 |
+
protected function validateApiResponse($result) {
|
698 |
+
if ( is_wp_error($result) ) { /** @var WP_Error $result */
|
699 |
+
return new WP_Error($result->get_error_code(), 'WP HTTP Error: ' . $result->get_error_message());
|
700 |
+
}
|
701 |
+
|
702 |
+
if ( !isset($result['response']['code']) ) {
|
703 |
+
return new WP_Error(
|
704 |
+
'puc_no_response_code',
|
705 |
+
'wp_remote_get() returned an unexpected result.'
|
706 |
+
);
|
707 |
+
}
|
708 |
+
|
709 |
+
if ( $result['response']['code'] !== 200 ) {
|
710 |
+
return new WP_Error(
|
711 |
+
'puc_unexpected_response_code',
|
712 |
+
'HTTP response code is ' . $result['response']['code'] . ' (expected: 200)'
|
713 |
+
);
|
714 |
+
}
|
715 |
+
|
716 |
+
if ( empty($result['body']) ) {
|
717 |
+
return new WP_Error('puc_empty_response', 'The metadata file appears to be empty.');
|
718 |
+
}
|
719 |
+
|
720 |
+
return true;
|
721 |
+
}
|
722 |
+
|
723 |
+
/* -------------------------------------------------------------------
|
724 |
+
* Language packs / Translation updates
|
725 |
+
* -------------------------------------------------------------------
|
726 |
+
*/
|
727 |
+
|
728 |
+
/**
|
729 |
+
* Filter a list of translation updates and return a new list that contains only updates
|
730 |
+
* that apply to the current site.
|
731 |
+
*
|
732 |
+
* @param array $translations
|
733 |
+
* @return array
|
734 |
+
*/
|
735 |
+
protected function filterApplicableTranslations($translations) {
|
736 |
+
$languages = array_flip(array_values(get_available_languages()));
|
737 |
+
$installedTranslations = $this->getInstalledTranslations();
|
738 |
+
|
739 |
+
$applicableTranslations = array();
|
740 |
+
foreach ($translations as $translation) {
|
741 |
+
//Does it match one of the available core languages?
|
742 |
+
$isApplicable = array_key_exists($translation->language, $languages);
|
743 |
+
//Is it more recent than an already-installed translation?
|
744 |
+
if ( isset($installedTranslations[$translation->language]) ) {
|
745 |
+
$updateTimestamp = strtotime($translation->updated);
|
746 |
+
$installedTimestamp = strtotime($installedTranslations[$translation->language]['PO-Revision-Date']);
|
747 |
+
$isApplicable = $updateTimestamp > $installedTimestamp;
|
748 |
+
}
|
749 |
+
|
750 |
+
if ( $isApplicable ) {
|
751 |
+
$applicableTranslations[] = $translation;
|
752 |
+
}
|
753 |
+
}
|
754 |
+
|
755 |
+
return $applicableTranslations;
|
756 |
+
}
|
757 |
+
|
758 |
+
/**
|
759 |
+
* Get a list of installed translations for this plugin or theme.
|
760 |
+
*
|
761 |
+
* @return array
|
762 |
+
*/
|
763 |
+
protected function getInstalledTranslations() {
|
764 |
+
if ( !function_exists('wp_get_installed_translations') ) {
|
765 |
+
return array();
|
766 |
+
}
|
767 |
+
$installedTranslations = wp_get_installed_translations($this->translationType . 's');
|
768 |
+
if ( isset($installedTranslations[$this->directoryName]) ) {
|
769 |
+
$installedTranslations = $installedTranslations[$this->directoryName];
|
770 |
+
} else {
|
771 |
+
$installedTranslations = array();
|
772 |
+
}
|
773 |
+
return $installedTranslations;
|
774 |
+
}
|
775 |
+
|
776 |
+
/**
|
777 |
+
* Insert translation updates into the list maintained by WordPress.
|
778 |
+
*
|
779 |
+
* @param stdClass $updates
|
780 |
+
* @return stdClass
|
781 |
+
*/
|
782 |
+
public function injectTranslationUpdates($updates) {
|
783 |
+
$translationUpdates = $this->getTranslationUpdates();
|
784 |
+
if ( empty($translationUpdates) ) {
|
785 |
+
return $updates;
|
786 |
+
}
|
787 |
+
|
788 |
+
//Being defensive.
|
789 |
+
if ( !is_object($updates) ) {
|
790 |
+
$updates = new stdClass();
|
791 |
+
}
|
792 |
+
if ( !isset($updates->translations) ) {
|
793 |
+
$updates->translations = array();
|
794 |
+
}
|
795 |
+
|
796 |
+
//In case there's a name collision with a plugin or theme hosted on wordpress.org,
|
797 |
+
//remove any preexisting updates that match our thing.
|
798 |
+
$updates->translations = array_values(array_filter(
|
799 |
+
$updates->translations,
|
800 |
+
array($this, 'isNotMyTranslation')
|
801 |
+
));
|
802 |
+
|
803 |
+
//Add our updates to the list.
|
804 |
+
foreach($translationUpdates as $update) {
|
805 |
+
$convertedUpdate = array_merge(
|
806 |
+
array(
|
807 |
+
'type' => $this->translationType,
|
808 |
+
'slug' => $this->directoryName,
|
809 |
+
'autoupdate' => 0,
|
810 |
+
//AFAICT, WordPress doesn't actually use the "version" field for anything.
|
811 |
+
//But lets make sure it's there, just in case.
|
812 |
+
'version' => isset($update->version) ? $update->version : ('1.' . strtotime($update->updated)),
|
813 |
+
),
|
814 |
+
(array)$update
|
815 |
+
);
|
816 |
+
|
817 |
+
$updates->translations[] = $convertedUpdate;
|
818 |
+
}
|
819 |
+
|
820 |
+
return $updates;
|
821 |
+
}
|
822 |
+
|
823 |
+
/**
|
824 |
+
* Get a list of available translation updates.
|
825 |
+
*
|
826 |
+
* This method will return an empty array if there are no updates.
|
827 |
+
* Uses cached update data.
|
828 |
+
*
|
829 |
+
* @return array
|
830 |
+
*/
|
831 |
+
public function getTranslationUpdates() {
|
832 |
+
return $this->updateState->getTranslations();
|
833 |
+
}
|
834 |
+
|
835 |
+
/**
|
836 |
+
* Remove all cached translation updates.
|
837 |
+
*
|
838 |
+
* @see wp_clean_update_cache
|
839 |
+
*/
|
840 |
+
public function clearCachedTranslationUpdates() {
|
841 |
+
$this->updateState->setTranslations(array());
|
842 |
+
}
|
843 |
+
|
844 |
+
/**
|
845 |
+
* Filter callback. Keeps only translations that *don't* match this plugin or theme.
|
846 |
+
*
|
847 |
+
* @param array $translation
|
848 |
+
* @return bool
|
849 |
+
*/
|
850 |
+
protected function isNotMyTranslation($translation) {
|
851 |
+
$isMatch = isset($translation['type'], $translation['slug'])
|
852 |
+
&& ($translation['type'] === $this->translationType)
|
853 |
+
&& ($translation['slug'] === $this->directoryName);
|
854 |
+
|
855 |
+
return !$isMatch;
|
856 |
+
}
|
857 |
+
|
858 |
+
/* -------------------------------------------------------------------
|
859 |
+
* Fix directory name when installing updates
|
860 |
+
* -------------------------------------------------------------------
|
861 |
+
*/
|
862 |
+
|
863 |
+
/**
|
864 |
+
* Rename the update directory to match the existing plugin/theme directory.
|
865 |
+
*
|
866 |
+
* When WordPress installs a plugin or theme update, it assumes that the ZIP file will contain
|
867 |
+
* exactly one directory, and that the directory name will be the same as the directory where
|
868 |
+
* the plugin or theme is currently installed.
|
869 |
+
*
|
870 |
+
* GitHub and other repositories provide ZIP downloads, but they often use directory names like
|
871 |
+
* "project-branch" or "project-tag-hash". We need to change the name to the actual plugin folder.
|
872 |
+
*
|
873 |
+
* This is a hook callback. Don't call it from a plugin.
|
874 |
+
*
|
875 |
+
* @access protected
|
876 |
+
*
|
877 |
+
* @param string $source The directory to copy to /wp-content/plugins or /wp-content/themes. Usually a subdirectory of $remoteSource.
|
878 |
+
* @param string $remoteSource WordPress has extracted the update to this directory.
|
879 |
+
* @param WP_Upgrader $upgrader
|
880 |
+
* @return string|WP_Error
|
881 |
+
*/
|
882 |
+
public function fixDirectoryName($source, $remoteSource, $upgrader) {
|
883 |
+
global $wp_filesystem;
|
884 |
+
/** @var WP_Filesystem_Base $wp_filesystem */
|
885 |
+
|
886 |
+
//Basic sanity checks.
|
887 |
+
if ( !isset($source, $remoteSource, $upgrader, $upgrader->skin, $wp_filesystem) ) {
|
888 |
+
return $source;
|
889 |
+
}
|
890 |
+
|
891 |
+
//If WordPress is upgrading anything other than our plugin/theme, leave the directory name unchanged.
|
892 |
+
if ( !$this->isBeingUpgraded($upgrader) ) {
|
893 |
+
return $source;
|
894 |
+
}
|
895 |
+
|
896 |
+
//Rename the source to match the existing directory.
|
897 |
+
$correctedSource = trailingslashit($remoteSource) . $this->directoryName . '/';
|
898 |
+
if ( $source !== $correctedSource ) {
|
899 |
+
//The update archive should contain a single directory that contains the rest of plugin/theme files.
|
900 |
+
//Otherwise, WordPress will try to copy the entire working directory ($source == $remoteSource).
|
901 |
+
//We can't rename $remoteSource because that would break WordPress code that cleans up temporary files
|
902 |
+
//after update.
|
903 |
+
if ( $this->isBadDirectoryStructure($remoteSource) ) {
|
904 |
+
return new WP_Error(
|
905 |
+
'puc-incorrect-directory-structure',
|
906 |
+
sprintf(
|
907 |
+
'The directory structure of the update is incorrect. All files should be inside ' .
|
908 |
+
'a directory named <span class="code">%s</span>, not at the root of the ZIP archive.',
|
909 |
+
htmlentities($this->slug)
|
910 |
+
)
|
911 |
+
);
|
912 |
+
}
|
913 |
+
|
914 |
+
/** @var WP_Upgrader_Skin $upgrader ->skin */
|
915 |
+
$upgrader->skin->feedback(sprintf(
|
916 |
+
'Renaming %s to %s…',
|
917 |
+
'<span class="code">' . basename($source) . '</span>',
|
918 |
+
'<span class="code">' . $this->directoryName . '</span>'
|
919 |
+
));
|
920 |
+
|
921 |
+
if ( $wp_filesystem->move($source, $correctedSource, true) ) {
|
922 |
+
$upgrader->skin->feedback('Directory successfully renamed.');
|
923 |
+
return $correctedSource;
|
924 |
+
} else {
|
925 |
+
return new WP_Error(
|
926 |
+
'puc-rename-failed',
|
927 |
+
'Unable to rename the update to match the existing directory.'
|
928 |
+
);
|
929 |
+
}
|
930 |
+
}
|
931 |
+
|
932 |
+
return $source;
|
933 |
+
}
|
934 |
+
|
935 |
+
/**
|
936 |
+
* Is there an update being installed right now, for this plugin or theme?
|
937 |
+
*
|
938 |
+
* @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
|
939 |
+
* @return bool
|
940 |
+
*/
|
941 |
+
abstract public function isBeingUpgraded($upgrader = null);
|
942 |
+
|
943 |
+
/**
|
944 |
+
* Check for incorrect update directory structure. An update must contain a single directory,
|
945 |
+
* all other files should be inside that directory.
|
946 |
+
*
|
947 |
+
* @param string $remoteSource Directory path.
|
948 |
+
* @return bool
|
949 |
+
*/
|
950 |
+
protected function isBadDirectoryStructure($remoteSource) {
|
951 |
+
global $wp_filesystem;
|
952 |
+
/** @var WP_Filesystem_Base $wp_filesystem */
|
953 |
+
|
954 |
+
$sourceFiles = $wp_filesystem->dirlist($remoteSource);
|
955 |
+
if ( is_array($sourceFiles) ) {
|
956 |
+
$sourceFiles = array_keys($sourceFiles);
|
957 |
+
$firstFilePath = trailingslashit($remoteSource) . $sourceFiles[0];
|
958 |
+
return (count($sourceFiles) > 1) || (!$wp_filesystem->is_dir($firstFilePath));
|
959 |
+
}
|
960 |
+
|
961 |
+
//Assume it's fine.
|
962 |
+
return false;
|
963 |
+
}
|
964 |
+
|
965 |
+
/* -------------------------------------------------------------------
|
966 |
+
* DebugBar integration
|
967 |
+
* -------------------------------------------------------------------
|
968 |
+
*/
|
969 |
+
|
970 |
+
/**
|
971 |
+
* Initialize the update checker Debug Bar plugin/add-on thingy.
|
972 |
+
*/
|
973 |
+
public function maybeInitDebugBar() {
|
974 |
+
if ( class_exists('Debug_Bar', false) && file_exists(dirname(__FILE__) . '/DebugBar') ) {
|
975 |
+
$this->debugBarExtension = $this->createDebugBarExtension();
|
976 |
+
}
|
977 |
+
}
|
978 |
+
|
979 |
+
protected function createDebugBarExtension() {
|
980 |
+
return new Puc_v4p10_DebugBar_Extension($this);
|
981 |
+
}
|
982 |
+
|
983 |
+
/**
|
984 |
+
* Display additional configuration details in the Debug Bar panel.
|
985 |
+
*
|
986 |
+
* @param Puc_v4p10_DebugBar_Panel $panel
|
987 |
+
*/
|
988 |
+
public function onDisplayConfiguration($panel) {
|
989 |
+
//Do nothing. Subclasses can use this to add additional info to the panel.
|
990 |
+
}
|
991 |
+
|
992 |
+
}
|
993 |
+
|
994 |
+
endif;
|
updater/Puc/v4p10/UpgraderStatus.php
ADDED
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_UpgraderStatus', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* A utility class that helps figure out which plugin or theme WordPress is upgrading.
|
6 |
+
*
|
7 |
+
* It may seem strange to have a separate class just for that, but the task is surprisingly complicated.
|
8 |
+
* Core classes like Plugin_Upgrader don't expose the plugin file name during an in-progress update (AFAICT).
|
9 |
+
* This class uses a few workarounds and heuristics to get the file name.
|
10 |
+
*/
|
11 |
+
class Puc_v4p10_UpgraderStatus {
|
12 |
+
private $currentType = null; //"plugin" or "theme".
|
13 |
+
private $currentId = null; //Plugin basename or theme directory name.
|
14 |
+
|
15 |
+
public function __construct() {
|
16 |
+
//Keep track of which plugin/theme WordPress is currently upgrading.
|
17 |
+
add_filter('upgrader_pre_install', array($this, 'setUpgradedThing'), 10, 2);
|
18 |
+
add_filter('upgrader_package_options', array($this, 'setUpgradedPluginFromOptions'), 10, 1);
|
19 |
+
add_filter('upgrader_post_install', array($this, 'clearUpgradedThing'), 10, 1);
|
20 |
+
add_action('upgrader_process_complete', array($this, 'clearUpgradedThing'), 10, 1);
|
21 |
+
}
|
22 |
+
|
23 |
+
/**
|
24 |
+
* Is there and update being installed RIGHT NOW, for a specific plugin?
|
25 |
+
*
|
26 |
+
* Caution: This method is unreliable. WordPress doesn't make it easy to figure out what it is upgrading,
|
27 |
+
* and upgrader implementations are liable to change without notice.
|
28 |
+
*
|
29 |
+
* @param string $pluginFile The plugin to check.
|
30 |
+
* @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
|
31 |
+
* @return bool True if the plugin identified by $pluginFile is being upgraded.
|
32 |
+
*/
|
33 |
+
public function isPluginBeingUpgraded($pluginFile, $upgrader = null) {
|
34 |
+
return $this->isBeingUpgraded('plugin', $pluginFile, $upgrader);
|
35 |
+
}
|
36 |
+
|
37 |
+
/**
|
38 |
+
* Is there an update being installed for a specific theme?
|
39 |
+
*
|
40 |
+
* @param string $stylesheet Theme directory name.
|
41 |
+
* @param WP_Upgrader|null $upgrader The upgrader that's performing the current update.
|
42 |
+
* @return bool
|
43 |
+
*/
|
44 |
+
public function isThemeBeingUpgraded($stylesheet, $upgrader = null) {
|
45 |
+
return $this->isBeingUpgraded('theme', $stylesheet, $upgrader);
|
46 |
+
}
|
47 |
+
|
48 |
+
/**
|
49 |
+
* Check if a specific theme or plugin is being upgraded.
|
50 |
+
*
|
51 |
+
* @param string $type
|
52 |
+
* @param string $id
|
53 |
+
* @param Plugin_Upgrader|WP_Upgrader|null $upgrader
|
54 |
+
* @return bool
|
55 |
+
*/
|
56 |
+
protected function isBeingUpgraded($type, $id, $upgrader = null) {
|
57 |
+
if ( isset($upgrader) ) {
|
58 |
+
list($currentType, $currentId) = $this->getThingBeingUpgradedBy($upgrader);
|
59 |
+
if ( $currentType !== null ) {
|
60 |
+
$this->currentType = $currentType;
|
61 |
+
$this->currentId = $currentId;
|
62 |
+
}
|
63 |
+
}
|
64 |
+
return ($this->currentType === $type) && ($this->currentId === $id);
|
65 |
+
}
|
66 |
+
|
67 |
+
/**
|
68 |
+
* Figure out which theme or plugin is being upgraded by a WP_Upgrader instance.
|
69 |
+
*
|
70 |
+
* Returns an array with two items. The first item is the type of the thing that's being
|
71 |
+
* upgraded: "plugin" or "theme". The second item is either the plugin basename or
|
72 |
+
* the theme directory name. If we can't determine what the upgrader is doing, both items
|
73 |
+
* will be NULL.
|
74 |
+
*
|
75 |
+
* Examples:
|
76 |
+
* ['plugin', 'plugin-dir-name/plugin.php']
|
77 |
+
* ['theme', 'theme-dir-name']
|
78 |
+
*
|
79 |
+
* @param Plugin_Upgrader|WP_Upgrader $upgrader
|
80 |
+
* @return array
|
81 |
+
*/
|
82 |
+
private function getThingBeingUpgradedBy($upgrader) {
|
83 |
+
if ( !isset($upgrader, $upgrader->skin) ) {
|
84 |
+
return array(null, null);
|
85 |
+
}
|
86 |
+
|
87 |
+
//Figure out which plugin or theme is being upgraded.
|
88 |
+
$pluginFile = null;
|
89 |
+
$themeDirectoryName = null;
|
90 |
+
|
91 |
+
$skin = $upgrader->skin;
|
92 |
+
if ( isset($skin->theme_info) && ($skin->theme_info instanceof WP_Theme) ) {
|
93 |
+
$themeDirectoryName = $skin->theme_info->get_stylesheet();
|
94 |
+
} elseif ( $skin instanceof Plugin_Upgrader_Skin ) {
|
95 |
+
if ( isset($skin->plugin) && is_string($skin->plugin) && ($skin->plugin !== '') ) {
|
96 |
+
$pluginFile = $skin->plugin;
|
97 |
+
}
|
98 |
+
} elseif ( $skin instanceof Theme_Upgrader_Skin ) {
|
99 |
+
if ( isset($skin->theme) && is_string($skin->theme) && ($skin->theme !== '') ) {
|
100 |
+
$themeDirectoryName = $skin->theme;
|
101 |
+
}
|
102 |
+
} elseif ( isset($skin->plugin_info) && is_array($skin->plugin_info) ) {
|
103 |
+
//This case is tricky because Bulk_Plugin_Upgrader_Skin (etc) doesn't actually store the plugin
|
104 |
+
//filename anywhere. Instead, it has the plugin headers in $plugin_info. So the best we can
|
105 |
+
//do is compare those headers to the headers of installed plugins.
|
106 |
+
$pluginFile = $this->identifyPluginByHeaders($skin->plugin_info);
|
107 |
+
}
|
108 |
+
|
109 |
+
if ( $pluginFile !== null ) {
|
110 |
+
return array('plugin', $pluginFile);
|
111 |
+
} elseif ( $themeDirectoryName !== null ) {
|
112 |
+
return array('theme', $themeDirectoryName);
|
113 |
+
}
|
114 |
+
return array(null, null);
|
115 |
+
}
|
116 |
+
|
117 |
+
/**
|
118 |
+
* Identify an installed plugin based on its headers.
|
119 |
+
*
|
120 |
+
* @param array $searchHeaders The plugin file header to look for.
|
121 |
+
* @return string|null Plugin basename ("foo/bar.php"), or NULL if we can't identify the plugin.
|
122 |
+
*/
|
123 |
+
private function identifyPluginByHeaders($searchHeaders) {
|
124 |
+
if ( !function_exists('get_plugins') ){
|
125 |
+
/** @noinspection PhpIncludeInspection */
|
126 |
+
require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
|
127 |
+
}
|
128 |
+
|
129 |
+
$installedPlugins = get_plugins();
|
130 |
+
$matches = array();
|
131 |
+
foreach($installedPlugins as $pluginBasename => $headers) {
|
132 |
+
$diff1 = array_diff_assoc($headers, $searchHeaders);
|
133 |
+
$diff2 = array_diff_assoc($searchHeaders, $headers);
|
134 |
+
if ( empty($diff1) && empty($diff2) ) {
|
135 |
+
$matches[] = $pluginBasename;
|
136 |
+
}
|
137 |
+
}
|
138 |
+
|
139 |
+
//It's possible (though very unlikely) that there could be two plugins with identical
|
140 |
+
//headers. In that case, we can't unambiguously identify the plugin that's being upgraded.
|
141 |
+
if ( count($matches) !== 1 ) {
|
142 |
+
return null;
|
143 |
+
}
|
144 |
+
|
145 |
+
return reset($matches);
|
146 |
+
}
|
147 |
+
|
148 |
+
/**
|
149 |
+
* @access private
|
150 |
+
*
|
151 |
+
* @param mixed $input
|
152 |
+
* @param array $hookExtra
|
153 |
+
* @return mixed Returns $input unaltered.
|
154 |
+
*/
|
155 |
+
public function setUpgradedThing($input, $hookExtra) {
|
156 |
+
if ( !empty($hookExtra['plugin']) && is_string($hookExtra['plugin']) ) {
|
157 |
+
$this->currentId = $hookExtra['plugin'];
|
158 |
+
$this->currentType = 'plugin';
|
159 |
+
} elseif ( !empty($hookExtra['theme']) && is_string($hookExtra['theme']) ) {
|
160 |
+
$this->currentId = $hookExtra['theme'];
|
161 |
+
$this->currentType = 'theme';
|
162 |
+
} else {
|
163 |
+
$this->currentType = null;
|
164 |
+
$this->currentId = null;
|
165 |
+
}
|
166 |
+
return $input;
|
167 |
+
}
|
168 |
+
|
169 |
+
/**
|
170 |
+
* @access private
|
171 |
+
*
|
172 |
+
* @param array $options
|
173 |
+
* @return array
|
174 |
+
*/
|
175 |
+
public function setUpgradedPluginFromOptions($options) {
|
176 |
+
if ( isset($options['hook_extra']['plugin']) && is_string($options['hook_extra']['plugin']) ) {
|
177 |
+
$this->currentType = 'plugin';
|
178 |
+
$this->currentId = $options['hook_extra']['plugin'];
|
179 |
+
} else {
|
180 |
+
$this->currentType = null;
|
181 |
+
$this->currentId = null;
|
182 |
+
}
|
183 |
+
return $options;
|
184 |
+
}
|
185 |
+
|
186 |
+
/**
|
187 |
+
* @access private
|
188 |
+
*
|
189 |
+
* @param mixed $input
|
190 |
+
* @return mixed Returns $input unaltered.
|
191 |
+
*/
|
192 |
+
public function clearUpgradedThing($input = null) {
|
193 |
+
$this->currentId = null;
|
194 |
+
$this->currentType = null;
|
195 |
+
return $input;
|
196 |
+
}
|
197 |
+
}
|
198 |
+
|
199 |
+
endif;
|
updater/Puc/v4p10/Utils.php
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_Utils', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_Utils {
|
6 |
+
/**
|
7 |
+
* Get a value from a nested array or object based on a path.
|
8 |
+
*
|
9 |
+
* @param array|object|null $collection Get an entry from this array.
|
10 |
+
* @param array|string $path A list of array keys in hierarchy order, or a string path like "foo.bar.baz".
|
11 |
+
* @param mixed $default The value to return if the specified path is not found.
|
12 |
+
* @param string $separator Path element separator. Only applies to string paths.
|
13 |
+
* @return mixed
|
14 |
+
*/
|
15 |
+
public static function get($collection, $path, $default = null, $separator = '.') {
|
16 |
+
if ( is_string($path) ) {
|
17 |
+
$path = explode($separator, $path);
|
18 |
+
}
|
19 |
+
|
20 |
+
//Follow the $path into $input as far as possible.
|
21 |
+
$currentValue = $collection;
|
22 |
+
foreach ($path as $node) {
|
23 |
+
if ( is_array($currentValue) && isset($currentValue[$node]) ) {
|
24 |
+
$currentValue = $currentValue[$node];
|
25 |
+
} else if ( is_object($currentValue) && isset($currentValue->$node) ) {
|
26 |
+
$currentValue = $currentValue->$node;
|
27 |
+
} else {
|
28 |
+
return $default;
|
29 |
+
}
|
30 |
+
}
|
31 |
+
|
32 |
+
return $currentValue;
|
33 |
+
}
|
34 |
+
|
35 |
+
/**
|
36 |
+
* Get the first array element that is not empty.
|
37 |
+
*
|
38 |
+
* @param array $values
|
39 |
+
* @param mixed|null $default Returns this value if there are no non-empty elements.
|
40 |
+
* @return mixed|null
|
41 |
+
*/
|
42 |
+
public static function findNotEmpty($values, $default = null) {
|
43 |
+
if ( empty($values) ) {
|
44 |
+
return $default;
|
45 |
+
}
|
46 |
+
|
47 |
+
foreach ($values as $value) {
|
48 |
+
if ( !empty($value) ) {
|
49 |
+
return $value;
|
50 |
+
}
|
51 |
+
}
|
52 |
+
|
53 |
+
return $default;
|
54 |
+
}
|
55 |
+
|
56 |
+
/**
|
57 |
+
* Check if the input string starts with the specified prefix.
|
58 |
+
*
|
59 |
+
* @param string $input
|
60 |
+
* @param string $prefix
|
61 |
+
* @return bool
|
62 |
+
*/
|
63 |
+
public static function startsWith($input, $prefix) {
|
64 |
+
$length = strlen($prefix);
|
65 |
+
return (substr($input, 0, $length) === $prefix);
|
66 |
+
}
|
67 |
+
}
|
68 |
+
|
69 |
+
endif;
|
updater/Puc/v4p10/Vcs/Api.php
ADDED
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Vcs_Api') ):
|
3 |
+
|
4 |
+
abstract class Puc_v4p10_Vcs_Api {
|
5 |
+
protected $tagNameProperty = 'name';
|
6 |
+
protected $slug = '';
|
7 |
+
|
8 |
+
/**
|
9 |
+
* @var string
|
10 |
+
*/
|
11 |
+
protected $repositoryUrl = '';
|
12 |
+
|
13 |
+
/**
|
14 |
+
* @var mixed Authentication details for private repositories. Format depends on service.
|
15 |
+
*/
|
16 |
+
protected $credentials = null;
|
17 |
+
|
18 |
+
/**
|
19 |
+
* @var string The filter tag that's used to filter options passed to wp_remote_get.
|
20 |
+
* For example, "puc_request_info_options-slug" or "puc_request_update_options_theme-slug".
|
21 |
+
*/
|
22 |
+
protected $httpFilterName = '';
|
23 |
+
|
24 |
+
/**
|
25 |
+
* @var string|null
|
26 |
+
*/
|
27 |
+
protected $localDirectory = null;
|
28 |
+
|
29 |
+
/**
|
30 |
+
* Puc_v4p10_Vcs_Api constructor.
|
31 |
+
*
|
32 |
+
* @param string $repositoryUrl
|
33 |
+
* @param array|string|null $credentials
|
34 |
+
*/
|
35 |
+
public function __construct($repositoryUrl, $credentials = null) {
|
36 |
+
$this->repositoryUrl = $repositoryUrl;
|
37 |
+
$this->setAuthentication($credentials);
|
38 |
+
}
|
39 |
+
|
40 |
+
/**
|
41 |
+
* @return string
|
42 |
+
*/
|
43 |
+
public function getRepositoryUrl() {
|
44 |
+
return $this->repositoryUrl;
|
45 |
+
}
|
46 |
+
|
47 |
+
/**
|
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_v4p10_Vcs_Reference
|
52 |
+
*/
|
53 |
+
abstract public function chooseReference($configBranch);
|
54 |
+
|
55 |
+
/**
|
56 |
+
* Get the readme.txt file from the remote repository and parse it
|
57 |
+
* according to the plugin readme standard.
|
58 |
+
*
|
59 |
+
* @param string $ref Tag or branch name.
|
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 |
+
}
|
67 |
+
|
68 |
+
$parser = new PucReadmeParser();
|
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_v4p10_Vcs_Reference|null
|
109 |
+
*/
|
110 |
+
abstract public function getBranch($branchName);
|
111 |
+
|
112 |
+
/**
|
113 |
+
* Get a specific tag.
|
114 |
+
*
|
115 |
+
* @param string $tagName
|
116 |
+
* @return Puc_v4p10_Vcs_Reference|null
|
117 |
+
*/
|
118 |
+
abstract public function getTag($tagName);
|
119 |
+
|
120 |
+
/**
|
121 |
+
* Get the tag that looks like the highest version number.
|
122 |
+
* (Implementations should skip pre-release versions if possible.)
|
123 |
+
*
|
124 |
+
* @return Puc_v4p10_Vcs_Reference|null
|
125 |
+
*/
|
126 |
+
abstract public function getLatestTag();
|
127 |
+
|
128 |
+
/**
|
129 |
+
* Check if a tag name string looks like a version number.
|
130 |
+
*
|
131 |
+
* @param string $name
|
132 |
+
* @return bool
|
133 |
+
*/
|
134 |
+
protected function looksLikeVersion($name) {
|
135 |
+
//Tag names may be prefixed with "v", e.g. "v1.2.3".
|
136 |
+
$name = ltrim($name, 'v');
|
137 |
+
|
138 |
+
//The version string must start with a number.
|
139 |
+
if ( !is_numeric(substr($name, 0, 1)) ) {
|
140 |
+
return false;
|
141 |
+
}
|
142 |
+
|
143 |
+
//The goal is to accept any SemVer-compatible or "PHP-standardized" version number.
|
144 |
+
return (preg_match('@^(\d{1,5}?)(\.\d{1,10}?){0,4}?($|[abrdp+_\-]|\s)@i', $name) === 1);
|
145 |
+
}
|
146 |
+
|
147 |
+
/**
|
148 |
+
* Check if a tag appears to be named like a version number.
|
149 |
+
*
|
150 |
+
* @param stdClass $tag
|
151 |
+
* @return bool
|
152 |
+
*/
|
153 |
+
protected function isVersionTag($tag) {
|
154 |
+
$property = $this->tagNameProperty;
|
155 |
+
return isset($tag->$property) && $this->looksLikeVersion($tag->$property);
|
156 |
+
}
|
157 |
+
|
158 |
+
/**
|
159 |
+
* Sort a list of tags as if they were version numbers.
|
160 |
+
* Tags that don't look like version number will be removed.
|
161 |
+
*
|
162 |
+
* @param stdClass[] $tags Array of tag objects.
|
163 |
+
* @return stdClass[] Filtered array of tags sorted in descending order.
|
164 |
+
*/
|
165 |
+
protected function sortTagsByVersion($tags) {
|
166 |
+
//Keep only those tags that look like version numbers.
|
167 |
+
$versionTags = array_filter($tags, array($this, 'isVersionTag'));
|
168 |
+
//Sort them in descending order.
|
169 |
+
usort($versionTags, array($this, 'compareTagNames'));
|
170 |
+
|
171 |
+
return $versionTags;
|
172 |
+
}
|
173 |
+
|
174 |
+
/**
|
175 |
+
* Compare two tags as if they were version number.
|
176 |
+
*
|
177 |
+
* @param stdClass $tag1 Tag object.
|
178 |
+
* @param stdClass $tag2 Another tag object.
|
179 |
+
* @return int
|
180 |
+
*/
|
181 |
+
protected function compareTagNames($tag1, $tag2) {
|
182 |
+
$property = $this->tagNameProperty;
|
183 |
+
if ( !isset($tag1->$property) ) {
|
184 |
+
return 1;
|
185 |
+
}
|
186 |
+
if ( !isset($tag2->$property) ) {
|
187 |
+
return -1;
|
188 |
+
}
|
189 |
+
return -version_compare(ltrim($tag1->$property, 'v'), ltrim($tag2->$property, 'v'));
|
190 |
+
}
|
191 |
+
|
192 |
+
/**
|
193 |
+
* Get the contents of a file from a specific branch or tag.
|
194 |
+
*
|
195 |
+
* @param string $path File name.
|
196 |
+
* @param string $ref
|
197 |
+
* @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
|
198 |
+
*/
|
199 |
+
abstract public function getRemoteFile($path, $ref = 'master');
|
200 |
+
|
201 |
+
/**
|
202 |
+
* Get the timestamp of the latest commit that changed the specified branch or tag.
|
203 |
+
*
|
204 |
+
* @param string $ref Reference name (e.g. branch or tag).
|
205 |
+
* @return string|null
|
206 |
+
*/
|
207 |
+
abstract public function getLatestCommitTime($ref);
|
208 |
+
|
209 |
+
/**
|
210 |
+
* Get the contents of the changelog file from the repository.
|
211 |
+
*
|
212 |
+
* @param string $ref
|
213 |
+
* @param string $localDirectory Full path to the local plugin or theme directory.
|
214 |
+
* @return null|string The HTML contents of the changelog.
|
215 |
+
*/
|
216 |
+
public function getRemoteChangelog($ref, $localDirectory) {
|
217 |
+
$filename = $this->findChangelogName($localDirectory);
|
218 |
+
if ( empty($filename) ) {
|
219 |
+
return null;
|
220 |
+
}
|
221 |
+
|
222 |
+
$changelog = $this->getRemoteFile($filename, $ref);
|
223 |
+
if ( $changelog === null ) {
|
224 |
+
return null;
|
225 |
+
}
|
226 |
+
|
227 |
+
/** @noinspection PhpUndefinedClassInspection */
|
228 |
+
return Parsedown::instance()->text($changelog);
|
229 |
+
}
|
230 |
+
|
231 |
+
/**
|
232 |
+
* Guess the name of the changelog file.
|
233 |
+
*
|
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 |
+
}
|
244 |
+
|
245 |
+
$possibleNames = array('CHANGES.md', 'CHANGELOG.md', 'changes.md', 'changelog.md');
|
246 |
+
$files = scandir($directory);
|
247 |
+
$foundNames = array_intersect($possibleNames, $files);
|
248 |
+
|
249 |
+
if ( !empty($foundNames) ) {
|
250 |
+
return reset($foundNames);
|
251 |
+
}
|
252 |
+
return null;
|
253 |
+
}
|
254 |
+
|
255 |
+
/**
|
256 |
+
* Set authentication credentials.
|
257 |
+
*
|
258 |
+
* @param $credentials
|
259 |
+
*/
|
260 |
+
public function setAuthentication($credentials) {
|
261 |
+
$this->credentials = $credentials;
|
262 |
+
}
|
263 |
+
|
264 |
+
public function isAuthenticationEnabled() {
|
265 |
+
return !empty($this->credentials);
|
266 |
+
}
|
267 |
+
|
268 |
+
/**
|
269 |
+
* @param string $url
|
270 |
+
* @return string
|
271 |
+
*/
|
272 |
+
public function signDownloadUrl($url) {
|
273 |
+
return $url;
|
274 |
+
}
|
275 |
+
|
276 |
+
/**
|
277 |
+
* @param string $filterName
|
278 |
+
*/
|
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;
|
updater/Puc/v4p10/Vcs/BaseChecker.php
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !interface_exists('Puc_v4p10_Vcs_BaseChecker', false) ):
|
3 |
+
|
4 |
+
interface Puc_v4p10_Vcs_BaseChecker {
|
5 |
+
/**
|
6 |
+
* Set the repository branch to use for updates. Defaults to 'master'.
|
7 |
+
*
|
8 |
+
* @param string $branch
|
9 |
+
* @return $this
|
10 |
+
*/
|
11 |
+
public function setBranch($branch);
|
12 |
+
|
13 |
+
/**
|
14 |
+
* Set authentication credentials.
|
15 |
+
*
|
16 |
+
* @param array|string $credentials
|
17 |
+
* @return $this
|
18 |
+
*/
|
19 |
+
public function setAuthentication($credentials);
|
20 |
+
|
21 |
+
/**
|
22 |
+
* @return Puc_v4p10_Vcs_Api
|
23 |
+
*/
|
24 |
+
public function getVcsApi();
|
25 |
+
}
|
26 |
+
|
27 |
+
endif;
|
updater/Puc/v4p10/Vcs/BitBucketApi.php
ADDED
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Vcs_BitBucketApi', false) ):
|
3 |
+
|
4 |
+
class Puc_v4p10_Vcs_BitBucketApi extends Puc_v4p10_Vcs_Api {
|
5 |
+
/**
|
6 |
+
* @var Puc_v4p10_OAuthSignature
|
7 |
+
*/
|
8 |
+
private $oauth = null;
|
9 |
+
|
10 |
+
/**
|
11 |
+
* @var string
|
12 |
+
*/
|
13 |
+
private $username;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @var string
|
17 |
+
*/
|
18 |
+
private $repository;
|
19 |
+
|
20 |
+
public function __construct($repositoryUrl, $credentials = array()) {
|
21 |
+
$path = parse_url($repositoryUrl, PHP_URL_PATH);
|
22 |
+
if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) {
|
23 |
+
$this->username = $matches['username'];
|
24 |
+
$this->repository = $matches['repository'];
|
25 |
+
} else {
|
26 |
+
throw new InvalidArgumentException('Invalid BitBucket repository URL: "' . $repositoryUrl . '"');
|
27 |
+
}
|
28 |
+
|
29 |
+
parent::__construct($repositoryUrl, $credentials);
|
30 |
+
}
|
31 |
+
|
32 |
+
/**
|
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_v4p10_Vcs_Reference
|
37 |
+
*/
|
38 |
+
public function chooseReference($configBranch) {
|
39 |
+
$updateSource = null;
|
40 |
+
|
41 |
+
//Check if there's a "Stable tag: 1.2.3" header that points to a valid tag.
|
42 |
+
$updateSource = $this->getStableTag($configBranch);
|
43 |
+
|
44 |
+
//Look for version-like tags.
|
45 |
+
if ( !$updateSource && ($configBranch === 'master') ) {
|
46 |
+
$updateSource = $this->getLatestTag();
|
47 |
+
}
|
48 |
+
//If all else fails, use the specified branch itself.
|
49 |
+
if ( !$updateSource ) {
|
50 |
+
$updateSource = $this->getBranch($configBranch);
|
51 |
+
}
|
52 |
+
|
53 |
+
return $updateSource;
|
54 |
+
}
|
55 |
+
|
56 |
+
public function getBranch($branchName) {
|
57 |
+
$branch = $this->api('/refs/branches/' . $branchName);
|
58 |
+
if ( is_wp_error($branch) || empty($branch) ) {
|
59 |
+
return null;
|
60 |
+
}
|
61 |
+
|
62 |
+
return new Puc_v4p10_Vcs_Reference(array(
|
63 |
+
'name' => $branch->name,
|
64 |
+
'updated' => $branch->target->date,
|
65 |
+
'downloadUrl' => $this->getDownloadUrl($branch->name),
|
66 |
+
));
|
67 |
+
}
|
68 |
+
|
69 |
+
/**
|
70 |
+
* Get a specific tag.
|
71 |
+
*
|
72 |
+
* @param string $tagName
|
73 |
+
* @return Puc_v4p10_Vcs_Reference|null
|
74 |
+
*/
|
75 |
+
public function getTag($tagName) {
|
76 |
+
$tag = $this->api('/refs/tags/' . $tagName);
|
77 |
+
if ( is_wp_error($tag) || empty($tag) ) {
|
78 |
+
return null;
|
79 |
+
}
|
80 |
+
|
81 |
+
return new Puc_v4p10_Vcs_Reference(array(
|
82 |
+
'name' => $tag->name,
|
83 |
+
'version' => ltrim($tag->name, 'v'),
|
84 |
+
'updated' => $tag->target->date,
|
85 |
+
'downloadUrl' => $this->getDownloadUrl($tag->name),
|
86 |
+
));
|
87 |
+
}
|
88 |
+
|
89 |
+
/**
|
90 |
+
* Get the tag that looks like the highest version number.
|
91 |
+
*
|
92 |
+
* @return Puc_v4p10_Vcs_Reference|null
|
93 |
+
*/
|
94 |
+
public function getLatestTag() {
|
95 |
+
$tags = $this->api('/refs/tags?sort=-target.date');
|
96 |
+
if ( !isset($tags, $tags->values) || !is_array($tags->values) ) {
|
97 |
+
return null;
|
98 |
+
}
|
99 |
+
|
100 |
+
//Filter and sort the list of tags.
|
101 |
+
$versionTags = $this->sortTagsByVersion($tags->values);
|
102 |
+
|
103 |
+
//Return the first result.
|
104 |
+
if ( !empty($versionTags) ) {
|
105 |
+
$tag = $versionTags[0];
|
106 |
+
return new Puc_v4p10_Vcs_Reference(array(
|
107 |
+
'name' => $tag->name,
|
108 |
+
'version' => ltrim($tag->name, 'v'),
|
109 |
+
'updated' => $tag->target->date,
|
110 |
+
'downloadUrl' => $this->getDownloadUrl($tag->name),
|
111 |
+
));
|
112 |
+
}
|
113 |
+
return null;
|
114 |
+
}
|
115 |
+
|
116 |
+
/**
|
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_v4p10_Vcs_Reference
|
121 |
+
*/
|
122 |
+
protected function getStableTag($branch) {
|
123 |
+
$remoteReadme = $this->getRemoteReadme($branch);
|
124 |
+
if ( !empty($remoteReadme['stable_tag']) ) {
|
125 |
+
$tag = $remoteReadme['stable_tag'];
|
126 |
+
|
127 |
+
//You can explicitly opt out of using tags by setting "Stable tag" to
|
128 |
+
//"trunk" or the name of the current branch.
|
129 |
+
if ( ($tag === $branch) || ($tag === 'trunk') ) {
|
130 |
+
return $this->getBranch($branch);
|
131 |
+
}
|
132 |
+
|
133 |
+
return $this->getTag($tag);
|
134 |
+
}
|
135 |
+
|
136 |
+
return null;
|
137 |
+
}
|
138 |
+
|
139 |
+
/**
|
140 |
+
* @param string $ref
|
141 |
+
* @return string
|
142 |
+
*/
|
143 |
+
protected function getDownloadUrl($ref) {
|
144 |
+
return sprintf(
|
145 |
+
'https://bitbucket.org/%s/%s/get/%s.zip',
|
146 |
+
$this->username,
|
147 |
+
$this->repository,
|
148 |
+
$ref
|
149 |
+
);
|
150 |
+
}
|
151 |
+
|
152 |
+
/**
|
153 |
+
* Get the contents of a file from a specific branch or tag.
|
154 |
+
*
|
155 |
+
* @param string $path File name.
|
156 |
+
* @param string $ref
|
157 |
+
* @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
|
158 |
+
*/
|
159 |
+
public function getRemoteFile($path, $ref = 'master') {
|
160 |
+
$response = $this->api('src/' . $ref . '/' . ltrim($path));
|
161 |
+
if ( is_wp_error($response) || !is_string($response) ) {
|
162 |
+
return null;
|
163 |
+
}
|
164 |
+
return $response;
|
165 |
+
}
|
166 |
+
|
167 |
+
/**
|
168 |
+
* Get the timestamp of the latest commit that changed the specified branch or tag.
|
169 |
+
*
|
170 |
+
* @param string $ref Reference name (e.g. branch or tag).
|
171 |
+
* @return string|null
|
172 |
+
*/
|
173 |
+
public function getLatestCommitTime($ref) {
|
174 |
+
$response = $this->api('commits/' . $ref);
|
175 |
+
if ( isset($response->values, $response->values[0], $response->values[0]->date) ) {
|
176 |
+
return $response->values[0]->date;
|
177 |
+
}
|
178 |
+
return null;
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* Perform a BitBucket API 2.0 request.
|
183 |
+
*
|
184 |
+
* @param string $url
|
185 |
+
* @param string $version
|
186 |
+
* @return mixed|WP_Error
|
187 |
+
*/
|
188 |
+
public function api($url, $version = '2.0') {
|
189 |
+
$url = ltrim($url, '/');
|
190 |
+
$isSrcResource = Puc_v4p10_Utils::startsWith($url, 'src/');
|
191 |
+
|
192 |
+
$url = implode('/', array(
|
193 |
+
'https://api.bitbucket.org',
|
194 |
+
$version,
|
195 |
+
'repositories',
|
196 |
+
$this->username,
|
197 |
+
$this->repository,
|
198 |
+
$url
|
199 |
+
));
|
200 |
+
$baseUrl = $url;
|
201 |
+
|
202 |
+
if ( $this->oauth ) {
|
203 |
+
$url = $this->oauth->sign($url,'GET');
|
204 |
+
}
|
205 |
+
|
206 |
+
$options = array('timeout' => 10);
|
207 |
+
if ( !empty($this->httpFilterName) ) {
|
208 |
+
$options = apply_filters($this->httpFilterName, $options);
|
209 |
+
}
|
210 |
+
$response = wp_remote_get($url, $options);
|
211 |
+
if ( is_wp_error($response) ) {
|
212 |
+
do_action('puc_api_error', $response, null, $url, $this->slug);
|
213 |
+
return $response;
|
214 |
+
}
|
215 |
+
|
216 |
+
$code = wp_remote_retrieve_response_code($response);
|
217 |
+
$body = wp_remote_retrieve_body($response);
|
218 |
+
if ( $code === 200 ) {
|
219 |
+
if ( $isSrcResource ) {
|
220 |
+
//Most responses are JSON-encoded, but src resources just
|
221 |
+
//return raw file contents.
|
222 |
+
$document = $body;
|
223 |
+
} else {
|
224 |
+
$document = json_decode($body);
|
225 |
+
}
|
226 |
+
return $document;
|
227 |
+
}
|
228 |
+
|
229 |
+
$error = new WP_Error(
|
230 |
+
'puc-bitbucket-http-error',
|
231 |
+
sprintf('BitBucket API error. Base URL: "%s", HTTP status code: %d.', $baseUrl, $code)
|
232 |
+
);
|
233 |
+
do_action('puc_api_error', $error, $response, $url, $this->slug);
|
234 |
+
|
235 |
+
return $error;
|
236 |
+
}
|
237 |
+
|
238 |
+
/**
|
239 |
+
* @param array $credentials
|
240 |
+
*/
|
241 |
+
public function setAuthentication($credentials) {
|
242 |
+
parent::setAuthentication($credentials);
|
243 |
+
|
244 |
+
if ( !empty($credentials) && !empty($credentials['consumer_key']) ) {
|
245 |
+
$this->oauth = new Puc_v4p10_OAuthSignature(
|
246 |
+
$credentials['consumer_key'],
|
247 |
+
$credentials['consumer_secret']
|
248 |
+
);
|
249 |
+
} else {
|
250 |
+
$this->oauth = null;
|
251 |
+
}
|
252 |
+
}
|
253 |
+
|
254 |
+
public function signDownloadUrl($url) {
|
255 |
+
//Add authentication data to download URLs. Since OAuth signatures incorporate
|
256 |
+
//timestamps, we have to do this immediately before inserting the update. Otherwise
|
257 |
+
//authentication could fail due to a stale timestamp.
|
258 |
+
if ( $this->oauth ) {
|
259 |
+
$url = $this->oauth->sign($url);
|
260 |
+
}
|
261 |
+
return $url;
|
262 |
+
}
|
263 |
+
}
|
264 |
+
|
265 |
+
endif;
|
updater/Puc/v4p10/Vcs/GitHubApi.php
ADDED
@@ -0,0 +1,441 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_Vcs_GitHubApi', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_Vcs_GitHubApi extends Puc_v4p10_Vcs_Api {
|
6 |
+
/**
|
7 |
+
* @var string GitHub username.
|
8 |
+
*/
|
9 |
+
protected $userName;
|
10 |
+
/**
|
11 |
+
* @var string GitHub repository name.
|
12 |
+
*/
|
13 |
+
protected $repositoryName;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* @var string Either a fully qualified repository URL, or just "user/repo-name".
|
17 |
+
*/
|
18 |
+
protected $repositoryUrl;
|
19 |
+
|
20 |
+
/**
|
21 |
+
* @var string GitHub authentication token. Optional.
|
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 |
+
/**
|
41 |
+
* @var bool
|
42 |
+
*/
|
43 |
+
private $downloadFilterAdded = false;
|
44 |
+
|
45 |
+
public function __construct($repositoryUrl, $accessToken = null) {
|
46 |
+
$path = parse_url($repositoryUrl, PHP_URL_PATH);
|
47 |
+
if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) {
|
48 |
+
$this->userName = $matches['username'];
|
49 |
+
$this->repositoryName = $matches['repository'];
|
50 |
+
} else {
|
51 |
+
throw new InvalidArgumentException('Invalid GitHub repository URL: "' . $repositoryUrl . '"');
|
52 |
+
}
|
53 |
+
|
54 |
+
parent::__construct($repositoryUrl, $accessToken);
|
55 |
+
}
|
56 |
+
|
57 |
+
/**
|
58 |
+
* Get the latest release from GitHub.
|
59 |
+
*
|
60 |
+
* @return Puc_v4p10_Vcs_Reference|null
|
61 |
+
*/
|
62 |
+
public function getLatestRelease() {
|
63 |
+
$release = $this->api('/repos/:user/:repo/releases/latest');
|
64 |
+
if ( is_wp_error($release) || !is_object($release) || !isset($release->tag_name) ) {
|
65 |
+
return null;
|
66 |
+
}
|
67 |
+
|
68 |
+
$reference = new Puc_v4p10_Vcs_Reference(array(
|
69 |
+
'name' => $release->tag_name,
|
70 |
+
'version' => ltrim($release->tag_name, 'v'), //Remove the "v" prefix from "v1.2.3".
|
71 |
+
'downloadUrl' => $release->zipball_url,
|
72 |
+
'updated' => $release->created_at,
|
73 |
+
'apiResponse' => $release,
|
74 |
+
));
|
75 |
+
|
76 |
+
if ( isset($release->assets[0]) ) {
|
77 |
+
$reference->downloadCount = $release->assets[0]->download_count;
|
78 |
+
}
|
79 |
+
|
80 |
+
if ( $this->releaseAssetsEnabled && isset($release->assets, $release->assets[0]) ) {
|
81 |
+
//Use the first release asset that matches the specified regular expression.
|
82 |
+
$matchingAssets = array_filter($release->assets, array($this, 'matchesAssetFilter'));
|
83 |
+
if ( !empty($matchingAssets) ) {
|
84 |
+
if ( $this->isAuthenticationEnabled() ) {
|
85 |
+
/**
|
86 |
+
* Keep in mind that we'll need to add an "Accept" header to download this asset.
|
87 |
+
*
|
88 |
+
* @see setUpdateDownloadHeaders()
|
89 |
+
*/
|
90 |
+
$reference->downloadUrl = $matchingAssets[0]->url;
|
91 |
+
} else {
|
92 |
+
//It seems that browser_download_url only works for public repositories.
|
93 |
+
//Using an access_token doesn't help. Maybe OAuth would work?
|
94 |
+
$reference->downloadUrl = $matchingAssets[0]->browser_download_url;
|
95 |
+
}
|
96 |
+
|
97 |
+
$reference->downloadCount = $matchingAssets[0]->download_count;
|
98 |
+
}
|
99 |
+
}
|
100 |
+
|
101 |
+
if ( !empty($release->body) ) {
|
102 |
+
/** @noinspection PhpUndefinedClassInspection */
|
103 |
+
$reference->changelog = Parsedown::instance()->text($release->body);
|
104 |
+
}
|
105 |
+
|
106 |
+
return $reference;
|
107 |
+
}
|
108 |
+
|
109 |
+
/**
|
110 |
+
* Get the tag that looks like the highest version number.
|
111 |
+
*
|
112 |
+
* @return Puc_v4p10_Vcs_Reference|null
|
113 |
+
*/
|
114 |
+
public function getLatestTag() {
|
115 |
+
$tags = $this->api('/repos/:user/:repo/tags');
|
116 |
+
|
117 |
+
if ( is_wp_error($tags) || !is_array($tags) ) {
|
118 |
+
return null;
|
119 |
+
}
|
120 |
+
|
121 |
+
$versionTags = $this->sortTagsByVersion($tags);
|
122 |
+
if ( empty($versionTags) ) {
|
123 |
+
return null;
|
124 |
+
}
|
125 |
+
|
126 |
+
$tag = $versionTags[0];
|
127 |
+
return new Puc_v4p10_Vcs_Reference(array(
|
128 |
+
'name' => $tag->name,
|
129 |
+
'version' => ltrim($tag->name, 'v'),
|
130 |
+
'downloadUrl' => $tag->zipball_url,
|
131 |
+
'apiResponse' => $tag,
|
132 |
+
));
|
133 |
+
}
|
134 |
+
|
135 |
+
/**
|
136 |
+
* Get a branch by name.
|
137 |
+
*
|
138 |
+
* @param string $branchName
|
139 |
+
* @return null|Puc_v4p10_Vcs_Reference
|
140 |
+
*/
|
141 |
+
public function getBranch($branchName) {
|
142 |
+
$branch = $this->api('/repos/:user/:repo/branches/' . $branchName);
|
143 |
+
if ( is_wp_error($branch) || empty($branch) ) {
|
144 |
+
return null;
|
145 |
+
}
|
146 |
+
|
147 |
+
$reference = new Puc_v4p10_Vcs_Reference(array(
|
148 |
+
'name' => $branch->name,
|
149 |
+
'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name),
|
150 |
+
'apiResponse' => $branch,
|
151 |
+
));
|
152 |
+
|
153 |
+
if ( isset($branch->commit, $branch->commit->commit, $branch->commit->commit->author->date) ) {
|
154 |
+
$reference->updated = $branch->commit->commit->author->date;
|
155 |
+
}
|
156 |
+
|
157 |
+
return $reference;
|
158 |
+
}
|
159 |
+
|
160 |
+
/**
|
161 |
+
* Get the latest commit that changed the specified file.
|
162 |
+
*
|
163 |
+
* @param string $filename
|
164 |
+
* @param string $ref Reference name (e.g. branch or tag).
|
165 |
+
* @return StdClass|null
|
166 |
+
*/
|
167 |
+
public function getLatestCommit($filename, $ref = 'master') {
|
168 |
+
$commits = $this->api(
|
169 |
+
'/repos/:user/:repo/commits',
|
170 |
+
array(
|
171 |
+
'path' => $filename,
|
172 |
+
'sha' => $ref,
|
173 |
+
)
|
174 |
+
);
|
175 |
+
if ( !is_wp_error($commits) && isset($commits[0]) ) {
|
176 |
+
return $commits[0];
|
177 |
+
}
|
178 |
+
return null;
|
179 |
+
}
|
180 |
+
|
181 |
+
/**
|
182 |
+
* Get the timestamp of the latest commit that changed the specified branch or tag.
|
183 |
+
*
|
184 |
+
* @param string $ref Reference name (e.g. branch or tag).
|
185 |
+
* @return string|null
|
186 |
+
*/
|
187 |
+
public function getLatestCommitTime($ref) {
|
188 |
+
$commits = $this->api('/repos/:user/:repo/commits', array('sha' => $ref));
|
189 |
+
if ( !is_wp_error($commits) && isset($commits[0]) ) {
|
190 |
+
return $commits[0]->commit->author->date;
|
191 |
+
}
|
192 |
+
return null;
|
193 |
+
}
|
194 |
+
|
195 |
+
/**
|
196 |
+
* Perform a GitHub API request.
|
197 |
+
*
|
198 |
+
* @param string $url
|
199 |
+
* @param array $queryParams
|
200 |
+
* @return mixed|WP_Error
|
201 |
+
*/
|
202 |
+
protected function api($url, $queryParams = array()) {
|
203 |
+
$baseUrl = $url;
|
204 |
+
$url = $this->buildApiUrl($url, $queryParams);
|
205 |
+
|
206 |
+
$options = array('timeout' => 10);
|
207 |
+
if ( $this->isAuthenticationEnabled() ) {
|
208 |
+
$options['headers'] = array('Authorization' => $this->getAuthorizationHeader());
|
209 |
+
}
|
210 |
+
|
211 |
+
if ( !empty($this->httpFilterName) ) {
|
212 |
+
$options = apply_filters($this->httpFilterName, $options);
|
213 |
+
}
|
214 |
+
$response = wp_remote_get($url, $options);
|
215 |
+
if ( is_wp_error($response) ) {
|
216 |
+
do_action('puc_api_error', $response, null, $url, $this->slug);
|
217 |
+
return $response;
|
218 |
+
}
|
219 |
+
|
220 |
+
$code = wp_remote_retrieve_response_code($response);
|
221 |
+
$body = wp_remote_retrieve_body($response);
|
222 |
+
if ( $code === 200 ) {
|
223 |
+
$document = json_decode($body);
|
224 |
+
return $document;
|
225 |
+
}
|
226 |
+
|
227 |
+
$error = new WP_Error(
|
228 |
+
'puc-github-http-error',
|
229 |
+
sprintf('GitHub API error. Base URL: "%s", HTTP status code: %d.', $baseUrl, $code)
|
230 |
+
);
|
231 |
+
do_action('puc_api_error', $error, $response, $url, $this->slug);
|
232 |
+
|
233 |
+
return $error;
|
234 |
+
}
|
235 |
+
|
236 |
+
/**
|
237 |
+
* Build a fully qualified URL for an API request.
|
238 |
+
*
|
239 |
+
* @param string $url
|
240 |
+
* @param array $queryParams
|
241 |
+
* @return string
|
242 |
+
*/
|
243 |
+
protected function buildApiUrl($url, $queryParams) {
|
244 |
+
$variables = array(
|
245 |
+
'user' => $this->userName,
|
246 |
+
'repo' => $this->repositoryName,
|
247 |
+
);
|
248 |
+
foreach ($variables as $name => $value) {
|
249 |
+
$url = str_replace('/:' . $name, '/' . urlencode($value), $url);
|
250 |
+
}
|
251 |
+
$url = 'https://api.github.com' . $url;
|
252 |
+
|
253 |
+
if ( !empty($queryParams) ) {
|
254 |
+
$url = add_query_arg($queryParams, $url);
|
255 |
+
}
|
256 |
+
|
257 |
+
return $url;
|
258 |
+
}
|
259 |
+
|
260 |
+
/**
|
261 |
+
* Get the contents of a file from a specific branch or tag.
|
262 |
+
*
|
263 |
+
* @param string $path File name.
|
264 |
+
* @param string $ref
|
265 |
+
* @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
|
266 |
+
*/
|
267 |
+
public function getRemoteFile($path, $ref = 'master') {
|
268 |
+
$apiUrl = '/repos/:user/:repo/contents/' . $path;
|
269 |
+
$response = $this->api($apiUrl, array('ref' => $ref));
|
270 |
+
|
271 |
+
if ( is_wp_error($response) || !isset($response->content) || ($response->encoding !== 'base64') ) {
|
272 |
+
return null;
|
273 |
+
}
|
274 |
+
return base64_decode($response->content);
|
275 |
+
}
|
276 |
+
|
277 |
+
/**
|
278 |
+
* Generate a URL to download a ZIP archive of the specified branch/tag/etc.
|
279 |
+
*
|
280 |
+
* @param string $ref
|
281 |
+
* @return string
|
282 |
+
*/
|
283 |
+
public function buildArchiveDownloadUrl($ref = 'master') {
|
284 |
+
$url = sprintf(
|
285 |
+
'https://api.github.com/repos/%1$s/%2$s/zipball/%3$s',
|
286 |
+
urlencode($this->userName),
|
287 |
+
urlencode($this->repositoryName),
|
288 |
+
urlencode($ref)
|
289 |
+
);
|
290 |
+
return $url;
|
291 |
+
}
|
292 |
+
|
293 |
+
/**
|
294 |
+
* Get a specific tag.
|
295 |
+
*
|
296 |
+
* @param string $tagName
|
297 |
+
* @return void
|
298 |
+
*/
|
299 |
+
public function getTag($tagName) {
|
300 |
+
//The current GitHub update checker doesn't use getTag, so I didn't bother to implement it.
|
301 |
+
throw new LogicException('The ' . __METHOD__ . ' method is not implemented and should not be used.');
|
302 |
+
}
|
303 |
+
|
304 |
+
public function setAuthentication($credentials) {
|
305 |
+
parent::setAuthentication($credentials);
|
306 |
+
$this->accessToken = is_string($credentials) ? $credentials : null;
|
307 |
+
|
308 |
+
//Optimization: Instead of filtering all HTTP requests, let's do it only when
|
309 |
+
//WordPress is about to download an update.
|
310 |
+
add_filter('upgrader_pre_download', array($this, 'addHttpRequestFilter'), 10, 1); //WP 3.7+
|
311 |
+
}
|
312 |
+
|
313 |
+
/**
|
314 |
+
* Figure out which reference (i.e tag or branch) contains the latest version.
|
315 |
+
*
|
316 |
+
* @param string $configBranch Start looking in this branch.
|
317 |
+
* @return null|Puc_v4p10_Vcs_Reference
|
318 |
+
*/
|
319 |
+
public function chooseReference($configBranch) {
|
320 |
+
$updateSource = null;
|
321 |
+
|
322 |
+
if ( $configBranch === 'master' ) {
|
323 |
+
//Use the latest release.
|
324 |
+
$updateSource = $this->getLatestRelease();
|
325 |
+
if ( $updateSource === null ) {
|
326 |
+
//Failing that, use the tag with the highest version number.
|
327 |
+
$updateSource = $this->getLatestTag();
|
328 |
+
}
|
329 |
+
}
|
330 |
+
//Alternatively, just use the branch itself.
|
331 |
+
if ( empty($updateSource) ) {
|
332 |
+
$updateSource = $this->getBranch($configBranch);
|
333 |
+
}
|
334 |
+
|
335 |
+
return $updateSource;
|
336 |
+
}
|
337 |
+
|
338 |
+
/**
|
339 |
+
* Enable updating via release assets.
|
340 |
+
*
|
341 |
+
* If the latest release contains no usable assets, the update checker
|
342 |
+
* will fall back to using the automatically generated ZIP archive.
|
343 |
+
*
|
344 |
+
* Private repositories will only work with WordPress 3.7 or later.
|
345 |
+
*
|
346 |
+
* @param string|null $fileNameRegex Optional. Use only those assets where the file name matches this regex.
|
347 |
+
*/
|
348 |
+
public function enableReleaseAssets($fileNameRegex = null) {
|
349 |
+
$this->releaseAssetsEnabled = true;
|
350 |
+
$this->assetFilterRegex = $fileNameRegex;
|
351 |
+
$this->assetApiBaseUrl = sprintf(
|
352 |
+
'//api.github.com/repos/%1$s/%2$s/releases/assets/',
|
353 |
+
$this->userName,
|
354 |
+
$this->repositoryName
|
355 |
+
);
|
356 |
+
}
|
357 |
+
|
358 |
+
/**
|
359 |
+
* Does this asset match the file name regex?
|
360 |
+
*
|
361 |
+
* @param stdClass $releaseAsset
|
362 |
+
* @return bool
|
363 |
+
*/
|
364 |
+
protected function matchesAssetFilter($releaseAsset) {
|
365 |
+
if ( $this->assetFilterRegex === null ) {
|
366 |
+
//The default is to accept all assets.
|
367 |
+
return true;
|
368 |
+
}
|
369 |
+
return isset($releaseAsset->name) && preg_match($this->assetFilterRegex, $releaseAsset->name);
|
370 |
+
}
|
371 |
+
|
372 |
+
/**
|
373 |
+
* @internal
|
374 |
+
* @param bool $result
|
375 |
+
* @return bool
|
376 |
+
*/
|
377 |
+
public function addHttpRequestFilter($result) {
|
378 |
+
if ( !$this->downloadFilterAdded && $this->isAuthenticationEnabled() ) {
|
379 |
+
add_filter('http_request_args', array($this, 'setUpdateDownloadHeaders'), 10, 2);
|
380 |
+
add_action('requests-requests.before_redirect', array($this, 'removeAuthHeaderFromRedirects'), 10, 4);
|
381 |
+
$this->downloadFilterAdded = true;
|
382 |
+
}
|
383 |
+
return $result;
|
384 |
+
}
|
385 |
+
|
386 |
+
/**
|
387 |
+
* Set the HTTP headers that are necessary to download updates from private repositories.
|
388 |
+
*
|
389 |
+
* See GitHub docs:
|
390 |
+
* @link https://developer.github.com/v3/repos/releases/#get-a-single-release-asset
|
391 |
+
* @link https://developer.github.com/v3/auth/#basic-authentication
|
392 |
+
*
|
393 |
+
* @internal
|
394 |
+
* @param array $requestArgs
|
395 |
+
* @param string $url
|
396 |
+
* @return array
|
397 |
+
*/
|
398 |
+
public function setUpdateDownloadHeaders($requestArgs, $url = '') {
|
399 |
+
//Is WordPress trying to download one of our release assets?
|
400 |
+
if ( $this->releaseAssetsEnabled && (strpos($url, $this->assetApiBaseUrl) !== false) ) {
|
401 |
+
$requestArgs['headers']['Accept'] = 'application/octet-stream';
|
402 |
+
}
|
403 |
+
//Use Basic authentication, but only if the download is from our repository.
|
404 |
+
$repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array());
|
405 |
+
if ( $this->isAuthenticationEnabled() && (strpos($url, $repoApiBaseUrl)) === 0 ) {
|
406 |
+
$requestArgs['headers']['Authorization'] = $this->getAuthorizationHeader();
|
407 |
+
}
|
408 |
+
return $requestArgs;
|
409 |
+
}
|
410 |
+
|
411 |
+
/**
|
412 |
+
* When following a redirect, the Requests library will automatically forward
|
413 |
+
* the authorization header to other hosts. We don't want that because it breaks
|
414 |
+
* AWS downloads and can leak authorization information.
|
415 |
+
*
|
416 |
+
* @internal
|
417 |
+
* @param string $location
|
418 |
+
* @param array $headers
|
419 |
+
*/
|
420 |
+
public function removeAuthHeaderFromRedirects(&$location, &$headers) {
|
421 |
+
$repoApiBaseUrl = $this->buildApiUrl('/repos/:user/:repo/', array());
|
422 |
+
if ( strpos($location, $repoApiBaseUrl) === 0 ) {
|
423 |
+
return; //This request is going to GitHub, so it's fine.
|
424 |
+
}
|
425 |
+
//Remove the header.
|
426 |
+
if ( isset($headers['Authorization']) ) {
|
427 |
+
unset($headers['Authorization']);
|
428 |
+
}
|
429 |
+
}
|
430 |
+
|
431 |
+
/**
|
432 |
+
* Generate the value of the "Authorization" header.
|
433 |
+
*
|
434 |
+
* @return string
|
435 |
+
*/
|
436 |
+
protected function getAuthorizationHeader() {
|
437 |
+
return 'Basic ' . base64_encode($this->userName . ':' . $this->accessToken);
|
438 |
+
}
|
439 |
+
}
|
440 |
+
|
441 |
+
endif;
|
updater/Puc/v4p10/Vcs/GitLabApi.php
ADDED
@@ -0,0 +1,309 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_Vcs_GitLabApi', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_Vcs_GitLabApi extends Puc_v4p10_Vcs_Api {
|
6 |
+
/**
|
7 |
+
* @var string GitLab username.
|
8 |
+
*/
|
9 |
+
protected $userName;
|
10 |
+
|
11 |
+
/**
|
12 |
+
* @var string GitLab server host.
|
13 |
+
*/
|
14 |
+
protected $repositoryHost;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* @var string Protocol used by this GitLab server: "http" or "https".
|
18 |
+
*/
|
19 |
+
protected $repositoryProtocol = 'https';
|
20 |
+
|
21 |
+
/**
|
22 |
+
* @var string GitLab repository name.
|
23 |
+
*/
|
24 |
+
protected $repositoryName;
|
25 |
+
|
26 |
+
/**
|
27 |
+
* @var string GitLab authentication token. Optional.
|
28 |
+
*/
|
29 |
+
protected $accessToken;
|
30 |
+
|
31 |
+
public function __construct($repositoryUrl, $accessToken = null, $subgroup = null) {
|
32 |
+
//Parse the repository host to support custom hosts.
|
33 |
+
$port = parse_url($repositoryUrl, PHP_URL_PORT);
|
34 |
+
if ( !empty($port) ) {
|
35 |
+
$port = ':' . $port;
|
36 |
+
}
|
37 |
+
$this->repositoryHost = parse_url($repositoryUrl, PHP_URL_HOST) . $port;
|
38 |
+
|
39 |
+
if ( $this->repositoryHost !== 'gitlab.com' ) {
|
40 |
+
$this->repositoryProtocol = parse_url($repositoryUrl, PHP_URL_SCHEME);
|
41 |
+
}
|
42 |
+
|
43 |
+
//Find the repository information
|
44 |
+
$path = parse_url($repositoryUrl, PHP_URL_PATH);
|
45 |
+
if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) {
|
46 |
+
$this->userName = $matches['username'];
|
47 |
+
$this->repositoryName = $matches['repository'];
|
48 |
+
} elseif ( ($this->repositoryHost === 'gitlab.com') ) {
|
49 |
+
//This is probably a repository in a subgroup, e.g. "/organization/category/repo".
|
50 |
+
$parts = explode('/', trim($path, '/'));
|
51 |
+
if ( count($parts) < 3 ) {
|
52 |
+
throw new InvalidArgumentException('Invalid GitLab.com repository URL: "' . $repositoryUrl . '"');
|
53 |
+
}
|
54 |
+
$lastPart = array_pop($parts);
|
55 |
+
$this->userName = implode('/', $parts);
|
56 |
+
$this->repositoryName = $lastPart;
|
57 |
+
} else {
|
58 |
+
//There could be subgroups in the URL: gitlab.domain.com/group/subgroup/subgroup2/repository
|
59 |
+
if ( $subgroup !== null ) {
|
60 |
+
$path = str_replace(trailingslashit($subgroup), '', $path);
|
61 |
+
}
|
62 |
+
|
63 |
+
//This is not a traditional url, it could be gitlab is in a deeper subdirectory.
|
64 |
+
//Get the path segments.
|
65 |
+
$segments = explode('/', untrailingslashit(ltrim($path, '/')));
|
66 |
+
|
67 |
+
//We need at least /user-name/repository-name/
|
68 |
+
if ( count($segments) < 2 ) {
|
69 |
+
throw new InvalidArgumentException('Invalid GitLab repository URL: "' . $repositoryUrl . '"');
|
70 |
+
}
|
71 |
+
|
72 |
+
//Get the username and repository name.
|
73 |
+
$usernameRepo = array_splice($segments, -2, 2);
|
74 |
+
$this->userName = $usernameRepo[0];
|
75 |
+
$this->repositoryName = $usernameRepo[1];
|
76 |
+
|
77 |
+
//Append the remaining segments to the host if there are segments left.
|
78 |
+
if ( count($segments) > 0 ) {
|
79 |
+
$this->repositoryHost = trailingslashit($this->repositoryHost) . implode('/', $segments);
|
80 |
+
}
|
81 |
+
|
82 |
+
//Add subgroups to username.
|
83 |
+
if ( $subgroup !== null ) {
|
84 |
+
$this->userName = $usernameRepo[0] . '/' . untrailingslashit($subgroup);
|
85 |
+
}
|
86 |
+
}
|
87 |
+
|
88 |
+
parent::__construct($repositoryUrl, $accessToken);
|
89 |
+
}
|
90 |
+
|
91 |
+
/**
|
92 |
+
* Get the latest release from GitLab.
|
93 |
+
*
|
94 |
+
* @return Puc_v4p10_Vcs_Reference|null
|
95 |
+
*/
|
96 |
+
public function getLatestRelease() {
|
97 |
+
return $this->getLatestTag();
|
98 |
+
}
|
99 |
+
|
100 |
+
/**
|
101 |
+
* Get the tag that looks like the highest version number.
|
102 |
+
*
|
103 |
+
* @return Puc_v4p10_Vcs_Reference|null
|
104 |
+
*/
|
105 |
+
public function getLatestTag() {
|
106 |
+
$tags = $this->api('/:id/repository/tags');
|
107 |
+
if ( is_wp_error($tags) || empty($tags) || !is_array($tags) ) {
|
108 |
+
return null;
|
109 |
+
}
|
110 |
+
|
111 |
+
$versionTags = $this->sortTagsByVersion($tags);
|
112 |
+
if ( empty($versionTags) ) {
|
113 |
+
return null;
|
114 |
+
}
|
115 |
+
|
116 |
+
$tag = $versionTags[0];
|
117 |
+
return new Puc_v4p10_Vcs_Reference(array(
|
118 |
+
'name' => $tag->name,
|
119 |
+
'version' => ltrim($tag->name, 'v'),
|
120 |
+
'downloadUrl' => $this->buildArchiveDownloadUrl($tag->name),
|
121 |
+
'apiResponse' => $tag,
|
122 |
+
));
|
123 |
+
}
|
124 |
+
|
125 |
+
/**
|
126 |
+
* Get a branch by name.
|
127 |
+
*
|
128 |
+
* @param string $branchName
|
129 |
+
* @return null|Puc_v4p10_Vcs_Reference
|
130 |
+
*/
|
131 |
+
public function getBranch($branchName) {
|
132 |
+
$branch = $this->api('/:id/repository/branches/' . $branchName);
|
133 |
+
if ( is_wp_error($branch) || empty($branch) ) {
|
134 |
+
return null;
|
135 |
+
}
|
136 |
+
|
137 |
+
$reference = new Puc_v4p10_Vcs_Reference(array(
|
138 |
+
'name' => $branch->name,
|
139 |
+
'downloadUrl' => $this->buildArchiveDownloadUrl($branch->name),
|
140 |
+
'apiResponse' => $branch,
|
141 |
+
));
|
142 |
+
|
143 |
+
if ( isset($branch->commit, $branch->commit->committed_date) ) {
|
144 |
+
$reference->updated = $branch->commit->committed_date;
|
145 |
+
}
|
146 |
+
|
147 |
+
return $reference;
|
148 |
+
}
|
149 |
+
|
150 |
+
/**
|
151 |
+
* Get the timestamp of the latest commit that changed the specified branch or tag.
|
152 |
+
*
|
153 |
+
* @param string $ref Reference name (e.g. branch or tag).
|
154 |
+
* @return string|null
|
155 |
+
*/
|
156 |
+
public function getLatestCommitTime($ref) {
|
157 |
+
$commits = $this->api('/:id/repository/commits/', array('ref_name' => $ref));
|
158 |
+
if ( is_wp_error($commits) || !is_array($commits) || !isset($commits[0]) ) {
|
159 |
+
return null;
|
160 |
+
}
|
161 |
+
|
162 |
+
return $commits[0]->committed_date;
|
163 |
+
}
|
164 |
+
|
165 |
+
/**
|
166 |
+
* Perform a GitLab API request.
|
167 |
+
*
|
168 |
+
* @param string $url
|
169 |
+
* @param array $queryParams
|
170 |
+
* @return mixed|WP_Error
|
171 |
+
*/
|
172 |
+
protected function api($url, $queryParams = array()) {
|
173 |
+
$baseUrl = $url;
|
174 |
+
$url = $this->buildApiUrl($url, $queryParams);
|
175 |
+
|
176 |
+
$options = array('timeout' => 10);
|
177 |
+
if ( !empty($this->httpFilterName) ) {
|
178 |
+
$options = apply_filters($this->httpFilterName, $options);
|
179 |
+
}
|
180 |
+
|
181 |
+
$response = wp_remote_get($url, $options);
|
182 |
+
if ( is_wp_error($response) ) {
|
183 |
+
do_action('puc_api_error', $response, null, $url, $this->slug);
|
184 |
+
return $response;
|
185 |
+
}
|
186 |
+
|
187 |
+
$code = wp_remote_retrieve_response_code($response);
|
188 |
+
$body = wp_remote_retrieve_body($response);
|
189 |
+
if ( $code === 200 ) {
|
190 |
+
return json_decode($body);
|
191 |
+
}
|
192 |
+
|
193 |
+
$error = new WP_Error(
|
194 |
+
'puc-gitlab-http-error',
|
195 |
+
sprintf('GitLab API error. URL: "%s", HTTP status code: %d.', $baseUrl, $code)
|
196 |
+
);
|
197 |
+
do_action('puc_api_error', $error, $response, $url, $this->slug);
|
198 |
+
|
199 |
+
return $error;
|
200 |
+
}
|
201 |
+
|
202 |
+
/**
|
203 |
+
* Build a fully qualified URL for an API request.
|
204 |
+
*
|
205 |
+
* @param string $url
|
206 |
+
* @param array $queryParams
|
207 |
+
* @return string
|
208 |
+
*/
|
209 |
+
protected function buildApiUrl($url, $queryParams) {
|
210 |
+
$variables = array(
|
211 |
+
'user' => $this->userName,
|
212 |
+
'repo' => $this->repositoryName,
|
213 |
+
'id' => $this->userName . '/' . $this->repositoryName,
|
214 |
+
);
|
215 |
+
|
216 |
+
foreach ($variables as $name => $value) {
|
217 |
+
$url = str_replace("/:{$name}", '/' . urlencode($value), $url);
|
218 |
+
}
|
219 |
+
|
220 |
+
$url = substr($url, 1);
|
221 |
+
$url = sprintf('%1$s://%2$s/api/v4/projects/%3$s', $this->repositoryProtocol, $this->repositoryHost, $url);
|
222 |
+
|
223 |
+
if ( !empty($this->accessToken) ) {
|
224 |
+
$queryParams['private_token'] = $this->accessToken;
|
225 |
+
}
|
226 |
+
|
227 |
+
if ( !empty($queryParams) ) {
|
228 |
+
$url = add_query_arg($queryParams, $url);
|
229 |
+
}
|
230 |
+
|
231 |
+
return $url;
|
232 |
+
}
|
233 |
+
|
234 |
+
/**
|
235 |
+
* Get the contents of a file from a specific branch or tag.
|
236 |
+
*
|
237 |
+
* @param string $path File name.
|
238 |
+
* @param string $ref
|
239 |
+
* @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
|
240 |
+
*/
|
241 |
+
public function getRemoteFile($path, $ref = 'master') {
|
242 |
+
$response = $this->api('/:id/repository/files/' . $path, array('ref' => $ref));
|
243 |
+
if ( is_wp_error($response) || !isset($response->content) || $response->encoding !== 'base64' ) {
|
244 |
+
return null;
|
245 |
+
}
|
246 |
+
|
247 |
+
return base64_decode($response->content);
|
248 |
+
}
|
249 |
+
|
250 |
+
/**
|
251 |
+
* Generate a URL to download a ZIP archive of the specified branch/tag/etc.
|
252 |
+
*
|
253 |
+
* @param string $ref
|
254 |
+
* @return string
|
255 |
+
*/
|
256 |
+
public function buildArchiveDownloadUrl($ref = 'master') {
|
257 |
+
$url = sprintf(
|
258 |
+
'%1$s://%2$s/api/v4/projects/%3$s/repository/archive.zip',
|
259 |
+
$this->repositoryProtocol,
|
260 |
+
$this->repositoryHost,
|
261 |
+
urlencode($this->userName . '/' . $this->repositoryName)
|
262 |
+
);
|
263 |
+
$url = add_query_arg('sha', urlencode($ref), $url);
|
264 |
+
|
265 |
+
if ( !empty($this->accessToken) ) {
|
266 |
+
$url = add_query_arg('private_token', $this->accessToken, $url);
|
267 |
+
}
|
268 |
+
|
269 |
+
return $url;
|
270 |
+
}
|
271 |
+
|
272 |
+
/**
|
273 |
+
* Get a specific tag.
|
274 |
+
*
|
275 |
+
* @param string $tagName
|
276 |
+
* @return void
|
277 |
+
*/
|
278 |
+
public function getTag($tagName) {
|
279 |
+
throw new LogicException('The ' . __METHOD__ . ' method is not implemented and should not be used.');
|
280 |
+
}
|
281 |
+
|
282 |
+
/**
|
283 |
+
* Figure out which reference (i.e tag or branch) contains the latest version.
|
284 |
+
*
|
285 |
+
* @param string $configBranch Start looking in this branch.
|
286 |
+
* @return null|Puc_v4p10_Vcs_Reference
|
287 |
+
*/
|
288 |
+
public function chooseReference($configBranch) {
|
289 |
+
$updateSource = null;
|
290 |
+
|
291 |
+
// GitLab doesn't handle releases the same as GitHub so just use the latest tag
|
292 |
+
if ( $configBranch === 'master' ) {
|
293 |
+
$updateSource = $this->getLatestTag();
|
294 |
+
}
|
295 |
+
|
296 |
+
if ( empty($updateSource) ) {
|
297 |
+
$updateSource = $this->getBranch($configBranch);
|
298 |
+
}
|
299 |
+
|
300 |
+
return $updateSource;
|
301 |
+
}
|
302 |
+
|
303 |
+
public function setAuthentication($credentials) {
|
304 |
+
parent::setAuthentication($credentials);
|
305 |
+
$this->accessToken = is_string($credentials) ? $credentials : null;
|
306 |
+
}
|
307 |
+
}
|
308 |
+
|
309 |
+
endif;
|
updater/Puc/v4p10/Vcs/PluginUpdateChecker.php
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Vcs_PluginUpdateChecker') ):
|
3 |
+
|
4 |
+
class Puc_v4p10_Vcs_PluginUpdateChecker extends Puc_v4p10_Plugin_UpdateChecker implements Puc_v4p10_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_v4p10_Vcs_Api Repository API client.
|
12 |
+
*/
|
13 |
+
protected $api = null;
|
14 |
+
|
15 |
+
/**
|
16 |
+
* Puc_v4p10_Vcs_PluginUpdateChecker constructor.
|
17 |
+
*
|
18 |
+
* @param Puc_v4p10_Vcs_Api $api
|
19 |
+
* @param string $pluginFile
|
20 |
+
* @param string $slug
|
21 |
+
* @param int $checkPeriod
|
22 |
+
* @param string $optionName
|
23 |
+
* @param string $muPluginFile
|
24 |
+
*/
|
25 |
+
public function __construct($api, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') {
|
26 |
+
$this->api = $api;
|
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) {
|
35 |
+
//We have to make several remote API requests to gather all the necessary info
|
36 |
+
//which can take a while on slow networks.
|
37 |
+
if ( function_exists('set_time_limit') ) {
|
38 |
+
@set_time_limit(60);
|
39 |
+
}
|
40 |
+
|
41 |
+
$api = $this->api;
|
42 |
+
$api->setLocalDirectory($this->package->getAbsoluteDirectoryPath());
|
43 |
+
|
44 |
+
$info = new Puc_v4p10_Plugin_Info();
|
45 |
+
$info->filename = $this->pluginFile;
|
46 |
+
$info->slug = $this->slug;
|
47 |
+
|
48 |
+
$this->setInfoFromHeader($this->package->getPluginHeader(), $info);
|
49 |
+
|
50 |
+
//Pick a branch or tag.
|
51 |
+
$updateSource = $api->chooseReference($this->branch);
|
52 |
+
if ( $updateSource ) {
|
53 |
+
$ref = $updateSource->name;
|
54 |
+
$info->version = $updateSource->version;
|
55 |
+
$info->last_updated = $updateSource->updated;
|
56 |
+
$info->download_url = $updateSource->downloadUrl;
|
57 |
+
|
58 |
+
if ( !empty($updateSource->changelog) ) {
|
59 |
+
$info->sections['changelog'] = $updateSource->changelog;
|
60 |
+
}
|
61 |
+
if ( isset($updateSource->downloadCount) ) {
|
62 |
+
$info->downloaded = $updateSource->downloadCount;
|
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 |
+
|
79 |
+
//Get headers from the main plugin file in this branch/tag. Its "Version" header and other metadata
|
80 |
+
//are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags.
|
81 |
+
$mainPluginFile = basename($this->pluginFile);
|
82 |
+
$remotePlugin = $api->getRemoteFile($mainPluginFile, $ref);
|
83 |
+
if ( !empty($remotePlugin) ) {
|
84 |
+
$remoteHeader = $this->package->getFileHeader($remotePlugin);
|
85 |
+
$this->setInfoFromHeader($remoteHeader, $info);
|
86 |
+
}
|
87 |
+
|
88 |
+
//Try parsing readme.txt. If it's formatted according to WordPress.org standards, it will contain
|
89 |
+
//a lot of useful information like the required/tested WP version, changelog, and so on.
|
90 |
+
if ( $this->readmeTxtExistsLocally() ) {
|
91 |
+
$this->setInfoFromRemoteReadme($ref, $info);
|
92 |
+
}
|
93 |
+
|
94 |
+
//The changelog might be in a separate file.
|
95 |
+
if ( empty($info->sections['changelog']) ) {
|
96 |
+
$info->sections['changelog'] = $api->getRemoteChangelog($ref, $this->package->getAbsoluteDirectoryPath());
|
97 |
+
if ( empty($info->sections['changelog']) ) {
|
98 |
+
$info->sections['changelog'] = __('There is no changelog available.', 'plugin-update-checker');
|
99 |
+
}
|
100 |
+
}
|
101 |
+
|
102 |
+
if ( empty($info->last_updated) ) {
|
103 |
+
//Fetch the latest commit that changed the tag or branch and use it as the "last_updated" date.
|
104 |
+
$latestCommitTime = $api->getLatestCommitTime($ref);
|
105 |
+
if ( $latestCommitTime !== null ) {
|
106 |
+
$info->last_updated = $latestCommitTime;
|
107 |
+
}
|
108 |
+
}
|
109 |
+
|
110 |
+
$info = apply_filters($this->getUniqueName('request_info_result'), $info, null);
|
111 |
+
return $info;
|
112 |
+
}
|
113 |
+
|
114 |
+
/**
|
115 |
+
* Check if the currently installed version has a readme.txt file.
|
116 |
+
*
|
117 |
+
* @return bool
|
118 |
+
*/
|
119 |
+
protected function readmeTxtExistsLocally() {
|
120 |
+
return $this->package->fileExists($this->api->getLocalReadmeName());
|
121 |
+
}
|
122 |
+
|
123 |
+
/**
|
124 |
+
* Copy plugin metadata from a file header to a Plugin Info object.
|
125 |
+
*
|
126 |
+
* @param array $fileHeader
|
127 |
+
* @param Puc_v4p10_Plugin_Info $pluginInfo
|
128 |
+
*/
|
129 |
+
protected function setInfoFromHeader($fileHeader, $pluginInfo) {
|
130 |
+
$headerToPropertyMap = array(
|
131 |
+
'Version' => 'version',
|
132 |
+
'Name' => 'name',
|
133 |
+
'PluginURI' => 'homepage',
|
134 |
+
'Author' => 'author',
|
135 |
+
'AuthorName' => 'author',
|
136 |
+
'AuthorURI' => 'author_homepage',
|
137 |
+
|
138 |
+
'Requires WP' => 'requires',
|
139 |
+
'Tested WP' => 'tested',
|
140 |
+
'Requires at least' => 'requires',
|
141 |
+
'Tested up to' => 'tested',
|
142 |
+
|
143 |
+
'Requires PHP' => 'requires_php',
|
144 |
+
);
|
145 |
+
foreach ($headerToPropertyMap as $headerName => $property) {
|
146 |
+
if ( isset($fileHeader[$headerName]) && !empty($fileHeader[$headerName]) ) {
|
147 |
+
$pluginInfo->$property = $fileHeader[$headerName];
|
148 |
+
}
|
149 |
+
}
|
150 |
+
|
151 |
+
if ( !empty($fileHeader['Description']) ) {
|
152 |
+
$pluginInfo->sections['description'] = $fileHeader['Description'];
|
153 |
+
}
|
154 |
+
}
|
155 |
+
|
156 |
+
/**
|
157 |
+
* Copy plugin metadata from the remote readme.txt file.
|
158 |
+
*
|
159 |
+
* @param string $ref GitHub tag or branch where to look for the readme.
|
160 |
+
* @param Puc_v4p10_Plugin_Info $pluginInfo
|
161 |
+
*/
|
162 |
+
protected function setInfoFromRemoteReadme($ref, $pluginInfo) {
|
163 |
+
$readme = $this->api->getRemoteReadme($ref);
|
164 |
+
if ( empty($readme) ) {
|
165 |
+
return;
|
166 |
+
}
|
167 |
+
|
168 |
+
if ( isset($readme['sections']) ) {
|
169 |
+
$pluginInfo->sections = array_merge($pluginInfo->sections, $readme['sections']);
|
170 |
+
}
|
171 |
+
if ( !empty($readme['tested_up_to']) ) {
|
172 |
+
$pluginInfo->tested = $readme['tested_up_to'];
|
173 |
+
}
|
174 |
+
if ( !empty($readme['requires_at_least']) ) {
|
175 |
+
$pluginInfo->requires = $readme['requires_at_least'];
|
176 |
+
}
|
177 |
+
if ( !empty($readme['requires_php']) ) {
|
178 |
+
$pluginInfo->requires_php = $readme['requires_php'];
|
179 |
+
}
|
180 |
+
|
181 |
+
if ( isset($readme['upgrade_notice'], $readme['upgrade_notice'][$pluginInfo->version]) ) {
|
182 |
+
$pluginInfo->upgrade_notice = $readme['upgrade_notice'][$pluginInfo->version];
|
183 |
+
}
|
184 |
+
}
|
185 |
+
|
186 |
+
public function setBranch($branch) {
|
187 |
+
$this->branch = $branch;
|
188 |
+
return $this;
|
189 |
+
}
|
190 |
+
|
191 |
+
public function setAuthentication($credentials) {
|
192 |
+
$this->api->setAuthentication($credentials);
|
193 |
+
return $this;
|
194 |
+
}
|
195 |
+
|
196 |
+
public function getVcsApi() {
|
197 |
+
return $this->api;
|
198 |
+
}
|
199 |
+
|
200 |
+
public function getUpdate() {
|
201 |
+
$update = parent::getUpdate();
|
202 |
+
|
203 |
+
if ( isset($update) && !empty($update->download_url) ) {
|
204 |
+
$update->download_url = $this->api->signDownloadUrl($update->download_url);
|
205 |
+
}
|
206 |
+
|
207 |
+
return $update;
|
208 |
+
}
|
209 |
+
|
210 |
+
public function onDisplayConfiguration($panel) {
|
211 |
+
parent::onDisplayConfiguration($panel);
|
212 |
+
$panel->row('Branch', $this->branch);
|
213 |
+
$panel->row('Authentication enabled', $this->api->isAuthenticationEnabled() ? 'Yes' : 'No');
|
214 |
+
$panel->row('API client', get_class($this->api));
|
215 |
+
}
|
216 |
+
}
|
217 |
+
|
218 |
+
endif;
|
updater/Puc/v4p10/Vcs/Reference.php
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Puc_v4p10_Vcs_Reference', false) ):
|
3 |
+
|
4 |
+
/**
|
5 |
+
* This class represents a VCS branch or tag. It's intended as a read only, short-lived container
|
6 |
+
* that only exists to provide a limited degree of type checking.
|
7 |
+
*
|
8 |
+
* @property string $name
|
9 |
+
* @property string|null version
|
10 |
+
* @property string $downloadUrl
|
11 |
+
* @property string $updated
|
12 |
+
*
|
13 |
+
* @property string|null $changelog
|
14 |
+
* @property int|null $downloadCount
|
15 |
+
*/
|
16 |
+
class Puc_v4p10_Vcs_Reference {
|
17 |
+
private $properties = array();
|
18 |
+
|
19 |
+
public function __construct($properties = array()) {
|
20 |
+
$this->properties = $properties;
|
21 |
+
}
|
22 |
+
|
23 |
+
/**
|
24 |
+
* @param string $name
|
25 |
+
* @return mixed|null
|
26 |
+
*/
|
27 |
+
public function __get($name) {
|
28 |
+
return array_key_exists($name, $this->properties) ? $this->properties[$name] : null;
|
29 |
+
}
|
30 |
+
|
31 |
+
/**
|
32 |
+
* @param string $name
|
33 |
+
* @param mixed $value
|
34 |
+
*/
|
35 |
+
public function __set($name, $value) {
|
36 |
+
$this->properties[$name] = $value;
|
37 |
+
}
|
38 |
+
|
39 |
+
/**
|
40 |
+
* @param string $name
|
41 |
+
* @return bool
|
42 |
+
*/
|
43 |
+
public function __isset($name) {
|
44 |
+
return isset($this->properties[$name]);
|
45 |
+
}
|
46 |
+
|
47 |
+
}
|
48 |
+
|
49 |
+
endif;
|
updater/Puc/v4p10/Vcs/ThemeUpdateChecker.php
ADDED
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('Puc_v4p10_Vcs_ThemeUpdateChecker', false) ):
|
4 |
+
|
5 |
+
class Puc_v4p10_Vcs_ThemeUpdateChecker extends Puc_v4p10_Theme_UpdateChecker implements Puc_v4p10_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_v4p10_Vcs_Api Repository API client.
|
13 |
+
*/
|
14 |
+
protected $api = null;
|
15 |
+
|
16 |
+
/**
|
17 |
+
* Puc_v4p10_Vcs_ThemeUpdateChecker constructor.
|
18 |
+
*
|
19 |
+
* @param Puc_v4p10_Vcs_Api $api
|
20 |
+
* @param null $stylesheet
|
21 |
+
* @param null $customSlug
|
22 |
+
* @param int $checkPeriod
|
23 |
+
* @param string $optionName
|
24 |
+
*/
|
25 |
+
public function __construct($api, $stylesheet = null, $customSlug = null, $checkPeriod = 12, $optionName = '') {
|
26 |
+
$this->api = $api;
|
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->package->getAbsoluteDirectoryPath());
|
37 |
+
|
38 |
+
$update = new Puc_v4p10_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.
|
42 |
+
$updateSource = $api->chooseReference($this->branch);
|
43 |
+
if ( $updateSource ) {
|
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->package->getFileHeader($api->getRemoteFile('style.css', $ref));
|
63 |
+
$update->version = Puc_v4p10_Utils::findNotEmpty(array(
|
64 |
+
$remoteHeader['Version'],
|
65 |
+
Puc_v4p10_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_v4p10_Utils::findNotEmpty(array(
|
70 |
+
$remoteHeader['ThemeURI'],
|
71 |
+
$this->package->getHeaderValue('ThemeURI'),
|
72 |
+
$this->metadataUrl,
|
73 |
+
));
|
74 |
+
|
75 |
+
if ( empty($update->version) ) {
|
76 |
+
//It looks like we didn't find a valid update after all.
|
77 |
+
$update = null;
|
78 |
+
}
|
79 |
+
|
80 |
+
$update = $this->filterUpdateResult($update);
|
81 |
+
return $update;
|
82 |
+
}
|
83 |
+
|
84 |
+
//FIXME: This is duplicated code. Both theme and plugin subclasses that use VCS share these methods.
|
85 |
+
|
86 |
+
public function setBranch($branch) {
|
87 |
+
$this->branch = $branch;
|
88 |
+
return $this;
|
89 |
+
}
|
90 |
+
|
91 |
+
public function setAuthentication($credentials) {
|
92 |
+
$this->api->setAuthentication($credentials);
|
93 |
+
return $this;
|
94 |
+
}
|
95 |
+
|
96 |
+
public function getVcsApi() {
|
97 |
+
return $this->api;
|
98 |
+
}
|
99 |
+
|
100 |
+
public function getUpdate() {
|
101 |
+
$update = parent::getUpdate();
|
102 |
+
|
103 |
+
if ( isset($update) && !empty($update->download_url) ) {
|
104 |
+
$update->download_url = $this->api->signDownloadUrl($update->download_url);
|
105 |
+
}
|
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;
|
updater/css/puc-debug-bar.css
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.puc-debug-bar-panel-v4 pre {
|
2 |
+
margin-top: 0;
|
3 |
+
}
|
4 |
+
|
5 |
+
/* Style the debug data table to match "widefat" table style used by WordPress. */
|
6 |
+
table.puc-debug-data {
|
7 |
+
width: 100%;
|
8 |
+
clear: both;
|
9 |
+
margin: 0;
|
10 |
+
|
11 |
+
border-spacing: 0;
|
12 |
+
background-color: #f9f9f9;
|
13 |
+
|
14 |
+
border-radius: 3px;
|
15 |
+
border: 1px solid #dfdfdf;
|
16 |
+
border-collapse: separate;
|
17 |
+
}
|
18 |
+
|
19 |
+
table.puc-debug-data * {
|
20 |
+
word-wrap: break-word;
|
21 |
+
}
|
22 |
+
|
23 |
+
table.puc-debug-data th {
|
24 |
+
width: 11em;
|
25 |
+
padding: 7px 7px 8px;
|
26 |
+
text-align: left;
|
27 |
+
|
28 |
+
font-family: "Georgia", "Times New Roman", "Bitstream Charter", "Times", serif;
|
29 |
+
font-weight: 400;
|
30 |
+
font-size: 14px;
|
31 |
+
line-height: 1.3em;
|
32 |
+
text-shadow: rgba(255, 255, 255, 0.804) 0 1px 0;
|
33 |
+
}
|
34 |
+
|
35 |
+
table.puc-debug-data td, table.puc-debug-data th {
|
36 |
+
border-width: 1px 0;
|
37 |
+
border-style: solid;
|
38 |
+
|
39 |
+
border-top-color: #fff;
|
40 |
+
border-bottom-color: #dfdfdf;
|
41 |
+
|
42 |
+
text-transform: none;
|
43 |
+
}
|
44 |
+
|
45 |
+
table.puc-debug-data td {
|
46 |
+
color: #555;
|
47 |
+
font-size: 12px;
|
48 |
+
padding: 4px 7px 2px;
|
49 |
+
vertical-align: top;
|
50 |
+
}
|
51 |
+
|
52 |
+
.puc-ajax-response {
|
53 |
+
border: 1px solid #dfdfdf;
|
54 |
+
border-radius: 3px;
|
55 |
+
padding: 0.5em;
|
56 |
+
margin: 5px 0;
|
57 |
+
background-color: white;
|
58 |
+
}
|
59 |
+
|
60 |
+
.puc-ajax-nonce {
|
61 |
+
display: none;
|
62 |
+
}
|
63 |
+
|
64 |
+
.puc-ajax-response dt {
|
65 |
+
margin: 0;
|
66 |
+
}
|
67 |
+
|
68 |
+
.puc-ajax-response dd {
|
69 |
+
margin: 0 0 1em;
|
70 |
+
}
|
updater/js/debug-bar.js
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
jQuery(function($) {
|
2 |
+
|
3 |
+
function runAjaxAction(button, action) {
|
4 |
+
button = $(button);
|
5 |
+
var panel = button.closest('.puc-debug-bar-panel-v4');
|
6 |
+
var responseBox = button.closest('td').find('.puc-ajax-response');
|
7 |
+
|
8 |
+
responseBox.text('Processing...').show();
|
9 |
+
$.post(
|
10 |
+
ajaxurl,
|
11 |
+
{
|
12 |
+
action : action,
|
13 |
+
uid : panel.data('uid'),
|
14 |
+
_wpnonce: panel.data('nonce')
|
15 |
+
},
|
16 |
+
function(data) {
|
17 |
+
responseBox.html(data);
|
18 |
+
},
|
19 |
+
'html'
|
20 |
+
);
|
21 |
+
}
|
22 |
+
|
23 |
+
$('.puc-debug-bar-panel-v4 input[name="puc-check-now-button"]').click(function() {
|
24 |
+
runAjaxAction(this, 'puc_v4_debug_check_now');
|
25 |
+
return false;
|
26 |
+
});
|
27 |
+
|
28 |
+
$('.puc-debug-bar-panel-v4 input[name="puc-request-info-button"]').click(function() {
|
29 |
+
runAjaxAction(this, 'puc_v4_debug_request_info');
|
30 |
+
return false;
|
31 |
+
});
|
32 |
+
|
33 |
+
|
34 |
+
// Debug Bar uses the panel class name as part of its link and container IDs. This means we can
|
35 |
+
// end up with multiple identical IDs if more than one plugin uses the update checker library.
|
36 |
+
// Fix it by replacing the class name with the plugin slug.
|
37 |
+
var panels = $('#debug-menu-targets').find('.puc-debug-bar-panel-v4');
|
38 |
+
panels.each(function() {
|
39 |
+
var panel = $(this);
|
40 |
+
var uid = panel.data('uid');
|
41 |
+
var target = panel.closest('.debug-menu-target');
|
42 |
+
|
43 |
+
//Change the panel wrapper ID.
|
44 |
+
target.attr('id', 'debug-menu-target-puc-' + uid);
|
45 |
+
|
46 |
+
//Change the menu link ID as well and point it at the new target ID.
|
47 |
+
$('#debug-bar-menu').find('.puc-debug-menu-link-' + uid)
|
48 |
+
.closest('.debug-menu-link')
|
49 |
+
.attr('id', 'debug-menu-link-puc-' + uid)
|
50 |
+
.attr('href', '#' + target.attr('id'));
|
51 |
+
});
|
52 |
+
});
|
updater/languages/plugin-update-checker-ca.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-ca.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2019-09-25 18:15+0200\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: ca\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprova si hi ha actualitzacions"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "L’extensió %s està actualitzada."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nova versió de l’extensió %s està disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No s’ha pogut determinar si hi ha actualitzacions per a %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estat del comprovador d’actualitzacions desconegut \"%s\""
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hi ha cap registre de canvis disponible."
|
updater/languages/plugin-update-checker-cs_CZ.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-cs_CZ.po
ADDED
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"Report-Msgid-Bugs-To: \n"
|
5 |
+
"POT-Creation-Date: 2017-05-20 10:53+0300\n"
|
6 |
+
"PO-Revision-Date: 2017-07-05 15:39+0000\n"
|
7 |
+
"Last-Translator: Vojtěch Sajdl <vojtech@sajdl.com>\n"
|
8 |
+
"Language-Team: Czech (Czech Republic)\n"
|
9 |
+
"Language: cs-CZ\n"
|
10 |
+
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
11 |
+
"MIME-Version: 1.0\n"
|
12 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
13 |
+
"Content-Transfer-Encoding: 8bit\n"
|
14 |
+
"X-Loco-Source-Locale: cs_CZ\n"
|
15 |
+
"X-Generator: Loco - https://localise.biz/\n"
|
16 |
+
"X-Poedit-Basepath: ..\n"
|
17 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
18 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
19 |
+
"X-Poedit-SearchPath-0: .\n"
|
20 |
+
"X-Loco-Parser: loco_parse_po"
|
21 |
+
|
22 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:358
|
23 |
+
msgid "Check for updates"
|
24 |
+
msgstr "Zkontrolovat aktualizace"
|
25 |
+
|
26 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:405
|
27 |
+
#, php-format
|
28 |
+
msgctxt "the plugin title"
|
29 |
+
msgid "The %s plugin is up to date."
|
30 |
+
msgstr "Plugin %s je aktuální."
|
31 |
+
|
32 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:407
|
33 |
+
#, php-format
|
34 |
+
msgctxt "the plugin title"
|
35 |
+
msgid "A new version of the %s plugin is available."
|
36 |
+
msgstr "Nová verze pluginu %s je dostupná."
|
37 |
+
|
38 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:409
|
39 |
+
#, php-format
|
40 |
+
msgid "Unknown update checker status \"%s\""
|
41 |
+
msgstr "Neznámý status kontroly aktualizací \"%s\""
|
42 |
+
|
43 |
+
#: Puc/v4p1/Vcs/PluginUpdateChecker.php:83
|
44 |
+
msgid "There is no changelog available."
|
45 |
+
msgstr "Changelog není dostupný."
|
updater/languages/plugin-update-checker-da_DK.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-da_DK.po
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-05-20 10:53+0300\n"
|
5 |
+
"PO-Revision-Date: 2017-10-17 11:07+0200\n"
|
6 |
+
"Last-Translator: Mikk3lRo\n"
|
7 |
+
"Language-Team: Mikk3lRo\n"
|
8 |
+
"MIME-Version: 1.0\n"
|
9 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
10 |
+
"Content-Transfer-Encoding: 8bit\n"
|
11 |
+
"X-Generator: Poedit 2.0.4\n"
|
12 |
+
"X-Poedit-Basepath: ..\n"
|
13 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
14 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
15 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
16 |
+
"Language: da_DK\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:358
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Undersøg for opdateringer"
|
22 |
+
|
23 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:405
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "Plugin'et %s er allerede opdateret."
|
28 |
+
|
29 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:407
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "En ny version af plugin'et %s er tilgængelig."
|
34 |
+
|
35 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:409
|
36 |
+
#, php-format
|
37 |
+
msgid "Unknown update checker status \"%s\""
|
38 |
+
msgstr "Ukendt opdateringsstatus: \"%s\""
|
39 |
+
|
40 |
+
#: Puc/v4p1/Vcs/PluginUpdateChecker.php:83
|
41 |
+
msgid "There is no changelog available."
|
42 |
+
msgstr "Der er ingen ændringslog tilgængelig."
|
updater/languages/plugin-update-checker-de_DE.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-de_DE.po
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2016-06-29 20:21+0100\n"
|
5 |
+
"PO-Revision-Date: 2016-06-29 20:23+0100\n"
|
6 |
+
"Last-Translator: Igor Lückel <info@igorlueckel.de>\n"
|
7 |
+
"Language-Team: \n"
|
8 |
+
"MIME-Version: 1.0\n"
|
9 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
10 |
+
"Content-Transfer-Encoding: 8bit\n"
|
11 |
+
"X-Generator: Poedit 1.8.1\n"
|
12 |
+
"X-Poedit-Basepath: ..\n"
|
13 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
14 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
15 |
+
"X-Poedit-KeywordsList: __;_e\n"
|
16 |
+
"Language: de_DE\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: github-checker.php:137
|
20 |
+
msgid "There is no changelog available."
|
21 |
+
msgstr "Es ist keine Liste von Programmänderungen verfügbar."
|
22 |
+
|
23 |
+
#: plugin-update-checker.php:852
|
24 |
+
msgid "Check for updates"
|
25 |
+
msgstr "Nach Update suchen"
|
26 |
+
|
27 |
+
#: plugin-update-checker.php:896
|
28 |
+
msgid "This plugin is up to date."
|
29 |
+
msgstr "Das Plugin ist aktuell."
|
30 |
+
|
31 |
+
#: plugin-update-checker.php:898
|
32 |
+
msgid "A new version of this plugin is available."
|
33 |
+
msgstr "Es ist eine neue Version für das Plugin verfügbar."
|
34 |
+
|
35 |
+
#: plugin-update-checker.php:900
|
36 |
+
#, php-format
|
37 |
+
msgid "Unknown update checker status \"%s\""
|
38 |
+
msgstr "Unbekannter Update Status \"%s\""
|
updater/languages/plugin-update-checker-es_AR.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_AR.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:13-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_CL.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_CL.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:14-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_CO.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_CO.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:14-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_CR.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_CR.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:14-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_DO.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_DO.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:14-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_ES.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_ES.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 14:56-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_GT.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_GT.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:14-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_HN.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_HN.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:14-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_MX.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_MX.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 14:57-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_PE.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_PE.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:15-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_PR.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_PR.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:15-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_UY.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_UY.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 15:15-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-es_VE.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-es_VE.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-03-21 14:57-0400\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.3\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: es_ES\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Comprobar si hay actualizaciones"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "El plugin %s está actualizado."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Una nueva versión del %s plugin está disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "No se pudo determinar si hay actualizaciones disponibles para %s."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Estado del comprobador de actualización desconocido «%s»"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "No hay un registro de cambios disponible."
|
updater/languages/plugin-update-checker-fa_IR.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-fa_IR.po
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2016-02-17 14:21+0100\n"
|
5 |
+
"PO-Revision-Date: 2016-10-28 14:30+0330\n"
|
6 |
+
"Last-Translator: studio RVOLA <hello@rvola.com>\n"
|
7 |
+
"Language-Team: Pro Style <info@prostyle.ir>\n"
|
8 |
+
"Language: fa_IR\n"
|
9 |
+
"MIME-Version: 1.0\n"
|
10 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
11 |
+
"Content-Transfer-Encoding: 8bit\n"
|
12 |
+
"X-Generator: Poedit 1.8.8\n"
|
13 |
+
"X-Poedit-Basepath: ..\n"
|
14 |
+
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
15 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
16 |
+
"X-Poedit-KeywordsList: __;_e\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: github-checker.php:120
|
20 |
+
msgid "There is no changelog available."
|
21 |
+
msgstr "شرحی برای تغییرات یافت نشد"
|
22 |
+
|
23 |
+
#: plugin-update-checker.php:637
|
24 |
+
msgid "Check for updates"
|
25 |
+
msgstr "بررسی برای بروزرسانی "
|
26 |
+
|
27 |
+
#: plugin-update-checker.php:681
|
28 |
+
msgid "This plugin is up to date."
|
29 |
+
msgstr "شما از آخرین نسخه استفاده میکنید . بهروز باشید"
|
30 |
+
|
31 |
+
#: plugin-update-checker.php:683
|
32 |
+
msgid "A new version of this plugin is available."
|
33 |
+
msgstr "نسخه جدیدی برای افزونه ارائه شده است ."
|
34 |
+
|
35 |
+
#: plugin-update-checker.php:685
|
36 |
+
#, php-format
|
37 |
+
msgid "Unknown update checker status \"%s\""
|
38 |
+
msgstr "وضعیت ناشناخته برای بروزرسانی \"%s\""
|
updater/languages/plugin-update-checker-fr_CA.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-fr_CA.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2018-02-12 10:32-0500\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.0.4\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: Eric Gagnon <eric.gagnon@banq.qc.ca>\n"
|
16 |
+
"Language: fr_CA\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Vérifier les mises à jour"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "L’extension %s est à jour."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Une nouvelle version de l’extension %s est disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "Impossible de déterminer si une mise à jour est disponible pour \"%s\""
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Un problème inconnu est survenu \"%s\""
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "Il n’y a aucun journal de mise à jour disponible."
|
updater/languages/plugin-update-checker-fr_FR.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-fr_FR.po
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-07-07 14:53+0200\n"
|
5 |
+
"PO-Revision-Date: 2017-07-07 14:54+0200\n"
|
6 |
+
"Language-Team: studio RVOLA <http://www.rvola.com>\n"
|
7 |
+
"Language: fr_FR\n"
|
8 |
+
"MIME-Version: 1.0\n"
|
9 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
10 |
+
"Content-Transfer-Encoding: 8bit\n"
|
11 |
+
"X-Generator: Poedit 2.0.2\n"
|
12 |
+
"X-Poedit-Basepath: ..\n"
|
13 |
+
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
14 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
15 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
16 |
+
"Last-Translator: Nicolas GEHIN\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:358
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Vérifier les mises à jour"
|
22 |
+
|
23 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:405
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "L’extension %s est à jour."
|
28 |
+
|
29 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:407
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Une nouvelle version de l’extension %s est disponible."
|
34 |
+
|
35 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:409
|
36 |
+
#, php-format
|
37 |
+
msgid "Unknown update checker status \"%s\""
|
38 |
+
msgstr "Un problème inconnu est survenu \"%s\""
|
39 |
+
|
40 |
+
#: Puc/v4p1/Vcs/PluginUpdateChecker.php:85
|
41 |
+
msgid "There is no changelog available."
|
42 |
+
msgstr "Il n’y a aucun journal de mise à jour disponible."
|
updater/languages/plugin-update-checker-hu_HU.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-hu_HU.po
ADDED
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2016-01-11 21:23+0100\n"
|
5 |
+
"PO-Revision-Date: 2016-01-11 21:25+0100\n"
|
6 |
+
"Last-Translator: Tamás András Horváth <htomy92@gmail.com>\n"
|
7 |
+
"Language-Team: \n"
|
8 |
+
"Language: hu_HU\n"
|
9 |
+
"MIME-Version: 1.0\n"
|
10 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
11 |
+
"Content-Transfer-Encoding: 8bit\n"
|
12 |
+
"X-Generator: Poedit 1.8.6\n"
|
13 |
+
"X-Poedit-Basepath: ..\n"
|
14 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
15 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
16 |
+
"X-Poedit-KeywordsList: __;_e\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: github-checker.php:137
|
20 |
+
msgid "There is no changelog available."
|
21 |
+
msgstr "Nem érhető el a changelog."
|
22 |
+
|
23 |
+
#: plugin-update-checker.php:852
|
24 |
+
msgid "Check for updates"
|
25 |
+
msgstr "Frissítés ellenőrzése"
|
26 |
+
|
27 |
+
#: plugin-update-checker.php:896
|
28 |
+
msgid "This plugin is up to date."
|
29 |
+
msgstr "Ez a plugin naprakész."
|
30 |
+
|
31 |
+
#: plugin-update-checker.php:898
|
32 |
+
msgid "A new version of this plugin is available."
|
33 |
+
msgstr "Új verzió érhető el a kiegészítőhöz"
|
34 |
+
|
35 |
+
#: plugin-update-checker.php:900
|
36 |
+
#, php-format
|
37 |
+
msgid "Unknown update checker status \"%s\""
|
38 |
+
msgstr "Ismeretlen a frissítés ellenőrző státusza \"%s\""
|
39 |
+
|
40 |
+
#~ msgid "Every %d hours"
|
41 |
+
#~ msgstr "Minden %d órában"
|
updater/languages/plugin-update-checker-it_IT.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-it_IT.po
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2016-06-29 20:21+0100\n"
|
5 |
+
"PO-Revision-Date: 2017-01-15 12:24+0100\n"
|
6 |
+
"Last-Translator: Igor Lückel <info@igorlueckel.de>\n"
|
7 |
+
"Language-Team: \n"
|
8 |
+
"MIME-Version: 1.0\n"
|
9 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
10 |
+
"Content-Transfer-Encoding: 8bit\n"
|
11 |
+
"X-Generator: Poedit 1.5.5\n"
|
12 |
+
"X-Poedit-Basepath: ..\n"
|
13 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
14 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
15 |
+
"X-Poedit-KeywordsList: __;_e\n"
|
16 |
+
"Language: de_DE\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: github-checker.php:137
|
20 |
+
msgid "There is no changelog available."
|
21 |
+
msgstr "Non c'è alcuna sezione di aggiornamento disponibile"
|
22 |
+
|
23 |
+
#: plugin-update-checker.php:852
|
24 |
+
msgid "Check for updates"
|
25 |
+
msgstr "Verifica aggiornamenti"
|
26 |
+
|
27 |
+
#: plugin-update-checker.php:896
|
28 |
+
msgid "This plugin is up to date."
|
29 |
+
msgstr "Il plugin è aggiornato"
|
30 |
+
|
31 |
+
#: plugin-update-checker.php:898
|
32 |
+
msgid "A new version of this plugin is available."
|
33 |
+
msgstr "Una nuova versione del plugin è disponibile"
|
34 |
+
|
35 |
+
#: plugin-update-checker.php:900
|
36 |
+
#, php-format
|
37 |
+
msgid "Unknown update checker status \"%s\""
|
38 |
+
msgstr "Si è verificato un problema sconosciuto \"%s\""
|
updater/languages/plugin-update-checker-ja.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-ja.po
ADDED
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: \n"
|
4 |
+
"POT-Creation-Date: 2019-07-15 17:07+0900\n"
|
5 |
+
"PO-Revision-Date: 2019-07-15 17:12+0900\n"
|
6 |
+
"Last-Translator: tak <tak7725@gmail.com>\n"
|
7 |
+
"Language-Team: \n"
|
8 |
+
"Language: ja_JP\n"
|
9 |
+
"MIME-Version: 1.0\n"
|
10 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
11 |
+
"Content-Transfer-Encoding: 8bit\n"
|
12 |
+
"X-Generator: Poedit 2.2.3\n"
|
13 |
+
"X-Poedit-Basepath: ../../../../../../Applications/XAMPP/xamppfiles/htdocs/"
|
14 |
+
"kisagai/wordpress/wp-content/plugins/simple-stripe-gateway/Puc\n"
|
15 |
+
"Plural-Forms: nplurals=1; plural=0;\n"
|
16 |
+
"X-Poedit-KeywordsList: __;_x:1,2c\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: v4p7/Plugin/Ui.php:54
|
20 |
+
msgid "View details"
|
21 |
+
msgstr "詳細を表示"
|
22 |
+
|
23 |
+
#: v4p7/Plugin/Ui.php:77
|
24 |
+
#, php-format
|
25 |
+
msgid "More information about %s"
|
26 |
+
msgstr "%sについての詳細"
|
27 |
+
|
28 |
+
#: v4p7/Plugin/Ui.php:128
|
29 |
+
msgid "Check for updates"
|
30 |
+
msgstr "アップデートを確認"
|
31 |
+
|
32 |
+
#: v4p7/Plugin/Ui.php:213
|
33 |
+
#, php-format
|
34 |
+
msgctxt "the plugin title"
|
35 |
+
msgid "The %s plugin is up to date."
|
36 |
+
msgstr "%s プラグインは、最新バージョンです。"
|
37 |
+
|
38 |
+
#: v4p7/Plugin/Ui.php:215
|
39 |
+
#, php-format
|
40 |
+
msgctxt "the plugin title"
|
41 |
+
msgid "A new version of the %s plugin is available."
|
42 |
+
msgstr "%s プラグインの最新バージョンがあります。"
|
43 |
+
|
44 |
+
#: v4p7/Plugin/Ui.php:217
|
45 |
+
#, php-format
|
46 |
+
msgctxt "the plugin title"
|
47 |
+
msgid "Could not determine if updates are available for %s."
|
48 |
+
msgstr "%s のアップデートがあるかどうかを判断できませんでした。"
|
49 |
+
|
50 |
+
#: v4p7/Plugin/Ui.php:223
|
51 |
+
#, php-format
|
52 |
+
msgid "Unknown update checker status \"%s\""
|
53 |
+
msgstr "バージョンアップの確認で想定外の状態になりました。ステータス:”%s”"
|
54 |
+
|
55 |
+
#: v4p7/Vcs/PluginUpdateChecker.php:98
|
56 |
+
msgid "There is no changelog available."
|
57 |
+
msgstr "更新履歴はありません。"
|
updater/languages/plugin-update-checker-nl_BE.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-nl_BE.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2018-03-25 18:15+0200\n"
|
5 |
+
"PO-Revision-Date: 2018-03-25 18:32+0200\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 1.8.7.1\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: Frank Goossens <frank@optimizingmatters.com>\n"
|
16 |
+
"Language: nl_BE\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Controleer op nieuwe versies"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "De meest recente %s versie is geïnstalleerd."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Er is een nieuwe versie van %s beschikbaar."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "Kon niet bepalen of er nieuwe versie van %s beschikbaar is."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Ongekende status bij controle op nieuwe versie: \"%s\""
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "Er is geen changelog beschikbaar."
|
updater/languages/plugin-update-checker-nl_NL.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-nl_NL.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2018-03-25 18:15+0200\n"
|
5 |
+
"PO-Revision-Date: 2018-03-25 18:32+0200\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 1.8.7.1\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: Frank Goossens <frank@optimizingmatters.com>\n"
|
16 |
+
"Language: nl_NL\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Controleer op nieuwe versies"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "De meest recente %s versie is geïnstalleerd."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Er is een nieuwe versie van %s beschikbaar."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "Kon niet bepalen of er nieuwe versie van %s beschikbaar is."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Ongekende status bij controle op nieuwe versie: \"%s\""
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "Er is geen changelog beschikbaar."
|
updater/languages/plugin-update-checker-pt_BR.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-pt_BR.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-05-19 15:41-0300\n"
|
5 |
+
"PO-Revision-Date: 2017-05-19 15:42-0300\n"
|
6 |
+
"Last-Translator: \n"
|
7 |
+
"Language-Team: \n"
|
8 |
+
"Language: pt_BR\n"
|
9 |
+
"MIME-Version: 1.0\n"
|
10 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
11 |
+
"Content-Transfer-Encoding: 8bit\n"
|
12 |
+
"X-Generator: Poedit 1.8.8\n"
|
13 |
+
"X-Poedit-Basepath: ..\n"
|
14 |
+
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
15 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
16 |
+
"X-Poedit-KeywordsList: __;_e;_x;_x:1,2c\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:358
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Verificar Atualizações"
|
22 |
+
|
23 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:401 Puc/v4p1/Plugin/UpdateChecker.php:406
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "O plugin %s já está na sua versão mais recente."
|
28 |
+
|
29 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:408
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Há uma nova versão para o plugin %s disponível para download."
|
34 |
+
|
35 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:410
|
36 |
+
#, php-format
|
37 |
+
msgid "Unknown update checker status \"%s\""
|
38 |
+
msgstr "Status \"%s\" desconhecido."
|
39 |
+
|
40 |
+
#: Puc/v4p1/Vcs/PluginUpdateChecker.php:83
|
41 |
+
msgid "There is no changelog available."
|
42 |
+
msgstr "Não há um changelog disponível."
|
43 |
+
|
44 |
+
#~ msgid "The %s plugin is up to date."
|
45 |
+
#~ msgstr "O plugin %s já está na sua versão mais recente."
|
46 |
+
|
47 |
+
#~ msgid "A new version of the %s plugin is available."
|
48 |
+
#~ msgstr "Há uma nova versão para o plugin %s disponível para download."
|
updater/languages/plugin-update-checker-sl_SI.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-sl_SI.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2018-10-27 20:36+0200\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.2\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=4; plural=(n%100==1 ? 0 : n%100==2 ? 1 : n%100>=3 && n%100<=4 ? 2 : 3);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: Igor Funa\n"
|
16 |
+
"Language: sl_SI\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Preveri posodobitve"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "Vtičnik %s je že posodobljen."
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Nova različica vtičnika %s je na razpolago."
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "Ne morem ugotoviti če se za vtičnik %s na razpolago posodobitve."
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "Neznan status preverjanja posodobitev za \"%s\""
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "Dnevnik sprememb ni na razpolago."
|
updater/languages/plugin-update-checker-sv_SE.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-sv_SE.po
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-05-20 10:53+0300\n"
|
5 |
+
"PO-Revision-Date: 2017-10-16 15:02+0200\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.0.4\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: \n"
|
16 |
+
"Language: sv_SE\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:358
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "Sök efter uppdateringar"
|
22 |
+
|
23 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:405
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "Tillägget %s är uppdaterat."
|
28 |
+
|
29 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:407
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "Det finns en ny version av tillägget %s."
|
34 |
+
|
35 |
+
#: Puc/v4p1/Plugin/UpdateChecker.php:409
|
36 |
+
#, php-format
|
37 |
+
msgid "Unknown update checker status \"%s\""
|
38 |
+
msgstr "Okänd status för kontroll av uppdatering “%s”"
|
39 |
+
|
40 |
+
#: Puc/v4p1/Vcs/PluginUpdateChecker.php:83
|
41 |
+
msgid "There is no changelog available."
|
42 |
+
msgstr "Det finns ingen ändringslogg tillgänglig."
|
updater/languages/plugin-update-checker-zh_CN.mo
ADDED
Binary file
|
updater/languages/plugin-update-checker-zh_CN.po
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
msgid ""
|
2 |
+
msgstr ""
|
3 |
+
"Project-Id-Version: plugin-update-checker\n"
|
4 |
+
"POT-Creation-Date: 2017-11-24 17:02+0200\n"
|
5 |
+
"PO-Revision-Date: 2020-08-04 08:10+0800\n"
|
6 |
+
"Language-Team: \n"
|
7 |
+
"MIME-Version: 1.0\n"
|
8 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
9 |
+
"Content-Transfer-Encoding: 8bit\n"
|
10 |
+
"X-Generator: Poedit 2.4\n"
|
11 |
+
"X-Poedit-Basepath: ..\n"
|
12 |
+
"Plural-Forms: nplurals=1; plural=0;\n"
|
13 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
14 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
15 |
+
"Last-Translator: Seaton Jiang <seaton@vtrois.com>\n"
|
16 |
+
"Language: zh_CN\n"
|
17 |
+
"X-Poedit-SearchPath-0: .\n"
|
18 |
+
|
19 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:395
|
20 |
+
msgid "Check for updates"
|
21 |
+
msgstr "检查更新"
|
22 |
+
|
23 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:548
|
24 |
+
#, php-format
|
25 |
+
msgctxt "the plugin title"
|
26 |
+
msgid "The %s plugin is up to date."
|
27 |
+
msgstr "%s 目前是最新版本。"
|
28 |
+
|
29 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:550
|
30 |
+
#, php-format
|
31 |
+
msgctxt "the plugin title"
|
32 |
+
msgid "A new version of the %s plugin is available."
|
33 |
+
msgstr "%s 当前有可用的更新。"
|
34 |
+
|
35 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:552
|
36 |
+
#, php-format
|
37 |
+
msgctxt "the plugin title"
|
38 |
+
msgid "Could not determine if updates are available for %s."
|
39 |
+
msgstr "%s 无法确定是否有可用的更新。"
|
40 |
+
|
41 |
+
#: Puc/v4p3/Plugin/UpdateChecker.php:558
|
42 |
+
#, php-format
|
43 |
+
msgid "Unknown update checker status \"%s\""
|
44 |
+
msgstr "未知的更新检查状态:%s"
|
45 |
+
|
46 |
+
#: Puc/v4p3/Vcs/PluginUpdateChecker.php:95
|
47 |
+
msgid "There is no changelog available."
|
48 |
+
msgstr "没有可用的更新日志。"
|
updater/languages/plugin-update-checker.pot
ADDED
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#, fuzzy
|
2 |
+
msgid ""
|
3 |
+
msgstr ""
|
4 |
+
"Project-Id-Version: plugin-update-checker\n"
|
5 |
+
"POT-Creation-Date: 2020-08-08 14:36+0300\n"
|
6 |
+
"PO-Revision-Date: 2016-01-10 20:59+0100\n"
|
7 |
+
"Last-Translator: Tamás András Horváth <htomy92@gmail.com>\n"
|
8 |
+
"Language-Team: \n"
|
9 |
+
"Language: en_US\n"
|
10 |
+
"MIME-Version: 1.0\n"
|
11 |
+
"Content-Type: text/plain; charset=UTF-8\n"
|
12 |
+
"Content-Transfer-Encoding: 8bit\n"
|
13 |
+
"X-Generator: Poedit 2.4\n"
|
14 |
+
"X-Poedit-Basepath: ..\n"
|
15 |
+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
16 |
+
"X-Poedit-SourceCharset: UTF-8\n"
|
17 |
+
"X-Poedit-KeywordsList: __;_e;_x:1,2c;_x\n"
|
18 |
+
"X-Poedit-SearchPath-0: .\n"
|
19 |
+
|
20 |
+
#: Puc/v4p10/Plugin/Ui.php:128
|
21 |
+
msgid "Check for updates"
|
22 |
+
msgstr ""
|
23 |
+
|
24 |
+
#: Puc/v4p10/Plugin/Ui.php:213
|
25 |
+
#, php-format
|
26 |
+
msgctxt "the plugin title"
|
27 |
+
msgid "The %s plugin is up to date."
|
28 |
+
msgstr ""
|
29 |
+
|
30 |
+
#: Puc/v4p10/Plugin/Ui.php:215
|
31 |
+
#, php-format
|
32 |
+
msgctxt "the plugin title"
|
33 |
+
msgid "A new version of the %s plugin is available."
|
34 |
+
msgstr ""
|
35 |
+
|
36 |
+
#: Puc/v4p10/Plugin/Ui.php:217
|
37 |
+
#, php-format
|
38 |
+
msgctxt "the plugin title"
|
39 |
+
msgid "Could not determine if updates are available for %s."
|
40 |
+
msgstr ""
|
41 |
+
|
42 |
+
#: Puc/v4p10/Plugin/Ui.php:223
|
43 |
+
#, php-format
|
44 |
+
msgid "Unknown update checker status \"%s\""
|
45 |
+
msgstr ""
|
46 |
+
|
47 |
+
#: Puc/v4p10/Vcs/PluginUpdateChecker.php:98
|
48 |
+
msgid "There is no changelog available."
|
49 |
+
msgstr ""
|
updater/load-v4p10.php
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
require dirname(__FILE__) . '/Puc/v4p10/Autoloader.php';
|
3 |
+
new Puc_v4p10_Autoloader();
|
4 |
+
|
5 |
+
require dirname(__FILE__) . '/Puc/v4p10/Factory.php';
|
6 |
+
require dirname(__FILE__) . '/Puc/v4/Factory.php';
|
7 |
+
|
8 |
+
//Register classes defined in this version with the factory.
|
9 |
+
foreach (
|
10 |
+
array(
|
11 |
+
'Plugin_UpdateChecker' => 'Puc_v4p10_Plugin_UpdateChecker',
|
12 |
+
'Theme_UpdateChecker' => 'Puc_v4p10_Theme_UpdateChecker',
|
13 |
+
|
14 |
+
'Vcs_PluginUpdateChecker' => 'Puc_v4p10_Vcs_PluginUpdateChecker',
|
15 |
+
'Vcs_ThemeUpdateChecker' => 'Puc_v4p10_Vcs_ThemeUpdateChecker',
|
16 |
+
|
17 |
+
'GitHubApi' => 'Puc_v4p10_Vcs_GitHubApi',
|
18 |
+
'BitBucketApi' => 'Puc_v4p10_Vcs_BitBucketApi',
|
19 |
+
'GitLabApi' => 'Puc_v4p10_Vcs_GitLabApi',
|
20 |
+
)
|
21 |
+
as $pucGeneralClass => $pucVersionedClass
|
22 |
+
) {
|
23 |
+
Puc_v4_Factory::addVersion($pucGeneralClass, $pucVersionedClass, '4.10');
|
24 |
+
//Also add it to the minor-version factory in case the major-version factory
|
25 |
+
//was already defined by another, older version of the update checker.
|
26 |
+
Puc_v4p10_Factory::addVersion($pucGeneralClass, $pucVersionedClass, '4.10');
|
27 |
+
}
|
28 |
+
|
updater/plugin-update-checker.php
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/**
|
3 |
+
* Plugin Update Checker Library 4.10
|
4 |
+
* http://w-shadow.com/
|
5 |
+
*
|
6 |
+
* Copyright 2020 Janis Elsts
|
7 |
+
* Released under the MIT license. See license.txt for details.
|
8 |
+
*/
|
9 |
+
|
10 |
+
require dirname(__FILE__) . '/load-v4p10.php';
|
updater/vendor/Parsedown.php
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
if ( !class_exists('Parsedown', false) ) {
|
3 |
+
//Load the Parsedown version that's compatible with the current PHP version.
|
4 |
+
if ( version_compare(PHP_VERSION, '5.3.0', '>=') ) {
|
5 |
+
require __DIR__ . '/ParsedownModern.php';
|
6 |
+
} else {
|
7 |
+
require __DIR__ . '/ParsedownLegacy.php';
|
8 |
+
}
|
9 |
+
}
|
updater/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 |
+
}
|
updater/vendor/ParsedownModern.php
ADDED
@@ -0,0 +1,1538 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.6.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 $unmarkedBlockTypes = array(
|
111 |
+
'Code',
|
112 |
+
);
|
113 |
+
|
114 |
+
#
|
115 |
+
# Blocks
|
116 |
+
#
|
117 |
+
|
118 |
+
protected function lines(array $lines)
|
119 |
+
{
|
120 |
+
$CurrentBlock = null;
|
121 |
+
|
122 |
+
foreach ($lines as $line)
|
123 |
+
{
|
124 |
+
if (chop($line) === '')
|
125 |
+
{
|
126 |
+
if (isset($CurrentBlock))
|
127 |
+
{
|
128 |
+
$CurrentBlock['interrupted'] = true;
|
129 |
+
}
|
130 |
+
|
131 |
+
continue;
|
132 |
+
}
|
133 |
+
|
134 |
+
if (strpos($line, "\t") !== false)
|
135 |
+
{
|
136 |
+
$parts = explode("\t", $line);
|
137 |
+
|
138 |
+
$line = $parts[0];
|
139 |
+
|
140 |
+
unset($parts[0]);
|
141 |
+
|
142 |
+
foreach ($parts as $part)
|
143 |
+
{
|
144 |
+
$shortage = 4 - mb_strlen($line, 'utf-8') % 4;
|
145 |
+
|
146 |
+
$line .= str_repeat(' ', $shortage);
|
147 |
+
$line .= $part;
|
148 |
+
}
|
149 |
+
}
|
150 |
+
|
151 |
+
$indent = 0;
|
152 |
+
|
153 |
+
while (isset($line[$indent]) and $line[$indent] === ' ')
|
154 |
+
{
|
155 |
+
$indent ++;
|
156 |
+
}
|
157 |
+
|
158 |
+
$text = $indent > 0 ? substr($line, $indent) : $line;
|
159 |
+
|
160 |
+
# ~
|
161 |
+
|
162 |
+
$Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
|
163 |
+
|
164 |
+
# ~
|
165 |
+
|
166 |
+
if (isset($CurrentBlock['continuable']))
|
167 |
+
{
|
168 |
+
$Block = $this->{'block'.$CurrentBlock['type'].'Continue'}($Line, $CurrentBlock);
|
169 |
+
|
170 |
+
if (isset($Block))
|
171 |
+
{
|
172 |
+
$CurrentBlock = $Block;
|
173 |
+
|
174 |
+
continue;
|
175 |
+
}
|
176 |
+
else
|
177 |
+
{
|
178 |
+
if ($this->isBlockCompletable($CurrentBlock['type']))
|
179 |
+
{
|
180 |
+
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
|
181 |
+
}
|
182 |
+
}
|
183 |
+
}
|
184 |
+
|
185 |
+
# ~
|
186 |
+
|
187 |
+
$marker = $text[0];
|
188 |
+
|
189 |
+
# ~
|
190 |
+
|
191 |
+
$blockTypes = $this->unmarkedBlockTypes;
|
192 |
+
|
193 |
+
if (isset($this->BlockTypes[$marker]))
|
194 |
+
{
|
195 |
+
foreach ($this->BlockTypes[$marker] as $blockType)
|
196 |
+
{
|
197 |
+
$blockTypes []= $blockType;
|
198 |
+
}
|
199 |
+
}
|
200 |
+
|
201 |
+
#
|
202 |
+
# ~
|
203 |
+
|
204 |
+
foreach ($blockTypes as $blockType)
|
205 |
+
{
|
206 |
+
$Block = $this->{'block'.$blockType}($Line, $CurrentBlock);
|
207 |
+
|
208 |
+
if (isset($Block))
|
209 |
+
{
|
210 |
+
$Block['type'] = $blockType;
|
211 |
+
|
212 |
+
if ( ! isset($Block['identified']))
|
213 |
+
{
|
214 |
+
$Blocks []= $CurrentBlock;
|
215 |
+
|
216 |
+
$Block['identified'] = true;
|
217 |
+
}
|
218 |
+
|
219 |
+
if ($this->isBlockContinuable($blockType))
|
220 |
+
{
|
221 |
+
$Block['continuable'] = true;
|
222 |
+
}
|
223 |
+
|
224 |
+
$CurrentBlock = $Block;
|
225 |
+
|
226 |
+
continue 2;
|
227 |
+
}
|
228 |
+
}
|
229 |
+
|
230 |
+
# ~
|
231 |
+
|
232 |
+
if (isset($CurrentBlock) and ! isset($CurrentBlock['type']) and ! isset($CurrentBlock['interrupted']))
|
233 |
+
{
|
234 |
+
$CurrentBlock['element']['text'] .= "\n".$text;
|
235 |
+
}
|
236 |
+
else
|
237 |
+
{
|
238 |
+
$Blocks []= $CurrentBlock;
|
239 |
+
|
240 |
+
$CurrentBlock = $this->paragraph($Line);
|
241 |
+
|
242 |
+
$CurrentBlock['identified'] = true;
|
243 |
+
}
|
244 |
+
}
|
245 |
+
|
246 |
+
# ~
|
247 |
+
|
248 |
+
if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
|
249 |
+
{
|
250 |
+
$CurrentBlock = $this->{'block'.$CurrentBlock['type'].'Complete'}($CurrentBlock);
|
251 |
+
}
|
252 |
+
|
253 |
+
# ~
|
254 |
+
|
255 |
+
$Blocks []= $CurrentBlock;
|
256 |
+
|
257 |
+
unset($Blocks[0]);
|
258 |
+
|
259 |
+
# ~
|
260 |
+
|
261 |
+
$markup = '';
|
262 |
+
|
263 |
+
foreach ($Blocks as $Block)
|
264 |
+
{
|
265 |
+
if (isset($Block['hidden']))
|
266 |
+
{
|
267 |
+
continue;
|
268 |
+
}
|
269 |
+
|
270 |
+
$markup .= "\n";
|
271 |
+
$markup .= isset($Block['markup']) ? $Block['markup'] : $this->element($Block['element']);
|
272 |
+
}
|
273 |
+
|
274 |
+
$markup .= "\n";
|
275 |
+
|
276 |
+
# ~
|
277 |
+
|
278 |
+
return $markup;
|
279 |
+
}
|
280 |
+
|
281 |
+
protected function isBlockContinuable($Type)
|
282 |
+
{
|
283 |
+
return method_exists($this, 'block'.$Type.'Continue');
|
284 |
+
}
|
285 |
+
|
286 |
+
protected function isBlockCompletable($Type)
|
287 |
+
{
|
288 |
+
return method_exists($this, 'block'.$Type.'Complete');
|
289 |
+
}
|
290 |
+
|
291 |
+
#
|
292 |
+
# Code
|
293 |
+
|
294 |
+
protected function blockCode($Line, $Block = null)
|
295 |
+
{
|
296 |
+
if (isset($Block) and ! isset($Block['type']) and ! isset($Block['interrupted']))
|
297 |
+
{
|
298 |
+
return;
|
299 |
+
}
|
300 |
+
|
301 |
+
if ($Line['indent'] >= 4)
|
302 |
+
{
|
303 |
+
$text = substr($Line['body'], 4);
|
304 |
+
|
305 |
+
$Block = array(
|
306 |
+
'element' => array(
|
307 |
+
'name' => 'pre',
|
308 |
+
'handler' => 'element',
|
309 |
+
'text' => array(
|
310 |
+
'name' => 'code',
|
311 |
+
'text' => $text,
|
312 |
+
),
|
313 |
+
),
|
314 |
+
);
|
315 |
+
|
316 |
+
return $Block;
|
317 |
+
}
|
318 |
+
}
|
319 |
+
|
320 |
+
protected function blockCodeContinue($Line, $Block)
|
321 |
+
{
|
322 |
+
if ($Line['indent'] >= 4)
|
323 |
+
{
|
324 |
+
if (isset($Block['interrupted']))
|
325 |
+
{
|
326 |
+
$Block['element']['text']['text'] .= "\n";
|
327 |
+
|
328 |
+
unset($Block['interrupted']);
|
329 |
+
}
|
330 |
+
|
331 |
+
$Block['element']['text']['text'] .= "\n";
|
332 |
+
|
333 |
+
$text = substr($Line['body'], 4);
|
334 |
+
|
335 |
+
$Block['element']['text']['text'] .= $text;
|
336 |
+
|
337 |
+
return $Block;
|
338 |
+
}
|
339 |
+
}
|
340 |
+
|
341 |
+
protected function blockCodeComplete($Block)
|
342 |
+
{
|
343 |
+
$text = $Block['element']['text']['text'];
|
344 |
+
|
345 |
+
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
|
346 |
+
|
347 |
+
$Block['element']['text']['text'] = $text;
|
348 |
+
|
349 |
+
return $Block;
|
350 |
+
}
|
351 |
+
|
352 |
+
#
|
353 |
+
# Comment
|
354 |
+
|
355 |
+
protected function blockComment($Line)
|
356 |
+
{
|
357 |
+
if ($this->markupEscaped)
|
358 |
+
{
|
359 |
+
return;
|
360 |
+
}
|
361 |
+
|
362 |
+
if (isset($Line['text'][3]) and $Line['text'][3] === '-' and $Line['text'][2] === '-' and $Line['text'][1] === '!')
|
363 |
+
{
|
364 |
+
$Block = array(
|
365 |
+
'markup' => $Line['body'],
|
366 |
+
);
|
367 |
+
|
368 |
+
if (preg_match('/-->$/', $Line['text']))
|
369 |
+
{
|
370 |
+
$Block['closed'] = true;
|
371 |
+
}
|
372 |
+
|
373 |
+
return $Block;
|
374 |
+
}
|
375 |
+
}
|
376 |
+
|
377 |
+
protected function blockCommentContinue($Line, array $Block)
|
378 |
+
{
|
379 |
+
if (isset($Block['closed']))
|
380 |
+
{
|
381 |
+
return;
|
382 |
+
}
|
383 |
+
|
384 |
+
$Block['markup'] .= "\n" . $Line['body'];
|
385 |
+
|
386 |
+
if (preg_match('/-->$/', $Line['text']))
|
387 |
+
{
|
388 |
+
$Block['closed'] = true;
|
389 |
+
}
|
390 |
+
|
391 |
+
return $Block;
|
392 |
+
}
|
393 |
+
|
394 |
+
#
|
395 |
+
# Fenced Code
|
396 |
+
|
397 |
+
protected function blockFencedCode($Line)
|
398 |
+
{
|
399 |
+
if (preg_match('/^['.$Line['text'][0].']{3,}[ ]*([\w-]+)?[ ]*$/', $Line['text'], $matches))
|
400 |
+
{
|
401 |
+
$Element = array(
|
402 |
+
'name' => 'code',
|
403 |
+
'text' => '',
|
404 |
+
);
|
405 |
+
|
406 |
+
if (isset($matches[1]))
|
407 |
+
{
|
408 |
+
$class = 'language-'.$matches[1];
|
409 |
+
|
410 |
+
$Element['attributes'] = array(
|
411 |
+
'class' => $class,
|
412 |
+
);
|
413 |
+
}
|
414 |
+
|
415 |
+
$Block = array(
|
416 |
+
'char' => $Line['text'][0],
|
417 |
+
'element' => array(
|
418 |
+
'name' => 'pre',
|
419 |
+
'handler' => 'element',
|
420 |
+
'text' => $Element,
|
421 |
+
),
|
422 |
+
);
|
423 |
+
|
424 |
+
return $Block;
|
425 |
+
}
|
426 |
+
}
|
427 |
+
|
428 |
+
protected function blockFencedCodeContinue($Line, $Block)
|
429 |
+
{
|
430 |
+
if (isset($Block['complete']))
|
431 |
+
{
|
432 |
+
return;
|
433 |
+
}
|
434 |
+
|
435 |
+
if (isset($Block['interrupted']))
|
436 |
+
{
|
437 |
+
$Block['element']['text']['text'] .= "\n";
|
438 |
+
|
439 |
+
unset($Block['interrupted']);
|
440 |
+
}
|
441 |
+
|
442 |
+
if (preg_match('/^'.$Block['char'].'{3,}[ ]*$/', $Line['text']))
|
443 |
+
{
|
444 |
+
$Block['element']['text']['text'] = substr($Block['element']['text']['text'], 1);
|
445 |
+
|
446 |
+
$Block['complete'] = true;
|
447 |
+
|
448 |
+
return $Block;
|
449 |
+
}
|
450 |
+
|
451 |
+
$Block['element']['text']['text'] .= "\n".$Line['body'];;
|
452 |
+
|
453 |
+
return $Block;
|
454 |
+
}
|
455 |
+
|
456 |
+
protected function blockFencedCodeComplete($Block)
|
457 |
+
{
|
458 |
+
$text = $Block['element']['text']['text'];
|
459 |
+
|
460 |
+
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
|
461 |
+
|
462 |
+
$Block['element']['text']['text'] = $text;
|
463 |
+
|
464 |
+
return $Block;
|
465 |
+
}
|
466 |
+
|
467 |
+
#
|
468 |
+
# Header
|
469 |
+
|
470 |
+
protected function blockHeader($Line)
|
471 |
+
{
|
472 |
+
if (isset($Line['text'][1]))
|
473 |
+
{
|
474 |
+
$level = 1;
|
475 |
+
|
476 |
+
while (isset($Line['text'][$level]) and $Line['text'][$level] === '#')
|
477 |
+
{
|
478 |
+
$level ++;
|
479 |
+
}
|
480 |
+
|
481 |
+
if ($level > 6)
|
482 |
+
{
|
483 |
+
return;
|
484 |
+
}
|
485 |
+
|
486 |
+
$text = trim($Line['text'], '# ');
|
487 |
+
|
488 |
+
$Block = array(
|
489 |
+
'element' => array(
|
490 |
+
'name' => 'h' . min(6, $level),
|
491 |
+
'text' => $text,
|
492 |
+
'handler' => 'line',
|
493 |
+
),
|
494 |
+
);
|
495 |
+
|
496 |
+
return $Block;
|
497 |
+
}
|
498 |
+
}
|
499 |
+
|
500 |
+
#
|
501 |
+
# List
|
502 |
+
|
503 |
+
protected function blockList($Line)
|
504 |
+
{
|
505 |
+
list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]+[.]');
|
506 |
+
|
507 |
+
if (preg_match('/^('.$pattern.'[ ]+)(.*)/', $Line['text'], $matches))
|
508 |
+
{
|
509 |
+
$Block = array(
|
510 |
+
'indent' => $Line['indent'],
|
511 |
+
'pattern' => $pattern,
|
512 |
+
'element' => array(
|
513 |
+
'name' => $name,
|
514 |
+
'handler' => 'elements',
|
515 |
+
),
|
516 |
+
);
|
517 |
+
|
518 |
+
$Block['li'] = array(
|
519 |
+
'name' => 'li',
|
520 |
+
'handler' => 'li',
|
521 |
+
'text' => array(
|
522 |
+
$matches[2],
|
523 |
+
),
|
524 |
+
);
|
525 |
+
|
526 |
+
$Block['element']['text'] []= & $Block['li'];
|
527 |
+
|
528 |
+
return $Block;
|
529 |
+
}
|
530 |
+
}
|
531 |
+
|
532 |
+
protected function blockListContinue($Line, array $Block)
|
533 |
+
{
|
534 |
+
if ($Block['indent'] === $Line['indent'] and preg_match('/^'.$Block['pattern'].'(?:[ ]+(.*)|$)/', $Line['text'], $matches))
|
535 |
+
{
|
536 |
+
if (isset($Block['interrupted']))
|
537 |
+
{
|
538 |
+
$Block['li']['text'] []= '';
|
539 |
+
|
540 |
+
unset($Block['interrupted']);
|
541 |
+
}
|
542 |
+
|
543 |
+
unset($Block['li']);
|
544 |
+
|
545 |
+
$text = isset($matches[1]) ? $matches[1] : '';
|
546 |
+
|
547 |
+
$Block['li'] = array(
|
548 |
+
'name' => 'li',
|
549 |
+
'handler' => 'li',
|
550 |
+
'text' => array(
|
551 |
+
$text,
|
552 |
+
),
|
553 |
+
);
|
554 |
+
|
555 |
+
$Block['element']['text'] []= & $Block['li'];
|
556 |
+
|
557 |
+
return $Block;
|
558 |
+
}
|
559 |
+
|
560 |
+
if ($Line['text'][0] === '[' and $this->blockReference($Line))
|
561 |
+
{
|
562 |
+
return $Block;
|
563 |
+
}
|
564 |
+
|
565 |
+
if ( ! isset($Block['interrupted']))
|
566 |
+
{
|
567 |
+
$text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
|
568 |
+
|
569 |
+
$Block['li']['text'] []= $text;
|
570 |
+
|
571 |
+
return $Block;
|
572 |
+
}
|
573 |
+
|
574 |
+
if ($Line['indent'] > 0)
|
575 |
+
{
|
576 |
+
$Block['li']['text'] []= '';
|
577 |
+
|
578 |
+
$text = preg_replace('/^[ ]{0,4}/', '', $Line['body']);
|
579 |
+
|
580 |
+
$Block['li']['text'] []= $text;
|
581 |
+
|
582 |
+
unset($Block['interrupted']);
|
583 |
+
|
584 |
+
return $Block;
|
585 |
+
}
|
586 |
+
}
|
587 |
+
|
588 |
+
#
|
589 |
+
# Quote
|
590 |
+
|
591 |
+
protected function blockQuote($Line)
|
592 |
+
{
|
593 |
+
if (preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
|
594 |
+
{
|
595 |
+
$Block = array(
|
596 |
+
'element' => array(
|
597 |
+
'name' => 'blockquote',
|
598 |
+
'handler' => 'lines',
|
599 |
+
'text' => (array) $matches[1],
|
600 |
+
),
|
601 |
+
);
|
602 |
+
|
603 |
+
return $Block;
|
604 |
+
}
|
605 |
+
}
|
606 |
+
|
607 |
+
protected function blockQuoteContinue($Line, array $Block)
|
608 |
+
{
|
609 |
+
if ($Line['text'][0] === '>' and preg_match('/^>[ ]?(.*)/', $Line['text'], $matches))
|
610 |
+
{
|
611 |
+
if (isset($Block['interrupted']))
|
612 |
+
{
|
613 |
+
$Block['element']['text'] []= '';
|
614 |
+
|
615 |
+
unset($Block['interrupted']);
|
616 |
+
}
|
617 |
+
|
618 |
+
$Block['element']['text'] []= $matches[1];
|
619 |
+
|
620 |
+
return $Block;
|
621 |
+
}
|
622 |
+
|
623 |
+
if ( ! isset($Block['interrupted']))
|
624 |
+
{
|
625 |
+
$Block['element']['text'] []= $Line['text'];
|
626 |
+
|
627 |
+
return $Block;
|
628 |
+
}
|
629 |
+
}
|
630 |
+
|
631 |
+
#
|
632 |
+
# Rule
|
633 |
+
|
634 |
+
protected function blockRule($Line)
|
635 |
+
{
|
636 |
+
if (preg_match('/^(['.$Line['text'][0].'])([ ]*\1){2,}[ ]*$/', $Line['text']))
|
637 |
+
{
|
638 |
+
$Block = array(
|
639 |
+
'element' => array(
|
640 |
+
'name' => 'hr'
|
641 |
+
),
|
642 |
+
);
|
643 |
+
|
644 |
+
return $Block;
|
645 |
+
}
|
646 |
+
}
|
647 |
+
|
648 |
+
#
|
649 |
+
# Setext
|
650 |
+
|
651 |
+
protected function blockSetextHeader($Line, array $Block = null)
|
652 |
+
{
|
653 |
+
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
|
654 |
+
{
|
655 |
+
return;
|
656 |
+
}
|
657 |
+
|
658 |
+
if (chop($Line['text'], $Line['text'][0]) === '')
|
659 |
+
{
|
660 |
+
$Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
|
661 |
+
|
662 |
+
return $Block;
|
663 |
+
}
|
664 |
+
}
|
665 |
+
|
666 |
+
#
|
667 |
+
# Markup
|
668 |
+
|
669 |
+
protected function blockMarkup($Line)
|
670 |
+
{
|
671 |
+
if ($this->markupEscaped)
|
672 |
+
{
|
673 |
+
return;
|
674 |
+
}
|
675 |
+
|
676 |
+
if (preg_match('/^<(\w*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
|
677 |
+
{
|
678 |
+
$element = strtolower($matches[1]);
|
679 |
+
|
680 |
+
if (in_array($element, $this->textLevelElements))
|
681 |
+
{
|
682 |
+
return;
|
683 |
+
}
|
684 |
+
|
685 |
+
$Block = array(
|
686 |
+
'name' => $matches[1],
|
687 |
+
'depth' => 0,
|
688 |
+
'markup' => $Line['text'],
|
689 |
+
);
|
690 |
+
|
691 |
+
$length = strlen($matches[0]);
|
692 |
+
|
693 |
+
$remainder = substr($Line['text'], $length);
|
694 |
+
|
695 |
+
if (trim($remainder) === '')
|
696 |
+
{
|
697 |
+
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
|
698 |
+
{
|
699 |
+
$Block['closed'] = true;
|
700 |
+
|
701 |
+
$Block['void'] = true;
|
702 |
+
}
|
703 |
+
}
|
704 |
+
else
|
705 |
+
{
|
706 |
+
if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
|
707 |
+
{
|
708 |
+
return;
|
709 |
+
}
|
710 |
+
|
711 |
+
if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
|
712 |
+
{
|
713 |
+
$Block['closed'] = true;
|
714 |
+
}
|
715 |
+
}
|
716 |
+
|
717 |
+
return $Block;
|
718 |
+
}
|
719 |
+
}
|
720 |
+
|
721 |
+
protected function blockMarkupContinue($Line, array $Block)
|
722 |
+
{
|
723 |
+
if (isset($Block['closed']))
|
724 |
+
{
|
725 |
+
return;
|
726 |
+
}
|
727 |
+
|
728 |
+
if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
|
729 |
+
{
|
730 |
+
$Block['depth'] ++;
|
731 |
+
}
|
732 |
+
|
733 |
+
if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
|
734 |
+
{
|
735 |
+
if ($Block['depth'] > 0)
|
736 |
+
{
|
737 |
+
$Block['depth'] --;
|
738 |
+
}
|
739 |
+
else
|
740 |
+
{
|
741 |
+
$Block['closed'] = true;
|
742 |
+
}
|
743 |
+
}
|
744 |
+
|
745 |
+
if (isset($Block['interrupted']))
|
746 |
+
{
|
747 |
+
$Block['markup'] .= "\n";
|
748 |
+
|
749 |
+
unset($Block['interrupted']);
|
750 |
+
}
|
751 |
+
|
752 |
+
$Block['markup'] .= "\n".$Line['body'];
|
753 |
+
|
754 |
+
return $Block;
|
755 |
+
}
|
756 |
+
|
757 |
+
#
|
758 |
+
# Reference
|
759 |
+
|
760 |
+
protected function blockReference($Line)
|
761 |
+
{
|
762 |
+
if (preg_match('/^\[(.+?)\]:[ ]*<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*$/', $Line['text'], $matches))
|
763 |
+
{
|
764 |
+
$id = strtolower($matches[1]);
|
765 |
+
|
766 |
+
$Data = array(
|
767 |
+
'url' => $matches[2],
|
768 |
+
'title' => null,
|
769 |
+
);
|
770 |
+
|
771 |
+
if (isset($matches[3]))
|
772 |
+
{
|
773 |
+
$Data['title'] = $matches[3];
|
774 |
+
}
|
775 |
+
|
776 |
+
$this->DefinitionData['Reference'][$id] = $Data;
|
777 |
+
|
778 |
+
$Block = array(
|
779 |
+
'hidden' => true,
|
780 |
+
);
|
781 |
+
|
782 |
+
return $Block;
|
783 |
+
}
|
784 |
+
}
|
785 |
+
|
786 |
+
#
|
787 |
+
# Table
|
788 |
+
|
789 |
+
protected function blockTable($Line, array $Block = null)
|
790 |
+
{
|
791 |
+
if ( ! isset($Block) or isset($Block['type']) or isset($Block['interrupted']))
|
792 |
+
{
|
793 |
+
return;
|
794 |
+
}
|
795 |
+
|
796 |
+
if (strpos($Block['element']['text'], '|') !== false and chop($Line['text'], ' -:|') === '')
|
797 |
+
{
|
798 |
+
$alignments = array();
|
799 |
+
|
800 |
+
$divider = $Line['text'];
|
801 |
+
|
802 |
+
$divider = trim($divider);
|
803 |
+
$divider = trim($divider, '|');
|
804 |
+
|
805 |
+
$dividerCells = explode('|', $divider);
|
806 |
+
|
807 |
+
foreach ($dividerCells as $dividerCell)
|
808 |
+
{
|
809 |
+
$dividerCell = trim($dividerCell);
|
810 |
+
|
811 |
+
if ($dividerCell === '')
|
812 |
+
{
|
813 |
+
continue;
|
814 |
+
}
|
815 |
+
|
816 |
+
$alignment = null;
|
817 |
+
|
818 |
+
if ($dividerCell[0] === ':')
|
819 |
+
{
|
820 |
+
$alignment = 'left';
|
821 |
+
}
|
822 |
+
|
823 |
+
if (substr($dividerCell, - 1) === ':')
|
824 |
+
{
|
825 |
+
$alignment = $alignment === 'left' ? 'center' : 'right';
|
826 |
+
}
|
827 |
+
|
828 |
+
$alignments []= $alignment;
|
829 |
+
}
|
830 |
+
|
831 |
+
# ~
|
832 |
+
|
833 |
+
$HeaderElements = array();
|
834 |
+
|
835 |
+
$header = $Block['element']['text'];
|
836 |
+
|
837 |
+
$header = trim($header);
|
838 |
+
$header = trim($header, '|');
|
839 |
+
|
840 |
+
$headerCells = explode('|', $header);
|
841 |
+
|
842 |
+
foreach ($headerCells as $index => $headerCell)
|
843 |
+
{
|
844 |
+
$headerCell = trim($headerCell);
|
845 |
+
|
846 |
+
$HeaderElement = array(
|
847 |
+
'name' => 'th',
|
848 |
+
'text' => $headerCell,
|
849 |
+
'handler' => 'line',
|
850 |
+
);
|
851 |
+
|
852 |
+
if (isset($alignments[$index]))
|
853 |
+
{
|
854 |
+
$alignment = $alignments[$index];
|
855 |
+
|
856 |
+
$HeaderElement['attributes'] = array(
|
857 |
+
'style' => 'text-align: '.$alignment.';',
|
858 |
+
);
|
859 |
+
}
|
860 |
+
|
861 |
+
$HeaderElements []= $HeaderElement;
|
862 |
+
}
|
863 |
+
|
864 |
+
# ~
|
865 |
+
|
866 |
+
$Block = array(
|
867 |
+
'alignments' => $alignments,
|
868 |
+
'identified' => true,
|
869 |
+
'element' => array(
|
870 |
+
'name' => 'table',
|
871 |
+
'handler' => 'elements',
|
872 |
+
),
|
873 |
+
);
|
874 |
+
|
875 |
+
$Block['element']['text'] []= array(
|
876 |
+
'name' => 'thead',
|
877 |
+
'handler' => 'elements',
|
878 |
+
);
|
879 |
+
|
880 |
+
$Block['element']['text'] []= array(
|
881 |
+
'name' => 'tbody',
|
882 |
+
'handler' => 'elements',
|
883 |
+
'text' => array(),
|
884 |
+
);
|
885 |
+
|
886 |
+
$Block['element']['text'][0]['text'] []= array(
|
887 |
+
'name' => 'tr',
|
888 |
+
'handler' => 'elements',
|
889 |
+
'text' => $HeaderElements,
|
890 |
+
);
|
891 |
+
|
892 |
+
return $Block;
|
893 |
+
}
|
894 |
+
}
|
895 |
+
|
896 |
+
protected function blockTableContinue($Line, array $Block)
|
897 |
+
{
|
898 |
+
if (isset($Block['interrupted']))
|
899 |
+
{
|
900 |
+
return;
|
901 |
+
}
|
902 |
+
|
903 |
+
if ($Line['text'][0] === '|' or strpos($Line['text'], '|'))
|
904 |
+
{
|
905 |
+
$Elements = array();
|
906 |
+
|
907 |
+
$row = $Line['text'];
|
908 |
+
|
909 |
+
$row = trim($row);
|
910 |
+
$row = trim($row, '|');
|
911 |
+
|
912 |
+
preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]+`|`)+/', $row, $matches);
|
913 |
+
|
914 |
+
foreach ($matches[0] as $index => $cell)
|
915 |
+
{
|
916 |
+
$cell = trim($cell);
|
917 |
+
|
918 |
+
$Element = array(
|
919 |
+
'name' => 'td',
|
920 |
+
'handler' => 'line',
|
921 |
+
'text' => $cell,
|
922 |
+
);
|
923 |
+
|
924 |
+
if (isset($Block['alignments'][$index]))
|
925 |
+
{
|
926 |
+
$Element['attributes'] = array(
|
927 |
+
'style' => 'text-align: '.$Block['alignments'][$index].';',
|
928 |
+
);
|
929 |
+
}
|
930 |
+
|
931 |
+
$Elements []= $Element;
|
932 |
+
}
|
933 |
+
|
934 |
+
$Element = array(
|
935 |
+
'name' => 'tr',
|
936 |
+
'handler' => 'elements',
|
937 |
+
'text' => $Elements,
|
938 |
+
);
|
939 |
+
|
940 |
+
$Block['element']['text'][1]['text'] []= $Element;
|
941 |
+
|
942 |
+
return $Block;
|
943 |
+
}
|
944 |
+
}
|
945 |
+
|
946 |
+
#
|
947 |
+
# ~
|
948 |
+
#
|
949 |
+
|
950 |
+
protected function paragraph($Line)
|
951 |
+
{
|
952 |
+
$Block = array(
|
953 |
+
'element' => array(
|
954 |
+
'name' => 'p',
|
955 |
+
'text' => $Line['text'],
|
956 |
+
'handler' => 'line',
|
957 |
+
),
|
958 |
+
);
|
959 |
+
|
960 |
+
return $Block;
|
961 |
+
}
|
962 |
+
|
963 |
+
#
|
964 |
+
# Inline Elements
|
965 |
+
#
|
966 |
+
|
967 |
+
protected $InlineTypes = array(
|
968 |
+
'"' => array('SpecialCharacter'),
|
969 |
+
'!' => array('Image'),
|
970 |
+
'&' => array('SpecialCharacter'),
|
971 |
+
'*' => array('Emphasis'),
|
972 |
+
':' => array('Url'),
|
973 |
+
'<' => array('UrlTag', 'EmailTag', 'Markup', 'SpecialCharacter'),
|
974 |
+
'>' => array('SpecialCharacter'),
|
975 |
+
'[' => array('Link'),
|
976 |
+
'_' => array('Emphasis'),
|
977 |
+
'`' => array('Code'),
|
978 |
+
'~' => array('Strikethrough'),
|
979 |
+
'\\' => array('EscapeSequence'),
|
980 |
+
);
|
981 |
+
|
982 |
+
# ~
|
983 |
+
|
984 |
+
protected $inlineMarkerList = '!"*_&[:<>`~\\';
|
985 |
+
|
986 |
+
#
|
987 |
+
# ~
|
988 |
+
#
|
989 |
+
|
990 |
+
public function line($text)
|
991 |
+
{
|
992 |
+
$markup = '';
|
993 |
+
|
994 |
+
# $excerpt is based on the first occurrence of a marker
|
995 |
+
|
996 |
+
while ($excerpt = strpbrk($text, $this->inlineMarkerList))
|
997 |
+
{
|
998 |
+
$marker = $excerpt[0];
|
999 |
+
|
1000 |
+
$markerPosition = strpos($text, $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 |
+
# makes sure that the inline belongs to "our" marker
|
1014 |
+
|
1015 |
+
if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
|
1016 |
+
{
|
1017 |
+
continue;
|
1018 |
+
}
|
1019 |
+
|
1020 |
+
# sets a default inline position
|
1021 |
+
|
1022 |
+
if ( ! isset($Inline['position']))
|
1023 |
+
{
|
1024 |
+
$Inline['position'] = $markerPosition;
|
1025 |
+
}
|
1026 |
+
|
1027 |
+
# the text that comes before the inline
|
1028 |
+
$unmarkedText = substr($text, 0, $Inline['position']);
|
1029 |
+
|
1030 |
+
# compile the unmarked text
|
1031 |
+
$markup .= $this->unmarkedText($unmarkedText);
|
1032 |
+
|
1033 |
+
# compile the inline
|
1034 |
+
$markup .= isset($Inline['markup']) ? $Inline['markup'] : $this->element($Inline['element']);
|
1035 |
+
|
1036 |
+
# remove the examined text
|
1037 |
+
$text = substr($text, $Inline['position'] + $Inline['extent']);
|
1038 |
+
|
1039 |
+
continue 2;
|
1040 |
+
}
|
1041 |
+
|
1042 |
+
# the marker does not belong to an inline
|
1043 |
+
|
1044 |
+
$unmarkedText = substr($text, 0, $markerPosition + 1);
|
1045 |
+
|
1046 |
+
$markup .= $this->unmarkedText($unmarkedText);
|
1047 |
+
|
1048 |
+
$text = substr($text, $markerPosition + 1);
|
1049 |
+
}
|
1050 |
+
|
1051 |
+
$markup .= $this->unmarkedText($text);
|
1052 |
+
|
1053 |
+
return $markup;
|
1054 |
+
}
|
1055 |
+
|
1056 |
+
#
|
1057 |
+
# ~
|
1058 |
+
#
|
1059 |
+
|
1060 |
+
protected function inlineCode($Excerpt)
|
1061 |
+
{
|
1062 |
+
$marker = $Excerpt['text'][0];
|
1063 |
+
|
1064 |
+
if (preg_match('/^('.$marker.'+)[ ]*(.+?)[ ]*(?<!'.$marker.')\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
|
1065 |
+
{
|
1066 |
+
$text = $matches[2];
|
1067 |
+
$text = htmlspecialchars($text, ENT_NOQUOTES, 'UTF-8');
|
1068 |
+
$text = preg_replace("/[ ]*\n/", ' ', $text);
|
1069 |
+
|
1070 |
+
return array(
|
1071 |
+
'extent' => strlen($matches[0]),
|
1072 |
+
'element' => array(
|
1073 |
+
'name' => 'code',
|
1074 |
+
'text' => $text,
|
1075 |
+
),
|
1076 |
+
);
|
1077 |
+
}
|
1078 |
+
}
|
1079 |
+
|
1080 |
+
protected function inlineEmailTag($Excerpt)
|
1081 |
+
{
|
1082 |
+
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<((mailto:)?\S+?@\S+?)>/i', $Excerpt['text'], $matches))
|
1083 |
+
{
|
1084 |
+
$url = $matches[1];
|
1085 |
+
|
1086 |
+
if ( ! isset($matches[2]))
|
1087 |
+
{
|
1088 |
+
$url = 'mailto:' . $url;
|
1089 |
+
}
|
1090 |
+
|
1091 |
+
return array(
|
1092 |
+
'extent' => strlen($matches[0]),
|
1093 |
+
'element' => array(
|
1094 |
+
'name' => 'a',
|
1095 |
+
'text' => $matches[1],
|
1096 |
+
'attributes' => array(
|
1097 |
+
'href' => $url,
|
1098 |
+
),
|
1099 |
+
),
|
1100 |
+
);
|
1101 |
+
}
|
1102 |
+
}
|
1103 |
+
|
1104 |
+
protected function inlineEmphasis($Excerpt)
|
1105 |
+
{
|
1106 |
+
if ( ! isset($Excerpt['text'][1]))
|
1107 |
+
{
|
1108 |
+
return;
|
1109 |
+
}
|
1110 |
+
|
1111 |
+
$marker = $Excerpt['text'][0];
|
1112 |
+
|
1113 |
+
if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
|
1114 |
+
{
|
1115 |
+
$emphasis = 'strong';
|
1116 |
+
}
|
1117 |
+
elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
|
1118 |
+
{
|
1119 |
+
$emphasis = 'em';
|
1120 |
+
}
|
1121 |
+
else
|
1122 |
+
{
|
1123 |
+
return;
|
1124 |
+
}
|
1125 |
+
|
1126 |
+
return array(
|
1127 |
+
'extent' => strlen($matches[0]),
|
1128 |
+
'element' => array(
|
1129 |
+
'name' => $emphasis,
|
1130 |
+
'handler' => 'line',
|
1131 |
+
'text' => $matches[1],
|
1132 |
+
),
|
1133 |
+
);
|
1134 |
+
}
|
1135 |
+
|
1136 |
+
protected function inlineEscapeSequence($Excerpt)
|
1137 |
+
{
|
1138 |
+
if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
|
1139 |
+
{
|
1140 |
+
return array(
|
1141 |
+
'markup' => $Excerpt['text'][1],
|
1142 |
+
'extent' => 2,
|
1143 |
+
);
|
1144 |
+
}
|
1145 |
+
}
|
1146 |
+
|
1147 |
+
protected function inlineImage($Excerpt)
|
1148 |
+
{
|
1149 |
+
if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
|
1150 |
+
{
|
1151 |
+
return;
|
1152 |
+
}
|
1153 |
+
|
1154 |
+
$Excerpt['text']= substr($Excerpt['text'], 1);
|
1155 |
+
|
1156 |
+
$Link = $this->inlineLink($Excerpt);
|
1157 |
+
|
1158 |
+
if ($Link === null)
|
1159 |
+
{
|
1160 |
+
return;
|
1161 |
+
}
|
1162 |
+
|
1163 |
+
$Inline = array(
|
1164 |
+
'extent' => $Link['extent'] + 1,
|
1165 |
+
'element' => array(
|
1166 |
+
'name' => 'img',
|
1167 |
+
'attributes' => array(
|
1168 |
+
'src' => $Link['element']['attributes']['href'],
|
1169 |
+
'alt' => $Link['element']['text'],
|
1170 |
+
),
|
1171 |
+
),
|
1172 |
+
);
|
1173 |
+
|
1174 |
+
$Inline['element']['attributes'] += $Link['element']['attributes'];
|
1175 |
+
|
1176 |
+
unset($Inline['element']['attributes']['href']);
|
1177 |
+
|
1178 |
+
return $Inline;
|
1179 |
+
}
|
1180 |
+
|
1181 |
+
protected function inlineLink($Excerpt)
|
1182 |
+
{
|
1183 |
+
$Element = array(
|
1184 |
+
'name' => 'a',
|
1185 |
+
'handler' => 'line',
|
1186 |
+
'text' => null,
|
1187 |
+
'attributes' => array(
|
1188 |
+
'href' => null,
|
1189 |
+
'title' => null,
|
1190 |
+
),
|
1191 |
+
);
|
1192 |
+
|
1193 |
+
$extent = 0;
|
1194 |
+
|
1195 |
+
$remainder = $Excerpt['text'];
|
1196 |
+
|
1197 |
+
if (preg_match('/\[((?:[^][]|(?R))*)\]/', $remainder, $matches))
|
1198 |
+
{
|
1199 |
+
$Element['text'] = $matches[1];
|
1200 |
+
|
1201 |
+
$extent += strlen($matches[0]);
|
1202 |
+
|
1203 |
+
$remainder = substr($remainder, $extent);
|
1204 |
+
}
|
1205 |
+
else
|
1206 |
+
{
|
1207 |
+
return;
|
1208 |
+
}
|
1209 |
+
|
1210 |
+
if (preg_match('/^[(]((?:[^ ()]|[(][^ )]+[)])+)(?:[ ]+("[^"]*"|\'[^\']*\'))?[)]/', $remainder, $matches))
|
1211 |
+
{
|
1212 |
+
$Element['attributes']['href'] = $matches[1];
|
1213 |
+
|
1214 |
+
if (isset($matches[2]))
|
1215 |
+
{
|
1216 |
+
$Element['attributes']['title'] = substr($matches[2], 1, - 1);
|
1217 |
+
}
|
1218 |
+
|
1219 |
+
$extent += strlen($matches[0]);
|
1220 |
+
}
|
1221 |
+
else
|
1222 |
+
{
|
1223 |
+
if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
|
1224 |
+
{
|
1225 |
+
$definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
|
1226 |
+
$definition = strtolower($definition);
|
1227 |
+
|
1228 |
+
$extent += strlen($matches[0]);
|
1229 |
+
}
|
1230 |
+
else
|
1231 |
+
{
|
1232 |
+
$definition = strtolower($Element['text']);
|
1233 |
+
}
|
1234 |
+
|
1235 |
+
if ( ! isset($this->DefinitionData['Reference'][$definition]))
|
1236 |
+
{
|
1237 |
+
return;
|
1238 |
+
}
|
1239 |
+
|
1240 |
+
$Definition = $this->DefinitionData['Reference'][$definition];
|
1241 |
+
|
1242 |
+
$Element['attributes']['href'] = $Definition['url'];
|
1243 |
+
$Element['attributes']['title'] = $Definition['title'];
|
1244 |
+
}
|
1245 |
+
|
1246 |
+
$Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']);
|
1247 |
+
|
1248 |
+
return array(
|
1249 |
+
'extent' => $extent,
|
1250 |
+
'element' => $Element,
|
1251 |
+
);
|
1252 |
+
}
|
1253 |
+
|
1254 |
+
protected function inlineMarkup($Excerpt)
|
1255 |
+
{
|
1256 |
+
if ($this->markupEscaped or strpos($Excerpt['text'], '>') === false)
|
1257 |
+
{
|
1258 |
+
return;
|
1259 |
+
}
|
1260 |
+
|
1261 |
+
if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w*[ ]*>/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('/^<!---?[^>-](?:-?[^-])*-->/s', $Excerpt['text'], $matches))
|
1270 |
+
{
|
1271 |
+
return array(
|
1272 |
+
'markup' => $matches[0],
|
1273 |
+
'extent' => strlen($matches[0]),
|
1274 |
+
);
|
1275 |
+
}
|
1276 |
+
|
1277 |
+
if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w*(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*\/?>/s', $Excerpt['text'], $matches))
|
1278 |
+
{
|
1279 |
+
return array(
|
1280 |
+
'markup' => $matches[0],
|
1281 |
+
'extent' => strlen($matches[0]),
|
1282 |
+
);
|
1283 |
+
}
|
1284 |
+
}
|
1285 |
+
|
1286 |
+
protected function inlineSpecialCharacter($Excerpt)
|
1287 |
+
{
|
1288 |
+
if ($Excerpt['text'][0] === '&' and ! preg_match('/^&#?\w+;/', $Excerpt['text']))
|
1289 |
+
{
|
1290 |
+
return array(
|
1291 |
+
'markup' => '&',
|
1292 |
+
'extent' => 1,
|
1293 |
+
);
|
1294 |
+
}
|
1295 |
+
|
1296 |
+
$SpecialCharacter = array('>' => 'gt', '<' => 'lt', '"' => 'quot');
|
1297 |
+
|
1298 |
+
if (isset($SpecialCharacter[$Excerpt['text'][0]]))
|
1299 |
+
{
|
1300 |
+
return array(
|
1301 |
+
'markup' => '&'.$SpecialCharacter[$Excerpt['text'][0]].';',
|
1302 |
+
'extent' => 1,
|
1303 |
+
);
|
1304 |
+
}
|
1305 |
+
}
|
1306 |
+
|
1307 |
+
protected function inlineStrikethrough($Excerpt)
|
1308 |
+
{
|
1309 |
+
if ( ! isset($Excerpt['text'][1]))
|
1310 |
+
{
|
1311 |
+
return;
|
1312 |
+
}
|
1313 |
+
|
1314 |
+
if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
|
1315 |
+
{
|
1316 |
+
return array(
|
1317 |
+
'extent' => strlen($matches[0]),
|
1318 |
+
'element' => array(
|
1319 |
+
'name' => 'del',
|
1320 |
+
'text' => $matches[1],
|
1321 |
+
'handler' => 'line',
|
1322 |
+
),
|
1323 |
+
);
|
1324 |
+
}
|
1325 |
+
}
|
1326 |
+
|
1327 |
+
protected function inlineUrl($Excerpt)
|
1328 |
+
{
|
1329 |
+
if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
|
1330 |
+
{
|
1331 |
+
return;
|
1332 |
+
}
|
1333 |
+
|
1334 |
+
if (preg_match('/\bhttps?:[\/]{2}[^\s<]+\b\/*/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE))
|
1335 |
+
{
|
1336 |
+
$Inline = array(
|
1337 |
+
'extent' => strlen($matches[0][0]),
|
1338 |
+
'position' => $matches[0][1],
|
1339 |
+
'element' => array(
|
1340 |
+
'name' => 'a',
|
1341 |
+
'text' => $matches[0][0],
|
1342 |
+
'attributes' => array(
|
1343 |
+
'href' => $matches[0][0],
|
1344 |
+
),
|
1345 |
+
),
|
1346 |
+
);
|
1347 |
+
|
1348 |
+
return $Inline;
|
1349 |
+
}
|
1350 |
+
}
|
1351 |
+
|
1352 |
+
protected function inlineUrlTag($Excerpt)
|
1353 |
+
{
|
1354 |
+
if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w+:\/{2}[^ >]+)>/i', $Excerpt['text'], $matches))
|
1355 |
+
{
|
1356 |
+
$url = str_replace(array('&', '<'), array('&', '<'), $matches[1]);
|
1357 |
+
|
1358 |
+
return array(
|
1359 |
+
'extent' => strlen($matches[0]),
|
1360 |
+
'element' => array(
|
1361 |
+
'name' => 'a',
|
1362 |
+
'text' => $url,
|
1363 |
+
'attributes' => array(
|
1364 |
+
'href' => $url,
|
1365 |
+
),
|
1366 |
+
),
|
1367 |
+
);
|
1368 |
+
}
|
1369 |
+
}
|
1370 |
+
|
1371 |
+
# ~
|
1372 |
+
|
1373 |
+
protected function unmarkedText($text)
|
1374 |
+
{
|
1375 |
+
if ($this->breaksEnabled)
|
1376 |
+
{
|
1377 |
+
$text = preg_replace('/[ ]*\n/', "<br />\n", $text);
|
1378 |
+
}
|
1379 |
+
else
|
1380 |
+
{
|
1381 |
+
$text = preg_replace('/(?:[ ][ ]+|[ ]*\\\\)\n/', "<br />\n", $text);
|
1382 |
+
$text = str_replace(" \n", "\n", $text);
|
1383 |
+
}
|
1384 |
+
|
1385 |
+
return $text;
|
1386 |
+
}
|
1387 |
+
|
1388 |
+
#
|
1389 |
+
# Handlers
|
1390 |
+
#
|
1391 |
+
|
1392 |
+
protected function element(array $Element)
|
1393 |
+
{
|
1394 |
+
$markup = '<'.$Element['name'];
|
1395 |
+
|
1396 |
+
if (isset($Element['attributes']))
|
1397 |
+
{
|
1398 |
+
foreach ($Element['attributes'] as $name => $value)
|
1399 |
+
{
|
1400 |
+
if ($value === null)
|
1401 |
+
{
|
1402 |
+
continue;
|
1403 |
+
}
|
1404 |
+
|
1405 |
+
$markup .= ' '.$name.'="'.$value.'"';
|
1406 |
+
}
|
1407 |
+
}
|
1408 |
+
|
1409 |
+
if (isset($Element['text']))
|
1410 |
+
{
|
1411 |
+
$markup .= '>';
|
1412 |
+
|
1413 |
+
if (isset($Element['handler']))
|
1414 |
+
{
|
1415 |
+
$markup .= $this->{$Element['handler']}($Element['text']);
|
1416 |
+
}
|
1417 |
+
else
|
1418 |
+
{
|
1419 |
+
$markup .= $Element['text'];
|
1420 |
+
}
|
1421 |
+
|
1422 |
+
$markup .= '</'.$Element['name'].'>';
|
1423 |
+
}
|
1424 |
+
else
|
1425 |
+
{
|
1426 |
+
$markup .= ' />';
|
1427 |
+
}
|
1428 |
+
|
1429 |
+
return $markup;
|
1430 |
+
}
|
1431 |
+
|
1432 |
+
protected function elements(array $Elements)
|
1433 |
+
{
|
1434 |
+
$markup = '';
|
1435 |
+
|
1436 |
+
foreach ($Elements as $Element)
|
1437 |
+
{
|
1438 |
+
$markup .= "\n" . $this->element($Element);
|
1439 |
+
}
|
1440 |
+
|
1441 |
+
$markup .= "\n";
|
1442 |
+
|
1443 |
+
return $markup;
|
1444 |
+
}
|
1445 |
+
|
1446 |
+
# ~
|
1447 |
+
|
1448 |
+
protected function li($lines)
|
1449 |
+
{
|
1450 |
+
$markup = $this->lines($lines);
|
1451 |
+
|
1452 |
+
$trimmedMarkup = trim($markup);
|
1453 |
+
|
1454 |
+
if ( ! in_array('', $lines) and substr($trimmedMarkup, 0, 3) === '<p>')
|
1455 |
+
{
|
1456 |
+
$markup = $trimmedMarkup;
|
1457 |
+
$markup = substr($markup, 3);
|
1458 |
+
|
1459 |
+
$position = strpos($markup, "</p>");
|
1460 |
+
|
1461 |
+
$markup = substr_replace($markup, '', $position, 4);
|
1462 |
+
}
|
1463 |
+
|
1464 |
+
return $markup;
|
1465 |
+
}
|
1466 |
+
|
1467 |
+
#
|
1468 |
+
# Deprecated Methods
|
1469 |
+
#
|
1470 |
+
|
1471 |
+
function parse($text)
|
1472 |
+
{
|
1473 |
+
$markup = $this->text($text);
|
1474 |
+
|
1475 |
+
return $markup;
|
1476 |
+
}
|
1477 |
+
|
1478 |
+
#
|
1479 |
+
# Static Methods
|
1480 |
+
#
|
1481 |
+
|
1482 |
+
static function instance($name = 'default')
|
1483 |
+
{
|
1484 |
+
if (isset(self::$instances[$name]))
|
1485 |
+
{
|
1486 |
+
return self::$instances[$name];
|
1487 |
+
}
|
1488 |
+
|
1489 |
+
$instance = new static();
|
1490 |
+
|
1491 |
+
self::$instances[$name] = $instance;
|
1492 |
+
|
1493 |
+
return $instance;
|
1494 |
+
}
|
1495 |
+
|
1496 |
+
private static $instances = array();
|
1497 |
+
|
1498 |
+
#
|
1499 |
+
# Fields
|
1500 |
+
#
|
1501 |
+
|
1502 |
+
protected $DefinitionData;
|
1503 |
+
|
1504 |
+
#
|
1505 |
+
# Read-Only
|
1506 |
+
|
1507 |
+
protected $specialCharacters = array(
|
1508 |
+
'\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|',
|
1509 |
+
);
|
1510 |
+
|
1511 |
+
protected $StrongRegex = array(
|
1512 |
+
'*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*[*])+?)[*]{2}(?![*])/s',
|
1513 |
+
'_' => '/^__((?:\\\\_|[^_]|_[^_]*_)+?)__(?!_)/us',
|
1514 |
+
);
|
1515 |
+
|
1516 |
+
protected $EmRegex = array(
|
1517 |
+
'*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
|
1518 |
+
'_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
|
1519 |
+
);
|
1520 |
+
|
1521 |
+
protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*(?:\s*=\s*(?:[^"\'=<>`\s]+|"[^"]*"|\'[^\']*\'))?';
|
1522 |
+
|
1523 |
+
protected $voidElements = array(
|
1524 |
+
'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
|
1525 |
+
);
|
1526 |
+
|
1527 |
+
protected $textLevelElements = array(
|
1528 |
+
'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
|
1529 |
+
'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
|
1530 |
+
'i', 'rp', 'del', 'code', 'strike', 'marquee',
|
1531 |
+
'q', 'rt', 'ins', 'font', 'strong',
|
1532 |
+
's', 'tt', 'sub', 'mark',
|
1533 |
+
'u', 'xm', 'sup', 'nobr',
|
1534 |
+
'var', 'ruby',
|
1535 |
+
'wbr', 'span',
|
1536 |
+
'time',
|
1537 |
+
);
|
1538 |
+
}
|
updater/vendor/PucReadmeParser.php
ADDED
@@ -0,0 +1,348 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
|
3 |
+
if ( !class_exists('PucReadmeParser', false) ):
|
4 |
+
|
5 |
+
/**
|
6 |
+
* This is a slightly modified version of github.com/markjaquith/WordPress-Plugin-Readme-Parser
|
7 |
+
* It uses Parsedown instead of the "Markdown Extra" parser.
|
8 |
+
*/
|
9 |
+
|
10 |
+
class PucReadmeParser {
|
11 |
+
|
12 |
+
function __construct() {
|
13 |
+
// This space intentionally blank
|
14 |
+
}
|
15 |
+
|
16 |
+
function parse_readme( $file ) {
|
17 |
+
$file_contents = @implode('', @file($file));
|
18 |
+
return $this->parse_readme_contents( $file_contents );
|
19 |
+
}
|
20 |
+
|
21 |
+
function parse_readme_contents( $file_contents ) {
|
22 |
+
$file_contents = str_replace(array("\r\n", "\r"), "\n", $file_contents);
|
23 |
+
$file_contents = trim($file_contents);
|
24 |
+
if ( 0 === strpos( $file_contents, "\xEF\xBB\xBF" ) )
|
25 |
+
$file_contents = substr( $file_contents, 3 );
|
26 |
+
|
27 |
+
// Markdown transformations
|
28 |
+
$file_contents = preg_replace( "|^###([^#]+)#*?\s*?\n|im", '=$1='."\n", $file_contents );
|
29 |
+
$file_contents = preg_replace( "|^##([^#]+)#*?\s*?\n|im", '==$1=='."\n", $file_contents );
|
30 |
+
$file_contents = preg_replace( "|^#([^#]+)#*?\s*?\n|im", '===$1==='."\n", $file_contents );
|
31 |
+
|
32 |
+
// === Plugin Name ===
|
33 |
+
// Must be the very first thing.
|
34 |
+
if ( !preg_match('|^===(.*)===|', $file_contents, $_name) )
|
35 |
+
return array(); // require a name
|
36 |
+
$name = trim($_name[1], '=');
|
37 |
+
$name = $this->sanitize_text( $name );
|
38 |
+
|
39 |
+
$file_contents = $this->chop_string( $file_contents, $_name[0] );
|
40 |
+
|
41 |
+
|
42 |
+
// Requires at least: 1.5
|
43 |
+
if ( preg_match('|Requires at least:(.*)|i', $file_contents, $_requires_at_least) )
|
44 |
+
$requires_at_least = $this->sanitize_text($_requires_at_least[1]);
|
45 |
+
else
|
46 |
+
$requires_at_least = NULL;
|
47 |
+
|
48 |
+
|
49 |
+
// Tested up to: 2.1
|
50 |
+
if ( preg_match('|Tested up to:(.*)|i', $file_contents, $_tested_up_to) )
|
51 |
+
$tested_up_to = $this->sanitize_text( $_tested_up_to[1] );
|
52 |
+
else
|
53 |
+
$tested_up_to = NULL;
|
54 |
+
|
55 |
+
// Requires PHP: 5.2.4
|
56 |
+
if ( preg_match('|Requires PHP:(.*)|i', $file_contents, $_requires_php) ) {
|
57 |
+
$requires_php = $this->sanitize_text( $_requires_php[1] );
|
58 |
+
} else {
|
59 |
+
$requires_php = null;
|
60 |
+
}
|
61 |
+
|
62 |
+
// Stable tag: 10.4-ride-the-fire-eagle-danger-day
|
63 |
+
if ( preg_match('|Stable tag:(.*)|i', $file_contents, $_stable_tag) )
|
64 |
+
$stable_tag = $this->sanitize_text( $_stable_tag[1] );
|
65 |
+
else
|
66 |
+
$stable_tag = NULL; // we assume trunk, but don't set it here to tell the difference between specified trunk and default trunk
|
67 |
+
|
68 |
+
|
69 |
+
// Tags: some tag, another tag, we like tags
|
70 |
+
if ( preg_match('|Tags:(.*)|i', $file_contents, $_tags) ) {
|
71 |
+
$tags = preg_split('|,[\s]*?|', trim($_tags[1]));
|
72 |
+
foreach ( array_keys($tags) as $t )
|
73 |
+
$tags[$t] = $this->sanitize_text( $tags[$t] );
|
74 |
+
} else {
|
75 |
+
$tags = array();
|
76 |
+
}
|
77 |
+
|
78 |
+
|
79 |
+
// Contributors: markjaquith, mdawaffe, zefrank
|
80 |
+
$contributors = array();
|
81 |
+
if ( preg_match('|Contributors:(.*)|i', $file_contents, $_contributors) ) {
|
82 |
+
$temp_contributors = preg_split('|,[\s]*|', trim($_contributors[1]));
|
83 |
+
foreach ( array_keys($temp_contributors) as $c ) {
|
84 |
+
$tmp_sanitized = $this->user_sanitize( $temp_contributors[$c] );
|
85 |
+
if ( strlen(trim($tmp_sanitized)) > 0 )
|
86 |
+
$contributors[$c] = $tmp_sanitized;
|
87 |
+
unset($tmp_sanitized);
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
+
|
92 |
+
// Donate Link: URL
|
93 |
+
if ( preg_match('|Donate link:(.*)|i', $file_contents, $_donate_link) )
|
94 |
+
$donate_link = esc_url( $_donate_link[1] );
|
95 |
+
else
|
96 |
+
$donate_link = NULL;
|
97 |
+
|
98 |
+
|
99 |
+
// togs, conts, etc are optional and order shouldn't matter. So we chop them only after we've grabbed their values.
|
100 |
+
foreach ( array('tags', 'contributors', 'requires_at_least', 'tested_up_to', 'stable_tag', 'donate_link') as $chop ) {
|
101 |
+
if ( $$chop ) {
|
102 |
+
$_chop = '_' . $chop;
|
103 |
+
$file_contents = $this->chop_string( $file_contents, ${$_chop}[0] );
|
104 |
+
}
|
105 |
+
}
|
106 |
+
|
107 |
+
$file_contents = trim($file_contents);
|
108 |
+
|
109 |
+
|
110 |
+
// short-description fu
|
111 |
+
if ( !preg_match('/(^(.*?))^[\s]*=+?[\s]*.+?[\s]*=+?/ms', $file_contents, $_short_description) )
|
112 |
+
$_short_description = array( 1 => &$file_contents, 2 => &$file_contents );
|
113 |
+
$short_desc_filtered = $this->sanitize_text( $_short_description[2] );
|
114 |
+
$short_desc_length = strlen($short_desc_filtered);
|
115 |
+
$short_description = substr($short_desc_filtered, 0, 150);
|
116 |
+
if ( $short_desc_length > strlen($short_description) )
|
117 |
+
$truncated = true;
|
118 |
+
else
|
119 |
+
$truncated = false;
|
120 |
+
if ( $_short_description[1] )
|
121 |
+
$file_contents = $this->chop_string( $file_contents, $_short_description[1] ); // yes, the [1] is intentional
|
122 |
+
|
123 |
+
// == Section ==
|
124 |
+
// Break into sections
|
125 |
+
// $_sections[0] will be the title of the first section, $_sections[1] will be the content of the first section
|
126 |
+
// the array alternates from there: title2, content2, title3, content3... and so forth
|
127 |
+
$_sections = preg_split('/^[\s]*==[\s]*(.+?)[\s]*==/m', $file_contents, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
|
128 |
+
|
129 |
+
$sections = array();
|
130 |
+
for ( $i=0; $i < count($_sections); $i +=2 ) {
|
131 |
+
$title = $this->sanitize_text( $_sections[$i] );
|
132 |
+
if ( isset($_sections[$i+1]) ) {
|
133 |
+
$content = preg_replace('/(^[\s]*)=[\s]+(.+?)[\s]+=/m', '$1<h4>$2</h4>', $_sections[$i+1]);
|
134 |
+
$content = $this->filter_text( $content, true );
|
135 |
+
} else {
|
136 |
+
$content = '';
|
137 |
+
}
|
138 |
+
$sections[str_replace(' ', '_', strtolower($title))] = array('title' => $title, 'content' => $content);
|
139 |
+
}
|
140 |
+
|
141 |
+
|
142 |
+
// Special sections
|
143 |
+
// This is where we nab our special sections, so we can enforce their order and treat them differently, if needed
|
144 |
+
// upgrade_notice is not a section, but parse it like it is for now
|
145 |
+
$final_sections = array();
|
146 |
+
foreach ( array('description', 'installation', 'frequently_asked_questions', 'screenshots', 'changelog', 'change_log', 'upgrade_notice') as $special_section ) {
|
147 |
+
if ( isset($sections[$special_section]) ) {
|
148 |
+
$final_sections[$special_section] = $sections[$special_section]['content'];
|
149 |
+
unset($sections[$special_section]);
|
150 |
+
}
|
151 |
+
}
|
152 |
+
if ( isset($final_sections['change_log']) && empty($final_sections['changelog']) )
|
153 |
+
$final_sections['changelog'] = $final_sections['change_log'];
|
154 |
+
|
155 |
+
|
156 |
+
$final_screenshots = array();
|
157 |
+
if ( isset($final_sections['screenshots']) ) {
|
158 |
+
preg_match_all('|<li>(.*?)</li>|s', $final_sections['screenshots'], $screenshots, PREG_SET_ORDER);
|
159 |
+
if ( $screenshots ) {
|
160 |
+
foreach ( (array) $screenshots as $ss )
|
161 |
+
$final_screenshots[] = $ss[1];
|
162 |
+
}
|
163 |
+
}
|
164 |
+
|
165 |
+
// Parse the upgrade_notice section specially:
|
166 |
+
// 1.0 => blah, 1.1 => fnord
|
167 |
+
$upgrade_notice = array();
|
168 |
+
if ( isset($final_sections['upgrade_notice']) ) {
|
169 |
+
$split = preg_split( '#<h4>(.*?)</h4>#', $final_sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
|
170 |
+
if ( count($split) >= 2 ) {
|
171 |
+
for ( $i = 0; $i < count( $split ); $i += 2 ) {
|
172 |
+
$upgrade_notice[$this->sanitize_text( $split[$i] )] = substr( $this->sanitize_text( $split[$i + 1] ), 0, 300 );
|
173 |
+
}
|
174 |
+
}
|
175 |
+
unset( $final_sections['upgrade_notice'] );
|
176 |
+
}
|
177 |
+
|
178 |
+
// No description?
|
179 |
+
// No problem... we'll just fall back to the old style of description
|
180 |
+
// We'll even let you use markup this time!
|
181 |
+
$excerpt = false;
|
182 |
+
if ( !isset($final_sections['description']) ) {
|
183 |
+
$final_sections = array_merge(array('description' => $this->filter_text( $_short_description[2], true )), $final_sections);
|
184 |
+
$excerpt = true;
|
185 |
+
}
|
186 |
+
|
187 |
+
|
188 |
+
// dump the non-special sections into $remaining_content
|
189 |
+
// their order will be determined by their original order in the readme.txt
|
190 |
+
$remaining_content = '';
|
191 |
+
foreach ( $sections as $s_name => $s_data ) {
|
192 |
+
$remaining_content .= "\n<h3>{$s_data['title']}</h3>\n{$s_data['content']}";
|
193 |
+
}
|
194 |
+
$remaining_content = trim($remaining_content);
|
195 |
+
|
196 |
+
|
197 |
+
// All done!
|
198 |
+
// $r['tags'] and $r['contributors'] are simple arrays
|
199 |
+
// $r['sections'] is an array with named elements
|
200 |
+
$r = array(
|
201 |
+
'name' => $name,
|
202 |
+
'tags' => $tags,
|
203 |
+
'requires_at_least' => $requires_at_least,
|
204 |
+
'tested_up_to' => $tested_up_to,
|
205 |
+
'requires_php' => $requires_php,
|
206 |
+
'stable_tag' => $stable_tag,
|
207 |
+
'contributors' => $contributors,
|
208 |
+
'donate_link' => $donate_link,
|
209 |
+
'short_description' => $short_description,
|
210 |
+
'screenshots' => $final_screenshots,
|
211 |
+
'is_excerpt' => $excerpt,
|
212 |
+
'is_truncated' => $truncated,
|
213 |
+
'sections' => $final_sections,
|
214 |
+
'remaining_content' => $remaining_content,
|
215 |
+
'upgrade_notice' => $upgrade_notice
|
216 |
+
);
|
217 |
+
|
218 |
+
return $r;
|
219 |
+
}
|
220 |
+
|
221 |
+
function chop_string( $string, $chop ) { // chop a "prefix" from a string: Agressive! uses strstr not 0 === strpos
|
222 |
+
if ( $_string = strstr($string, $chop) ) {
|
223 |
+
$_string = substr($_string, strlen($chop));
|
224 |
+
return trim($_string);
|
225 |
+
} else {
|
226 |
+
return trim($string);
|
227 |
+
}
|
228 |
+
}
|
229 |
+
|
230 |
+
function user_sanitize( $text, $strict = false ) { // whitelisted chars
|
231 |
+
if ( function_exists('user_sanitize') ) // bbPress native
|
232 |
+
return user_sanitize( $text, $strict );
|
233 |
+
|
234 |
+
if ( $strict ) {
|
235 |
+
$text = preg_replace('/[^a-z0-9-]/i', '', $text);
|
236 |
+
$text = preg_replace('|-+|', '-', $text);
|
237 |
+
} else {
|
238 |
+
$text = preg_replace('/[^a-z0-9_-]/i', '', $text);
|
239 |
+
}
|
240 |
+
return $text;
|
241 |
+
}
|
242 |
+
|
243 |
+
function sanitize_text( $text ) { // not fancy
|
244 |
+
$text = strip_tags($text);
|
245 |
+
$text = esc_html($text);
|
246 |
+
$text = trim($text);
|
247 |
+
return $text;
|
248 |
+
}
|
249 |
+
|
250 |
+
function filter_text( $text, $markdown = false ) { // fancy, Markdown
|
251 |
+
$text = trim($text);
|
252 |
+
|
253 |
+
$text = call_user_func( array( __CLASS__, 'code_trick' ), $text, $markdown ); // A better parser than Markdown's for: backticks -> CODE
|
254 |
+
|
255 |
+
if ( $markdown ) { // Parse markdown.
|
256 |
+
if ( !class_exists('Parsedown', false) ) {
|
257 |
+
/** @noinspection PhpIncludeInspection */
|
258 |
+
require_once(dirname(__FILE__) . '/Parsedown' . (version_compare(PHP_VERSION, '5.3.0', '>=') ? '' : 'Legacy') . '.php');
|
259 |
+
}
|
260 |
+
$instance = Parsedown::instance();
|
261 |
+
$text = $instance->text($text);
|
262 |
+
}
|
263 |
+
|
264 |
+
$allowed = array(
|
265 |
+
'a' => array(
|
266 |
+
'href' => array(),
|
267 |
+
'title' => array(),
|
268 |
+
'rel' => array()),
|
269 |
+
'blockquote' => array('cite' => array()),
|
270 |
+
'br' => array(),
|
271 |
+
'p' => array(),
|
272 |
+
'code' => array(),
|
273 |
+
'pre' => array(),
|
274 |
+
'em' => array(),
|
275 |
+
'strong' => array(),
|
276 |
+
'ul' => array(),
|
277 |
+
'ol' => array(),
|
278 |
+
'li' => array(),
|
279 |
+
'h3' => array(),
|
280 |
+
'h4' => array()
|
281 |
+
);
|
282 |
+
|
283 |
+
$text = balanceTags($text);
|
284 |
+
|
285 |
+
$text = wp_kses( $text, $allowed );
|
286 |
+
$text = trim($text);
|
287 |
+
return $text;
|
288 |
+
}
|
289 |
+
|
290 |
+
function code_trick( $text, $markdown ) { // Don't use bbPress native function - it's incompatible with Markdown
|
291 |
+
// If doing markdown, first take any user formatted code blocks and turn them into backticks so that
|
292 |
+
// markdown will preserve things like underscores in code blocks
|
293 |
+
if ( $markdown )
|
294 |
+
$text = preg_replace_callback("!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", array( __CLASS__,'decodeit'), $text);
|
295 |
+
|
296 |
+
$text = str_replace(array("\r\n", "\r"), "\n", $text);
|
297 |
+
if ( !$markdown ) {
|
298 |
+
// This gets the "inline" code blocks, but can't be used with Markdown.
|
299 |
+
$text = preg_replace_callback("|(`)(.*?)`|", array( __CLASS__, 'encodeit'), $text);
|
300 |
+
// This gets the "block level" code blocks and converts them to PRE CODE
|
301 |
+
$text = preg_replace_callback("!(^|\n)`(.*?)`!s", array( __CLASS__, 'encodeit'), $text);
|
302 |
+
} else {
|
303 |
+
// Markdown can do inline code, we convert bbPress style block level code to Markdown style
|
304 |
+
$text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent'), $text);
|
305 |
+
}
|
306 |
+
return $text;
|
307 |
+
}
|
308 |
+
|
309 |
+
function indent( $matches ) {
|
310 |
+
$text = $matches[3];
|
311 |
+
$text = preg_replace('|^|m', $matches[2] . ' ', $text);
|
312 |
+
return $matches[1] . $text;
|
313 |
+
}
|
314 |
+
|
315 |
+
function encodeit( $matches ) {
|
316 |
+
if ( function_exists('encodeit') ) // bbPress native
|
317 |
+
return encodeit( $matches );
|
318 |
+
|
319 |
+
$text = trim($matches[2]);
|
320 |
+
$text = htmlspecialchars($text, ENT_QUOTES);
|
321 |
+
$text = str_replace(array("\r\n", "\r"), "\n", $text);
|
322 |
+
$text = preg_replace("|\n\n\n+|", "\n\n", $text);
|
323 |
+
$text = str_replace('&lt;', '<', $text);
|
324 |
+
$text = str_replace('&gt;', '>', $text);
|
325 |
+
$text = "<code>$text</code>";
|
326 |
+
if ( "`" != $matches[1] )
|
327 |
+
$text = "<pre>$text</pre>";
|
328 |
+
return $text;
|
329 |
+
}
|
330 |
+
|
331 |
+
function decodeit( $matches ) {
|
332 |
+
if ( function_exists('decodeit') ) // bbPress native
|
333 |
+
return decodeit( $matches );
|
334 |
+
|
335 |
+
$text = $matches[2];
|
336 |
+
$trans_table = array_flip(get_html_translation_table(HTML_ENTITIES));
|
337 |
+
$text = strtr($text, $trans_table);
|
338 |
+
$text = str_replace('<br />', '', $text);
|
339 |
+
$text = str_replace('&', '&', $text);
|
340 |
+
$text = str_replace(''', "'", $text);
|
341 |
+
if ( '<pre><code>' == $matches[1] )
|
342 |
+
$text = "\n$text\n";
|
343 |
+
return "`$text`";
|
344 |
+
}
|
345 |
+
|
346 |
+
} // end class
|
347 |
+
|
348 |
+
endif;
|