2015-10-04 12:37:12 +00:00
< ? php
register_module ([
" name " => " Parsedown " ,
2016-04-09 09:55:44 +00:00
" version " => " 0.7.1 " ,
2016-03-19 18:24:52 +00:00
" author " => " Emanuil Rusev & Starbeamrainbowlabs " ,
" 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 a some weight to your installation, and also *requires* write access to the disk on first load. " ,
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 " );
add_parser ( " parsedown " , function ( $source ) use ( $parser ) {
2016-04-08 20:05:42 +00:00
global $settings ;
if ( $settings -> clean_raw_html )
$parser -> setMarkupEscaped ( true );
else
$parser -> setMarkupEscaped ( false );
2016-03-12 15:26:30 +00:00
$result = $parser -> text ( $source );
2015-10-04 13:04:42 +00:00
return $result ;
2015-10-04 12:37:12 +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>
< 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 >
< tr >< td >< code > [[ Display Text | Internal link ]] </ code ></ td >< td >< a href = '?page=Internal%20link' > Display Text </ a ></ td >< td > An internal link with some display text .</ td ></ tr >
< 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 >
2016-03-20 17:57:47 +00:00
</ table >
< 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 >
< 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>
< tr >< td >< code > {{{ ~ }}} </ code ></ td >< td > Outputs the requested pagee ' s name .</ td ></ tr >
< 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 >
</ table > " );
2015-10-04 12:37:12 +00:00
}
]);
2016-03-12 15:26:30 +00:00
/*** Parsedown versions ***
* Parsedown Core : 1.6 . 0 *
* Parsedown Extra : 0.7 . 0 *
**************************/
$env -> parsedown_paths = new stdClass ();
$env -> parsedown_paths -> parsedown = " https://cdn.rawgit.com/erusev/parsedown/3ebbd730b5c2cf5ce78bc1bf64071407fc6674b7/Parsedown.php " ;
$env -> parsedown_paths -> parsedown_extra = " https://cdn.rawgit.com/erusev/parsedown-extra/11a44e076d02ffcc4021713398a60cd73f78b6f5/ParsedownExtra.php " ;
// Download parsedown and parsedown extra if they don't already exist
if ( ! file_exists ( " ./Parsedown.php " ) || filesize ( " ./Parsedown.php " ) === 0 )
file_put_contents ( " ./Parsedown.php " , fopen ( $env -> parsedown_paths -> parsedown , " r " ));
if ( ! file_exists ( " ./ParsedownExtra.php " ) || filesize ( " ./ParsedownExtra.php " ) === 0 )
file_put_contents ( " ./ParsedownExtra.php " , fopen ( $env -> parsedown_paths -> parsedown_extra , " r " ));
2015-10-04 12:37:12 +00:00
2016-03-12 15:26:30 +00:00
require_once ( " ./Parsedown.php " );
require_once ( " ./ParsedownExtra.php " );
2015-10-04 12:37:12 +00:00
2015-12-26 12:55:19 +00:00
/*
2016-03-12 15:26:30 +00:00
* ██████ █████ ██████ ███████ ███████ ██████ ██████ ██ ██ ███ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██
* ██████ ███████ ██████ ███████ █████ ██ ██ ██ ██ ██ █ ██ ██ ██ ██
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██
* ██ ██ ██ ██ ██ ███████ ███████ ██████ ██████ ███ ███ ██ ████
*
* ███████ ██ ██ ████████ ███████ ███ ██ ███████ ██ ██████ ███ ██ ███████
* ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ████ ██ ██
* █████ ███ ██ █████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ███████
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ███████ ██ ██ ██ ███████ ██ ████ ███████ ██ ██████ ██ ████ ███████
*/
class PeppermintParsedown extends ParsedownExtra
2015-10-04 12:37:12 +00:00
{
2016-03-12 15:26:30 +00:00
private $internalLinkBase = " ./%s " ;
2016-03-20 16:16:55 +00:00
protected $maxParamDepth = 0 ;
protected $paramStack = [];
2016-03-12 15:26:30 +00:00
function __construct ()
{
// Prioritise our internal link parsing over the regular link parsing
array_unshift ( $this -> InlineTypes [ " [ " ], " InternalLink " );
// Prioritise our image parser over the regular image parser
array_unshift ( $this -> InlineTypes [ " ! " ], " ExtendedImage " );
2015-10-04 14:20:47 +00:00
2016-03-20 14:05:55 +00:00
$this -> inlineMarkerList .= " { " ;
2016-03-25 17:52:32 +00:00
if ( ! isset ( $this -> InlineTypes [ " { " ]) or ! is_array ( $this -> InlineTypes [ " { " ]))
2016-03-20 14:05:55 +00:00
$this -> InlineTypes [ " { " ] = [];
$this -> InlineTypes [ " { " ][] = " Template " ;
}
2016-04-09 06:51:29 +00:00
/*
* ████████ ███████ ███ ███ ██████ ██ █████ ████████ ██ ███ ██ ██████
* ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██
* ██ █████ ██ ████ ██ ██████ ██ ███████ ██ ██ ██ ██ ██ ██ ███
* ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
* ██ ███████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ████ ██████
*/
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 ];
$params = ! empty ( $stackEntry ) ? $stackEntry [ " params " ] : false ;
}
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 ;
2016-03-20 17:05:16 +00:00
switch ( $variableKey )
{
2016-04-09 09:55:44 +00:00
case " @ " : // Lists all variables and their values
2016-03-20 17:05:16 +00:00
if ( ! empty ( $params ))
{
$variableValue = " <table>
< tr >< th > Key </ th >< th > Value </ th ></ tr > \n " ;
foreach ( $params as $key => $value )
{
$variableValue .= " \t <tr><td> " . $this -> escapeText ( $key ) . " </td><td> " . $this -> escapeText ( $value ) . " </td></tr> \n " ;
}
$variableValue .= " </table> " ;
}
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 " ;
foreach ( $this -> paramStack as $curStackEntry )
{
$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 );
$previewHtml = " " ;
switch ( substr ( $mime_type , 0 , strpos ( $mime_type , " / " )))
{
case " video " :
$previewHtml .= " <video src=' $previewUrl ' controls preload='metadata'> $pagename </video> \n " ;
break ;
case " audio " :
$previewHtml .= " <audio src=' $previewUrl ' controls preload='metadata'> $pagename </audio> \n " ;
break ;
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 ]))
2016-03-20 16:42:21 +00:00
{
2016-03-20 17:05:16 +00:00
$variableValue = $params [ $variableKey ];
2016-03-20 16:42:21 +00:00
$variableValue = $this -> escapeText ( $variableValue );
}
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 ]),
2016-03-20 16:38:36 +00:00
" markup " => $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
];
}
}
}
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-03-20 14:05:55 +00:00
$parts = explode ( " | " , 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 );
// If the page that we are supposed to use as the tempalte doesn't
// exist, then there's no point in continuing.
if ( empty ( $pageindex -> $templatePagename ))
return false ;
// Parse the parameters
2016-03-20 16:16:55 +00:00
$this -> maxParamDepth ++ ;
2016-03-20 14:05:55 +00:00
$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
$parsedTemplateSource = $this -> text ( file_get_contents ( $templateFilePath ));
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 " ,
" text " => $parsedTemplateSource ,
" attributes " => [
" class " => " template "
]
];
2016-03-12 15:26:30 +00:00
}
protected function inlineInternalLink ( $fragment )
{
2016-04-08 08:28:59 +00:00
global $pageindex ;
if ( preg_match ( '/^\[\[([^\]]*)\]\]/' , $fragment [ " text " ], $matches ))
2016-03-12 15:26:30 +00:00
{
$display = $linkPage = $matches [ 1 ];
if ( strpos ( $matches [ 1 ], " | " ))
{
// We have a bar character
$parts = explode ( " | " , $matches [ 1 ], 2 );
$linkPage = $parts [ 0 ];
$display = $parts [ 1 ];
}
// Construct the full url
$linkUrl = str_replace (
" %s " , rawurlencode ( $linkPage ),
$this -> internalLinkBase
);
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 ,
" attributes " => [
" href " => $linkUrl
]
]
];
2016-04-08 08:28:59 +00:00
if ( empty ( $pageindex -> $linkPage ))
$result [ " element " ][ " attributes " ][ " class " ] = " redlink " ;
return $result ;
2016-03-12 15:26:30 +00:00
}
return ;
}
protected function inlineExtendedImage ( $fragment )
{
2016-04-08 08:01:12 +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-03-12 15:26:30 +00:00
* 4 - Second Param ( optional )
*/
2015-10-04 14:20:47 +00:00
2016-03-12 15:26:30 +00:00
$altText = $matches [ 1 ];
2016-04-08 08:01:12 +00:00
$imageUrl = str_replace ( " & " , " & " , $matches [ 2 ]); // Decode & to allow it in preview urls
$param1 = ! empty ( $matches [ 3 ]) ? strtolower ( trim ( $matches [ 3 ])) : false ;
2016-03-12 15:26:30 +00:00
$param2 = empty ( $matches [ 4 ]) ? false : strtolower ( trim ( $matches [ 4 ]));
$floatDirection = false ;
$imageSize = false ;
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-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-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 );
switch ( substr ( $urlType , 0 , strpos ( $urlType , " / " )))
{
case " audio " :
return [
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " audio " ,
" text " => $altText ,
" attributes " => [
" src " => $imageUrl ,
" controls " => " controls " ,
" preload " => " metadata " ,
" style " => trim ( $style )
]
]
];
case " video " :
return [
" extent " => strlen ( $matches [ 0 ]),
" element " => [
" name " => " video " ,
" text " => $altText ,
" attributes " => [
" src " => $imageUrl ,
" controls " => " controls " ,
" preload " => " metadata " ,
" style " => trim ( $style )
]
]
];
case " image " :
default :
// If we can't work out what it is, then assume it's an image
return [
" 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 )
]
]
];
}
2015-10-04 14:20:47 +00:00
}
2016-03-12 15:26:30 +00:00
}
2016-03-20 16:16:55 +00:00
# ~
# Utility Methods
# ~
2016-03-12 15:26:30 +00:00
private function isFloatValue ( $value )
{
return in_array ( strtolower ( $value ), [ " left " , " right " ]);
}
private function parseSizeSpec ( $text )
{
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 ]
];
}
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
}
?>