PluginProbe
If-So Dynamic Content – Elementor & All Page Builders Personalization / trunk
If-So Dynamic Content – Elementor & All Page Builders Personalization vtrunk
1.10.1 1.10.0.1 1.10 1.9.9 1.9.2.2 1.9.3 1.9.3.1 1.9.3.2 1.9.4 1.9.4.1 1.9.5 1.9.5.1 1.9.6 1.9.7 1.9.8 trunk 0.1 1.0 1.0.1 1.0.2 1.0.3 1.0.5 1.0.6 1.0.7 1.0.8 All 100 releases
if-so / extensions / extension-base / lib / plugin-update-checker / vendor / PucReadmeParser.php

PucReadmeParser.php in If-So Dynamic Content – Elementor & All Page Builders Personalization trunk, at extensions/extension-base/lib/plugin-update-checker/vendor/PucReadmeParser.php

353 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if ( !class_exists('PucReadmeParser', false) ):
4
5 /**
6 * This is a slightly modified version of github.com/markjaquith/WordPress-Plugin-Readme-Parser
7 * It uses Parsedown instead of the "Markdown Extra" parser.
8 */
9
10 class PucReadmeParser {
11
12 function __construct() {
13 // This space intentionally blank
14 }
15
16 function parse_readme( $file ) {
17 $file_contents = @implode('', @file($file));
18 return $this->parse_readme_contents( $file_contents );
19 }
20
21 function parse_readme_contents( $file_contents ) {
22 $file_contents = str_replace(array("\r\n", "\r"), "\n", $file_contents);
23 $file_contents = trim($file_contents);
24 if ( 0 === strpos( $file_contents, "\xEF\xBB\xBF" ) )
25 $file_contents = substr( $file_contents, 3 );
26
27 // Markdown transformations
28 $file_contents = preg_replace( "|^###([^#]+)#*?\s*?\n|im", '=$1='."\n", $file_contents );
29 $file_contents = preg_replace( "|^##([^#]+)#*?\s*?\n|im", '==$1=='."\n", $file_contents );
30 $file_contents = preg_replace( "|^#([^#]+)#*?\s*?\n|im", '===$1==='."\n", $file_contents );
31
32 // === Plugin Name ===
33 // Must be the very first thing.
34 if ( !preg_match('|^===(.*)===|', $file_contents, $_name) )
35 return array(); // require a name
36 $name = trim($_name[1], '=');
37 $name = $this->sanitize_text( $name );
38
39 $file_contents = $this->chop_string( $file_contents, $_name[0] );
40
41
42 // Requires at least: 1.5
43 if ( preg_match('|Requires at least:(.*)|i', $file_contents, $_requires_at_least) )
44 $requires_at_least = $this->sanitize_text($_requires_at_least[1]);
45 else
46 $requires_at_least = NULL;
47
48
49 // Tested up to: 2.1
50 if ( preg_match('|Tested up to:(.*)|i', $file_contents, $_tested_up_to) )
51 $tested_up_to = $this->sanitize_text( $_tested_up_to[1] );
52 else
53 $tested_up_to = NULL;
54
55 // Requires PHP: 5.2.4
56 if ( preg_match('|Requires PHP:(.*)|i', $file_contents, $_requires_php) ) {
57 $requires_php = $this->sanitize_text( $_requires_php[1] );
58 } else {
59 $requires_php = null;
60 }
61
62 // Stable tag: 10.4-ride-the-fire-eagle-danger-day
63 if ( preg_match('|Stable tag:(.*)|i', $file_contents, $_stable_tag) )
64 $stable_tag = $this->sanitize_text( $_stable_tag[1] );
65 else
66 $stable_tag = NULL; // we assume trunk, but don't set it here to tell the difference between specified trunk and default trunk
67
68
69 // Tags: some tag, another tag, we like tags
70 if ( preg_match('|Tags:(.*)|i', $file_contents, $_tags) ) {
71 $tags = preg_split('|,[\s]*?|', trim($_tags[1]));
72 foreach ( array_keys($tags) as $t )
73 $tags[$t] = $this->sanitize_text( $tags[$t] );
74 } else {
75 $tags = array();
76 }
77
78
79 // Contributors: markjaquith, mdawaffe, zefrank
80 $contributors = array();
81 if ( preg_match('|Contributors:(.*)|i', $file_contents, $_contributors) ) {
82 $temp_contributors = preg_split('|,[\s]*|', trim($_contributors[1]));
83 foreach ( array_keys($temp_contributors) as $c ) {
84 $tmp_sanitized = $this->user_sanitize( $temp_contributors[$c] );
85 if ( strlen(trim($tmp_sanitized)) > 0 )
86 $contributors[$c] = $tmp_sanitized;
87 unset($tmp_sanitized);
88 }
89 }
90
91
92 // Donate Link: URL
93 if ( preg_match('|Donate link:(.*)|i', $file_contents, $_donate_link) )
94 $donate_link = esc_url( $_donate_link[1] );
95 else
96 $donate_link = NULL;
97
98
99 // togs, conts, etc are optional and order shouldn't matter. So we chop them only after we've grabbed their values.
100 foreach ( array('tags', 'contributors', 'requires_at_least', 'tested_up_to', 'stable_tag', 'donate_link') as $chop ) {
101 if ( $$chop ) {
102 $_chop = '_' . $chop;
103 $file_contents = $this->chop_string( $file_contents, ${$_chop}[0] );
104 }
105 }
106
107 $file_contents = trim($file_contents);
108
109
110 // short-description fu
111 if ( !preg_match('/(^(.*?))^[\s]*=+?[\s]*.+?[\s]*=+?/ms', $file_contents, $_short_description) )
112 $_short_description = array( 1 => &$file_contents, 2 => &$file_contents );
113 $short_desc_filtered = $this->sanitize_text( $_short_description[2] );
114 $short_desc_length = strlen($short_desc_filtered);
115 $short_description = substr($short_desc_filtered, 0, 150);
116 if ( $short_desc_length > strlen($short_description) )
117 $truncated = true;
118 else
119 $truncated = false;
120 if ( $_short_description[1] )
121 $file_contents = $this->chop_string( $file_contents, $_short_description[1] ); // yes, the [1] is intentional
122
123 // == Section ==
124 // Break into sections
125 // $_sections[0] will be the title of the first section, $_sections[1] will be the content of the first section
126 // the array alternates from there: title2, content2, title3, content3... and so forth
127 $_sections = preg_split('/^[\s]*==[\s]*(.+?)[\s]*==/m', $file_contents, -1, PREG_SPLIT_DELIM_CAPTURE|PREG_SPLIT_NO_EMPTY);
128
129 $sections = array();
130 for ( $i=0; $i < count($_sections); $i +=2 ) {
131 $title = $this->sanitize_text( $_sections[$i] );
132 if ( isset($_sections[$i+1]) ) {
133 $content = preg_replace('/(^[\s]*)=[\s]+(.+?)[\s]+=/m', '$1<h4>$2</h4>', $_sections[$i+1]);
134 $content = $this->filter_text( $content, true );
135 } else {
136 $content = '';
137 }
138 $sections[str_replace(' ', '_', strtolower($title))] = array('title' => $title, 'content' => $content);
139 }
140
141
142 // Special sections
143 // This is where we nab our special sections, so we can enforce their order and treat them differently, if needed
144 // upgrade_notice is not a section, but parse it like it is for now
145 $final_sections = array();
146 foreach ( array('description', 'installation', 'frequently_asked_questions', 'screenshots', 'changelog', 'change_log', 'upgrade_notice') as $special_section ) {
147 if ( isset($sections[$special_section]) ) {
148 $final_sections[$special_section] = $sections[$special_section]['content'];
149 unset($sections[$special_section]);
150 }
151 }
152 if ( isset($final_sections['change_log']) && empty($final_sections['changelog']) )
153 $final_sections['changelog'] = $final_sections['change_log'];
154
155
156 $final_screenshots = array();
157 if ( isset($final_sections['screenshots']) ) {
158 preg_match_all('|<li>(.*?)</li>|s', $final_sections['screenshots'], $screenshots, PREG_SET_ORDER);
159 if ( $screenshots ) {
160 foreach ( (array) $screenshots as $ss )
161 $final_screenshots[] = $ss[1];
162 }
163 }
164
165 // Parse the upgrade_notice section specially:
166 // 1.0 => blah, 1.1 => fnord
167 $upgrade_notice = array();
168 if ( isset($final_sections['upgrade_notice']) ) {
169 $split = preg_split( '#<h4>(.*?)</h4>#', $final_sections['upgrade_notice'], -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY );
170 if ( count($split) >= 2 ) {
171 for ( $i = 0; $i < count( $split ); $i += 2 ) {
172 $upgrade_notice[$this->sanitize_text( $split[$i] )] = substr( $this->sanitize_text( $split[$i + 1] ), 0, 300 );
173 }
174 }
175 unset( $final_sections['upgrade_notice'] );
176 }
177
178 // No description?
179 // No problem... we'll just fall back to the old style of description
180 // We'll even let you use markup this time!
181 $excerpt = false;
182 if ( !isset($final_sections['description']) ) {
183 $final_sections = array_merge(array('description' => $this->filter_text( $_short_description[2], true )), $final_sections);
184 $excerpt = true;
185 }
186
187
188 // dump the non-special sections into $remaining_content
189 // their order will be determined by their original order in the readme.txt
190 $remaining_content = '';
191 foreach ( $sections as $s_name => $s_data ) {
192 $remaining_content .= "\n<h3>{$s_data['title']}</h3>\n{$s_data['content']}";
193 }
194 $remaining_content = trim($remaining_content);
195
196
197 // All done!
198 // $r['tags'] and $r['contributors'] are simple arrays
199 // $r['sections'] is an array with named elements
200 $r = array(
201 'name' => $name,
202 'tags' => $tags,
203 'requires_at_least' => $requires_at_least,
204 'tested_up_to' => $tested_up_to,
205 'requires_php' => $requires_php,
206 'stable_tag' => $stable_tag,
207 'contributors' => $contributors,
208 'donate_link' => $donate_link,
209 'short_description' => $short_description,
210 'screenshots' => $final_screenshots,
211 'is_excerpt' => $excerpt,
212 'is_truncated' => $truncated,
213 'sections' => $final_sections,
214 'remaining_content' => $remaining_content,
215 'upgrade_notice' => $upgrade_notice
216 );
217
218 return $r;
219 }
220
221 function chop_string( $string, $chop ) { // chop a "prefix" from a string: Agressive! uses strstr not 0 === strpos
222 if ( $_string = strstr($string, $chop) ) {
223 $_string = substr($_string, strlen($chop));
224 return trim($_string);
225 } else {
226 return trim($string);
227 }
228 }
229
230 function user_sanitize( $text, $strict = false ) { // whitelisted chars
231 if ( function_exists('user_sanitize') ) // bbPress native
232 return user_sanitize( $text, $strict );
233
234 if ( $strict ) {
235 $text = preg_replace('/[^a-z0-9-]/i', '', $text);
236 $text = preg_replace('|-+|', '-', $text);
237 } else {
238 $text = preg_replace('/[^a-z0-9_-]/i', '', $text);
239 }
240 return $text;
241 }
242
243 function sanitize_text( $text ) { // not fancy
244 $text = function_exists('wp_strip_all_tags')
245 ? wp_strip_all_tags($text)
246 //phpcs:ignore WordPressVIPMinimum.Functions.StripTags.StripTagsOneParameter -- Using wp_strip_all_tags() if available
247 : strip_tags($text);
248
249 $text = esc_html($text);
250 $text = trim($text);
251 return $text;
252 }
253
254 function filter_text( $text, $markdown = false ) { // fancy, Markdown
255 $text = trim($text);
256
257 $text = call_user_func( array( __CLASS__, 'code_trick' ), $text, $markdown ); // A better parser than Markdown's for: backticks -> CODE
258
259 if ( $markdown ) { // Parse markdown.
260 if ( !class_exists('Parsedown', false) ) {
261 /** @noinspection PhpIncludeInspection */
262 require_once(dirname(__FILE__) . '/Parsedown' . (version_compare(PHP_VERSION, '5.3.0', '>=') ? '' : 'Legacy') . '.php');
263 }
264 $instance = Parsedown::instance();
265 $text = $instance->text($text);
266 }
267
268 $allowed = array(
269 'a' => array(
270 'href' => array(),
271 'title' => array(),
272 'rel' => array()),
273 'blockquote' => array('cite' => array()),
274 'br' => array(),
275 'p' => array(),
276 'code' => array(),
277 'pre' => array(),
278 'em' => array(),
279 'strong' => array(),
280 'ul' => array(),
281 'ol' => array(),
282 'li' => array(),
283 'h3' => array(),
284 'h4' => array()
285 );
286
287 $text = balanceTags($text);
288
289 $text = wp_kses( $text, $allowed );
290 $text = trim($text);
291 return $text;
292 }
293
294 function code_trick( $text, $markdown ) { // Don't use bbPress native function - it's incompatible with Markdown
295 // If doing markdown, first take any user formatted code blocks and turn them into backticks so that
296 // markdown will preserve things like underscores in code blocks
297 if ( $markdown )
298 $text = preg_replace_callback("!(<pre><code>|<code>)(.*?)(</code></pre>|</code>)!s", array( __CLASS__,'decodeit'), $text);
299
300 $text = str_replace(array("\r\n", "\r"), "\n", $text);
301 if ( !$markdown ) {
302 // This gets the "inline" code blocks, but can't be used with Markdown.
303 $text = preg_replace_callback("|(`)(.*?)`|", array( __CLASS__, 'encodeit'), $text);
304 // This gets the "block level" code blocks and converts them to PRE CODE
305 $text = preg_replace_callback("!(^|\n)`(.*?)`!s", array( __CLASS__, 'encodeit'), $text);
306 } else {
307 // Markdown can do inline code, we convert bbPress style block level code to Markdown style
308 $text = preg_replace_callback("!(^|\n)([ \t]*?)`(.*?)`!s", array( __CLASS__, 'indent'), $text);
309 }
310 return $text;
311 }
312
313 function indent( $matches ) {
314 $text = $matches[3];
315 $text = preg_replace('|^|m', $matches[2] . ' ', $text);
316 return $matches[1] . $text;
317 }
318
319 function encodeit( $matches ) {
320 if ( function_exists('encodeit') ) // bbPress native
321 return encodeit( $matches );
322
323 $text = trim($matches[2]);
324 $text = htmlspecialchars($text, ENT_QUOTES);
325 $text = str_replace(array("\r\n", "\r"), "\n", $text);
326 $text = preg_replace("|\n\n\n+|", "\n\n", $text);
327 $text = str_replace('&amp;lt;', '&lt;', $text);
328 $text = str_replace('&amp;gt;', '&gt;', $text);
329 $text = "<code>$text</code>";
330 if ( "`" != $matches[1] )
331 $text = "<pre>$text</pre>";
332 return $text;
333 }
334
335 function decodeit( $matches ) {
336 if ( function_exists('decodeit') ) // bbPress native
337 return decodeit( $matches );
338
339 $text = $matches[2];
340 $trans_table = array_flip(get_html_translation_table(HTML_ENTITIES));
341 $text = strtr($text, $trans_table);
342 $text = str_replace('<br />', '', $text);
343 $text = str_replace('&#38;', '&', $text);
344 $text = str_replace('&#39;', "'", $text);
345 if ( '<pre><code>' == $matches[1] )
346 $text = "\n$text\n";
347 return "`$text`";
348 }
349
350 } // end class
351
352 endif;
353