2015-10-04 12:37:12 +00:00
< ? php
2020-09-23 22:22:39 +00:00
/* This Source Code Form is subject to the terms of the Mozilla Public
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at https :// mozilla . org / MPL / 2.0 /. */
2015-10-04 12:37:12 +00:00
register_module ([
" name " => " Parsedown " ,
2023-01-26 20:51:07 +00:00
" version " => " 0.12.3 " ,
2016-03-19 18:24:52 +00:00
" author " => " Emanuil Rusev & Starbeamrainbowlabs " ,
2019-08-17 19:47:51 +00:00
" description " => " An upgraded (now default!) parser based on Emanuil Rusev's Parsedown Extra PHP library (https://github.com/erusev/parsedown-extra), which is licensed MIT. Please be careful, as this module adds some weight to your installation. " ,
2019-02-26 23:01:09 +00:00
" extra_data " => [
2019-08-28 23:15:19 +00:00
/********** Parsedown versions **********
2019-08-30 12:29:15 +00:00
* Parsedown Core : 1.8 . 0 - beta - 7 *
2019-08-28 23:15:19 +00:00
* Parsedown Extra : 0.8 . 0 - beta - 1 *
2020-05-23 01:06:59 +00:00
* Parsedown Extreme : 0.1 . 6 * removed * *
2019-08-28 23:15:19 +00:00
****************************************/
2020-05-23 01:06:59 +00:00
" Parsedown.php " => " https://raw.githubusercontent.com/erusev/parsedown/1610e4747c88a53676f94f752b447f4eff03c28d/Parsedown.php " ,
// "ParsedownExtra.php" => "https://raw.githubusercontent.com/erusev/parsedown-extra/91ac3ff98f0cea243bdccc688df43810f044dcef/ParsedownExtra.php",
// "Parsedown.php" => "https://raw.githubusercontent.com/erusev/parsedown/3825db53a2be5d9ce54436a9cc557c6bdce1808a/Parsedown.php",
" ParsedownExtra.php " => " https://raw.githubusercontent.com/erusev/parsedown-extra/352d03d941fc801724e82e49424ff409175261fd/ParsedownExtra.php "
// Parsedown Extreme is causing PHP 7.4+ errors, and isn't rendering correctly with the security features we have turned on.
// "ParsedownExtended.php" => "https://raw.githubusercontent.com/BenjaminHoegh/ParsedownExtended/8e1224e61a199cb513c47398353a27f6ba822da6/ParsedownExtended.php"
// "ParsedownExtreme.php" => "https://raw.githubusercontent.com/BenjaminHoegh/parsedown-extreme/adae4136534ad1e4159fe04c74c4683681855b84/ParsedownExtreme.php"
2019-02-26 23:01:09 +00:00
],
2015-10-04 12:37:12 +00:00
" id " => " parser-parsedown " ,
" code " => function () {
2016-03-12 18:30:40 +00:00
global $settings ;
2016-03-12 15:26:30 +00:00
$parser = new PeppermintParsedown ();
$parser -> setInternalLinkBase ( " ?page=%s " );
2019-03-02 21:59:50 +00:00
add_parser ( " parsedown " , function ( $source , $untrusted ) use ( $parser ) {
2016-04-08 20:05:42 +00:00
global $settings ;
2019-03-02 21:59:50 +00:00
$parser -> setsafeMode ( $untrusted || $settings -> all_untrusted );
2019-01-05 16:23:12 +00:00
$parser -> setMarkupEscaped ( $settings -> clean_raw_html );
2016-03-12 15:26:30 +00:00
$result = $parser -> text ( $source );
2015-10-04 13:04:42 +00:00
return $result ;
2019-02-10 23:01:01 +00:00
}, function ( $source ) {
global $version , $settings , $pageindex ;
$id_text = " $version | $settings->parser | $source " ;
// Find template includes
preg_match_all (
'/\{\{\s*([^|]+)\s*(?:\|[^}]*)?\}\}/' ,
$source , $includes
);
foreach ( $includes [ 1 ] as $include_pagename ) {
if ( empty ( $pageindex -> $include_pagename ))
2019-02-10 23:08:16 +00:00
continue ;
2019-02-10 23:01:01 +00:00
$id_text .= " | $include_pagename : " . parsedown_pagename_resolve (
$pageindex -> $include_pagename -> lastmodified
);
}
return str_replace ([ " + " , " / " ], [ " - " , " _ " ], base64_encode ( hash (
" sha256 " ,
$id_text ,
true
)));
2015-10-04 12:37:12 +00:00
});
2016-03-12 18:30:40 +00:00
2019-10-20 20:42:13 +00:00
add_action ( " parsedown-render-ext " , function () {
global $settings , $env , $paths ;
2019-10-20 21:00:52 +00:00
if ( ! $settings -> parser_ext_renderers_enabled ) {
http_response_code ( 403 );
header ( " content-type: image/png " );
imagepng ( errorimage ( " Error: External diagram renderer support \n has been disabled on $settings->sitename . \n Try contacting { $settings -> admindetails_name } , $settings->sitename 's administrator. " ));
exit ();
}
2019-10-20 20:42:13 +00:00
if ( ! isset ( $_GET [ " source " ])) {
http_response_code ( 400 );
header ( " content-type: image/png " );
imagepng ( errorimage ( " Error: No source text \n specified. " ));
2019-10-20 20:54:00 +00:00
exit ();
2019-10-20 20:42:13 +00:00
}
2019-10-20 20:54:00 +00:00
if ( ! isset ( $_GET [ " language " ])) {
http_response_code ( 400 );
header ( " content-type: image/png " );
imagepng ( errorimage ( " Error: No external renderer \n language specified. " ));
exit ();
}
$source = $_GET [ " source " ];
$language = $_GET [ " language " ];
if ( ! isset ( $settings -> parser_ext_renderers -> $language )) {
$message = " Error: Unknown language { $_GET [ " language " ] } . \n Supported languages: \n " ;
foreach ( $settings -> parser_ext_renderers as $language => $spec )
$message .= " $spec->name ( $language ) \n " ;
http_response_code ( 400 );
header ( " content-type: image/png " );
imagepng ( errorimage ( trim ( $message )));
exit ();
}
2019-10-20 23:31:18 +00:00
$renderer = $settings -> parser_ext_renderers -> $language ;
$cache_id = hash ( " sha256 " ,
hash ( " sha256 " , $language ) .
hash ( " sha256 " , $source ) .
( $_GET [ " immutable_key " ] ? ? " " )
);
$cache_file_location = " { $paths -> cache_directory } /render_ext/ $cache_id . " . system_mime_type_extension ( $renderer -> output_format );
// If it exists on disk already, then serve that instead
if ( file_exists ( $cache_file_location )) {
header ( " cache-control: public, max-age=31536000, immutable " );
header ( " content-type: $renderer->output_format " );
header ( " content-length: " . filesize ( $cache_file_location ));
header ( " x-cache: render_ext/hit " );
readfile ( $cache_file_location );
exit ();
}
2019-10-24 20:08:18 +00:00
if ( ! $settings -> parser_ext_allow_anon && ! $env -> is_logged_in ) {
http_response_code ( 401 );
header ( " content-type: image/png " );
imagepng ( errorimage ( wordwrap ( " Error: You aren't logged in, that image hasn't yet been cached, and $settings->sitename does not allow anonymous users to invoke external renderers, so that image can't be generated right now. Try contacting $settings->admindetails_name , $settings->sitename 's administrator (their details can be found at the bottom of every page). " )));
exit ();
}
2019-10-20 23:31:18 +00:00
// Create the cache directory if doesn't exist already
if ( ! file_exists ( dirname ( $cache_file_location )))
mkdir ( dirname ( $cache_file_location ), 0750 , true );
2019-10-24 19:13:03 +00:00
$cli_to_execute = $renderer -> cli ;
$descriptors = [
0 => null , // stdin
1 => null , // stdout
2 => tmpfile () // stderr
];
2019-10-20 23:31:18 +00:00
2019-10-24 19:13:03 +00:00
switch ( $renderer -> cli_mode ) {
case " pipe " :
// Fill stdin with the input text
$descriptors [ 0 ] = tmpfile ();
fwrite ( $descriptors [ 0 ], $source );
fseek ( $descriptors [ 0 ], 0 );
// Pipe the output to be the cache file
$descriptors [ 1 ] = fopen ( $cache_file_location , " wb+ " );
break ;
2019-10-24 20:08:18 +00:00
2019-10-24 19:13:03 +00:00
case " substitution_pipe " :
// Update the command that we're going to execute
$cli_to_execute = str_replace (
" { input_text} " ,
escapeshellarg ( $source ),
$cli_to_execute
);
// Set the descriptors
$descriptors [ 0 ] = tmpfile ();
$descriptors [ 1 ] = fopen ( $cache_file_location , " wb+ " );
break ;
case " file " :
$descriptors [ 0 ] = tmpfile ();
fwrite ( $descriptors [ 0 ], $source );
$descriptors [ 1 ] = tmpfile ();
$cli_to_execute = str_replace (
[ " { input_file} " , " { output_file} " ],
[
escapeshellarg ( stream_get_meta_data ( $descriptors [ 0 ])[ " uri " ]),
escapeshellarg ( $cache_file_location )
],
$cli_to_execute
);
break ;
default :
http_response_code ( 503 );
header ( " cache-control: no-cache, no-store, must-revalidate " );
header ( " content-type: image/png " );
imagepng ( errorimage ( " Error: Unknown external renderer mode ' $renderer->cli_mode '. \n Please contact $settings->admindetails_name , $settings->sitename 's administrator. " ));
exit ();
break ;
}
if ( '\\' !== DIRECTORY_SEPARATOR ) {
// We're not on Windows, so we can use timeout to force-kill if it takes too long
$cli_to_execute = " timeout { $settings -> parser_ext_time_limit } $cli_to_execute " ;
}
2019-10-20 23:31:18 +00:00
$start_time = microtime ( true );
$process_handle = proc_open (
2019-10-24 19:13:03 +00:00
$cli_to_execute ,
$descriptors ,
2019-10-20 23:31:18 +00:00
$pipes ,
null , // working directory
null // environment variables
);
if ( ! is_resource ( $process_handle )) {
2019-10-24 19:13:03 +00:00
fclose ( $descriptors [ 0 ]);
fclose ( $descriptors [ 1 ]);
fclose ( $descriptors [ 2 ]);
2019-10-20 23:41:14 +00:00
2019-10-24 19:13:03 +00:00
if ( file_exists ( $cache_file_location )) unlink ( $cache_file_location );
2019-10-20 23:41:14 +00:00
2019-10-20 23:31:18 +00:00
http_response_code ( 503 );
header ( " cache-control: no-cache, no-store, must-revalidate " );
header ( " content-type: image/png " );
imagepng ( errorimage ( " Error: Failed to start external renderer. \n Is $renderer->name installed? " ));
exit ();
}
// Wait for it to exit
$exit_code = proc_close ( $process_handle );
2019-10-24 19:13:03 +00:00
fclose ( $descriptors [ 0 ]);
fclose ( $descriptors [ 1 ]);
2019-10-20 23:31:18 +00:00
$time_taken = round (( microtime ( true ) - $start_time ) * 1000 , 2 );
2019-10-24 19:13:03 +00:00
if ( $exit_code !== 0 || ! file_exists ( $cache_file_location )) {
fseek ( $descriptors [ 2 ], 0 );
$error_details = stream_get_contents ( $descriptors [ 2 ]);
2019-10-20 23:41:14 +00:00
// Delete the cache file, which is guaranteed to exist because
// we pre-emptively create it above
2019-10-24 19:13:03 +00:00
if ( file_exists ( $cache_file_location )) unlink ( $cache_file_location );
2019-10-20 23:31:18 +00:00
http_response_code ( 503 );
header ( " content-type: image/png " );
imagepng ( errorimage (
2019-10-24 19:13:03 +00:00
" Error: The external renderer ( $renderer->name ) \n exited with code $exit_code , \n or potentially did not create the output file. \n Details: \n " . wordwrap ( $error_details )
2019-10-20 23:31:18 +00:00
));
exit ();
}
header ( " cache-control: public, max-age=31536000, immutable " );
header ( " content-type: $renderer->output_format " );
header ( " content-length: " . filesize ( $cache_file_location ));
header ( " x-cache: render_ext/miss, renderer took { $time_taken } ms " );
readfile ( $cache_file_location );
2019-10-20 20:42:13 +00:00
});
2018-04-28 09:59:08 +00:00
/*
* ███████ ████████ █████ ████████ ██ ███████ ████████ ██ ██████ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ███████ ██ ██ ███████ ██ ██ ██ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ██ ██ ██ ██ ███████ ██ ██ ██████ ███████
*/
2017-07-14 19:35:55 +00:00
statistic_add ([
" id " => " wanted-pages " ,
" name " => " Wanted Pages " ,
2017-09-16 12:26:12 +00:00
" type " => " page " ,
2017-07-14 19:35:55 +00:00
" update " => function ( $old_stats ) {
global $pageindex , $env ;
$result = new stdClass (); // completed, value, state
$pages = [];
foreach ( $pageindex as $pagename => $pagedata ) {
2018-04-26 22:51:41 +00:00
if ( ! file_exists ( $env -> storage_prefix . $pagedata -> filename ))
2017-07-14 20:22:37 +00:00
continue ;
2017-07-14 19:35:55 +00:00
$page_content = file_get_contents ( $env -> storage_prefix . $pagedata -> filename );
2018-04-26 22:14:35 +00:00
$page_links = PeppermintParsedown :: extract_page_names ( $page_content );
foreach ( $page_links as $linked_page ) {
2017-07-14 19:35:55 +00:00
// We're only interested in pages that don't exist
2017-07-14 20:22:37 +00:00
if ( ! empty ( $pageindex -> $linked_page )) continue ;
2017-07-14 19:35:55 +00:00
if ( empty ( $pages [ $linked_page ]))
$pages [ $linked_page ] = 0 ;
$pages [ $linked_page ] ++ ;
}
}
arsort ( $pages );
$result -> value = $pages ;
$result -> completed = true ;
return $result ;
2017-07-14 21:13:13 +00:00
},
" render " => function ( $stats_data ) {
$result = " <h2> $stats_data->name </h2> \n " ;
$result .= " <table class='wanted-pages'> \n " ;
$result .= " \t <tr><th>Page Name</th><th>Linking Pages</th></tr> \n " ;
foreach ( $stats_data -> value as $pagename => $linking_pages ) {
$result .= " \t <tr><td> $pagename </td><td> $linking_pages </td></tr> \n " ;
}
$result .= " </table> \n " ;
return $result ;
2017-07-14 19:35:55 +00:00
}
]);
2018-04-26 22:27:44 +00:00
statistic_add ([
" id " => " orphan-pages " ,
" name " => " Orphan Pages " ,
2018-04-28 10:06:23 +00:00
" type " => " page-list " ,
2018-04-26 22:27:44 +00:00
" update " => function ( $old_stats ) {
global $pageindex , $env ;
$result = new stdClass (); // completed, value, state
$pages = [];
foreach ( $pageindex as $pagename => $pagedata ) {
2018-04-26 22:51:41 +00:00
if ( ! file_exists ( $env -> storage_prefix . $pagedata -> filename ))
2018-04-26 22:27:44 +00:00
continue ;
$page_content = file_get_contents ( $env -> storage_prefix . $pagedata -> filename );
$page_links = PeppermintParsedown :: extract_page_names ( $page_content );
foreach ( $page_links as $linked_page ) {
// We're only interested in pages that exist
if ( empty ( $pageindex -> $linked_page )) continue ;
$pages [ $linked_page ] = true ;
}
}
$orphaned_pages = [];
foreach ( $pageindex as $pagename => $page_data ) {
if ( empty ( $pages [ $pagename ]))
$orphaned_pages [] = $pagename ;
}
2019-09-03 17:16:01 +00:00
$sorter = new Collator ( " " );
$sorter -> sort ( $orphaned_pages );
2018-04-26 22:27:44 +00:00
$result -> value = $orphaned_pages ;
$result -> completed = true ;
return $result ;
}
]);
2018-04-28 09:59:08 +00:00
statistic_add ([
" id " => " most-linked-to-pages " ,
" name " => " Most Linked-To Pages " ,
" type " => " page " ,
" update " => function ( $old_stats ) {
global $pageindex , $env ;
$result = new stdClass (); // completed, value, state
$pages = [];
foreach ( $pageindex as $pagename => $pagedata ) {
if ( ! file_exists ( $env -> storage_prefix . $pagedata -> filename ))
continue ;
$page_content = file_get_contents ( $env -> storage_prefix . $pagedata -> filename );
$page_links = PeppermintParsedown :: extract_page_names ( $page_content );
foreach ( $page_links as $linked_page ) {
// We're only interested in pages that exist
if ( empty ( $pageindex -> $linked_page )) continue ;
if ( empty ( $pages [ $linked_page ]))
$pages [ $linked_page ] = 0 ;
$pages [ $linked_page ] ++ ;
}
}
arsort ( $pages );
$result -> value = $pages ;
$result -> completed = true ;
return $result ;
},
" render " => function ( $stats_data ) {
global $pageindex ;
$result = " <h2> $stats_data->name </h2> \n " ;
$result .= " <table class='most-linked-to-pages'> \n " ;
$result .= " \t <tr><th>Page Name</th><th>Linking Pages</th></tr> \n " ;
foreach ( $stats_data -> value as $pagename => $link_count ) {
$pagename_display = ! empty ( $pageindex -> $pagename -> redirect ) && $pageindex -> $pagename -> redirect ? " <em> $pagename </em> " : $pagename ;
$result .= " \t <tr><td><a href='?page= " . rawurlencode ( $pagename ) . " '> $pagename_display </a></td><td> $link_count </td></tr> \n " ;
}
$result .= " </table> \n " ;
return $result ;
}
]);
2017-07-14 19:35:55 +00:00
2016-03-12 18:30:40 +00:00
add_help_section ( " 20-parser-default " , " Editor Syntax " ,
" <p> $settings->sitename 's editor uses an extended version of <a href='http://parsedown.org/'>Parsedown</a> to render pages, which is a fantastic open source Github flavoured markdown parser. You can find a quick reference guide on Github flavoured markdown <a href='https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet'>here</a> by <a href='https://github.com/adam-p/'>adam-p</a>, or if you prefer a book <a href='https://www.gitbook.com/book/roachhd/master-markdown/details'>Mastering Markdown</a> by KB is a good read, and free too!</p>
2016-08-19 19:28:47 +00:00
< h3 > Tips </ h3 >
< ul >
< li > Put 2 spaces at the end of a line to add a soft line break . Leave a blank line to add a head line break ( i . e . a new paragraph ) .</ li >
2020-08-09 12:04:29 +00:00
< li > If you don 't like the default id given to a header, you can add a custom one instead. Put it in curly braces after the heading name like this: <code># Heading Name {#HeadingId}</code>. Then you can link to like like this: <code>[[Page name#HeadingId}]]</code>. You can also link to a heading id on the current page by omitting the page name: <code>[[#HeadingId]]</code>. Finally, a heading id is automatically generated for every heading by default. Take the heading name, make it lowercase, and replace the spaces with dashes <code>.</code>, and that' s the heading ID that you can link to ( although sometimes some special characters are removed ) .</ li >
2016-08-19 19:28:47 +00:00
</ ul >
2016-03-12 18:30:40 +00:00
< h3 > Extra Syntax </ h3 >
< p > $settings -> sitename 's editor also supports some extra custom syntax, some of which is inspired by <a href=' https :// mediawiki . org / ' > Mediawiki </ a >.
< table >
< tr >< th style = 'width: 40%' > Type this </ th >< th style = 'width: 20%' > To get this </ th >< th > Comments </ th ></ th >
< tr >< td >< code > [[ Internal link ]] </ code ></ td >< td >< a href = '?page=Internal%20link' > Internal Link </ a ></ td >< td > An internal link .</ td ></ tr >
2020-04-20 00:12:21 +00:00
< tr >< td >< code > [[ Internal link | Display Text ]] </ code ></ td >< td >< a href = '?page=Internal%20link' > Display Text </ a ></ td >< td > An internal link with some display text .</ td ></ tr >
2016-03-12 18:30:40 +00:00
< tr >< td >< code >! [ Alt text ]( http :// example . com / path / to / image . png | 256 x256 | right ) </ code ></ td >< td >< img src = 'http://example.com/path/to/image.png' alt = 'Alt text' style = 'float: right; max-width: 256px; max-height: 256px;' /></ td >< td > An image floating to the right of the page that fits inside a 256 px x 256 px box , preserving aspect ratio .</ td ></ tr >
2018-07-01 19:56:08 +00:00
< tr >< td >< code >! [ Alt text ]( http :// example . com / path / to / image . png | 256 x256 | caption ) </ code ></ td >< td >< figure >< img src = 'http://example.com/path/to/image.png' alt = 'Alt text' style = 'max-width: 256px; max-height: 256px;' />< figcaption > Alt text </ figcaption ></ figure ></ td >< td > An image with a caption that fits inside a 256 px x 256 px box , preserving aspect ratio . The presence of the word < code > caption </ code > in the regular braces causes the alt text to be taken and displayed below the image itself .</ td ></ tr >
2016-05-30 11:35:23 +00:00
< tr >< td >< code >! [ Alt text ]( Files / Cheese . png ) </ code ></ td >< td >< img src = 'index.php?action=preview&page=Files/Cheese.png' alt = 'Alt text' style = '' /></ td >< td > An example of the short url syntax for images . Simply enter the page name of an image ( or video / audio file ), and Pepperminty Wiki will sort out the url for you .</ td ></ tr >
2020-05-23 11:57:04 +00:00
< tr >< td >< code > Some text == marked text == more text </ code ></ td >< td > Some text < mark > marked text </ mark > more text </ td >< td > Marked / highlighted text </ td ></ tr >
2020-05-23 21:41:43 +00:00
< tr >< td >< code > Some text ^ superscript ^ more text </ code ></ td >< td > Some text < sup > superscript </ sup > more text </ td >< td > Superscript </ td ></ tr >
2020-05-23 21:43:32 +00:00
< tr >< td >< code > Some text ~ subscript ~ more text </ code ></ td >< td > Some text < sub > subscript </ sub > more text </ td >< td > Subscript ( note that we use a single tilda < code >~</ code > here - a double results in strikethrough text instead ) </ td ></ tr >
2020-05-23 21:37:42 +00:00
< tr >< td >< code > [ ] Unticked checkbox </ code ></ td >< td >< input type = 'checkbox' disabled /> Unticked checkbox </ td >< td > An unticked checkbox . Must be at the beginning of a line or directly after a list item ( e . g . < code > - </ code > or < code > 1. </ code > ) .</ td ></ tr >
< tr >< td >< code > [ x ] Ticked checkbox </ code ></ td >< td >< input type = 'checkbox' checked = 'checked' disabled /> Ticked checkbox </ td >< td > An ticked checkbox . The same rules as unticked checkboxes apply here too .</ td ></ tr >
2020-05-23 21:43:32 +00:00
< tr >< td >< code > some text & gt ; ! spoiler text !& lt ; more text </ code ></ td >< td > some text < a class = 'spoiler' href = '#spoiler-example' id = 'spoiler-example' > spoiler text </ a > more text </ td >< td > A spoiler . Users must click it to reveal the content hidden beneath .</ td ></ tr >
2020-05-23 21:37:42 +00:00
< tr >< td >< code > some text || spoiler text || more text </ code ></ td >< td > some text < a class = 'spoiler' href = '#spoiler-example-2' id = 'spoiler-example-2' > spoiler text </ a > more text </ td >< td > Alternative spoiler syntax inspired by < a href = 'https://support.discord.com/hc/en-us/articles/360022320632-Spoiler-Tags-' > Discord </ a >.</ td ></ tr >
< tr >< td >< code > [ __TOC__ ] </ code ></ td >< td ></ td >< td > An automatic table of contents . Note that this must be on a line by itself with no text before or after it on that line for it to work .</ td ></ tr >
2016-03-20 17:57:47 +00:00
</ table >
2016-05-30 11:35:23 +00:00
< p > Note that the all image image syntax above can be mixed and matched to your liking . The < code > caption </ code > option in particular must come last or next to last .</ p >
2016-03-20 17:57:47 +00:00
< h4 > Templating </ h4 >
< p > $settings -> sitename also supports including one page in another page as a < em > template </ em >. The syntax is very similar to that of Mediawiki . For example , < code > {{ Announcement banner }} </ code > will include the contents of the \ " Announcement banner \" page, assuming it exists.</p>
2016-04-09 09:55:44 +00:00
< p > You can also use variables . Again , the syntax here is very similar to that of Mediawiki - they can be referenced in the included page by surrrounding the variable name in triple curly braces ( e . g . < code > {{{ Announcement text }}} </ code > ), and set when including a page with the bar syntax ( e . g . < code > {{ Announcement banner | importance = high | text = Maintenance has been planned for tonight . }} </ code > ) . Currently the only restriction in templates and variables is that you may not include a closing curly brace ( < code > } </ code > ) in the page name , variable name , or value .</ p >
2016-05-30 11:35:23 +00:00
< h5 > Special Variables </ h5 >
2016-04-09 09:55:44 +00:00
< p > $settings -> sitename also supports a number of special built - in variables . Their syntax and function are described below :</ p >
< table >
< tr >< th > Type this </ th >< th > To get this </ th ></ tr >
< tr >< td >< code > {{{ @ }}} </ code ></ td >< td > Lists all variables and their values in a table .</ td ></ tr >
< tr >< td >< code > {{{ #}}}</code></td><td>Shows a 'stack trace', outlining all the parent includes of the current page being parsed.</td></tr>
2016-11-03 20:29:15 +00:00
< tr >< td >< code > {{{ ~ }}} </ code ></ td >< td > Outputs the requested page ' s name .</ td ></ tr >
2016-04-09 09:55:44 +00:00
< tr >< td >< code > {{{ * }}} </ code ></ td >< td > Outputs a comma separated list of all the subpages of the current page .</ td ></ tr >
< tr >< td >< code > {{{ + }}} </ code ></ td >< td > Shows a gallery containing all the files that are sub pages of the current page .</ td ></ tr >
2020-08-09 16:11:12 +00:00
</ table >
2022-01-30 02:36:48 +00:00
< p > Note that a page < em > does not </ em > need to be included as a template to use these variables . " );
2019-10-26 14:19:05 +00:00
if ( $settings -> parser_ext_renderers_enabled ) {
$doc_help = " <p> $settings->sitename supports external renderers. External renderers take the content of a code fence block, like this:</p>
< pre >< code > `` ` language_code
Insert text here
`` ` </ code ></ pre >
< p >... and render it to an image . This is based on the < code > language_code </ code > specified , as is done in the above example . Precisely what the output of a external renderer is depends on the external renderers defined , but $settings -> sitename currently has the following external renderers registered :</ p >
< table >
< tr >< th > Name </ th >< th > Language code </ th >< th > Description </ th >< th > Reference Link </ th ></ tr >
" ;
foreach ( $settings -> parser_ext_renderers as $code => $renderer ) {
2020-08-08 00:54:10 +00:00
$row = array_map ( function ( $value ) { return htmlentities ( $value , ENT_COMPAT | ENT_HTML5 ); }, [
2019-10-26 14:19:05 +00:00
$renderer -> name ,
$code ,
$renderer -> description ,
$renderer -> url
]);
$row [ 3 ] = " <a href=' $row[3] '>🔗</a> " ;
$doc_help .= " <tr><td> " . implode ( " </td><td> " , $row ) . " </td></tr> \n " ;
}
$doc_help .= " </table>
$settings -> admindetails_name can register more external renderers - see the < a href = 'https://starbeamrainbowlabs.com/labs/peppermint/__nightdocs/06.8-External-Renderers.html' > documentation </ a > for more information .</ p > " ;
add_help_section ( " 24-external-renderers " , " External Renderers " , $doc_help );
}
2015-10-04 12:37:12 +00:00
}
]);
2019-03-02 13:32:12 +00:00
require_once ( " $paths->extra_data_directory /parser-parsedown/Parsedown.php " );
require_once ( " $paths->extra_data_directory /parser-parsedown/ParsedownExtra.php " );
2020-05-23 01:06:59 +00:00
// require_once("$paths->extra_data_directory/parser-parsedown/ParsedownExtended.php");
2015-10-04 12:37:12 +00:00
2019-02-10 23:01:01 +00:00
/**
* Attempts to 'auto-correct' a page name by trying different capitalisation
* combinations .
2021-04-11 20:47:41 +00:00
*
2019-02-10 23:01:01 +00:00
* @ param string $pagename The page name to auto - correct .
* @ return string The auto - corrected page name .
*/
function parsedown_pagename_resolve ( $pagename ) {
global $pageindex ;
// If the page doesn't exist, check varying different
// capitalisations to see if it exists under some variant.
if ( ! empty ( $pageindex -> $pagename ))
return $pagename ;
$pagename = ucfirst ( $pagename );
if ( ! empty ( $pageindex -> $pagename ))
return $pagename ;
$pagename = ucwords ( $pagename );
return $pagename ;
}
2015-12-26 12:55:19 +00:00
/*
2016-03-12 15:26:30 +00:00
* ██████ █████ ██████ ███████ ███████ ██████ ██████ ██ ██ ███ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
* ██████ ███████ ██████ ███████ █████ ██ ██ ██ ██ ██ █ ██ ██ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██
* ██ ██ ██ ██ ██ ███████ ███████ ██████ ██████ ███ ███ ██ ████
*
* ███████ ██ ██ ████████ ███████ ███ ██ ███████ ██ ██████ ███ ██ ███████
* ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ████ ██ ██
* █████ ███ ██ █████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ██ ██ ███████ ██ ████ ███████ ██ ██████ ██ ████ ███████
*/
2019-03-02 13:49:12 +00:00
/**
* The Peppermint - flavoured Parsedown parser .
*/
2020-05-23 01:06:59 +00:00
class PeppermintParsedown extends ParsedownExtra
2015-10-04 12:37:12 +00:00
{
2020-05-23 21:28:33 +00:00
/**
* A long random and extremely unlikely string to identify where we want
* to put a table of contents .
* Hopefully nobody is unlucky enough to include this in a page ...!
* @ var string
*/
private const TOC_ID = " █yyZiy9c9oHVExhVummYZId_dO9-fvaGFvgQirEapxOtaL-s7WnK34lF9ObBoQ0EH2kvtd6VKcAL2█ " ;
2019-03-02 13:49:12 +00:00
/**
* The base directory with which internal links will be resolved .
* @ var string
*/
2016-03-12 15:26:30 +00:00
private $internalLinkBase = " ./%s " ;
2019-03-02 13:49:12 +00:00
/**
* The parameter stack . Used for recursive templating .
* @ var array
*/
2016-03-20 16:16:55 +00:00
protected $paramStack = [];
2019-03-02 13:49:12 +00:00
/**
* Creates a new Peppermint Parsedown instance .
*/
2016-03-12 15:26:30 +00:00
function __construct ()
{
2019-09-21 10:35:50 +00:00
parent :: __construct ();
2020-05-23 21:28:33 +00:00
array_unshift ( $this -> BlockTypes [ " [ " ], " TableOfContents " );
2022-03-12 02:52:53 +00:00
array_unshift ( $this -> BlockTypes [ " [ " ], " OneBox " );
2020-05-23 21:28:33 +00:00
2016-03-12 15:26:30 +00:00
// Prioritise our internal link parsing over the regular link parsing
2020-05-23 17:44:55 +00:00
$this -> addInlineType ( " [ " , " InternalLink " , true );
2020-05-23 01:06:59 +00:00
// Prioritise the checkbox handling - this is fine 'cause it doesn't step on InternalLink's toes
2020-05-23 17:44:55 +00:00
$this -> addInlineType ( " [ " , " Checkbox " , true );
2016-03-12 15:26:30 +00:00
// Prioritise our image parser over the regular image parser
2020-05-23 17:44:55 +00:00
$this -> addInlineType ( " ! " , " ExtendedImage " , true );
2015-10-04 14:20:47 +00:00
2020-05-23 17:44:55 +00:00
$this -> addInlineType ( " { " , " Template " );
$this -> addInlineType ( " = " , " Mark " );
$this -> addInlineType ( " ^ " , " Superscript " );
$this -> addInlineType ( " ~ " , " Subscript " );
2020-05-23 19:38:01 +00:00
$this -> addInlineType ( " > " , " Spoiler " , true );
$this -> addInlineType ( " | " , " Spoiler " , true );
2020-05-23 17:44:55 +00:00
}
/**
* Helper method to add an inline type .
* @ param string $char The char to match against .
* @ param string $function_id The name bit of the function to call .
* @ param boolean $before_others Whether to prioritise this function over other existing ones ( default : false ) .
*/
protected function addInlineType ( string $char , string $function_id , bool $before_others = false ) {
if ( mb_strlen ( $char ) > 1 )
throw new Exception ( " Error: ' $char ' is longer than a single character. " );
if ( ! isset ( $this -> InlineTypes [ $char ]) or ! is_array ( $this -> InlineTypes [ $char ]))
$this -> InlineTypes [ $char ] = [];
if ( strpos ( $this -> inlineMarkerList , $char ) === false )
$this -> inlineMarkerList .= $char ;
if ( ! $before_others )
$this -> InlineTypes [ $char ][] = $function_id ;
else
array_unshift ( $this -> InlineTypes [ $char ], $function_id );
2016-03-20 14:05:55 +00:00
}
2020-05-23 21:28:33 +00:00
/*
* Override the text method here to insert the table of contents after
* rendering has been completed
*/
public function text ( $text ) {
$result = parent :: text ( $text );
$toc_html = $this -> generateTableOfContents ();
$result = str_replace ( self :: TOC_ID , $toc_html , $result );
return $result ;
}
2016-04-09 06:51:29 +00:00
/*
* ████████ ███████ ███ ███ ██████ ██ █████ ████████ ██ ███ ██ ██████
* ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
* ██ █████ ██ ████ ██ ██████ ██ ███████ ██ ██ ██ ██ ██ ██ ███
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ███████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ████ ██████
2020-08-07 23:56:16 +00:00
* % templating
2016-04-09 06:51:29 +00:00
*/
2019-03-02 13:49:12 +00:00
/**
* Parses templating definitions .
* @ param string $fragment The fragment to parse it out from .
*/
2016-03-20 14:05:55 +00:00
protected function inlineTemplate ( $fragment )
{
2016-04-09 06:51:29 +00:00
global $env , $pageindex ;
2016-03-20 17:05:16 +00:00
2016-03-20 16:16:55 +00:00
// Variable parsing
if ( preg_match ( " / \ { \ { \ { ([^}]+) \ } \ } \ }/ " , $fragment [ " text " ], $matches ))
{
2016-04-08 08:01:12 +00:00
$params = [];
if ( ! empty ( $this -> paramStack ))
{
$stackEntry = array_slice ( $this -> paramStack , - 1 )[ 0 ];
2023-01-26 20:51:07 +00:00
$params = ! empty ( $stackEntry ) ? $stackEntry [ " params " ] : null ;
2016-04-08 08:01:12 +00:00
}
2016-03-20 17:05:16 +00:00
2016-03-20 16:16:55 +00:00
$variableKey = trim ( $matches [ 1 ]);
2016-03-20 16:38:36 +00:00
$variableValue = false ;
2020-08-08 00:54:10 +00:00
switch ( $variableKey ) {
2016-04-09 09:55:44 +00:00
case " @ " : // Lists all variables and their values
2019-01-05 17:51:51 +00:00
if ( ! empty ( $params )) {
2016-03-20 17:05:16 +00:00
$variableValue = " <table>
< tr >< th > Key </ th >< th > Value </ th ></ tr > \n " ;
2020-08-08 00:54:10 +00:00
foreach ( $params as $key => $value ) {
2016-03-20 17:05:16 +00:00
$variableValue .= " \t <tr><td> " . $this -> escapeText ( $key ) . " </td><td> " . $this -> escapeText ( $value ) . " </td></tr> \n " ;
}
$variableValue .= " </table> " ;
}
2019-01-05 17:51:51 +00:00
else {
2020-08-08 00:54:10 +00:00
$variableValue = " <em>(no variables are currently defined)</em> " ;
2019-01-05 17:51:51 +00:00
}
2016-03-20 17:05:16 +00:00
break ;
2016-04-09 06:51:29 +00:00
case " # " : // Shows a stack trace
2016-03-20 17:05:16 +00:00
$variableValue = " <ol start= \" 0 \" > \n " ;
$variableValue .= " \t <li> $env->page </li> \n " ;
2020-08-08 00:54:10 +00:00
foreach ( $this -> paramStack as $curStackEntry ) {
2016-03-20 17:05:16 +00:00
$variableValue .= " \t <li> " . $curStackEntry [ " pagename " ] . " </li> \n " ;
}
$variableValue .= " </ol> \n " ;
2016-04-08 08:01:12 +00:00
break ;
2016-04-09 06:51:29 +00:00
case " ~ " : // Show requested page's name
2016-04-08 08:50:13 +00:00
if ( ! empty ( $this -> paramStack ))
$variableValue = $this -> escapeText ( $env -> page );
2016-04-09 06:51:29 +00:00
break ;
case " * " : // Lists subpages
$subpages = get_subpages ( $pageindex , $env -> page );
$variableValue = [];
foreach ( $subpages as $pagename => $depth )
{
$variableValue [] = $pagename ;
}
$variableValue = implode ( " , " , $variableValue );
if ( strlen ( $variableValue ) === 0 )
$variableValue = " <em>(none yet!)</em> " ;
break ;
2016-04-09 09:55:44 +00:00
case " + " : // Shows a file gallery for subpages with files
2016-04-09 08:26:18 +00:00
// If the upload module isn't present, then there's no point
// in checking for uploaded files
if ( ! module_exists ( " feature-upload " ))
break ;
$variableValue = [];
$subpages = get_subpages ( $pageindex , $env -> page );
foreach ( $subpages as $pagename => $depth )
{
// Make sure that this is an uploaded file
if ( ! $pageindex -> $pagename -> uploadedfile )
continue ;
$mime_type = $pageindex -> $pagename -> uploadedfilemime ;
$previewSize = 300 ;
$previewUrl = " ?action=preview&size= $previewSize &page= " . rawurlencode ( $pagename );
2020-08-06 14:28:24 +00:00
$previewType = substr ( $mime_type , 0 , strpos ( $mime_type , " / " ));
if ( $mime_type == " application/pdf " )
$previewType = " pdf " ;
2016-04-09 08:26:18 +00:00
$previewHtml = " " ;
2020-08-06 14:28:24 +00:00
switch ( $previewType )
2016-04-09 08:26:18 +00:00
{
case " video " :
2020-08-08 00:54:10 +00:00
$previewHtml .= " <video src=' $previewUrl ' controls preload='metadata'> " . $this -> escapeText ( $pagename ) . " </video> \n " ;
2016-04-09 08:26:18 +00:00
break ;
case " audio " :
2020-08-08 00:54:10 +00:00
$previewHtml .= " <audio src=' $previewUrl ' controls preload='metadata'> " . $this -> escapeText ( $pagename ) . " </audio> \n " ;
2016-04-09 08:26:18 +00:00
break ;
2020-08-06 14:28:24 +00:00
case " pdf " :
$previewHtml .= " <object type='application/pdf' data=' $previewUrl ' style='width: { $previewStyle } px;'></object> " ;
break ;
2016-04-09 08:26:18 +00:00
case " application " :
case " image " :
default :
$previewHtml .= " <img src=' $previewUrl ' /> \n " ;
break ;
}
$previewHtml = " <a href='?page= " . rawurlencode ( $pagename ) . " '> $previewHtml $pagename </a> " ;
$variableValue [ $pagename ] = " <li style='min-width: $previewSize " . " px; min-height: $previewSize " . " px;'> $previewHtml </li> " ;
}
if ( count ( $variableValue ) === 0 )
$variableValue [ " default " ] = " <li><em>(No files found)</em></li> \n " ;
$variableValue = implode ( " \n " , $variableValue );
$variableValue = " <ul class='file-gallery'> $variableValue </ul> " ;
break ;
2016-03-20 17:05:16 +00:00
}
if ( isset ( $params [ $variableKey ]))
2020-08-08 00:54:10 +00:00
$variableValue = $this -> escapeText ( $params [ $variableKey ]);
2016-03-20 16:38:36 +00:00
2016-04-09 06:51:29 +00:00
if ( $variableValue !== false )
2016-03-20 16:16:55 +00:00
{
return [
" extent " => strlen ( $matches [ 0 ]),
2020-08-08 00:54:10 +00:00
" element " => [
" name " => " span " ,
" attributes " => [
" class " => " template-var-value "
],
// rawHtml is fine here 'cause we escape above
// Note also that we *must* return some sort of element here: we can't just return rawHtml directly. It needs to be a property of an element.
" rawHtml " => $variableValue
]
2016-03-20 16:16:55 +00:00
];
}
}
else if ( preg_match ( " / \ { \ { ([^}]+) \ } \ }/ " , $fragment [ " text " ], $matches ))
2016-03-20 14:05:55 +00:00
{
$templateElement = $this -> templateHandler ( $matches [ 1 ]);
if ( ! empty ( $templateElement ))
{
return [
" extent " => strlen ( $matches [ 0 ]),
" element " => $templateElement
];
}
}
}
2019-03-02 13:49:12 +00:00
/**
* Handles parsing out templates - recursively - and the parameter stack associated with it .
2019-03-02 21:59:50 +00:00
* @ param string $source The source string to process .
* @ return array The parsed result
2019-03-02 13:49:12 +00:00
*/
2016-03-20 14:05:55 +00:00
protected function templateHandler ( $source )
{
2016-04-08 08:01:12 +00:00
global $pageindex , $env ;
2016-03-20 14:05:55 +00:00
2016-03-20 16:16:55 +00:00
2016-08-22 08:36:22 +00:00
$parts = preg_split ( " / \\ ||¦/ " , trim ( $source , " { } " ));
2016-04-08 08:01:12 +00:00
$parts = array_map ( " trim " , $parts );
2016-03-20 14:05:55 +00:00
2016-04-10 10:49:50 +00:00
// Extract the name of the template page
2016-03-20 14:05:55 +00:00
$templatePagename = array_shift ( $parts );
2023-01-26 20:51:07 +00:00
// If the page that we are supposed to use as the template doesn't
2016-03-20 14:05:55 +00:00
// exist, then there's no point in continuing.
if ( empty ( $pageindex -> $templatePagename ))
return false ;
// Parse the parameters
$params = [];
$i = 0 ;
foreach ( $parts as $part )
{
if ( strpos ( $part , " = " ) !== false )
{
// This param contains an equals sign, so it's a named parameter
$keyValuePair = explode ( " = " , $part , 2 );
2016-04-08 08:01:12 +00:00
$keyValuePair = array_map ( " trim " , $keyValuePair );
2016-03-20 14:05:55 +00:00
$params [ $keyValuePair [ 0 ]] = $keyValuePair [ 1 ];
}
else
{
// This isn't a named parameter
2016-03-20 16:16:55 +00:00
$params [ " $i " ] = trim ( $part );
2016-03-20 14:05:55 +00:00
$i ++ ;
}
}
2016-03-20 16:16:55 +00:00
// Add the parsed parameters to the parameter stack
2016-03-20 17:05:16 +00:00
$this -> paramStack [] = [
" pagename " => $templatePagename ,
" params " => $params
];
2016-03-20 14:05:55 +00:00
2016-04-08 08:01:12 +00:00
$templateFilePath = $env -> storage_prefix . $pageindex -> $templatePagename -> filename ;
2016-03-20 14:05:55 +00:00
2020-08-18 12:49:16 +00:00
// We use linesElements here to avoid resetting $this->DefinitionData (which is done in ->textElements)
// Consequently, we have to do what textElements does directly
$parsedTemplateSource = $this -> linesElements ( explode ( " \n " ,
trim ( str_replace ([ " \r \n " , " \r " ], " \n " , file_get_contents ( $templateFilePath )), " \n " )
));
2023-01-26 20:51:07 +00:00
// Render it out. Important to preserve scope.
$parsedTemplateSource = $this -> elements ( $parsedTemplateSource );
// HACK: Find/replace to ensure variables are inserted inside HTML. Note this does NOTE support special variables inside HTML - only simple ones.
// This would cause issues if we later allow variables to be unset.
foreach ( $params as $param_key => $param_value ) {
$parsedTemplateSource = str_replace ( " { { { " . $param_key . " }}} " , $param_value , $parsedTemplateSource );
}
2016-03-20 14:05:55 +00:00
2016-03-20 16:16:55 +00:00
// Remove the parsed parameters from the stack
array_pop ( $this -> paramStack );
2016-03-20 14:05:55 +00:00
return [
" name " => " div " ,
2023-01-26 20:51:07 +00:00
" element " => [
" rawHtml " => $parsedTemplateSource ,
],
2016-03-20 14:05:55 +00:00
" attributes " => [
" class " => " template "
]
];
2016-03-12 15:26:30 +00:00
}
2020-05-23 01:06:59 +00:00
/*
* ██████ ██ ██ ███████ ██████ ██ ██ ██████ ██████ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ███████ █████ ██ █████ ██████ ██ ██ ███
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██████ ██ ██ ███████ ██████ ██ ██ ██████ ██████ ██ ██
2020-08-07 23:56:16 +00:00
* % checkbox
2020-05-23 01:06:59 +00:00
*/
protected function inlineCheckbox ( $fragment ) {
// We're not interested if it's not at the beginning of a line
if ( strpos ( $fragment [ " context " ], $fragment [ " text " ]) !== 0 )
return ;
// If it doesn't match, then we're not interested
if ( preg_match ( '/\[([ x])\]/u' , $fragment [ " text " ], $matches ) !== 1 )
return ;
$checkbox_content = $matches [ 1 ];
$result = [
" extent " => 3 ,
" element " => [
" name " => " input " ,
" attributes " => [
" type " => " checkbox " ,
" disabled " => " disabled "
]
]
];
if ( $checkbox_content === " x " )
$result [ " element " ][ " attributes " ][ " checked " ] = " checked " ;
return $result ;
}
2020-05-23 11:57:04 +00:00
/*
* ███ ███ █████ ██████ ██ ██ ███████ ██████
* ████ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ████ ██ ███████ ██████ █████ █████ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ███████ ██████
*
* ████████ ███████ ██ ██ ████████
* ██ ██ ██ ██ ██
* ██ █████ ███ ██
* ██ ██ ██ ██ ██
* ██ ███████ ██ ██ ██
2020-08-07 23:56:16 +00:00
* % markedtext
2020-05-23 11:57:04 +00:00
*/
protected function inlineMark ( $fragment ) {
2020-05-23 19:38:01 +00:00
// A question mark makes the PCRE 1-or-more + there lazy instead of greedy.
// Ref https://www.rexegg.com/regex-quantifiers.html#greedytrap
if ( preg_match ( '/==([^=]+?)==/' , $fragment [ " text " ], $matches ) !== 1 )
2020-05-23 11:57:04 +00:00
return ;
$marked_text = $matches [ 1 ];
$result = [
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " mark " ,
" handler " => [
" function " => " lineElements " ,
" argument " => $marked_text ,
" destination " => " elements "
]
]
];
2020-05-23 17:44:55 +00:00
return $result ;
}
/*
* ██████ ██ ██ ██████ ██ ██████ ██ ██ ██████ ███████ ██████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ██ ██████ ██ ███████ ██ ██ ██████ █████ ██████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██████ ██████ ██████ ██ ██████ ██████ ██ ███████ ██ ██
*
* ██████ ██████ ██████ ██ ██████ ████████
* ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ██████ ██ ██████ ██
* ██ ██ ██ ██ ██ ██ ██
* ██████ ██████ ██ ██ ██ ██ ██
2020-08-07 23:56:16 +00:00
* % subsuperscript
2020-05-23 17:44:55 +00:00
*/
protected function inlineSuperscript ( $fragment ) {
2020-05-23 19:38:01 +00:00
if ( preg_match ( '/\^([^^]+?)\^/' , $fragment [ " text " ], $matches ) !== 1 )
2020-05-23 17:44:55 +00:00
return ;
2020-05-23 11:57:04 +00:00
2020-05-23 17:44:55 +00:00
$superscript_text = $matches [ 1 ];
$result = [
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " sup " ,
" handler " => [
" function " => " lineElements " ,
" argument " => $superscript_text ,
" destination " => " elements "
]
]
];
return $result ;
}
protected function inlineSubscript ( $fragment ) {
2020-05-23 19:38:01 +00:00
if ( preg_match ( '/~([^~]+?)~/' , $fragment [ " text " ], $matches ) !== 1 )
2020-05-23 17:44:55 +00:00
return ;
$subscript_text = $matches [ 1 ];
$result = [
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " sub " ,
" handler " => [
" function " => " lineElements " ,
" argument " => $subscript_text ,
" destination " => " elements "
]
]
];
2020-05-23 11:57:04 +00:00
return $result ;
}
2020-05-23 19:38:01 +00:00
/*
* ███████ ██████ ██████ ██ ██ ███████ ██████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██████ ██ ██ ██ ██ █████ ██████
* ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ██████ ██ ███████ ███████ ██ ██
2020-08-07 23:56:16 +00:00
* % spoiler
2020-05-23 19:38:01 +00:00
*/
protected function inlineSpoiler ( $fragment ) {
if ( preg_match ( '/(?:\|\||>!)([^|]+?)(?:\|\||!<)/' , $fragment [ " text " ], $matches ) !== 1 )
return ;
$spoiler_text = $matches [ 1 ];
$id = " spoiler- " . crypto_id ( 24 );
$result = [
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " a " ,
" attributes " => [
" id " => $id ,
" class " => " spoiler " ,
" href " => " # $id "
],
" handler " => [
" function " => " lineElements " ,
" argument " => $spoiler_text ,
" destination " => " elements "
]
]
];
return $result ;
}
2016-08-22 08:40:17 +00:00
/*
* ██ ███ ██ ████████ ███████ ██████ ███ ██ █████ ██
* ██ ████ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██
* ██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ███████ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ████ ██ ███████ ██ ██ ██ ████ ██ ██ ███████
*
* ██ ██ ███ ██ ██ ██ ███████
* ██ ██ ████ ██ ██ ██ ██
* ██ ██ ██ ██ ██ █████ ███████
* ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ██ ████ ██ ██ ███████
2020-08-07 23:56:16 +00:00
* % internallinks
2016-08-22 08:40:17 +00:00
*/
2019-03-02 13:49:12 +00:00
/**
* Parses internal links
* @ param string $fragment The fragment to parse .
*/
2016-03-12 15:26:30 +00:00
protected function inlineInternalLink ( $fragment )
{
2016-08-19 18:39:53 +00:00
global $pageindex , $env ;
2016-04-08 08:28:59 +00:00
2020-05-23 19:40:30 +00:00
if ( preg_match ( '/^\[\[([^\]]*?)\]\]([^\s!?",;.()\[\]{}*=+\/]*)/u' , $fragment [ " text " ], $matches ) === 1 ) {
2019-01-05 17:48:27 +00:00
// 1: Parse parameters out
// -------------------------------
2020-04-21 14:00:54 +00:00
$link_page = str_replace ([ " \r " , " \n " ], [ " " , " " ], trim ( $matches [ 1 ]));
2019-01-05 17:48:27 +00:00
$display = $link_page . trim ( $matches [ 2 ]);
2016-08-22 08:36:22 +00:00
if ( strpos ( $matches [ 1 ], " | " ) !== false || strpos ( $matches [ 1 ], " ¦ " ) !== false )
2016-03-12 15:26:30 +00:00
{
// We have a bar character
2016-08-22 08:36:22 +00:00
$parts = preg_split ( " / \\ ||¦/ " , $matches [ 1 ], 2 );
2019-01-05 17:48:27 +00:00
$link_page = trim ( $parts [ 0 ]); // The page to link to
2016-08-19 09:59:49 +00:00
$display = trim ( $parts [ 1 ]); // The text to display
2016-03-12 15:26:30 +00:00
}
2019-01-05 17:48:27 +00:00
// 2: Parse the hash out
// -------------------------------
$hash_code = " " ;
if ( strpos ( $link_page , " # " ) !== false )
2016-08-19 18:39:53 +00:00
{
// We want to link to a subsection of a page
2019-01-05 17:48:27 +00:00
$hash_code = substr ( $link_page , strpos ( $link_page , " # " ) + 1 );
$link_page = substr ( $link_page , 0 , strpos ( $link_page , " # " ));
2016-08-19 18:39:53 +00:00
2019-01-05 17:48:27 +00:00
// If $link_page is empty then we want to link to the current page
if ( strlen ( $link_page ) === 0 )
$link_page = $env -> page ;
2016-08-19 18:39:53 +00:00
}
2019-01-05 17:48:27 +00:00
// 3: Page name auto-correction
// -------------------------------
$is_interwiki_link = module_exists ( " feature-interwiki-links " ) && is_interwiki_link ( $link_page );
2019-02-10 23:01:01 +00:00
// Try different variants on the pagename to try and get it to
// match something automagically
if ( ! $is_interwiki_link && empty ( $pageindex -> $link_page ))
$link_page = parsedown_pagename_resolve ( $link_page );
2016-08-19 09:59:49 +00:00
2019-01-05 17:48:27 +00:00
// 4: Construct the full url
// -------------------------------
$link_url = null ;
// If it's an interwiki link, then handle it as such
if ( $is_interwiki_link )
$link_url = interwiki_get_pagename_url ( $link_page );
// If it isn't (or it failed), then try it as a normal link instead
if ( empty ( $link_url )) {
$link_url = str_replace (
" %s " , rawurlencode ( $link_page ),
$this -> internalLinkBase
);
// We failed to handle it as an interwiki link, so we should
// tell everyone that
$is_interwiki_link = false ;
}
2016-03-12 15:26:30 +00:00
2019-02-10 23:01:01 +00:00
// 5: Construct the title
// -------------------------------
$title = $link_page ;
if ( $is_interwiki_link )
$title = interwiki_pagename_resolve ( $link_page ) -> name . " : " . interwiki_pagename_parse ( $link_page )[ 1 ] . " (Interwiki) " ;
2019-01-05 17:48:27 +00:00
if ( strlen ( $hash_code ) > 0 )
$link_url .= " # $hash_code " ;
2016-08-19 18:39:53 +00:00
2019-01-05 21:44:24 +00:00
2019-01-05 17:48:27 +00:00
// 6: Result encoding
// -------------------------------
2016-04-08 08:28:59 +00:00
$result = [
2016-03-12 15:26:30 +00:00
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " a " ,
" text " => $display ,
2019-02-10 23:01:01 +00:00
2016-03-12 15:26:30 +00:00
" attributes " => [
2019-02-10 23:01:01 +00:00
" href " => $link_url ,
" title " => $title
2016-03-12 15:26:30 +00:00
]
]
];
2016-04-08 08:28:59 +00:00
2019-01-05 17:48:27 +00:00
// Attach some useful classes based on how we handled it
$class_list = [];
// Interwiki links can never be redlinks
if ( ! $is_interwiki_link && empty ( $pageindex -> { makepathsafe ( $link_page )}))
$class_list [] = " redlink " ;
if ( $is_interwiki_link )
$class_list [] = " interwiki_link " ;
$result [ " element " ][ " attributes " ][ " class " ] = implode ( " " , $class_list );
2016-04-08 08:28:59 +00:00
return $result ;
2016-03-12 15:26:30 +00:00
}
}
2021-04-11 20:47:41 +00:00
/*
███ █████ ███ ██ ██ ██████ ███ ███ ██████ ██
██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ██
██ ███████ ██ ██ ██ ██████ ██ ████ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
███ ██ ██ ███ ██ ██ ██ ██████ ██ ██ ██ ██████ ██
*/
// This wraps the native [display text](url) syntax
protected function inlineLink ( $fragment ) {
global $settings ;
// If this feature is disabled, defer to the parent implementation unconditionally
if ( ! $settings -> parser_mangle_external_links )
return parent :: inlineLink ( $fragment );
// Extract the URL from the internal link. If it fails defer to the parent function
// 1 = display text, 2 = url
if ( preg_match ( " /^ \ [([^[ \ ]]+?) \ ] \ ( \ s*([^()]+) \ s* \ )/ " , $fragment [ " text " ], $matches ) !== 1 )
return parent :: inlineLink ( $fragment );
// Check the URL. If it doesn't match our *exact* format, then it doesn't happen.
if ( ! is_string ( $matches [ 2 ]) || preg_match ( " /^ \ . \ /(.+) \ .md $ / " , $matches [ 2 ], $matches_url ) !== 1 )
return parent :: inlineLink ( $fragment );
// The page name is made safe when Pepperminty Wiki does initial consistency checks (if it's unsafe it results in a 301 redirect)
$page_name = parsedown_pagename_resolve ( $matches_url [ 1 ]);
$internal_link_text = " [[ ${ page_name } ]] " ;
if ( ! empty ( $matches [ 1 ])) // If the display text isn't empty, then respect it
$internal_link_text = " [[ ${ page_name}¦{$matches[1] } ]] " ;
$result = $this -> inlineInternalLink ([
" text " => $internal_link_text
]);
$result [ " extent " ] = strlen ( $fragment [ " text " ]); // Parsedown isn't mb_ friendly
return $result ;
}
2016-08-22 08:40:17 +00:00
/*
* ███████ ██ ██ ████████ ███████ ███ ██ ██████ ███████ ██████
* ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██
* █████ ███ ██ █████ ██ ██ ██ ██ ██ █████ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ██ ██ ███████ ██ ████ ██████ ███████ ██████
*
* ██ ███ ███ █████ ██████ ███████ ███████
* ██ ████ ████ ██ ██ ██ ██ ██
* ██ ██ ████ ██ ███████ ██ ███ █████ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██ ██ ██ ██████ ███████ ███████
2020-08-07 23:56:16 +00:00
* % extendedimages
2016-08-22 08:40:17 +00:00
*/
2019-03-02 13:49:12 +00:00
/**
* Parses the extended image syntax .
* @ param string $fragment The source fragment to parse .
*/
2016-03-12 15:26:30 +00:00
protected function inlineExtendedImage ( $fragment )
{
2016-05-30 09:54:09 +00:00
global $pageindex ;
2016-08-21 11:00:40 +00:00
2016-08-22 10:14:49 +00:00
if ( preg_match ( '/^!\[(.*)\]\(([^|¦)]+)\s*(?:(?:\||¦)([^|¦)]*))?(?:(?:\||¦)([^|¦)]*))?(?:(?:\||¦)([^)]*))?\)/' , $fragment [ " text " ], $matches ))
2015-10-04 14:20:47 +00:00
{
2016-03-12 15:26:30 +00:00
/*
* 0 - Everything
* 1 - Alt text
* 2 - Url
2016-04-10 10:49:50 +00:00
* 3 - First param ( optional )
2016-05-29 19:34:34 +00:00
* 4 - Second param ( optional )
* 5 - Third param ( optional )
2016-03-12 15:26:30 +00:00
*/
$altText = $matches [ 1 ];
2016-10-11 18:26:42 +00:00
$imageUrl = trim ( str_replace ( " & " , " & " , $matches [ 2 ])); // Decode & to allow it in preview urls
2016-05-29 19:34:34 +00:00
$param1 = empty ( $matches [ 3 ]) ? false : strtolower ( trim ( $matches [ 3 ]));
2016-03-12 15:26:30 +00:00
$param2 = empty ( $matches [ 4 ]) ? false : strtolower ( trim ( $matches [ 4 ]));
2016-05-29 19:34:34 +00:00
$param3 = empty ( $matches [ 5 ]) ? false : strtolower ( trim ( $matches [ 5 ]));
2016-03-12 15:26:30 +00:00
$floatDirection = false ;
$imageSize = false ;
2016-05-29 19:34:34 +00:00
$imageCaption = false ;
2016-10-11 18:44:01 +00:00
$shortImageUrl = false ;
2016-03-12 15:26:30 +00:00
if ( $this -> isFloatValue ( $param1 ))
{
2016-04-08 08:01:12 +00:00
// Param 1 is a valid css float: ... value
2016-03-12 15:26:30 +00:00
$floatDirection = $param1 ;
$imageSize = $this -> parseSizeSpec ( $param2 );
}
else if ( $this -> isFloatValue ( $param2 ))
{
2016-04-08 08:01:12 +00:00
// Param 2 is a valid css float: ... value
2016-03-12 15:26:30 +00:00
$floatDirection = $param2 ;
$imageSize = $this -> parseSizeSpec ( $param1 );
}
2016-05-29 19:34:34 +00:00
else if ( $this -> isFloatValue ( $param3 ))
{
$floatDirection = $param3 ;
$imageSize = $this -> parseSizeSpec ( $param1 );
}
2016-04-08 08:01:12 +00:00
else if ( $param1 === false and $param2 === false )
{
// Neither params were specified
$floatDirection = false ;
$imageSize = false ;
}
2016-03-12 15:26:30 +00:00
else
{
2016-04-08 08:01:12 +00:00
// Neither of them are floats, but at least one is specified
// This must mean that the first param is a size spec like
// 250x128.
2016-03-12 15:26:30 +00:00
$imageSize = $this -> parseSizeSpec ( $param1 );
}
2015-10-04 14:20:47 +00:00
2016-06-03 08:32:26 +00:00
if ( $param1 !== false && strtolower ( trim ( $param1 )) == " caption " )
2016-05-29 19:34:34 +00:00
$imageCaption = true ;
2020-04-21 20:56:31 +00:00
if ( $param2 !== false && strtolower ( trim ( $param2 )) == " caption " )
$imageCaption = true ;
2016-05-29 19:34:34 +00:00
if ( $param3 !== false && strtolower ( trim ( $param3 )) == " caption " )
$imageCaption = true ;
2016-08-22 16:24:22 +00:00
//echo("Image url: $imageUrl, Pageindex entry: " . var_export(isset($pageindex->$imageUrl), true) . "\n");
2016-05-30 09:54:09 +00:00
if ( isset ( $pageindex -> $imageUrl ) and $pageindex -> $imageUrl -> uploadedfile )
{
// We have a short url! Expand it.
2016-10-11 18:44:01 +00:00
$shortImageUrl = $imageUrl ;
2020-08-06 14:28:24 +00:00
$imageUrl = " index.php?action=preview " ;
if ( $imageSize !== false ) $imageUrl .= " &size= " . max ( $imageSize [ " x " ], $imageSize [ " y " ]);
else $imageUrl .= " &size=original " ;
// This has to be last in order for the extension auto-detection to work correctly
$imageUrl .= " &page= " . rawurlencode ( $shortImageUrl );
2016-05-30 09:54:09 +00:00
}
2016-03-12 15:26:30 +00:00
$style = " " ;
if ( $imageSize !== false )
2016-03-12 18:32:58 +00:00
$style .= " max-width: " . $imageSize [ " x " ] . " px; max-height: " . $imageSize [ " y " ] . " px; " ;
2016-03-12 15:26:30 +00:00
if ( $floatDirection )
$style .= " float: $floatDirection ; " ;
2015-10-04 14:20:47 +00:00
2016-04-08 08:01:12 +00:00
$urlExtension = pathinfo ( $imageUrl , PATHINFO_EXTENSION );
$urlType = system_extension_mime_type ( $urlExtension );
2020-08-06 14:28:24 +00:00
$embed_type = substr ( $urlType , 0 , strpos ( $urlType , " / " ));
if ( $urlType == " application/pdf " ) $embed_type = " pdf " ;
2022-02-07 02:46:47 +00:00
// Check if the URL is a recognised external provider
$ext_provider_result = $this -> __make_embed ( $imageUrl , $altText );
if ( $ext_provider_result !== null ) $embed_type = " ext_provider " ;
2016-05-29 19:34:34 +00:00
$result = [];
2020-08-06 14:28:24 +00:00
switch ( $embed_type )
2016-04-08 08:01:12 +00:00
{
2020-08-06 14:28:24 +00:00
case " pdf " :
$imageUrl = preg_replace ( " /&size=[0-9]+/ " , " &size=original " , $imageUrl );
if ( $imageSize === false ) $style .= " width: 100%; " ;
else $style = str_replace ( " max-width " , " width " , $style );
$result = [
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " object " ,
" text " => " " ,
" attributes " => [
" type " => " application/pdf " ,
" data " => $imageUrl ,
" style " => trim ( $style )
]
]
];
break ;
2022-02-07 02:46:47 +00:00
case " ext_provider " :
$result = [
" extent " => strlen ( $matches [ 0 ]),
" element " => $ext_provider_result
];
break ;
2016-04-08 08:01:12 +00:00
case " audio " :
2016-05-29 19:34:34 +00:00
$result = [
2016-04-08 08:01:12 +00:00
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " audio " ,
" text " => $altText ,
" attributes " => [
" src " => $imageUrl ,
" controls " => " controls " ,
" preload " => " metadata " ,
" style " => trim ( $style )
]
]
];
2016-05-29 19:34:34 +00:00
break ;
2016-04-08 08:01:12 +00:00
case " video " :
2016-05-29 19:34:34 +00:00
$result = [
2016-04-08 08:01:12 +00:00
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " video " ,
" text " => $altText ,
" attributes " => [
" src " => $imageUrl ,
" controls " => " controls " ,
" preload " => " metadata " ,
" style " => trim ( $style )
]
]
];
2016-05-29 19:34:34 +00:00
break ;
2016-04-08 08:01:12 +00:00
case " image " :
default :
// If we can't work out what it is, then assume it's an image
2016-05-29 19:34:34 +00:00
$result = [
2016-04-08 08:01:12 +00:00
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " img " ,
" attributes " => [
" src " => $imageUrl ,
" alt " => $altText ,
2016-04-10 10:49:50 +00:00
" title " => $altText ,
2016-04-08 08:01:12 +00:00
" style " => trim ( $style )
]
]
];
2016-05-29 19:34:34 +00:00
break ;
2016-04-08 08:01:12 +00:00
}
2016-05-29 19:34:34 +00:00
2016-10-11 18:44:01 +00:00
// ~ Image linker ~
$imageHref = $shortImageUrl !== false ? " ?page= " . rawurlencode ( $shortImageUrl ) : $imageUrl ;
2020-08-06 14:28:24 +00:00
if ( $embed_type !== " pdf " ) {
$result [ " element " ] = [
" name " => " a " ,
" attributes " => [
" href " => $imageHref
],
" text " => [ $result [ " element " ]],
" handler " => " elements "
];
}
2016-10-11 18:44:01 +00:00
// ~
2019-10-20 15:44:32 +00:00
if ( $imageCaption ) {
2018-04-05 08:47:29 +00:00
$rawStyle = $result [ " element " ][ " text " ][ 0 ][ " attributes " ][ " style " ];
2016-05-29 19:34:34 +00:00
$containerStyle = preg_replace ( '/^.*float/' , " float " , $rawStyle );
$mediaStyle = preg_replace ( '/\s*float.*;/' , " " , $rawStyle );
$result [ " element " ] = [
" name " => " figure " ,
2016-10-11 18:44:01 +00:00
" attributes " => [
" style " => $containerStyle
],
2016-05-29 19:34:34 +00:00
" text " => [
$result [ " element " ],
[
" name " => " figcaption " ,
2020-08-18 12:49:16 +00:00
// We use lineElements here because of issue #209 - in short it makes it appear as part of the same document to avoid breaking footnotes
// Specifically ->text() calls ->textElements(), which resets $this->DefinitionData, which is used to hold information about footnotes
// lineElements = inline text, and linesElements = multiline text
" handler " => [
" function " => " lineElements " ,
" argument " => $altText ,
" destination " => " elements "
]
2016-05-29 19:34:34 +00:00
],
],
" handler " => " elements "
];
$result [ " element " ][ " text " ][ 0 ][ " attributes " ][ " style " ] = $mediaStyle ;
}
return $result ;
2015-10-04 14:20:47 +00:00
}
2016-03-12 15:26:30 +00:00
}
2022-02-07 02:46:47 +00:00
/**
* Makes an embed HTML tree for a given URL .
* Example URLs include YouTube links , Vimeo , etc .
* @ param string $url The URL to generate an embed for .
* @ return array | null The embed HTML tree , or null if generation failed ( e . g . if the URL wasn ' t recognised ) .
*/
protected function __make_embed ( string $url_text , string $alt_text ) {
// 1: URL parsing
$url = parse_url ( $url_text );
$url [ " host " ] = preg_replace ( " /^www \ ./ " , " " , $url [ " host " ] ? ? " " );
parse_str ( $url [ " query " ] ? ? " " , $query );
// 2: Pre-generation transforms
switch ( $url [ " host " ]) {
case " player.vimeo.com " :
$url [ " host " ] = " vimeo.com " ;
$url [ " path " ] = preg_replace ( " /^ \ /video/ " , " " , $url [ " path " ]);
break ;
case " youtu.be " :
$query [ " v " ] = preg_replace ( " /^ \ // " , " " , $url [ " path " ]);
$url [ " host " ] = " youtube.com " ;
$url [ " path " ] = " /watch " ;
break ;
}
// 3: Actual embed generation
switch ( $url [ " host " ]) {
case " youtube.com " :
if ( $url [ " path " ] !== " /watch " || empty ( $query [ " v " ]))
return null ;
return [
" name " => " iframe " ,
" attributes " => [
" src " => " https://www.youtube.com/embed/ { $query [ " v " ] } " ,
" frameborder " => " 0 " ,
" allow " => " fullscreen; encrypted-media; picture-in-picture "
],
" text " => " YouTube: $alt_text "
];
break ;
case " vimeo.com " :
if ( strlen ( $url [ " path " ]) <= 1 || preg_match ( " /[^0-9 \ /]/ " , $url [ " path " ]) === 1 ) return null ;
return [
" name " => " iframe " ,
" attributes " => [
" src " => " https://player.vimeo.com/video { $url [ " path " ] } " ,
" frameborder " => " 0 " ,
" allow " => " fullscreen; picture-in-picture "
],
" text " => " Vimeo: $alt_text "
];
break ;
default :
return null ;
}
}
2019-10-20 23:31:18 +00:00
/*
* ██████ ██████ ██████ ███████ ██████ ██ ██████ ██████ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██ ██ ██ █████ ██████ ██ ██ ██ ██ █████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██████ ██████ ██████ ███████ ██████ ███████ ██████ ██████ ██ ██
*
* ██ ██ ██████ ██████ ██████ █████ ██████ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██████ ██ ███ ██████ ███████ ██ ██ █████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██████ ██ ██████ ██ ██ ██ ██ ██████ ███████
2020-08-07 23:56:16 +00:00
* % codeblockupgrade
2019-10-20 23:31:18 +00:00
*/
2019-10-20 17:05:57 +00:00
protected function blockFencedCodeComplete ( $block ) {
2019-10-20 19:54:50 +00:00
global $settings ;
2019-10-20 17:05:57 +00:00
$result = parent :: blockFencedCodeComplete ( $block );
2019-10-20 19:54:50 +00:00
2020-06-04 00:42:29 +00:00
// Default value: "text"
$language = preg_replace ( " /^language-/ " , " " , $block [ " element " ][ " element " ][ " attributes " ][ " class " ] ? ? " language-text " );
2019-10-20 19:54:50 +00:00
if ( ! isset ( $settings -> parser_ext_renderers -> $language ))
return $result ;
$text = $result [ " element " ][ " element " ][ " text " ];
$renderer = $settings -> parser_ext_renderers -> $language ;
$result [ " element " ] = [
2019-10-24 23:16:08 +00:00
" name " => " p " ,
" element " => [
" name " => " img " ,
" attributes " => [
" alt " => " Diagram rendered by { $renderer -> name } " ,
2019-10-25 11:12:57 +00:00
" src " => " ?action=parsedown-render-ext&language= " . rawurlencode ( $language ) . " &immutable_key= " . hash ( " crc32b " , json_encode ( $renderer )) . " &source= " . rawurlencode ( $text )
2019-10-24 23:16:08 +00:00
]
2019-10-20 19:54:50 +00:00
]
];
2019-10-25 11:12:57 +00:00
if ( ! empty ( $renderer -> output_classes ))
$result [ " element " ][ " element " ][ " attributes " ][ " class " ] = implode ( " " , $renderer -> output_classes );
2019-10-20 19:54:50 +00:00
2019-10-20 17:05:57 +00:00
return $result ;
}
2019-10-20 15:44:32 +00:00
/*
* ██ ██ ███████ █████ ██████ ███████ ██████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ █████ ███████ ██ ██ █████ ██████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ███████ ██ ██ ██████ ███████ ██ ██
2020-08-07 23:56:16 +00:00
* % header % toc
2019-10-20 15:44:32 +00:00
*/
private $headingIdsUsed = [];
2020-05-23 21:28:33 +00:00
private $tableOfContents = [];
/**
* Inserts an item into the table of contents .
* @ param int $level The level to insert it at ( valid values : 1 - 6 )
* @ param string $id The id of the item .
* @ param string $text The text to display .
*/
protected function addTableOfContentsEntry ( int $level , string $id , string $text ) : void {
$new_obj = ( object ) [
" level " => $level ,
" id " => $id ,
" text " => $text ,
" children " => []
];
if ( count ( $this -> tableOfContents ) == 0 ) {
$this -> tableOfContents [] = $new_obj ;
return ;
}
$lastEntry = end ( $this -> tableOfContents );
if ( $level > $lastEntry -> level ) {
$this -> insertTableOfContentsObject ( $new_obj , $lastEntry );
return ;
}
$this -> tableOfContents [] = $new_obj ;
return ;
}
private function insertTableOfContentsObject ( object $obj , object $target ) {
if ( $obj -> level - 1 > $target -> level && ! empty ( $target -> children )) {
$this -> insertTableOfContentsObject ( $obj , end ( $target -> children ));
2022-02-18 23:59:42 +00:00
return ;
2020-05-23 21:28:33 +00:00
}
$target -> children [] = $obj ;
}
protected function generateTableOfContents () : string {
global $settings ;
2022-03-12 02:52:53 +00:00
2020-05-23 21:28:33 +00:00
$elements = [ $this -> generateTableOfContentsElement ( $this -> tableOfContents ) ];
if ( $settings -> parser_toc_heading_level > 1 )
array_unshift (
$elements ,
2020-05-23 22:00:46 +00:00
[ " name " => " h $settings->parser_toc_heading_level " , " text " => " Table of Contents " , " attributes " => [ " id " => " table-of-contents " ] ]
2020-05-23 21:28:33 +00:00
);
return trim ( $this -> elements ( $elements ), " \n " );
}
private function generateTableOfContentsElement ( $toc ) : array {
$elements = [];
foreach ( $toc as $entry ) {
$next = [
" name " => " li " ,
" attributes " => [
" data-level " => $entry -> level
],
" elements " => [ [
" name " => " a " ,
" attributes " => [
" href " => " # $entry->id "
],
" handler " => [
" function " => " lineElements " ,
" argument " => $entry -> text ,
" destination " => " elements "
]
] ]
];
if ( isset ( $entry -> children ))
$next [ " elements " ][] = $this -> generateTableOfContentsElement ( $entry -> children );
$elements [] = $next ;
}
return [
" name " => " ul " ,
" elements " => $elements
];
}
2019-10-20 15:44:32 +00:00
protected function blockHeader ( $line ) {
// This function overrides the header function defined in ParsedownExtra
$result = parent :: blockHeader ( $line );
// If this heading doesn't have an id already, add an automatic one
if ( ! isset ( $result [ " element " ][ " attributes " ][ " id " ])) {
$heading_id = str_replace ( " " , " - " ,
mb_strtolower ( makepathsafe (
$result [ " element " ][ " handler " ][ " argument " ]
))
);
$suffix = " " ;
while ( in_array ( $heading_id . $suffix , $this -> headingIdsUsed )) {
$heading_number = intval ( str_replace ( " _ " , " " , $suffix ));
if ( $heading_number == 0 ) $heading_number ++ ;
$suffix = " _ " . ( $heading_number + 1 );
}
$result [ " element " ][ " attributes " ][ " id " ] = $heading_id . $suffix ;
$this -> headingIdsUsed [] = $result [ " element " ][ " attributes " ][ " id " ];
}
2020-05-23 21:28:33 +00:00
$this -> addTableOfContentsEntry (
intval ( strtr ( $result [ " element " ][ " name " ], [ " h " => " " ])),
$result [ " element " ][ " attributes " ][ " id " ],
$result [ " element " ][ " handler " ][ " argument " ]
);
return $result ;
}
/*
* Inserts a special string to identify where we need to put the table of contents later
*/
protected function blockTableOfContents ( $fragment ) {
// Indent? Don't even want to know
if ( $fragment [ " indent " ] > 0 ) return ;
// If it doesn't match, then we're not interested
2020-05-23 21:30:12 +00:00
if ( preg_match ( '/^\[_*(?:TOC|toc)_*\]$/u' , $fragment [ " text " ], $matches ) !== 1 )
2020-05-23 21:28:33 +00:00
return ;
$result = [
" element " => [ " text " => self :: TOC_ID ]
];
2019-10-20 15:44:32 +00:00
return $result ;
}
2022-03-17 03:20:21 +00:00
/*
* ██████ ███ ██ ███████ ██████ ██████ ██ ██ ██ ███ ██ ██████
* ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
* ██ ██ ██ ██ ██ █████ ██████ ██ ██ ███ ██ ██ ██ ██ ██ ███
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██████ ██ ████ ███████ ██████ ██████ ██ ██ ██ ██ ████ ██████
*/
protected function blockOneBox ( $fragment , $current_block ) {
2022-03-12 02:52:53 +00:00
global $env , $settings , $pageindex ;
2022-08-03 00:07:00 +00:00
// error_log("FRAGMENT ".var_export($fragment, true));
2022-03-12 02:52:53 +00:00
if ( $fragment [ " indent " ] > 0 || ! $settings -> parser_onebox_enabled ) return ;
2022-03-17 03:20:21 +00:00
if ( preg_match ( '/^\[\[\[([^\]]*?)\]\]\]$/u' , $fragment [ " text " ], $matches ) !== 1 )
2022-03-12 02:52:53 +00:00
return ;
// 1: Parse parameters out
// -------------------------------
$link_page = trim ( str_replace ([ " \r " , " \n " ], [ " " , " " ], $matches [ 1 ]));
if ( empty ( $pageindex -> $link_page )) return ;
$link_page_content = file_get_contents ( $env -> storage_prefix . $pageindex -> $link_page -> filename );
$preview = $link_page_content ;
if ( mb_strlen ( $preview ) > $settings -> parser_onebox_preview_length )
$preview = mb_substr ( $preview , 0 , $settings -> parser_onebox_preview_length ) . " … " ;
// 2: Generate onebox
// -------------------------------
$result = [
" element " => [
" name " => " a " ,
" attributes " => [
" class " => " onebox " ,
" href " => " ?page= " . rawurlencode ( $link_page )
],
" elements " => [
[
" name " => " div " ,
" attributes " => [ " class " => " onebox-header " ],
" text " => $link_page
],
[
" name " => " div " ,
" attributes " => [ " class " => " onebox-preview " ],
" text " => $preview
]
]
]
];
return $result ;
}
2018-04-26 22:14:35 +00:00
# ~
# Static Methods
# ~
2019-03-02 13:49:12 +00:00
/**
* Extracts the page names from internal links in a given markdown source .
* Does not actually _parse_ the source - only extracts via a regex .
* @ param string $page_text The source text to extract a list of page names from .
* @ return array A list of page names that the given source text links to .
*/
2018-04-26 22:14:35 +00:00
public static function extract_page_names ( $page_text ) {
global $pageindex ;
2022-04-24 13:38:21 +00:00
preg_match_all ( " / \ [ \ [([^ \ ]]+) \ ] \ ]/u " , $page_text , $linked_pages );
2018-04-26 22:14:35 +00:00
if ( count ( $linked_pages [ 1 ]) === 0 )
return []; // No linked pages here
$result = [];
foreach ( $linked_pages [ 1 ] as $linked_page ) {
// Strip everything after the | and the #
2022-04-24 13:38:21 +00:00
$linked_page = preg_replace ( " /[|¦#].*/u " , " " , $linked_page );
2018-04-26 22:14:35 +00:00
if ( strlen ( $linked_page ) === 0 )
continue ;
// Make sure we try really hard to find this page in the
// pageindex
$altered_linked_page = $linked_page ;
if ( ! empty ( $pageindex -> { ucfirst ( $linked_page )}))
$altered_linked_page = ucfirst ( $linked_page );
else if ( ! empty ( $pageindex -> { ucwords ( $linked_page )}))
$altered_linked_page = ucwords ( $linked_page );
else // Our efforts were in vain, so reset to the original
$altered_linked_page = $linked_page ;
$result [] = $altered_linked_page ;
}
return $result ;
}
2019-10-20 15:44:32 +00:00
/*
* ██ ██ ████████ ██ ██ ██ ████████ ██ ███████ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ █████ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██████ ██ ██ ███████ ██ ██ ██ ███████ ███████
2020-08-07 23:56:16 +00:00
* % utilities
2019-10-20 15:44:32 +00:00
*/
2016-03-20 16:16:55 +00:00
2019-03-02 13:49:12 +00:00
/**
* Returns whether a string is a valid float : XXXXXX ; value .
* Used in parsing the extended image syntax .
* @ param string $value The value check .
* @ return bool Whether it ' s valid or not .
*/
private function isFloatValue ( string $value )
2016-03-12 15:26:30 +00:00
{
return in_array ( strtolower ( $value ), [ " left " , " right " ]);
}
2019-03-02 13:49:12 +00:00
/**
* Parses a size specifier into an array .
* @ param string $text The source text to parse . e . g . " 256x128 "
* @ return array | bool The parsed size specifier . Example : [ " x " => 256 , " y " => 128 ] . Returns false if parsing failed .
*/
private function parseSizeSpec ( string $text )
2016-03-12 15:26:30 +00:00
{
if ( strpos ( $text , " x " ) === false )
return false ;
$parts = explode ( " x " , $text , 2 );
2015-10-04 14:20:47 +00:00
2016-03-12 15:26:30 +00:00
if ( count ( $parts ) != 2 )
return false ;
2015-10-04 14:20:47 +00:00
2016-03-12 15:26:30 +00:00
array_map ( " trim " , $parts );
array_map ( " intval " , $parts );
if ( in_array ( 0 , $parts ))
return false ;
return [
" x " => $parts [ 0 ],
" y " => $parts [ 1 ]
];
}
2019-03-02 13:49:12 +00:00
/**
* Escapes the source text via htmlentities .
* @ param string $text The text to escape .
* @ return string The escaped string .
*/
2016-03-20 16:42:21 +00:00
protected function escapeText ( $text )
{
return htmlentities ( $text , ENT_COMPAT | ENT_HTML5 );
}
2016-03-12 15:26:30 +00:00
/**
* Sets the base url to be used for internal links . '%s' will be replaced
* with a URL encoded version of the page name .
* @ param string $url The url to use when parsing internal links .
*/
public function setInternalLinkBase ( $url )
{
$this -> internalLinkBase = $url ;
}
2015-10-04 12:37:12 +00:00
}
?>