TubePress - Version 3.1.7

Version Description

  • Transitional release that provides a seamless upgrade path from 3.x.x to 4.x.x. Once you upgrade to 3.1.7, you will prompted again to upgrade to the latest TubePress from the 4.x.x series.
  • Final release from wordpress.org plugin repository.
Download this release

Release Info

Developer k2eric
Plugin Icon wp plugin TubePress
Version 3.1.7
Comparing to
See all releases

Code changes from version 3.1.6 to 3.1.7

readme.txt CHANGED
@@ -3,34 +3,23 @@ Contributors: k2eric
3
  Donate link: http://tubepress.com
4
  Tags: video, youtube, gallery, videos, vimeo
5
  Requires at least: 2.2
6
- Tested up to: 3.8
7
  License: Mozilla Public License v2.0
8
- Stable tag: 3.1.6
9
 
10
- Modern video galleries for the web. Visit us at tubepress.com for more!
11
 
12
  == Description ==
13
 
14
- **Demos**
15
 
16
- * [Simple gallery](http://tubepress.com/demo/)
17
- * [Feature showcase](http://tubepress.com/showcase/)
18
- * [Standalone PHP](http://tubepress.com/standalone_php_demo/)
19
-
20
- **Support**
21
-
22
- * [Documentation](http://tubepress.com/documentation/)
23
- * [Support forum](http://community.tubepress.com/)
24
- * [GitHub page](http://github.com/tubepress/tubepress)
25
-
26
- == Installation ==
27
-
28
- 1. Unzip into your plugins directory at `(wp-content/plugins)`
29
- 1. Activate TubePress from `Site Admin > Plugins`
30
- 1. Configure from `Site Admin > Settings > TubePress`
31
- 1. Type `[tubepress]` in a post or a page where you'd like to insert your first gallery
32
 
33
  == Changelog ==
 
 
 
 
34
  = 3.1.6 =
35
  * Bug Fixes
36
  * Fixed bug that prevented fullscreen playback in Chrome
@@ -607,7 +596,7 @@ Modern video galleries for the web. Visit us at tubepress.com for more!
607
  * Fixed major bug with CSS file path
608
 
609
  = 0.1 =
610
- * Initial release
611
 
612
  == Screenshots ==
613
 
3
  Donate link: http://tubepress.com
4
  Tags: video, youtube, gallery, videos, vimeo
5
  Requires at least: 2.2
6
+ Tested up to: 4.2
7
  License: Mozilla Public License v2.0
8
+ Stable tag: 3.1.7
9
 
10
+ Modern video galleries for the web. Now moved entirely to tubepress.com.
11
 
12
  == Description ==
13
 
14
+ **Available for download exclusively from [tubepress.com](http://tubepress.com/).**
15
 
16
+ We are no longer providing updates or support here at wordpress.org. Please see [this blog post](http://tubepress.com/blog/2015/04/the-death-of-the-youtube-api-v2-and-its-impact-on-tubepress/) for details.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  == Changelog ==
19
+ = 3.1.7 =
20
+ * Transitional release that provides a seamless upgrade path from 3.x.x to 4.x.x. Once you upgrade to 3.1.7, you will prompted again to upgrade to the latest TubePress from the 4.x.x series.
21
+ * Final release from wordpress.org plugin repository.
22
+
23
  = 3.1.6 =
24
  * Bug Fixes
25
  * Fixed bug that prevented fullscreen playback in Chrome
596
  * Fixed major bug with CSS file path
597
 
598
  = 0.1 =
599
+ * Initial release
600
 
601
  == Screenshots ==
602
 
tubepress.php CHANGED
@@ -4,7 +4,7 @@ Plugin Name: TubePress
4
  Plugin URI: http://tubepress.com
5
  Description: Displays gorgeous YouTube and Vimeo galleries in your posts, pages, and/or sidebar. Upgrade to <a href="http://tubepress.com/pro/">TubePress Pro</a> for more features!
6
  Author: TubePress LLC
7
- Version: 3.1.6
8
  Author URI: http://tubepress.com
9
 
10
  Copyright 2006 - 2014 TubePress LLC (http://tubepress.com)
@@ -17,3 +17,10 @@ file, You can obtain one at http://mozilla.org/MPL/2.0/.
17
  */
18
 
19
  include 'src/main/php/scripts/boot.php';
 
 
 
 
 
 
 
4
  Plugin URI: http://tubepress.com
5
  Description: Displays gorgeous YouTube and Vimeo galleries in your posts, pages, and/or sidebar. Upgrade to <a href="http://tubepress.com/pro/">TubePress Pro</a> for more features!
6
  Author: TubePress LLC
7
+ Version: 3.1.7
8
  Author URI: http://tubepress.com
9
 
10
  Copyright 2006 - 2014 TubePress LLC (http://tubepress.com)
17
  */
18
 
19
  include 'src/main/php/scripts/boot.php';
20
+
21
+ require 'vendor/yahnis-elsts/plugin-update-checker/plugin-update-checker.php';
22
+ $myUpdateChecker = PucFactory::buildUpdateChecker(
23
+ 'http://snippets.wp.tubepress.com/update.php',
24
+ __FILE__,
25
+ plugin_basename(basename(dirname(__FILE__)) . '/tubepress.php')
26
+ );
vendor/yahnis-elsts/plugin-update-checker/README.md ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Plugin Update Checker
2
+ =====================
3
+
4
+ This is a custom update checker library for WordPress plugins. It lets you add automatic update notifications and one-click upgrades to your commercial and private plugins. All you need to do is put your plugin details in a JSON file, place the file on your server, and pass the URL to the library. The library periodically checks the URL to see if there's a new version available and displays an update notification to the user if necessary.
5
+
6
+ From the users' perspective, it works just like with plugins hosted on WordPress.org. The update checker uses the default plugin upgrade UI that will already be familiar to most WordPress users.
7
+
8
+ [See this blog post](http://w-shadow.com/blog/2010/09/02/automatic-updates-for-any-plugin/) for more information and usage instructions.
9
+
10
+ Getting Started
11
+ ---------------
12
+
13
+ ### Self-hosted Plugins
14
+
15
+ 1. Make a JSON file that describes your plugin. Here's a minimal example:
16
+
17
+ ```json
18
+ {
19
+ "name" : "My Cool Plugin",
20
+ "version" : "2.0",
21
+ "author" : "John Smith",
22
+ "download_url" : "http://example.com/plugins/my-cool-plugin.zip",
23
+ "sections" : {
24
+ "description" : "Plugin description here. You can use HTML."
25
+ }
26
+ }
27
+ ```
28
+ See [this table](https://spreadsheets.google.com/pub?key=0AqP80E74YcUWdEdETXZLcXhjd2w0cHMwX2U1eDlWTHc&authkey=CK7h9toK&hl=en&single=true&gid=0&output=html) for a full list of supported fields.
29
+ 2. Upload this file to a publicly accessible location.
30
+ 3. Download [the update checker](https://github.com/YahnisElsts/plugin-update-checker/releases/latest), unzip the archive and copy the `plugin-update-checker` directory to your plugin.
31
+ 4. Add the following code to the main plugin file:
32
+
33
+ ```php
34
+ require 'plugin-update-checker/plugin-update-checker.php';
35
+ $myUpdateChecker = PucFactory::buildUpdateChecker(
36
+ 'http://example.com/path/to/metadata.json',
37
+ __FILE__
38
+ );
39
+ ```
40
+
41
+ #### Notes
42
+ - You could use [wp-update-server](https://github.com/YahnisElsts/wp-update-server) to automatically generate JSON metadata from ZIP packages.
43
+ - The second argument passed to `buildUpdateChecker` should be the full path to the main plugin file.
44
+ - There are more options available - see the [blog](http://w-shadow.com/blog/2010/09/02/automatic-updates-for-any-plugin/) for details.
45
+
46
+ ### Plugins Hosted on GitHub
47
+
48
+ *(GitHub support is experimental.)*
49
+
50
+ 1. Download [the latest release](https://github.com/YahnisElsts/plugin-update-checker/releases/latest), unzip it and copy the `plugin-update-checker` directory to your plugin.
51
+ 2. Add the following code to the main file of your plugin:
52
+
53
+ ```php
54
+ require 'plugin-update-checker/plugin-update-checker.php';
55
+ $className = PucFactory::getLatestClassVersion('PucGitHubChecker');
56
+ $myUpdateChecker = new $className(
57
+ 'https://github.com/user-name/plugin-repo-name/',
58
+ __FILE__,
59
+ 'master'
60
+ );
61
+ ```
62
+ The third argument specifies the branch to use for updating your plugin. The default is `master`. If the branch name is omitted or set to `master`, the update checker will use the latest release or tag (if available). Otherwise it will use the specified branch.
63
+ 3. Optional: Add a `readme.txt` file formatted according to the [WordPress.org plugin readme standard](https://wordpress.org/plugins/about/readme.txt). The contents of this file will be shown when the user clicks the "View version 1.2.3 details" link.
64
+
65
+ #### Notes
66
+
67
+ If your GitHub repository requires an access token, you can specify it like this:
68
+ ```php
69
+ $myUpdateChecker->setAccessToken('your-token-here');
70
+ ```
71
+
72
+ The GitHub version of the library will pull update details from the following parts of a release/tag/branch:
73
+
74
+ - Changelog
75
+ - The "Changelog" section of `readme.txt`.
76
+ - One of the following files:
77
+ CHANGES.md, CHANGELOG.md, changes.md, changelog.md
78
+ - Release notes.
79
+ - Version number
80
+ - The "Version" plugin header.
81
+ - The latest release or tag name.
82
+ - Required and tested WordPress versions
83
+ - The "Requires at least" and "Tested up to" fields in `readme.txt`.
84
+ - The following plugin headers:
85
+ `Required WP`, `Tested WP`, `Requires at least`, `Tested up to`
86
+ - "Last updated" timestamp
87
+ - The creation timestamp of the latest release.
88
+ - The latest commit of the selected tag or branch that changed the main plugin file.
89
+ - Number of downloads
90
+ - The `download_count` statistic of the latest release.
91
+ - If you're not using GitHub releases, there will be no download stats.
92
+ - Other plugin details - author, homepage URL, description
93
+ - The "Description" section of `readme.txt`.
94
+ - Remote plugin headers (i.e. the latest version on GitHub).
95
+ - Local plugin headers (i.e. the currently installed version).
96
+ - Ratings, banners, screenshots
97
+ - Not supported.
vendor/yahnis-elsts/plugin-update-checker/composer.json ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "yahnis-elsts/plugin-update-checker",
3
+ "type": "library",
4
+ "description": "A custom update checker for WordPress plugins. Useful if you can't host your plugin in the official WP plugin repository but still want it to support automatic plugin updates.",
5
+ "keywords": ["wordpress", "plugin updates", "automatic updates"],
6
+ "homepage": "https://github.com/YahnisElsts/plugin-update-checker/",
7
+ "license": "MIT",
8
+ "authors": [
9
+ {
10
+ "name": "Yahnis Elsts",
11
+ "email": "whiteshadow@w-shadow.com",
12
+ "homepage": "http://w-shadow.com/",
13
+ "role": "Developer"
14
+ }
15
+ ],
16
+ "require": {
17
+ "php": ">=5.2.0"
18
+ }
19
+ }
vendor/yahnis-elsts/plugin-update-checker/css/puc-debug-bar.css ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .puc-debug-bar-panel 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
+ }
vendor/yahnis-elsts/plugin-update-checker/debug-bar-panel.php ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( !class_exists('PluginUpdateCheckerPanel') && class_exists('Debug_Bar_Panel') ) {
4
+
5
+ /**
6
+ * A Debug Bar panel for the plugin update checker.
7
+ */
8
+ class PluginUpdateCheckerPanel extends Debug_Bar_Panel {
9
+ /** @var PluginUpdateChecker */
10
+ private $updateChecker;
11
+
12
+ public function __construct($updateChecker) {
13
+ $this->updateChecker = $updateChecker;
14
+ $title = sprintf(
15
+ '<span id="puc-debug-menu-link-%s">PUC (%s)</span>',
16
+ esc_attr($this->updateChecker->slug),
17
+ $this->updateChecker->slug
18
+ );
19
+ parent::Debug_Bar_Panel($title);
20
+ }
21
+
22
+ public function render() {
23
+ printf(
24
+ '<div class="puc-debug-bar-panel" id="puc-debug-bar-panel_%1$s" data-slug="%1$s" data-nonce="%2$s">',
25
+ esc_attr($this->updateChecker->slug),
26
+ esc_attr(wp_create_nonce('puc-ajax'))
27
+ );
28
+
29
+ $responseBox = '<div class="puc-ajax-response" style="display: none;"></div>';
30
+
31
+ echo '<h3>Configuration</h3>';
32
+ echo '<table class="puc-debug-data">';
33
+ $this->row('Plugin file', htmlentities($this->updateChecker->pluginFile));
34
+ $this->row('Slug', htmlentities($this->updateChecker->slug));
35
+ $this->row('DB option', htmlentities($this->updateChecker->optionName));
36
+
37
+ $requestInfoButton = '';
38
+ if ( function_exists('get_submit_button') ) {
39
+ $requestInfoButton = get_submit_button('Request Info', 'secondary', 'puc-request-info-button', false, array('id' => 'puc-request-info-button-' . $this->updateChecker->slug));
40
+ }
41
+ $this->row('Metadata URL', htmlentities($this->updateChecker->metadataUrl) . ' ' . $requestInfoButton . $responseBox);
42
+
43
+ if ( $this->updateChecker->checkPeriod > 0 ) {
44
+ $this->row('Automatic checks', 'Every ' . $this->updateChecker->checkPeriod . ' hours');
45
+ } else {
46
+ $this->row('Automatic checks', 'Disabled');
47
+ }
48
+
49
+ if ( isset($this->updateChecker->throttleRedundantChecks) ) {
50
+ if ( $this->updateChecker->throttleRedundantChecks && ($this->updateChecker->checkPeriod > 0) ) {
51
+ $this->row(
52
+ 'Throttling',
53
+ sprintf(
54
+ 'Enabled. If an update is already available, check for updates every %1$d hours instead of every %2$d hours.',
55
+ $this->updateChecker->throttledCheckPeriod,
56
+ $this->updateChecker->checkPeriod
57
+ )
58
+ );
59
+ } else {
60
+ $this->row('Throttling', 'Disabled');
61
+ }
62
+ }
63
+ echo '</table>';
64
+
65
+ echo '<h3>Status</h3>';
66
+ echo '<table class="puc-debug-data">';
67
+ $state = $this->updateChecker->getUpdateState();
68
+ $checkNowButton = '';
69
+ if ( function_exists('get_submit_button') ) {
70
+ $checkNowButton = get_submit_button('Check Now', 'secondary', 'puc-check-now-button', false, array('id' => 'puc-check-now-button-' . $this->updateChecker->slug));
71
+ }
72
+
73
+ if ( isset($state, $state->lastCheck) ) {
74
+ $this->row('Last check', $this->formatTimeWithDelta($state->lastCheck) . ' ' . $checkNowButton . $responseBox);
75
+ } else {
76
+ $this->row('Last check', 'Never');
77
+ }
78
+
79
+ $nextCheck = wp_next_scheduled($this->updateChecker->getCronHookName());
80
+ $this->row('Next automatic check', $this->formatTimeWithDelta($nextCheck));
81
+
82
+ if ( isset($state, $state->checkedVersion) ) {
83
+ $this->row('Checked version', htmlentities($state->checkedVersion));
84
+ $this->row('Cached update', $state->update);
85
+ }
86
+ $this->row('Update checker class', htmlentities(get_class($this->updateChecker)));
87
+ echo '</table>';
88
+
89
+ $update = $this->updateChecker->getUpdate();
90
+ if ( $update !== null ) {
91
+ echo '<h3>An Update Is Available</h3>';
92
+ echo '<table class="puc-debug-data">';
93
+ $fields = array('version', 'download_url', 'slug', 'homepage', 'upgrade_notice');
94
+ foreach($fields as $field) {
95
+ $this->row(ucwords(str_replace('_', ' ', $field)), htmlentities($update->$field));
96
+ }
97
+ echo '</table>';
98
+ } else {
99
+ echo '<h3>No updates currently available</h3>';
100
+ }
101
+
102
+ echo '</div>';
103
+ }
104
+
105
+ private function formatTimeWithDelta($unixTime) {
106
+ if ( empty($unixTime) ) {
107
+ return 'Never';
108
+ }
109
+
110
+ $delta = time() - $unixTime;
111
+ $result = human_time_diff(time(), $unixTime);
112
+ if ( $delta < 0 ) {
113
+ $result = 'after ' . $result;
114
+ } else {
115
+ $result = $result . ' ago';
116
+ }
117
+ $result .= ' (' . $this->formatTimestamp($unixTime) . ')';
118
+ return $result;
119
+ }
120
+
121
+ private function formatTimestamp($unixTime) {
122
+ return gmdate('Y-m-d H:i:s', $unixTime + (get_option('gmt_offset') * 3600));
123
+ }
124
+
125
+ private function row($name, $value) {
126
+ if ( is_object($value) || is_array($value) ) {
127
+ $value = '<pre>' . htmlentities(print_r($value, true)) . '</pre>';
128
+ } else if ($value === null) {
129
+ $value = '<code>null</code>';
130
+ }
131
+ printf('<tr><th scope="row">%1$s</th> <td>%2$s</td></tr>', $name, $value);
132
+ }
133
+ }
134
+
135
+ }
vendor/yahnis-elsts/plugin-update-checker/debug-bar-plugin.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ if ( !class_exists('PucDebugBarPlugin') ) {
3
+
4
+ class PucDebugBarPlugin {
5
+ /** @var PluginUpdateChecker */
6
+ private $updateChecker;
7
+
8
+ public function __construct($updateChecker) {
9
+ $this->updateChecker = $updateChecker;
10
+
11
+ add_filter('debug_bar_panels', array($this, 'addDebugBarPanel'));
12
+ add_action('debug_bar_enqueue_scripts', array($this, 'enqueuePanelDependencies'));
13
+
14
+ add_action('wp_ajax_puc_debug_check_now', array($this, 'ajaxCheckNow'));
15
+ add_action('wp_ajax_puc_debug_request_info', array($this, 'ajaxRequestInfo'));
16
+ }
17
+
18
+ /**
19
+ * Register the PUC Debug Bar panel.
20
+ *
21
+ * @param array $panels
22
+ * @return array
23
+ */
24
+ public function addDebugBarPanel($panels) {
25
+ require_once dirname(__FILE__) . '/debug-bar-panel.php';
26
+ if ( current_user_can('update_plugins') && class_exists('PluginUpdateCheckerPanel') ) {
27
+ $panels[] = new PluginUpdateCheckerPanel($this->updateChecker);
28
+ }
29
+ return $panels;
30
+ }
31
+
32
+ /**
33
+ * Enqueue our Debug Bar scripts and styles.
34
+ */
35
+ public function enqueuePanelDependencies() {
36
+ wp_enqueue_style(
37
+ 'puc-debug-bar-style',
38
+ plugins_url( "/css/puc-debug-bar.css", __FILE__ ),
39
+ array('debug-bar'),
40
+ '20130927'
41
+ );
42
+
43
+ wp_enqueue_script(
44
+ 'puc-debug-bar-js',
45
+ plugins_url( "/js/debug-bar.js", __FILE__ ),
46
+ array('jquery'),
47
+ '20121026'
48
+ );
49
+ }
50
+
51
+ /**
52
+ * Run an update check and output the result. Useful for making sure that
53
+ * the update checking process works as expected.
54
+ */
55
+ public function ajaxCheckNow() {
56
+ if ( $_POST['slug'] !== $this->updateChecker->slug ) {
57
+ return;
58
+ }
59
+ $this->preAjaxReqest();
60
+ $update = $this->updateChecker->checkForUpdates();
61
+ if ( $update !== null ) {
62
+ echo "An update is available:";
63
+ echo '<pre>', htmlentities(print_r($update, true)), '</pre>';
64
+ } else {
65
+ echo 'No updates found.';
66
+ }
67
+ exit;
68
+ }
69
+
70
+ /**
71
+ * Request plugin info and output it.
72
+ */
73
+ public function ajaxRequestInfo() {
74
+ if ( $_POST['slug'] !== $this->updateChecker->slug ) {
75
+ return;
76
+ }
77
+ $this->preAjaxReqest();
78
+ $info = $this->updateChecker->requestInfo();
79
+ if ( $info !== null ) {
80
+ echo 'Successfully retrieved plugin info from the metadata URL:';
81
+ echo '<pre>', htmlentities(print_r($info, true)), '</pre>';
82
+ } else {
83
+ echo 'Failed to retrieve plugin info from the metadata URL.';
84
+ }
85
+ exit;
86
+ }
87
+
88
+ /**
89
+ * Check access permissions and enable error display (for debugging).
90
+ */
91
+ private function preAjaxReqest() {
92
+ if ( !current_user_can('update_plugins') ) {
93
+ die('Access denied');
94
+ }
95
+ check_ajax_referer('puc-ajax');
96
+
97
+ error_reporting(E_ALL);
98
+ @ini_set('display_errors','On');
99
+ }
100
+ }
101
+
102
+ }
vendor/yahnis-elsts/plugin-update-checker/github-checker.php ADDED
@@ -0,0 +1,449 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if ( !class_exists('PucGitHubChecker_2_0') ):
4
+
5
+ class PucGitHubChecker_2_0 extends PluginUpdateChecker_2_0 {
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 The branch to use as the latest version. Defaults to "master".
22
+ */
23
+ protected $branch;
24
+
25
+ /**
26
+ * @var string GitHub authentication token. Optional.
27
+ */
28
+ protected $accessToken;
29
+
30
+ public function __construct(
31
+ $repositoryUrl,
32
+ $pluginFile,
33
+ $branch = 'master',
34
+ $checkPeriod = 12,
35
+ $optionName = '',
36
+ $muPluginFile = ''
37
+ ) {
38
+
39
+ $this->repositoryUrl = $repositoryUrl;
40
+ $this->branch = empty($branch) ? 'master' : $branch;
41
+
42
+ $path = @parse_url($repositoryUrl, PHP_URL_PATH);
43
+ if ( preg_match('@^/?(?P<username>[^/]+?)/(?P<repository>[^/#?&]+?)/?$@', $path, $matches) ) {
44
+ $this->userName = $matches['username'];
45
+ $this->repositoryName = $matches['repository'];
46
+ } else {
47
+ throw new InvalidArgumentException('Invalid GitHub repository URL: "' . $repositoryUrl . '"');
48
+ }
49
+
50
+ parent::__construct($repositoryUrl, $pluginFile, '', $checkPeriod, $optionName, $muPluginFile);
51
+ }
52
+
53
+ /**
54
+ * Retrieve details about the latest plugin version from GitHub.
55
+ *
56
+ * @param array $unusedQueryArgs Unused.
57
+ * @return PluginInfo
58
+ */
59
+ public function requestInfo($unusedQueryArgs = array()) {
60
+ $info = new PluginInfo_2_0();
61
+ $info->filename = $this->pluginFile;
62
+ $info->slug = $this->slug;
63
+ $info->sections = array();
64
+
65
+ $this->setInfoFromHeader($this->getPluginHeader(), $info);
66
+
67
+ //Figure out which reference (tag or branch) we'll use to get the latest version of the plugin.
68
+ $ref = $this->branch;
69
+ if ( $this->branch === 'master' ) {
70
+ //Use the latest release.
71
+ $release = $this->getLatestRelease();
72
+ if ( $release !== null ) {
73
+ $ref = $release->tag_name;
74
+ $info->version = ltrim($release->tag_name, 'v'); //Remove the "v" prefix from "v1.2.3".
75
+ $info->last_updated = $release->created_at;
76
+ $info->download_url = $release->zipball_url;
77
+
78
+ if ( !empty($release->body) ) {
79
+ $info->sections['changelog'] = $this->parseMarkdown($release->body);
80
+ }
81
+ if ( isset($release->assets[0]) ) {
82
+ $info->downloaded = $release->assets[0]->download_count;
83
+ }
84
+ } else {
85
+ //Failing that, use the tag with the highest version number.
86
+ $tag = $this->getLatestTag();
87
+ if ( $tag !== null ) {
88
+ $ref = $tag->name;
89
+ $info->version = $tag->name;
90
+ $info->download_url = $tag->zipball_url;
91
+ }
92
+ }
93
+ }
94
+
95
+ if ( empty($info->download_url) ) {
96
+ $info->download_url = $this->buildArchiveDownloadUrl($ref);
97
+ }
98
+
99
+ //Get headers from the main plugin file in this branch/tag. Its "Version" header and other metadata
100
+ //are what the WordPress install will actually see after upgrading, so they take precedence over releases/tags.
101
+ $mainPluginFile = basename($this->pluginFile);
102
+ $remotePlugin = $this->getRemoteFile($mainPluginFile, $ref);
103
+ if ( !empty($remotePlugin) ) {
104
+ $remoteHeader = $this->getFileHeader($remotePlugin);
105
+ $this->setInfoFromHeader($remoteHeader, $info);
106
+ }
107
+
108
+ //Try parsing readme.txt. If it's formatted according to WordPress.org standards, it will contain
109
+ //a lot of useful information like the required/tested WP version, changelog, and so on.
110
+ if ( $this->readmeTxtExistsLocally() ) {
111
+ $readmeTxt = $this->getRemoteFile('readme.txt', $ref);
112
+ if ( !empty($readmeTxt) ) {
113
+ $readme = $this->parseReadme($readmeTxt);
114
+
115
+ if ( isset($readme['sections']) ) {
116
+ $info->sections = array_merge($info->sections, $readme['sections']);
117
+ }
118
+ if ( !empty($readme['tested_up_to']) ) {
119
+ $info->tested = $readme['tested_up_to'];
120
+ }
121
+ if ( !empty($readme['requires_at_least']) ) {
122
+ $info->requires = $readme['requires_at_least'];
123
+ }
124
+
125
+ if ( isset($readme['upgrade_notice'], $readme['upgrade_notice'][$info->version]) ) {
126
+ $info->upgrade_notice = $readme['upgrade_notice'][$info->version];
127
+ }
128
+ }
129
+ }
130
+
131
+ //The changelog might be in a separate file.
132
+ if ( empty($info->sections['changelog']) ) {
133
+ $info->sections['changelog'] = $this->getRemoteChangelog($ref);
134
+ if ( empty($info->sections['changelog']) ) {
135
+ $info->sections['changelog'] = 'There is no changelog available.';
136
+ }
137
+ }
138
+
139
+ if ( empty($info->last_updated) ) {
140
+ //Fetch the latest commit that changed the main plugin file and use it as the "last_updated" date.
141
+ //It's reasonable to assume that every update will change the version number in that file.
142
+ $latestCommit = $this->getLatestCommit($mainPluginFile, $ref);
143
+ if ( $latestCommit !== null ) {
144
+ $info->last_updated = $latestCommit->commit->author->date;
145
+ }
146
+ }
147
+
148
+ $info = apply_filters('puc_request_info_result-' . $this->slug, $info, null);
149
+ return $info;
150
+ }
151
+
152
+ /**
153
+ * Get the latest release from GitHub.
154
+ *
155
+ * @return StdClass|null
156
+ */
157
+ protected function getLatestRelease() {
158
+ $releases = $this->api('/repos/:user/:repo/releases');
159
+ if ( is_wp_error($releases) || !is_array($releases) || !isset($releases[0]) ) {
160
+ return null;
161
+ }
162
+
163
+ $latestRelease = $releases[0];
164
+ return $latestRelease;
165
+ }
166
+
167
+ /**
168
+ * Get the tag that looks like the highest version number.
169
+ *
170
+ * @return StdClass|null
171
+ */
172
+ protected function getLatestTag() {
173
+ $tags = $this->api('/repos/:user/:repo/tags');
174
+
175
+ if ( is_wp_error($tags) || empty($tags) || !is_array($tags) ) {
176
+ return null;
177
+ }
178
+
179
+ usort($tags, array($this, 'compareTagNames')); //Sort from highest to lowest.
180
+ return $tags[0];
181
+ }
182
+
183
+ /**
184
+ * Compare two GitHub tags as if they were version number.
185
+ *
186
+ * @param string $tag1
187
+ * @param string $tag2
188
+ * @return int
189
+ */
190
+ protected function compareTagNames($tag1, $tag2) {
191
+ if ( !isset($tag1->name) ) {
192
+ return 1;
193
+ }
194
+ if ( !isset($tag2->name) ) {
195
+ return -1;
196
+ }
197
+ return -version_compare($tag1->name, $tag2->name);
198
+ }
199
+
200
+ /**
201
+ * Get the latest commit that changed the specified file.
202
+ *
203
+ * @param string $filename
204
+ * @param string $ref Reference name (e.g. branch or tag).
205
+ * @return StdClass|null
206
+ */
207
+ protected function getLatestCommit($filename, $ref = 'master') {
208
+ $commits = $this->api(
209
+ '/repos/:user/:repo/commits',
210
+ array(
211
+ 'path' => $filename,
212
+ 'sha' => $ref,
213
+ )
214
+ );
215
+ if ( !is_wp_error($commits) && is_array($commits) && isset($commits[0]) ) {
216
+ return $commits[0];
217
+ }
218
+ return null;
219
+ }
220
+
221
+ protected function getRemoteChangelog($ref = '') {
222
+ $filename = $this->getChangelogFilename();
223
+ if ( empty($filename) ) {
224
+ return null;
225
+ }
226
+
227
+ $changelog = $this->getRemoteFile($filename, $ref);
228
+ if ( $changelog === null ) {
229
+ return null;
230
+ }
231
+ return $this->parseMarkdown($changelog);
232
+ }
233
+
234
+ protected function getChangelogFilename() {
235
+ $pluginDirectory = dirname($this->pluginAbsolutePath);
236
+ if ( empty($this->pluginAbsolutePath) || !is_dir($pluginDirectory) || ($pluginDirectory === '.') ) {
237
+ return null;
238
+ }
239
+
240
+ $possibleNames = array('CHANGES.md', 'CHANGELOG.md', 'changes.md', 'changelog.md');
241
+ $files = scandir($pluginDirectory);
242
+ $foundNames = array_intersect($possibleNames, $files);
243
+
244
+ if ( !empty($foundNames) ) {
245
+ return reset($foundNames);
246
+ }
247
+ return null;
248
+ }
249
+
250
+ /**
251
+ * Convert Markdown to HTML.
252
+ *
253
+ * @param string $markdown
254
+ * @return string
255
+ */
256
+ protected function parseMarkdown($markdown) {
257
+ if ( !class_exists('Parsedown') ) {
258
+ require_once(dirname(__FILE__) . '/vendor/Parsedown.php');
259
+ }
260
+
261
+ $instance = Parsedown::instance();
262
+ return $instance->text($markdown);
263
+ }
264
+
265
+ /**
266
+ * Perform a GitHub API request.
267
+ *
268
+ * @param string $url
269
+ * @param array $queryParams
270
+ * @return mixed|WP_Error
271
+ */
272
+ protected function api($url, $queryParams = array()) {
273
+ $variables = array(
274
+ 'user' => $this->userName,
275
+ 'repo' => $this->repositoryName,
276
+ );
277
+ foreach ($variables as $name => $value) {
278
+ $url = str_replace('/:' . $name, '/' . urlencode($value), $url);
279
+ }
280
+ $url = 'https://api.github.com' . $url;
281
+
282
+ if ( !empty($this->accessToken) ) {
283
+ $queryParams['access_token'] = $this->accessToken;
284
+ }
285
+ if ( !empty($queryParams) ) {
286
+ $url = add_query_arg($queryParams, $url);
287
+ }
288
+
289
+ $response = wp_remote_get($url, array('timeout' => 10));
290
+ if ( is_wp_error($response) ) {
291
+ return $response;
292
+ }
293
+
294
+ $code = wp_remote_retrieve_response_code($response);
295
+ $body = wp_remote_retrieve_body($response);
296
+ if ( $code === 200 ) {
297
+ $document = json_decode($body);
298
+ return $document;
299
+ }
300
+
301
+ return new WP_Error(
302
+ 'puc-github-http-error',
303
+ 'GitHub API error. HTTP status: ' . $code
304
+ );
305
+ }
306
+
307
+ /**
308
+ * Set the access token that will be used to make authenticated GitHub API requests.
309
+ *
310
+ * @param string $accessToken
311
+ */
312
+ public function setAccessToken($accessToken) {
313
+ $this->accessToken = $accessToken;
314
+ }
315
+
316
+ /**
317
+ * Get the contents of a file from a specific branch or tag.
318
+ *
319
+ * @param string $path File name.
320
+ * @param string $ref
321
+ * @return null|string Either the contents of the file, or null if the file doesn't exist or there's an error.
322
+ */
323
+ protected function getRemoteFile($path, $ref = 'master') {
324
+ $apiUrl = '/repos/:user/:repo/contents/' . $path;
325
+ $response = $this->api($apiUrl, array('ref' => $ref));
326
+
327
+ if ( is_wp_error($response) || !isset($response->content) || ($response->encoding !== 'base64') ) {
328
+ return null;
329
+ }
330
+ return base64_decode($response->content);
331
+ }
332
+
333
+ /**
334
+ * Parse plugin metadata from the header comment.
335
+ * This is basically a simplified version of the get_file_data() function from /wp-includes/functions.php.
336
+ *
337
+ * @param $content
338
+ * @return array
339
+ */
340
+ protected function getFileHeader($content) {
341
+ $headers = array(
342
+ 'Name' => 'Plugin Name',
343
+ 'PluginURI' => 'Plugin URI',
344
+ 'Version' => 'Version',
345
+ 'Description' => 'Description',
346
+ 'Author' => 'Author',
347
+ 'AuthorURI' => 'Author URI',
348
+ 'TextDomain' => 'Text Domain',
349
+ 'DomainPath' => 'Domain Path',
350
+ 'Network' => 'Network',
351
+
352
+ //The newest WordPress version that this plugin requires or has been tested with.
353
+ //We support several different formats for compatibility with other libraries.
354
+ 'Tested WP' => 'Tested WP',
355
+ 'Requires WP' => 'Requires WP',
356
+ 'Tested up to' => 'Tested up to',
357
+ 'Requires at least' => 'Requires at least',
358
+ );
359
+
360
+ $content = str_replace("\r", "\n", $content); //Normalize line endings.
361
+ $results = array();
362
+ foreach ($headers as $field => $name) {
363
+ $success = preg_match('/^[ \t\/*#@]*' . preg_quote($name, '/') . ':(.*)$/mi', $content, $matches);
364
+ if ( ($success === 1) && $matches[1] ) {
365
+ $results[$field] = _cleanup_header_comment($matches[1]);
366
+ } else {
367
+ $results[$field] = '';
368
+ }
369
+ }
370
+
371
+ return $results;
372
+ }
373
+
374
+ /**
375
+ * Copy plugin metadata from a file header to a PluginInfo object.
376
+ *
377
+ * @param array $fileHeader
378
+ * @param PluginInfo_2_0 $pluginInfo
379
+ */
380
+ protected function setInfoFromHeader($fileHeader, $pluginInfo) {
381
+ $headerToPropertyMap = array(
382
+ 'Version' => 'version',
383
+ 'Name' => 'name',
384
+ 'PluginURI' => 'homepage',
385
+ 'Author' => 'author',
386
+ 'AuthorName' => 'author',
387
+ 'AuthorURI' => 'author_homepage',
388
+
389
+ 'Requires WP' => 'requires',
390
+ 'Tested WP' => 'tested',
391
+ 'Requires at least' => 'requires',
392
+ 'Tested up to' => 'tested',
393
+ );
394
+ foreach ($headerToPropertyMap as $headerName => $property) {
395
+ if ( isset($fileHeader[$headerName]) && !empty($fileHeader[$headerName]) ) {
396
+ $pluginInfo->$property = $fileHeader[$headerName];
397
+ }
398
+ }
399
+
400
+ if ( !isset($pluginInfo->sections) ) {
401
+ $pluginInfo->sections = array();
402
+ }
403
+ if ( !empty($fileHeader['Description']) ) {
404
+ $pluginInfo->sections['description'] = $fileHeader['Description'];
405
+ }
406
+ }
407
+
408
+ protected function parseReadme($content) {
409
+ if ( !class_exists('PucReadmeParser') ) {
410
+ require_once(dirname(__FILE__) . '/vendor/readme-parser.php');
411
+ }
412
+ $parser = new PucReadmeParser();
413
+ return $parser->parse_readme_contents($content);
414
+ }
415
+
416
+ /**
417
+ * Check if the currently installed version has a readme.txt file.
418
+ *
419
+ * @return bool
420
+ */
421
+ protected function readmeTxtExistsLocally() {
422
+ $pluginDirectory = dirname($this->pluginAbsolutePath);
423
+ if ( empty($this->pluginAbsolutePath) || !is_dir($pluginDirectory) || ($pluginDirectory === '.') ) {
424
+ return false;
425
+ }
426
+ return is_file($pluginDirectory . '/readme.txt');
427
+ }
428
+
429
+ /**
430
+ * Generate a URL to download a ZIP archive of the specified branch/tag/etc.
431
+ *
432
+ * @param string $ref
433
+ * @return string
434
+ */
435
+ protected function buildArchiveDownloadUrl($ref = 'master') {
436
+ $url = sprintf(
437
+ 'https://api.github.com/repos/%1$s/%2$s/zipball/%3$s',
438
+ urlencode($this->userName),
439
+ urlencode($this->repositoryName),
440
+ urlencode($ref)
441
+ );
442
+ if ( !empty($this->accessToken) ) {
443
+ $url = add_query_arg('access_token', $this->accessToken, $url);
444
+ }
445
+ return $url;
446
+ }
447
+ }
448
+
449
+ endif;
vendor/yahnis-elsts/plugin-update-checker/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');
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
+ slug : panel.data('slug'),
14
+ _wpnonce: panel.data('nonce')
15
+ },
16
+ function(data) {
17
+ responseBox.html(data);
18
+ },
19
+ 'html'
20
+ );
21
+ }
22
+
23
+ $('.puc-debug-bar-panel input[name="puc-check-now-button"]').click(function() {
24
+ runAjaxAction(this, 'puc_debug_check_now');
25
+ return false;
26
+ });
27
+
28
+ $('.puc-debug-bar-panel input[name="puc-request-info-button"]').click(function() {
29
+ runAjaxAction(this, 'puc_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');
38
+ panels.each(function(index) {
39
+ var panel = $(this);
40
+ var slug = panel.data('slug');
41
+ var target = panel.closest('.debug-menu-target');
42
+
43
+ //Change the panel wrapper ID.
44
+ target.attr('id', 'debug-menu-target-puc-' + slug);
45
+
46
+ //Change the menu link ID as well and point it at the new target ID.
47
+ $('#puc-debug-menu-link-' + panel.data('slug'))
48
+ .closest('.debug-menu-link')
49
+ .attr('id', 'debug-menu-link-puc-' + slug)
50
+ .attr('href', '#' + target.attr('id'));
51
+ });
52
+ });
vendor/yahnis-elsts/plugin-update-checker/license.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ Copyright (c) 2014 Jānis Elsts
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
vendor/yahnis-elsts/plugin-update-checker/plugin-update-checker.php ADDED
@@ -0,0 +1,1214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ /**
3
+ * Plugin Update Checker Library 2.0.0
4
+ * http://w-shadow.com/
5
+ *
6
+ * Copyright 2015 Janis Elsts
7
+ * Released under the MIT license. See license.txt for details.
8
+ */
9
+
10
+ if ( !class_exists('PluginUpdateChecker_2_0') ):
11
+
12
+ /**
13
+ * A custom plugin update checker.
14
+ *
15
+ * @author Janis Elsts
16
+ * @copyright 2015
17
+ * @version 2.0
18
+ * @access public
19
+ */
20
+ class PluginUpdateChecker_2_0 {
21
+ public $metadataUrl = ''; //The URL of the plugin's metadata file.
22
+ public $pluginAbsolutePath = ''; //Full path of the main plugin file.
23
+ public $pluginFile = ''; //Plugin filename relative to the plugins directory. Many WP APIs use this to identify plugins.
24
+ public $slug = ''; //Plugin slug.
25
+ public $checkPeriod = 12; //How often to check for updates (in hours).
26
+ public $optionName = ''; //Where to store the update info.
27
+ public $muPluginFile = ''; //For MU plugins, the plugin filename relative to the mu-plugins directory.
28
+
29
+ public $debugMode = false; //Set to TRUE to enable error reporting. Errors are raised using trigger_error()
30
+ //and should be logged to the standard PHP error log.
31
+
32
+ public $throttleRedundantChecks = false; //Check less often if we already know that an update is available.
33
+ public $throttledCheckPeriod = 72;
34
+
35
+ private $cronHook = null;
36
+ private $debugBarPlugin = null;
37
+ private $cachedInstalledVersion = null;
38
+
39
+ /**
40
+ * Class constructor.
41
+ *
42
+ * @param string $metadataUrl The URL of the plugin's metadata file.
43
+ * @param string $pluginFile Fully qualified path to the main plugin file.
44
+ * @param string $slug The plugin's 'slug'. If not specified, the filename part of $pluginFile sans '.php' will be used as the slug.
45
+ * @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.
46
+ * @param string $optionName Where to store book-keeping info about update checks. Defaults to 'external_updates-$slug'.
47
+ * @param string $muPluginFile Optional. The plugin filename relative to the mu-plugins directory.
48
+ */
49
+ public function __construct($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = ''){
50
+ $this->metadataUrl = $metadataUrl;
51
+ $this->pluginAbsolutePath = $pluginFile;
52
+ $this->pluginFile = plugin_basename($this->pluginAbsolutePath);
53
+ $this->muPluginFile = $muPluginFile;
54
+ $this->checkPeriod = $checkPeriod;
55
+ $this->slug = $slug;
56
+ $this->optionName = $optionName;
57
+ $this->debugMode = defined('WP_DEBUG') && WP_DEBUG;
58
+
59
+ //If no slug is specified, use the name of the main plugin file as the slug.
60
+ //For example, 'my-cool-plugin/cool-plugin.php' becomes 'cool-plugin'.
61
+ if ( empty($this->slug) ){
62
+ $this->slug = basename($this->pluginFile, '.php');
63
+ }
64
+
65
+ if ( empty($this->optionName) ){
66
+ $this->optionName = 'external_updates-' . $this->slug;
67
+ }
68
+
69
+ //Backwards compatibility: If the plugin is a mu-plugin but no $muPluginFile is specified, assume
70
+ //it's the same as $pluginFile given that it's not in a subdirectory (WP only looks in the base dir).
71
+ if ( empty($this->muPluginFile) && (strpbrk($this->pluginFile, '/\\') === false) && $this->isMuPlugin() ) {
72
+ $this->muPluginFile = $this->pluginFile;
73
+ }
74
+
75
+ $this->installHooks();
76
+ }
77
+
78
+ /**
79
+ * Install the hooks required to run periodic update checks and inject update info
80
+ * into WP data structures.
81
+ *
82
+ * @return void
83
+ */
84
+ protected function installHooks(){
85
+ //Override requests for plugin information
86
+ add_filter('plugins_api', array($this, 'injectInfo'), 20, 3);
87
+
88
+ //Insert our update info into the update array maintained by WP
89
+ add_filter('site_transient_update_plugins', array($this,'injectUpdate')); //WP 3.0+
90
+ add_filter('transient_update_plugins', array($this,'injectUpdate')); //WP 2.8+
91
+
92
+ add_filter('plugin_row_meta', array($this, 'addCheckForUpdatesLink'), 10, 2);
93
+ add_action('admin_init', array($this, 'handleManualCheck'));
94
+ add_action('all_admin_notices', array($this, 'displayManualCheckResult'));
95
+
96
+ //Clear the version number cache when something - anything - is upgraded or WP clears the update cache.
97
+ add_filter('upgrader_post_install', array($this, 'clearCachedVersion'));
98
+ add_action('delete_site_transient_update_plugins', array($this, 'clearCachedVersion'));
99
+
100
+ //Set up the periodic update checks
101
+ $this->cronHook = 'check_plugin_updates-' . $this->slug;
102
+ if ( $this->checkPeriod > 0 ){
103
+
104
+ //Trigger the check via Cron.
105
+ //Try to use one of the default schedules if possible as it's less likely to conflict
106
+ //with other plugins and their custom schedules.
107
+ $defaultSchedules = array(
108
+ 1 => 'hourly',
109
+ 12 => 'twicedaily',
110
+ 24 => 'daily',
111
+ );
112
+ if ( array_key_exists($this->checkPeriod, $defaultSchedules) ) {
113
+ $scheduleName = $defaultSchedules[$this->checkPeriod];
114
+ } else {
115
+ //Use a custom cron schedule.
116
+ $scheduleName = 'every' . $this->checkPeriod . 'hours';
117
+ add_filter('cron_schedules', array($this, '_addCustomSchedule'));
118
+ }
119
+
120
+ if ( !wp_next_scheduled($this->cronHook) && !defined('WP_INSTALLING') ) {
121
+ wp_schedule_event(time(), $scheduleName, $this->cronHook);
122
+ }
123
+ add_action($this->cronHook, array($this, 'maybeCheckForUpdates'));
124
+
125
+ register_deactivation_hook($this->pluginFile, array($this, '_removeUpdaterCron'));
126
+
127
+ //In case Cron is disabled or unreliable, we also manually trigger
128
+ //the periodic checks while the user is browsing the Dashboard.
129
+ add_action( 'admin_init', array($this, 'maybeCheckForUpdates') );
130
+
131
+ //Like WordPress itself, we check more often on certain pages.
132
+ /** @see wp_update_plugins */
133
+ add_action('load-update-core.php', array($this, 'maybeCheckForUpdates'));
134
+ add_action('load-plugins.php', array($this, 'maybeCheckForUpdates'));
135
+ add_action('load-update.php', array($this, 'maybeCheckForUpdates'));
136
+ //This hook fires after a bulk update is complete.
137
+ add_action('upgrader_process_complete', array($this, 'maybeCheckForUpdates'), 11, 0);
138
+
139
+ } else {
140
+ //Periodic checks are disabled.
141
+ wp_clear_scheduled_hook($this->cronHook);
142
+ }
143
+
144
+ if ( did_action('plugins_loaded') ) {
145
+ $this->initDebugBarPanel();
146
+ } else {
147
+ add_action('plugins_loaded', array($this, 'initDebugBarPanel'));
148
+ }
149
+
150
+ //Rename the update directory to be the same as the existing directory.
151
+ add_filter('upgrader_source_selection', array($this, 'fixDirectoryName'), 10, 3);
152
+ }
153
+
154
+ /**
155
+ * Add our custom schedule to the array of Cron schedules used by WP.
156
+ *
157
+ * @param array $schedules
158
+ * @return array
159
+ */
160
+ public function _addCustomSchedule($schedules){
161
+ if ( $this->checkPeriod && ($this->checkPeriod > 0) ){
162
+ $scheduleName = 'every' . $this->checkPeriod . 'hours';
163
+ $schedules[$scheduleName] = array(
164
+ 'interval' => $this->checkPeriod * 3600,
165
+ 'display' => sprintf('Every %d hours', $this->checkPeriod),
166
+ );
167
+ }
168
+ return $schedules;
169
+ }
170
+
171
+ /**
172
+ * Remove the scheduled cron event that the library uses to check for updates.
173
+ *
174
+ * @return void
175
+ */
176
+ public function _removeUpdaterCron(){
177
+ wp_clear_scheduled_hook($this->cronHook);
178
+ }
179
+
180
+ /**
181
+ * Get the name of the update checker's WP-cron hook. Mostly useful for debugging.
182
+ *
183
+ * @return string
184
+ */
185
+ public function getCronHookName() {
186
+ return $this->cronHook;
187
+ }
188
+
189
+ /**
190
+ * Retrieve plugin info from the configured API endpoint.
191
+ *
192
+ * @uses wp_remote_get()
193
+ *
194
+ * @param array $queryArgs Additional query arguments to append to the request. Optional.
195
+ * @return PluginInfo
196
+ */
197
+ public function requestInfo($queryArgs = array()){
198
+ //Query args to append to the URL. Plugins can add their own by using a filter callback (see addQueryArgFilter()).
199
+ $installedVersion = $this->getInstalledVersion();
200
+ $queryArgs['installed_version'] = ($installedVersion !== null) ? $installedVersion : '';
201
+ $queryArgs = apply_filters('puc_request_info_query_args-'.$this->slug, $queryArgs);
202
+
203
+ //Various options for the wp_remote_get() call. Plugins can filter these, too.
204
+ $options = array(
205
+ 'timeout' => 10, //seconds
206
+ 'headers' => array(
207
+ 'Accept' => 'application/json'
208
+ ),
209
+ );
210
+ $options = apply_filters('puc_request_info_options-'.$this->slug, $options);
211
+
212
+ //The plugin info should be at 'http://your-api.com/url/here/$slug/info.json'
213
+ $url = $this->metadataUrl;
214
+ if ( !empty($queryArgs) ){
215
+ $url = add_query_arg($queryArgs, $url);
216
+ }
217
+
218
+ $result = wp_remote_get(
219
+ $url,
220
+ $options
221
+ );
222
+
223
+ //Try to parse the response
224
+ $pluginInfo = null;
225
+ if ( !is_wp_error($result) && isset($result['response']['code']) && ($result['response']['code'] == 200) && !empty($result['body']) ){
226
+ $pluginInfo = PluginInfo_2_0::fromJson($result['body'], $this->debugMode);
227
+ $pluginInfo->filename = $this->pluginFile;
228
+ $pluginInfo->slug = $this->slug;
229
+ } else if ( $this->debugMode ) {
230
+ $message = sprintf("The URL %s does not point to a valid plugin metadata file. ", $url);
231
+ if ( is_wp_error($result) ) {
232
+ $message .= "WP HTTP error: " . $result->get_error_message();
233
+ } else if ( isset($result['response']['code']) ) {
234
+ $message .= "HTTP response code is " . $result['response']['code'] . " (expected: 200)";
235
+ } else {
236
+ $message .= "wp_remote_get() returned an unexpected result.";
237
+ }
238
+ trigger_error($message, E_USER_WARNING);
239
+ }
240
+
241
+ $pluginInfo = apply_filters('puc_request_info_result-'.$this->slug, $pluginInfo, $result);
242
+ return $pluginInfo;
243
+ }
244
+
245
+ /**
246
+ * Retrieve the latest update (if any) from the configured API endpoint.
247
+ *
248
+ * @uses PluginUpdateChecker::requestInfo()
249
+ *
250
+ * @return PluginUpdate An instance of PluginUpdate, or NULL when no updates are available.
251
+ */
252
+ public function requestUpdate(){
253
+ //For the sake of simplicity, this function just calls requestInfo()
254
+ //and transforms the result accordingly.
255
+ $pluginInfo = $this->requestInfo(array('checking_for_updates' => '1'));
256
+ if ( $pluginInfo == null ){
257
+ return null;
258
+ }
259
+ return PluginUpdate_2_0::fromPluginInfo($pluginInfo);
260
+ }
261
+
262
+ /**
263
+ * Get the currently installed version of the plugin.
264
+ *
265
+ * @return string Version number.
266
+ */
267
+ public function getInstalledVersion(){
268
+ if ( isset($this->cachedInstalledVersion) ) {
269
+ return $this->cachedInstalledVersion;
270
+ }
271
+
272
+ $pluginHeader = $this->getPluginHeader();
273
+ if ( isset($pluginHeader['Version']) ) {
274
+ $this->cachedInstalledVersion = $pluginHeader['Version'];
275
+ return $pluginHeader['Version'];
276
+ } else {
277
+ //This can happen if the filename points to something that is not a plugin.
278
+ if ( $this->debugMode ) {
279
+ trigger_error(
280
+ sprintf(
281
+ "Can't to read the Version header for '%s'. The filename is incorrect or is not a plugin.",
282
+ $this->pluginFile
283
+ ),
284
+ E_USER_WARNING
285
+ );
286
+ }
287
+ return null;
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Get plugin's metadata from its file header.
293
+ *
294
+ * @return array
295
+ */
296
+ protected function getPluginHeader() {
297
+ if ( !is_file($this->pluginAbsolutePath) ) {
298
+ //This can happen if the plugin filename is wrong.
299
+ if ( $this->debugMode ) {
300
+ trigger_error(
301
+ sprintf(
302
+ "Can't to read the plugin header for '%s'. The file does not exist.",
303
+ $this->pluginFile
304
+ ),
305
+ E_USER_WARNING
306
+ );
307
+ }
308
+ return array();
309
+ }
310
+
311
+ if ( !function_exists('get_plugin_data') ){
312
+ require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
313
+ }
314
+ return get_plugin_data($this->pluginAbsolutePath, false, false);
315
+ }
316
+
317
+ /**
318
+ * Check for plugin updates.
319
+ * The results are stored in the DB option specified in $optionName.
320
+ *
321
+ * @return PluginUpdate|null
322
+ */
323
+ public function checkForUpdates(){
324
+ $installedVersion = $this->getInstalledVersion();
325
+ //Fail silently if we can't find the plugin or read its header.
326
+ if ( $installedVersion === null ) {
327
+ if ( $this->debugMode ) {
328
+ trigger_error(
329
+ sprintf('Skipping update check for %s - installed version unknown.', $this->pluginFile),
330
+ E_USER_WARNING
331
+ );
332
+ }
333
+ return null;
334
+ }
335
+
336
+ $state = $this->getUpdateState();
337
+ if ( empty($state) ){
338
+ $state = new StdClass;
339
+ $state->lastCheck = 0;
340
+ $state->checkedVersion = '';
341
+ $state->update = null;
342
+ }
343
+
344
+ $state->lastCheck = time();
345
+ $state->checkedVersion = $installedVersion;
346
+ $this->setUpdateState($state); //Save before checking in case something goes wrong
347
+
348
+ $state->update = $this->requestUpdate();
349
+ $this->setUpdateState($state);
350
+
351
+ return $this->getUpdate();
352
+ }
353
+
354
+ /**
355
+ * Check for updates if the configured check interval has already elapsed.
356
+ * Will use a shorter check interval on certain admin pages like "Dashboard -> Updates" or when doing cron.
357
+ *
358
+ * You can override the default behaviour by using the "puc_check_now-$slug" filter.
359
+ * The filter callback will be passed three parameters:
360
+ * - Current decision. TRUE = check updates now, FALSE = don't check now.
361
+ * - Last check time as a Unix timestamp.
362
+ * - Configured check period in hours.
363
+ * Return TRUE to check for updates immediately, or FALSE to cancel.
364
+ *
365
+ * This method is declared public because it's a hook callback. Calling it directly is not recommended.
366
+ */
367
+ public function maybeCheckForUpdates(){
368
+ if ( empty($this->checkPeriod) ){
369
+ return;
370
+ }
371
+
372
+ $currentFilter = current_filter();
373
+ if ( in_array($currentFilter, array('load-update-core.php', 'upgrader_process_complete')) ) {
374
+ //Check more often when the user visits "Dashboard -> Updates" or does a bulk update.
375
+ $timeout = 60;
376
+ } else if ( in_array($currentFilter, array('load-plugins.php', 'load-update.php')) ) {
377
+ //Also check more often on the "Plugins" page and /wp-admin/update.php.
378
+ $timeout = 3600;
379
+ } else if ( $this->throttleRedundantChecks && ($this->getUpdate() !== null) ) {
380
+ //Check less frequently if it's already known that an update is available.
381
+ $timeout = $this->throttledCheckPeriod * 3600;
382
+ } else if ( defined('DOING_CRON') && constant('DOING_CRON') ) {
383
+ //WordPress cron schedules are not exact, so lets do an update check even
384
+ //if slightly less than $checkPeriod hours have elapsed since the last check.
385
+ $cronFuzziness = 20 * 60;
386
+ $timeout = $this->checkPeriod * 3600 - $cronFuzziness;
387
+ } else {
388
+ $timeout = $this->checkPeriod * 3600;
389
+ }
390
+
391
+ $state = $this->getUpdateState();
392
+ $shouldCheck =
393
+ empty($state) ||
394
+ !isset($state->lastCheck) ||
395
+ ( (time() - $state->lastCheck) >= $timeout );
396
+
397
+ //Let plugin authors substitute their own algorithm.
398
+ $shouldCheck = apply_filters(
399
+ 'puc_check_now-' . $this->slug,
400
+ $shouldCheck,
401
+ (!empty($state) && isset($state->lastCheck)) ? $state->lastCheck : 0,
402
+ $this->checkPeriod
403
+ );
404
+
405
+ if ( $shouldCheck ){
406
+ $this->checkForUpdates();
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Load the update checker state from the DB.
412
+ *
413
+ * @return StdClass|null
414
+ */
415
+ public function getUpdateState() {
416
+ $state = get_site_option($this->optionName, null);
417
+ if ( empty($state) || !is_object($state)) {
418
+ $state = null;
419
+ }
420
+
421
+ if ( !empty($state) && isset($state->update) && is_object($state->update) ){
422
+ $state->update = PluginUpdate_2_0::fromObject($state->update);
423
+ }
424
+ return $state;
425
+ }
426
+
427
+
428
+ /**
429
+ * Persist the update checker state to the DB.
430
+ *
431
+ * @param StdClass $state
432
+ * @return void
433
+ */
434
+ private function setUpdateState($state) {
435
+ if ( isset($state->update) && is_object($state->update) && method_exists($state->update, 'toStdClass') ) {
436
+ $update = $state->update; /** @var PluginUpdate $update */
437
+ $state->update = $update->toStdClass();
438
+ }
439
+ update_site_option($this->optionName, $state);
440
+ }
441
+
442
+ /**
443
+ * Reset update checker state - i.e. last check time, cached update data and so on.
444
+ *
445
+ * Call this when your plugin is being uninstalled, or if you want to
446
+ * clear the update cache.
447
+ */
448
+ public function resetUpdateState() {
449
+ delete_site_option($this->optionName);
450
+ }
451
+
452
+ /**
453
+ * Intercept plugins_api() calls that request information about our plugin and
454
+ * use the configured API endpoint to satisfy them.
455
+ *
456
+ * @see plugins_api()
457
+ *
458
+ * @param mixed $result
459
+ * @param string $action
460
+ * @param array|object $args
461
+ * @return mixed
462
+ */
463
+ public function injectInfo($result, $action = null, $args = null){
464
+ $relevant = ($action == 'plugin_information') && isset($args->slug) && (
465
+ ($args->slug == $this->slug) || ($args->slug == dirname($this->pluginFile))
466
+ );
467
+ if ( !$relevant ){
468
+ return $result;
469
+ }
470
+
471
+ $pluginInfo = $this->requestInfo();
472
+ $pluginInfo = apply_filters('puc_pre_inject_info-' . $this->slug, $pluginInfo);
473
+ if ($pluginInfo){
474
+ return $pluginInfo->toWpFormat();
475
+ }
476
+
477
+ return $result;
478
+ }
479
+
480
+ /**
481
+ * Insert the latest update (if any) into the update list maintained by WP.
482
+ *
483
+ * @param StdClass $updates Update list.
484
+ * @return StdClass Modified update list.
485
+ */
486
+ public function injectUpdate($updates){
487
+ //Is there an update to insert?
488
+ $update = $this->getUpdate();
489
+
490
+ //No update notifications for mu-plugins unless explicitly enabled. The MU plugin file
491
+ //is usually different from the main plugin file so the update wouldn't show up properly anyway.
492
+ if ( !empty($update) && empty($this->muPluginFile) && $this->isMuPlugin() ) {
493
+ $update = null;
494
+ }
495
+
496
+ if ( !empty($update) ) {
497
+ //Let plugins filter the update info before it's passed on to WordPress.
498
+ $update = apply_filters('puc_pre_inject_update-' . $this->slug, $update);
499
+ if ( !is_object($updates) ) {
500
+ $updates = new StdClass();
501
+ $updates->response = array();
502
+ }
503
+
504
+ $wpUpdate = $update->toWpFormat();
505
+ $pluginFile = $this->pluginFile;
506
+
507
+ if ( $this->isMuPlugin() ) {
508
+ //WP does not support automatic update installation for mu-plugins, but we can still display a notice.
509
+ $wpUpdate->package = null;
510
+ $pluginFile = $this->muPluginFile;
511
+ }
512
+ $updates->response[$pluginFile] = $wpUpdate;
513
+
514
+ } else if ( isset($updates, $updates->response) ) {
515
+ unset($updates->response[$this->pluginFile]);
516
+ if ( !empty($this->muPluginFile) ) {
517
+ unset($updates->response[$this->muPluginFile]);
518
+ }
519
+ }
520
+
521
+ return $updates;
522
+ }
523
+
524
+ /**
525
+ * Rename the update directory to match the existing plugin directory.
526
+ *
527
+ * When WordPress installs a plugin or theme update, it assumes that the ZIP file will contain
528
+ * exactly one directory, and that the directory name will be the same as the directory where
529
+ * the plugin/theme is currently installed.
530
+ *
531
+ * GitHub and other repositories provide ZIP downloads, but they often use directory names like
532
+ * "project-branch" or "project-tag-hash". We need to change the name to the actual plugin folder.
533
+ *
534
+ * @param string $source The directory to copy to /wp-content/plugins. Usually a subdirectory of $remoteSource.
535
+ * @param string $remoteSource WordPress has extracted the update to this directory.
536
+ * @param WP_Upgrader $upgrader
537
+ * @return string|WP_Error
538
+ */
539
+ function fixDirectoryName($source, $remoteSource, $upgrader) {
540
+ global $wp_filesystem; /** @var WP_Filesystem_Base $wp_filesystem */
541
+
542
+ //Basic sanity checks.
543
+ if ( !isset($source, $remoteSource, $upgrader, $upgrader->skin, $wp_filesystem) ) {
544
+ return $source;
545
+ }
546
+
547
+ //Figure out which plugin is being upgraded.
548
+ $pluginFile = null;
549
+ $skin = $upgrader->skin;
550
+ if ( $skin instanceof Plugin_Upgrader_Skin ) {
551
+ if ( isset($skin->plugin) && is_string($skin->plugin) && ($skin->plugin !== '') ) {
552
+ $pluginFile = $skin->plugin;
553
+ }
554
+ } elseif ( $upgrader->skin instanceof Bulk_Plugin_Upgrader_Skin ) {
555
+ //This case is tricky because Bulk_Plugin_Upgrader_Skin doesn't actually store the plugin
556
+ //filename anywhere. Instead, it has the plugin headers in $plugin_info. So the best we can
557
+ //do is compare those headers to the headers of installed plugins.
558
+ if ( isset($skin->plugin_info) && is_array($skin->plugin_info) ) {
559
+ if ( !function_exists('get_plugins') ){
560
+ require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
561
+ }
562
+
563
+ $installedPlugins = get_plugins();
564
+ $matches = array();
565
+ foreach($installedPlugins as $pluginBasename => $headers) {
566
+ $diff1 = array_diff_assoc($headers, $skin->plugin_info);
567
+ $diff2 = array_diff_assoc($skin->plugin_info, $headers);
568
+ if ( empty($diff1) && empty($diff2) ) {
569
+ $matches[] = $pluginBasename;
570
+ }
571
+ }
572
+
573
+ //It's possible (though very unlikely) that there could be two plugins with identical
574
+ //headers. In that case, we can't unambiguously identify the plugin that's being upgraded.
575
+ if ( count($matches) !== 1 ) {
576
+ return $source;
577
+ }
578
+
579
+ $pluginFile = reset($matches);
580
+ }
581
+ }
582
+
583
+ //If WordPress is upgrading anything other than our plugin, leave the directory name unchanged.
584
+ if ( empty($pluginFile) || ($pluginFile !== $this->pluginFile) ) {
585
+ return $source;
586
+ }
587
+
588
+ //Rename the source to match the existing plugin directory.
589
+ $pluginDirectoryName = dirname($this->pluginFile);
590
+ if ( ($pluginDirectoryName === '.') || ($pluginDirectoryName === '/') ) {
591
+ return $source;
592
+ }
593
+ $correctedSource = trailingslashit($remoteSource) . $pluginDirectoryName . '/';
594
+ if ( $source !== $correctedSource ) {
595
+ //The update archive should contain a single directory that contains the rest of plugin files. Otherwise,
596
+ //WordPress will try to copy the entire working directory ($source == $remoteSource). We can't rename
597
+ //$remoteSource because that would break WordPress code that cleans up temporary files after update.
598
+ $sourceFiles = $wp_filesystem->dirlist($remoteSource);
599
+ if ( is_array($sourceFiles) ) {
600
+ $sourceFiles = array_keys($sourceFiles);
601
+ $firstFilePath = trailingslashit($remoteSource) . $sourceFiles[0];
602
+
603
+ if ( (count($sourceFiles) > 1) || (!$wp_filesystem->is_dir($firstFilePath)) ) {
604
+ return new WP_Error(
605
+ 'puc-incorrect-directory-structure',
606
+ sprintf(
607
+ 'The directory structure of the update is incorrect. All plugin files should be inside ' .
608
+ 'a directory named <span class="code">%s</span>, not at the root of the ZIP file.',
609
+ htmlentities($this->slug)
610
+ )
611
+ );
612
+ }
613
+ }
614
+
615
+ $upgrader->skin->feedback(sprintf(
616
+ 'Renaming %s to %s&#8230;',
617
+ '<span class="code">' . basename($source) . '</span>',
618
+ '<span class="code">' . $pluginDirectoryName . '</span>'
619
+ ));
620
+
621
+ if ( $wp_filesystem->move($source, $correctedSource, true) ) {
622
+ $upgrader->skin->feedback('Plugin directory successfully renamed.');
623
+ return $correctedSource;
624
+ } else {
625
+ return new WP_Error(
626
+ 'puc-rename-failed',
627
+ 'Unable to rename the update to match the existing plugin directory.'
628
+ );
629
+ }
630
+ }
631
+
632
+ return $source;
633
+ }
634
+
635
+
636
+ /**
637
+ * Get the details of the currently available update, if any.
638
+ *
639
+ * If no updates are available, or if the last known update version is below or equal
640
+ * to the currently installed version, this method will return NULL.
641
+ *
642
+ * Uses cached update data. To retrieve update information straight from
643
+ * the metadata URL, call requestUpdate() instead.
644
+ *
645
+ * @return PluginUpdate|null
646
+ */
647
+ public function getUpdate() {
648
+ $state = $this->getUpdateState(); /** @var StdClass $state */
649
+
650
+ //Is there an update available insert?
651
+ if ( !empty($state) && isset($state->update) && !empty($state->update) ){
652
+ $update = $state->update;
653
+ //Check if the update is actually newer than the currently installed version.
654
+ $installedVersion = $this->getInstalledVersion();
655
+ if ( ($installedVersion !== null) && version_compare($update->version, $installedVersion, '>') ){
656
+ $update->filename = $this->pluginFile;
657
+ return $update;
658
+ }
659
+ }
660
+ return null;
661
+ }
662
+
663
+ /**
664
+ * Add a "Check for updates" link to the plugin row in the "Plugins" page. By default,
665
+ * the new link will appear after the "Visit plugin site" link.
666
+ *
667
+ * You can change the link text by using the "puc_manual_check_link-$slug" filter.
668
+ * Returning an empty string from the filter will disable the link.
669
+ *
670
+ * @param array $pluginMeta Array of meta links.
671
+ * @param string $pluginFile
672
+ * @return array
673
+ */
674
+ public function addCheckForUpdatesLink($pluginMeta, $pluginFile) {
675
+ $isRelevant = ($pluginFile == $this->pluginFile)
676
+ || (!empty($this->muPluginFile) && $pluginFile == $this->muPluginFile);
677
+
678
+ if ( $isRelevant && current_user_can('update_plugins') ) {
679
+ $linkUrl = wp_nonce_url(
680
+ add_query_arg(
681
+ array(
682
+ 'puc_check_for_updates' => 1,
683
+ 'puc_slug' => $this->slug,
684
+ ),
685
+ is_network_admin() ? network_admin_url('plugins.php') : admin_url('plugins.php')
686
+ ),
687
+ 'puc_check_for_updates'
688
+ );
689
+
690
+ $linkText = apply_filters('puc_manual_check_link-' . $this->slug, 'Check for updates');
691
+ if ( !empty($linkText) ) {
692
+ $pluginMeta[] = sprintf('<a href="%s">%s</a>', esc_attr($linkUrl), $linkText);
693
+ }
694
+ }
695
+ return $pluginMeta;
696
+ }
697
+
698
+ /**
699
+ * Check for updates when the user clicks the "Check for updates" link.
700
+ * @see self::addCheckForUpdatesLink()
701
+ *
702
+ * @return void
703
+ */
704
+ public function handleManualCheck() {
705
+ $shouldCheck =
706
+ isset($_GET['puc_check_for_updates'], $_GET['puc_slug'])
707
+ && $_GET['puc_slug'] == $this->slug
708
+ && current_user_can('update_plugins')
709
+ && check_admin_referer('puc_check_for_updates');
710
+
711
+ if ( $shouldCheck ) {
712
+ $update = $this->checkForUpdates();
713
+ $status = ($update === null) ? 'no_update' : 'update_available';
714
+ wp_redirect(add_query_arg(
715
+ array(
716
+ 'puc_update_check_result' => $status,
717
+ 'puc_slug' => $this->slug,
718
+ ),
719
+ is_network_admin() ? network_admin_url('plugins.php') : admin_url('plugins.php')
720
+ ));
721
+ }
722
+ }
723
+
724
+ /**
725
+ * Display the results of a manual update check.
726
+ * @see self::handleManualCheck()
727
+ *
728
+ * You can change the result message by using the "puc_manual_check_message-$slug" filter.
729
+ */
730
+ public function displayManualCheckResult() {
731
+ if ( isset($_GET['puc_update_check_result'], $_GET['puc_slug']) && ($_GET['puc_slug'] == $this->slug) ) {
732
+ $status = strval($_GET['puc_update_check_result']);
733
+ if ( $status == 'no_update' ) {
734
+ $message = 'This plugin is up to date.';
735
+ } else if ( $status == 'update_available' ) {
736
+ $message = 'A new version of this plugin is available.';
737
+ } else {
738
+ $message = sprintf('Unknown update checker status "%s"', htmlentities($status));
739
+ }
740
+ printf(
741
+ '<div class="updated"><p>%s</p></div>',
742
+ apply_filters('puc_manual_check_message-' . $this->slug, $message, $status)
743
+ );
744
+ }
745
+ }
746
+
747
+ /**
748
+ * Check if the plugin file is inside the mu-plugins directory.
749
+ *
750
+ * @return bool
751
+ */
752
+ protected function isMuPlugin() {
753
+ static $cachedResult = null;
754
+
755
+ if ( $cachedResult === null ) {
756
+ //Convert both paths to the canonical form before comparison.
757
+ $muPluginDir = realpath(WPMU_PLUGIN_DIR);
758
+ $pluginPath = realpath($this->pluginAbsolutePath);
759
+
760
+ $cachedResult = (strpos($pluginPath, $muPluginDir) === 0);
761
+ }
762
+
763
+ return $cachedResult;
764
+ }
765
+
766
+ /**
767
+ * Clear the cached plugin version. This method can be set up as a filter (hook) and will
768
+ * return the filter argument unmodified.
769
+ *
770
+ * @param mixed $filterArgument
771
+ * @return mixed
772
+ */
773
+ public function clearCachedVersion($filterArgument = null) {
774
+ $this->cachedInstalledVersion = null;
775
+ return $filterArgument;
776
+ }
777
+
778
+ /**
779
+ * Register a callback for filtering query arguments.
780
+ *
781
+ * The callback function should take one argument - an associative array of query arguments.
782
+ * It should return a modified array of query arguments.
783
+ *
784
+ * @uses add_filter() This method is a convenience wrapper for add_filter().
785
+ *
786
+ * @param callable $callback
787
+ * @return void
788
+ */
789
+ public function addQueryArgFilter($callback){
790
+ add_filter('puc_request_info_query_args-'.$this->slug, $callback);
791
+ }
792
+
793
+ /**
794
+ * Register a callback for filtering arguments passed to wp_remote_get().
795
+ *
796
+ * The callback function should take one argument - an associative array of arguments -
797
+ * and return a modified array or arguments. See the WP documentation on wp_remote_get()
798
+ * for details on what arguments are available and how they work.
799
+ *
800
+ * @uses add_filter() This method is a convenience wrapper for add_filter().
801
+ *
802
+ * @param callable $callback
803
+ * @return void
804
+ */
805
+ public function addHttpRequestArgFilter($callback){
806
+ add_filter('puc_request_info_options-'.$this->slug, $callback);
807
+ }
808
+
809
+ /**
810
+ * Register a callback for filtering the plugin info retrieved from the external API.
811
+ *
812
+ * The callback function should take two arguments. If the plugin info was retrieved
813
+ * successfully, the first argument passed will be an instance of PluginInfo. Otherwise,
814
+ * it will be NULL. The second argument will be the corresponding return value of
815
+ * wp_remote_get (see WP docs for details).
816
+ *
817
+ * The callback function should return a new or modified instance of PluginInfo or NULL.
818
+ *
819
+ * @uses add_filter() This method is a convenience wrapper for add_filter().
820
+ *
821
+ * @param callable $callback
822
+ * @return void
823
+ */
824
+ public function addResultFilter($callback){
825
+ add_filter('puc_request_info_result-'.$this->slug, $callback, 10, 2);
826
+ }
827
+
828
+ /**
829
+ * Register a callback for one of the update checker filters.
830
+ *
831
+ * Identical to add_filter(), except it automatically adds the "puc_" prefix
832
+ * and the "-$plugin_slug" suffix to the filter name. For example, "request_info_result"
833
+ * becomes "puc_request_info_result-your_plugin_slug".
834
+ *
835
+ * @param string $tag
836
+ * @param callable $callback
837
+ * @param int $priority
838
+ * @param int $acceptedArgs
839
+ */
840
+ public function addFilter($tag, $callback, $priority = 10, $acceptedArgs = 1) {
841
+ add_filter('puc_' . $tag . '-' . $this->slug, $callback, $priority, $acceptedArgs);
842
+ }
843
+
844
+ /**
845
+ * Initialize the update checker Debug Bar plugin/add-on thingy.
846
+ */
847
+ public function initDebugBarPanel() {
848
+ if ( class_exists('Debug_Bar') ) {
849
+ require_once dirname(__FILE__) . '/debug-bar-plugin.php';
850
+ $this->debugBarPlugin = new PucDebugBarPlugin($this);
851
+ }
852
+ }
853
+ }
854
+
855
+ endif;
856
+
857
+ if ( !class_exists('PluginInfo_2_0') ):
858
+
859
+ /**
860
+ * A container class for holding and transforming various plugin metadata.
861
+ *
862
+ * @author Janis Elsts
863
+ * @copyright 2015
864
+ * @version 2.0
865
+ * @access public
866
+ */
867
+ class PluginInfo_2_0 {
868
+ //Most fields map directly to the contents of the plugin's info.json file.
869
+ //See the relevant docs for a description of their meaning.
870
+ public $name;
871
+ public $slug;
872
+ public $version;
873
+ public $homepage;
874
+ public $sections;
875
+ public $banners;
876
+ public $download_url;
877
+
878
+ public $author;
879
+ public $author_homepage;
880
+
881
+ public $requires;
882
+ public $tested;
883
+ public $upgrade_notice;
884
+
885
+ public $rating;
886
+ public $num_ratings;
887
+ public $downloaded;
888
+ public $last_updated;
889
+
890
+ public $id = 0; //The native WP.org API returns numeric plugin IDs, but they're not used for anything.
891
+
892
+ public $filename; //Plugin filename relative to the plugins directory.
893
+
894
+ /**
895
+ * Create a new instance of PluginInfo from JSON-encoded plugin info
896
+ * returned by an external update API.
897
+ *
898
+ * @param string $json Valid JSON string representing plugin info.
899
+ * @param bool $triggerErrors
900
+ * @return PluginInfo|null New instance of PluginInfo, or NULL on error.
901
+ */
902
+ public static function fromJson($json, $triggerErrors = false){
903
+ /** @var StdClass $apiResponse */
904
+ $apiResponse = json_decode($json);
905
+ if ( empty($apiResponse) || !is_object($apiResponse) ){
906
+ if ( $triggerErrors ) {
907
+ trigger_error(
908
+ "Failed to parse plugin metadata. Try validating your .json file with http://jsonlint.com/",
909
+ E_USER_NOTICE
910
+ );
911
+ }
912
+ return null;
913
+ }
914
+
915
+ //Very, very basic validation.
916
+ $valid = isset($apiResponse->name) && !empty($apiResponse->name) && isset($apiResponse->version) && !empty($apiResponse->version);
917
+ if ( !$valid ){
918
+ if ( $triggerErrors ) {
919
+ trigger_error(
920
+ "The plugin metadata file does not contain the required 'name' and/or 'version' keys.",
921
+ E_USER_NOTICE
922
+ );
923
+ }
924
+ return null;
925
+ }
926
+
927
+ $info = new self();
928
+ foreach(get_object_vars($apiResponse) as $key => $value){
929
+ $info->$key = $value;
930
+ }
931
+
932
+ return $info;
933
+ }
934
+
935
+ /**
936
+ * Transform plugin info into the format used by the native WordPress.org API
937
+ *
938
+ * @return object
939
+ */
940
+ public function toWpFormat(){
941
+ $info = new StdClass;
942
+
943
+ //The custom update API is built so that many fields have the same name and format
944
+ //as those returned by the native WordPress.org API. These can be assigned directly.
945
+ $sameFormat = array(
946
+ 'name', 'slug', 'version', 'requires', 'tested', 'rating', 'upgrade_notice',
947
+ 'num_ratings', 'downloaded', 'homepage', 'last_updated',
948
+ );
949
+ foreach($sameFormat as $field){
950
+ if ( isset($this->$field) ) {
951
+ $info->$field = $this->$field;
952
+ } else {
953
+ $info->$field = null;
954
+ }
955
+ }
956
+
957
+ //Other fields need to be renamed and/or transformed.
958
+ $info->download_link = $this->download_url;
959
+
960
+ if ( !empty($this->author_homepage) ){
961
+ $info->author = sprintf('<a href="%s">%s</a>', $this->author_homepage, $this->author);
962
+ } else {
963
+ $info->author = $this->author;
964
+ }
965
+
966
+ if ( is_object($this->sections) ){
967
+ $info->sections = get_object_vars($this->sections);
968
+ } elseif ( is_array($this->sections) ) {
969
+ $info->sections = $this->sections;
970
+ } else {
971
+ $info->sections = array('description' => '');
972
+ }
973
+
974
+ if ( !empty($this->banners) ) {
975
+ //WP expects an array with two keys: "high" and "low". Both are optional.
976
+ //Docs: https://wordpress.org/plugins/about/faq/#banners
977
+ $info->banners = is_object($this->banners) ? get_object_vars($this->banners) : $this->banners;
978
+ $info->banners = array_intersect_key($info->banners, array('high' => true, 'low' => true));
979
+ }
980
+
981
+ return $info;
982
+ }
983
+ }
984
+
985
+ endif;
986
+
987
+ if ( !class_exists('PluginUpdate_2_0') ):
988
+
989
+ /**
990
+ * A simple container class for holding information about an available update.
991
+ *
992
+ * @author Janis Elsts
993
+ * @copyright 2015
994
+ * @version 2.0
995
+ * @access public
996
+ */
997
+ class PluginUpdate_2_0 {
998
+ public $id = 0;
999
+ public $slug;
1000
+ public $version;
1001
+ public $homepage;
1002
+ public $download_url;
1003
+ public $upgrade_notice;
1004
+ public $filename; //Plugin filename relative to the plugins directory.
1005
+
1006
+ private static $fields = array('id', 'slug', 'version', 'homepage', 'download_url', 'upgrade_notice', 'filename');
1007
+
1008
+ /**
1009
+ * Create a new instance of PluginUpdate from its JSON-encoded representation.
1010
+ *
1011
+ * @param string $json
1012
+ * @param bool $triggerErrors
1013
+ * @return PluginUpdate|null
1014
+ */
1015
+ public static function fromJson($json, $triggerErrors = false){
1016
+ //Since update-related information is simply a subset of the full plugin info,
1017
+ //we can parse the update JSON as if it was a plugin info string, then copy over
1018
+ //the parts that we care about.
1019
+ $pluginInfo = PluginInfo_2_0::fromJson($json, $triggerErrors);
1020
+ if ( $pluginInfo != null ) {
1021
+ return self::fromPluginInfo($pluginInfo);
1022
+ } else {
1023
+ return null;
1024
+ }
1025
+ }
1026
+
1027
+ /**
1028
+ * Create a new instance of PluginUpdate based on an instance of PluginInfo.
1029
+ * Basically, this just copies a subset of fields from one object to another.
1030
+ *
1031
+ * @param PluginInfo $info
1032
+ * @return PluginUpdate
1033
+ */
1034
+ public static function fromPluginInfo($info){
1035
+ return self::fromObject($info);
1036
+ }
1037
+
1038
+ /**
1039
+ * Create a new instance of PluginUpdate by copying the necessary fields from
1040
+ * another object.
1041
+ *
1042
+ * @param StdClass|PluginInfo|PluginUpdate $object The source object.
1043
+ * @return PluginUpdate The new copy.
1044
+ */
1045
+ public static function fromObject($object) {
1046
+ $update = new self();
1047
+ $fields = self::$fields;
1048
+ if (!empty($object->slug)) $fields = apply_filters('puc_retain_fields-'.$object->slug, $fields);
1049
+ foreach($fields as $field){
1050
+ if (property_exists($object, $field)) {
1051
+ $update->$field = $object->$field;
1052
+ }
1053
+ }
1054
+ return $update;
1055
+ }
1056
+
1057
+ /**
1058
+ * Create an instance of StdClass that can later be converted back to
1059
+ * a PluginUpdate. Useful for serialization and caching, as it avoids
1060
+ * the "incomplete object" problem if the cached value is loaded before
1061
+ * this class.
1062
+ *
1063
+ * @return StdClass
1064
+ */
1065
+ public function toStdClass() {
1066
+ $object = new StdClass();
1067
+ $fields = self::$fields;
1068
+ if (!empty($this->slug)) $fields = apply_filters('puc_retain_fields-'.$this->slug, $fields);
1069
+ foreach($fields as $field){
1070
+ if (property_exists($this, $field)) {
1071
+ $object->$field = $this->$field;
1072
+ }
1073
+ }
1074
+ return $object;
1075
+ }
1076
+
1077
+
1078
+ /**
1079
+ * Transform the update into the format used by WordPress native plugin API.
1080
+ *
1081
+ * @return object
1082
+ */
1083
+ public function toWpFormat(){
1084
+ $update = new StdClass;
1085
+
1086
+ $update->id = $this->id;
1087
+ $update->slug = $this->slug;
1088
+ $update->new_version = $this->version;
1089
+ $update->url = $this->homepage;
1090
+ $update->package = $this->download_url;
1091
+ $update->plugin = $this->filename;
1092
+
1093
+ if ( !empty($this->upgrade_notice) ){
1094
+ $update->upgrade_notice = $this->upgrade_notice;
1095
+ }
1096
+
1097
+ return $update;
1098
+ }
1099
+ }
1100
+
1101
+ endif;
1102
+
1103
+ if ( !class_exists('PucFactory') ):
1104
+
1105
+ /**
1106
+ * A factory that builds instances of other classes from this library.
1107
+ *
1108
+ * When multiple versions of the same class have been loaded (e.g. PluginUpdateChecker 1.2
1109
+ * and 1.3), this factory will always use the latest available version. Register class
1110
+ * versions by calling {@link PucFactory::addVersion()}.
1111
+ *
1112
+ * At the moment it can only build instances of the PluginUpdateChecker class. Other classes
1113
+ * are intended mainly for internal use and refer directly to specific implementations. If you
1114
+ * want to instantiate one of them anyway, you can use {@link PucFactory::getLatestClassVersion()}
1115
+ * to get the class name and then create it with <code>new $class(...)</code>.
1116
+ */
1117
+ class PucFactory {
1118
+ protected static $classVersions = array();
1119
+ protected static $sorted = false;
1120
+
1121
+ /**
1122
+ * Create a new instance of PluginUpdateChecker.
1123
+ *
1124
+ * @see PluginUpdateChecker::__construct()
1125
+ *
1126
+ * @param $metadataUrl
1127
+ * @param $pluginFile
1128
+ * @param string $slug
1129
+ * @param int $checkPeriod
1130
+ * @param string $optionName
1131
+ * @param string $muPluginFile
1132
+ * @return PluginUpdateChecker
1133
+ */
1134
+ public static function buildUpdateChecker($metadataUrl, $pluginFile, $slug = '', $checkPeriod = 12, $optionName = '', $muPluginFile = '') {
1135
+ $class = self::getLatestClassVersion('PluginUpdateChecker');
1136
+ return new $class($metadataUrl, $pluginFile, $slug, $checkPeriod, $optionName, $muPluginFile);
1137
+ }
1138
+
1139
+ /**
1140
+ * Get the specific class name for the latest available version of a class.
1141
+ *
1142
+ * @param string $class
1143
+ * @return string|null
1144
+ */
1145
+ public static function getLatestClassVersion($class) {
1146
+ if ( !self::$sorted ) {
1147
+ self::sortVersions();
1148
+ }
1149
+
1150
+ if ( isset(self::$classVersions[$class]) ) {
1151
+ return reset(self::$classVersions[$class]);
1152
+ } else {
1153
+ return null;
1154
+ }
1155
+ }
1156
+
1157
+ /**
1158
+ * Sort available class versions in descending order (i.e. newest first).
1159
+ */
1160
+ protected static function sortVersions() {
1161
+ foreach ( self::$classVersions as $class => $versions ) {
1162
+ uksort($versions, array(__CLASS__, 'compareVersions'));
1163
+ self::$classVersions[$class] = $versions;
1164
+ }
1165
+ self::$sorted = true;
1166
+ }
1167
+
1168
+ protected static function compareVersions($a, $b) {
1169
+ return -version_compare($a, $b);
1170
+ }
1171
+
1172
+ /**
1173
+ * Register a version of a class.
1174
+ *
1175
+ * @access private This method is only for internal use by the library.
1176
+ *
1177
+ * @param string $generalClass Class name without version numbers, e.g. 'PluginUpdateChecker'.
1178
+ * @param string $versionedClass Actual class name, e.g. 'PluginUpdateChecker_1_2'.
1179
+ * @param string $version Version number, e.g. '1.2'.
1180
+ */
1181
+ public static function addVersion($generalClass, $versionedClass, $version) {
1182
+ if ( !isset(self::$classVersions[$generalClass]) ) {
1183
+ self::$classVersions[$generalClass] = array();
1184
+ }
1185
+ self::$classVersions[$generalClass][$version] = $versionedClass;
1186
+ self::$sorted = false;
1187
+ }
1188
+ }
1189
+
1190
+ endif;
1191
+
1192
+ require_once(dirname(__FILE__) . '/github-checker.php');
1193
+
1194
+ //Register classes defined in this file with the factory.
1195
+ PucFactory::addVersion('PluginUpdateChecker', 'PluginUpdateChecker_2_0', '2.0');
1196
+ PucFactory::addVersion('PluginUpdate', 'PluginUpdate_2_0', '2.0');
1197
+ PucFactory::addVersion('PluginInfo', 'PluginInfo_2_0', '2.0');
1198
+ PucFactory::addVersion('PucGitHubChecker', 'PucGitHubChecker_2_0', '2.0');
1199
+
1200
+ /**
1201
+ * Create non-versioned variants of the update checker classes. This allows for backwards
1202
+ * compatibility with versions that did not use a factory, and it simplifies doc-comments.
1203
+ */
1204
+ if ( !class_exists('PluginUpdateChecker') ) {
1205
+ class PluginUpdateChecker extends PluginUpdateChecker_2_0 { }
1206
+ }
1207
+
1208
+ if ( !class_exists('PluginUpdate') ) {
1209
+ class PluginUpdate extends PluginUpdate_2_0 {}
1210
+ }
1211
+
1212
+ if ( !class_exists('PluginInfo') ) {
1213
+ class PluginInfo extends PluginInfo_2_0 {}
1214
+ }
vendor/yahnis-elsts/plugin-update-checker/vendor/Parsedown.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('&amp;', '&lt;'), $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' => '&amp;',
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('&amp;', '&lt;'), $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
+ }
vendor/yahnis-elsts/plugin-update-checker/vendor/readme-parser.php ADDED
@@ -0,0 +1,331 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /**
4
+ * This is a slightly modified version of github.com/markjaquith/WordPress-Plugin-Readme-Parser
5
+ * It uses Parsedown instead of the "Markdown Extra" parser.
6
+ */
7
+
8
+ Class PucReadmeParser {
9
+
10
+ function __construct() {
11
+ // This space intentially blank
12
+ }
13
+
14
+ function parse_readme( $file ) {
15
+ $file_contents = @implode('', @file($file));
16
+ return $this->parse_readme_contents( $file_contents );
17
+ }
18
+
19
+ function parse_readme_contents( $file_contents ) {
20
+ $file_contents = str_replace(array("\r\n", "\r"), "\n", $file_contents);
21
+ $file_contents = trim($file_contents);
22
+ if ( 0 === strpos( $file_contents, "\xEF\xBB\xBF" ) )
23
+ $file_contents = substr( $file_contents, 3 );
24
+
25
+ // Markdown transformations
26
+ $file_contents = preg_replace( "|^###([^#]+)#*?\s*?\n|im", '=$1='."\n", $file_contents );
27
+ $file_contents = preg_replace( "|^##([^#]+)#*?\s*?\n|im", '==$1=='."\n", $file_contents );
28
+ $file_contents = preg_replace( "|^#([^#]+)#*?\s*?\n|im", '===$1==='."\n", $file_contents );
29
+
30
+ // === Plugin Name ===
31
+ // Must be the very first thing.
32
+ if ( !preg_match('|^===(.*)===|', $file_contents, $_name) )
33
+ return array(); // require a name
34
+ $name = trim($_name[1], '=');
35
+ $name = $this->sanitize_text( $name );
36
+
37
+ $file_contents = $this->chop_string( $file_contents, $_name[0] );
38
+
39
+
40
+ // Requires at least: 1.5
41
+ if ( preg_match('|Requires at least:(.*)|i', $file_contents, $_requires_at_least) )
42
+ $requires_at_least = $this->sanitize_text($_requires_at_least[1]);
43
+ else
44
+ $requires_at_least = NULL;
45
+
46
+
47
+ // Tested up to: 2.1
48
+ if ( preg_match('|Tested up to:(.*)|i', $file_contents, $_tested_up_to) )
49
+ $tested_up_to = $this->sanitize_text( $_tested_up_to[1] );
50
+ else
51
+ $tested_up_to = NULL;
52
+
53
+
54
+ // Stable tag: 10.4-ride-the-fire-eagle-danger-day
55
+ if ( preg_match('|Stable tag:(.*)|i', $file_contents, $_stable_tag) )
56
+ $stable_tag = $this->sanitize_text( $_stable_tag[1] );
57
+ else
58
+ $stable_tag = NULL; // we assume trunk, but don't set it here to tell the difference between specified trunk and default trunk
59
+
60
+
61
+ // Tags: some tag, another tag, we like tags
62
+ if ( preg_match('|Tags:(.*)|i', $file_contents, $_tags) ) {
63
+ $tags = preg_split('|,[\s]*?|', trim($_tags[1]));
64
+ foreach ( array_keys($tags) as $t )
65
+ $tags[$t] = $this->sanitize_text( $tags[$t] );
66
+ } else {
67
+ $tags = array();
68
+ }
69
+
70
+
71
+ // Contributors: markjaquith, mdawaffe, zefrank
72
+ $contributors = array();
73
+ if ( preg_match('|Contributors:(.*)|i', $file_contents, $_contributors) ) {
74
+ $temp_contributors = preg_split('|,[\s]*|', trim($_contributors[1]));
75
+ foreach ( array_keys($temp_contributors) as $c ) {
76
+ $tmp_sanitized = $this->user_sanitize( $temp_contributors[$c] );
77
+ if ( strlen(trim($tmp_sanitized)) > 0 )
78
+ $contributors[$c] = $tmp_sanitized;
79
+ unset($tmp_sanitized);
80
+ }
81
+ }
82
+
83
+
84
+ // Donate Link: URL
85
+ if ( preg_match('|Donate link:(.*)|i', $file_contents, $_donate_link) )
86
+ $donate_link = esc_url( $_donate_link[1] );
87
+ else
88
+ $donate_link = NULL;
89
+
90
+
91
+ // togs, conts, etc are optional and order shouldn't matter. So we chop them only after we've grabbed their values.
92
+ foreach ( array('tags', 'contributors', 'requires_at_least', 'tested_up_to', 'stable_tag', 'donate_link') as $chop ) {
93
+ if ( $$chop ) {
94
+ $_chop = '_' . $chop;
95
+ $file_contents = $this->chop_string( $file_contents, ${$_chop}[0] );
96
+ }
97
+ }
98
+
99
+ $file_contents = trim($file_contents);
100
+
101
+
102
+ // short-description fu
103
+ if ( !preg_match('/(^(.*?))^[\s]*=+?[\s]*.+?[\s]*=+?/ms', $file_contents, $_short_description) )
104
+ $_short_description = array( 1 => &$file_contents, 2 => &$file_contents );
105
+ $short_desc_filtered = $this->sanitize_text( $_short_description[2] );
106
+ $short_desc_length = strlen($short_desc_filtered);
107
+ $short_description = substr($short_desc_filtered, 0, 150);
108
+ if ( $short_desc_length > strlen($short_description) )
109
+ $truncated = true;
110
+ else
111
+ $truncated = false;
112
+ if ( $_short_description[1] )
113
+ $file_contents = $this->chop_string( $file_contents, $_short_description[1] ); // yes, the [1] is intentional
114
+
115
+ // == Section ==
116
+ // Break into sections
117
+ // $_sections[0] will be the title of the first section, $_sections[1] will be the content of the first section
118
+ // the array alternates from there: title2, content2, title3, content3... and so forth
119
+ $_sections = preg_split('/^[\s]*==[\s]*(.+?)[\s]*==/m', $file_contents, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
120
+
121
+ $sections = array();
122
+ for ( $i=1; $i <= count($_sections); $i +=2 ) {
123
+ $_sections[$i] = preg_replace('/^[\s]*=[\s]+(.+?)[\s]+=/m', '<h4>$1</h4>', $_sections[$i]);
124
+ $_sections[$i] = $this->filter_text( $_sections[$i], true );
125
+ $title = $this->sanitize_text( $_sections[$i-1] );
126
+ $sections[str_replace(' ', '_', strtolower($title))] = array('title' => $title, 'content' => $_sections[$i]);
127
+ }
128
+
129
+
130
+ // Special sections
131
+ // This is where we nab our special sections, so we can enforce their order and treat them differently, if needed
132
+ // upgrade_notice is not a section, but parse it like it is for now
133
+ $final_sections = array();
134
+ foreach ( array('description', 'installation', 'frequently_asked_questions', 'screenshots', 'changelog', 'change_log', 'upgrade_notice') as $special_section ) {
135
+ if ( isset($sections[$special_section]) ) {
136
+ $final_sections[$special_section] = $sections[$special_section]['content'];
137
+ unset($sections[$special_section]);
138
+ }
139
+ }
140
+ if ( isset($final_sections['change_log']) && empty($final_sections['changelog']) )
141
+ $final_sections['changelog'] = $final_sections['change_log'];
142
+
143
+
144
+ $final_screenshots = array();
145
+ if ( isset($final_sections['screenshots']) ) {
146
+ preg_match_all('|<li>(.*?)</li>|s', $final_sections['screenshots'], $screenshots, PREG_SET_ORDER);
147
+ if ( $screenshots ) {
148
+ foreach ( (array) $screenshots as $ss )
149
+ $final_screenshots[] = $ss[1];
150
+ }
151
+ }
152
+
153
+ // Parse the upgrade_notice section specially:
154
+ // 1.0 => blah, 1.1 => fnord
155
+ $upgrade_notice = array();
156
+ if ( isset($final_sections['upgrade_notice']) ) {
157
+ $split = preg_split( '#<h4>(.*?)</h4>#', $final_sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
158
+ for ( $i = 0; $i < count( $split ); $i += 2 )
159
+ $upgrade_notice[$this->sanitize_text( $split[$i] )] = substr( $this->sanitize_text( $split[$i + 1] ), 0, 300 );
160
+ unset( $final_sections['upgrade_notice'] );
161
+ }
162
+
163
+ // No description?
164
+ // No problem... we'll just fall back to the old style of description
165
+ // We'll even let you use markup this time!
166
+ $excerpt = false;
167
+ if ( !isset($final_sections['description']) ) {
168
+ $final_sections = array_merge(array('description' => $this->filter_text( $_short_description[2], true )), $final_sections);
169
+ $excerpt = true;
170
+ }
171
+
172
+
173
+ // dump the non-special sections into $remaining_content
174
+ // their order will be determined by their original order in the readme.txt
175
+ $remaining_content = '';
176
+ foreach ( $sections as $s_name => $s_data ) {
177
+ $remaining_content .= "\n<h3>{$s_data['title']}</h3>\n{$s_data['content']}";
178
+ }
179
+ $remaining_content = trim($remaining_content);
180
+
181
+
182
+ // All done!
183
+ // $r['tags'] and $r['contributors'] are simple arrays
184
+ // $r['sections'] is an array with named elements
185
+ $r = array(
186
+ 'name' => $name,
187
+ 'tags' => $tags,
188
+ 'requires_at_least' => $requires_at_least,
189
+ 'tested_up_to' => $tested_up_to,
190
+ 'stable_tag' => $stable_tag,
191
+ 'contributors' => $contributors,
192
+ 'donate_link' => $donate_link,
193
+ 'short_description' => $short_description,
194
+ 'screenshots' => $final_screenshots,
195
+ 'is_excerpt' => $excerpt,
196
+ 'is_truncated' => $truncated,
197
+ 'sections' => $final_sections,
198
+ 'remaining_content' => $remaining_content,
199
+ 'upgrade_notice' => $upgrade_notice
200
+ );
201
+
202
+ return $r;
203
+ }
204
+
205
+ function chop_string( $string, $chop ) { // chop a "prefix" from a string: Agressive! uses strstr not 0 === strpos
206
+ if ( $_string = strstr($string, $chop) ) {
207
+ $_string = substr($_string, strlen($chop));
208
+ return trim($_string);
209
+ } else {
210
+ return trim($string);
211
+ }
212
+ }
213
+
214
+ function user_sanitize( $text, $strict = false ) { // whitelisted chars
215
+ if ( function_exists('user_sanitize') ) // bbPress native
216
+ return user_sanitize( $text, $strict );
217
+
218
+ if ( $strict ) {
219
+ $text = preg_replace('/[^a-z0-9-]/i', '', $text);
220
+ $text = preg_replace('|-+|', '-', $text);
221
+ } else {
222
+ $text = preg_replace('/[^a-z0-9_-]/i', '', $text);
223
+ }
224
+ return $text;
225
+ }
226
+
227
+ function sanitize_text( $text ) { // not fancy
228
+ $text = strip_tags($text);
229
+ $text = esc_html($text);
230
+ $text = trim($text);
231
+ return $text;
232
+ }
233
+
234
+ function filter_text( $text, $markdown = false ) { // fancy, Markdown
235
+ $text = trim($text);
236
+
237
+ $text = call_user_func( array( __CLASS__, 'code_trick' ), $text, $markdown ); // A better parser than Markdown's for: backticks -> CODE
238
+
239
+ if ( $markdown ) { // Parse markdown.
240
+ if ( !class_exists('Parsedown') ) {
241
+ require_once(dirname(__FILE__) . '/Parsedown.php');
242
+ }
243
+ $instance = Parsedown::instance();
244
+ $text = $instance->text($text);
245
+ }
246
+
247
+ $allowed = array(
248
+ 'a' => array(
249
+ 'href' => array(),
250
+ 'title' => array(),
251
+ 'rel' => array()),
252
+ 'blockquote' => array('cite' => array()),
253
+ 'br' => array(),
254
+ 'p' => array(),
255
+ 'code' => array(),
256
+ 'pre' => array(),
257
+ 'em' => array(),
258
+ 'strong' => array(),
259
+ 'ul' => array(),
260
+ 'ol' => array(),
261
+ 'li' => array(),
262
+ 'h3' => array(),
263
+ 'h4' => array()
264
+ );
265
+
266
+ $text = balanceTags($text);
267
+
268
+ $text = wp_kses( $text, $allowed );
269
+ $text = trim($text);
270
+ return $text;
271
+ }
272
+
273
+ function code_trick( $text, $markdown ) { // Don't use bbPress native function - it's incompatible with Markdown
274
+ // If doing markdown, first take any user formatted code blocks and turn them into backticks so that
275
+ // markdown will preserve things like underscores in code blocks
276
+ if ( $markdown )
277
+ $text = preg_replace_callback("!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", array( __CLASS__,'decodeit'), $text);
278
+
279
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
280
+ if ( !$markdown ) {
281
+ // This gets the "inline" code blocks, but can't be used with Markdown.
282
+ $text = preg_replace_callback("|(`)(.*?)`|", array( __CLASS__, 'encodeit'), $text);
283
+ // This gets the "block level" code blocks and converts them to PRE CODE
284
+ $text = preg_replace_callback("!(^|\n)`(.*?)`!s", array( __CLASS__, 'encodeit'), $text);
285
+ } else {
286
+ // Markdown can do inline code, we convert bbPress style block level code to Markdown style
287
+ $text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent'), $text);
288
+ }
289
+ return $text;
290
+ }
291
+
292
+ function indent( $matches ) {
293
+ $text = $matches[3];
294
+ $text = preg_replace('|^|m', $matches[2] . ' ', $text);
295
+ return $matches[1] . $text;
296
+ }
297
+
298
+ function encodeit( $matches ) {
299
+ if ( function_exists('encodeit') ) // bbPress native
300
+ return encodeit( $matches );
301
+
302
+ $text = trim($matches[2]);
303
+ $text = htmlspecialchars($text, ENT_QUOTES);
304
+ $text = str_replace(array("\r\n", "\r"), "\n", $text);
305
+ $text = preg_replace("|\n\n\n+|", "\n\n", $text);
306
+ $text = str_replace('&amp;lt;', '&lt;', $text);
307
+ $text = str_replace('&amp;gt;', '&gt;', $text);
308
+ $text = "<code>$text</code>";
309
+ if ( "`" != $matches[1] )
310
+ $text = "<pre>$text</pre>";
311
+ return $text;
312
+ }
313
+
314
+ function decodeit( $matches ) {
315
+ if ( function_exists('decodeit') ) // bbPress native
316
+ return decodeit( $matches );
317
+
318
+ $text = $matches[2];
319
+ $trans_table = array_flip(get_html_translation_table(HTML_ENTITIES));
320
+ $text = strtr($text, $trans_table);
321
+ $text = str_replace('<br />', '', $text);
322
+ $text = str_replace('&#38;', '&', $text);
323
+ $text = str_replace('&#39;', "'", $text);
324
+ if ( '<pre><code>' == $matches[1] )
325
+ $text = "\n$text\n";
326
+ return "`$text`";
327
+ }
328
+
329
+ } // end class
330
+
331
+ Class Automattic_Readme extends PucReadmeParser {}