188 lines
6.2 KiB
Text
188 lines
6.2 KiB
Text
|
<?php namespace ProcessWire;
|
||
|
|
||
|
/**
|
||
|
* ProcessWire Markdown Textformatter
|
||
|
*
|
||
|
* ProcessWire 3.x, Copyright 2021 by Ryan Cramer
|
||
|
|
||
|
* https://processwire.com
|
||
|
*
|
||
|
* Parsedown copyright Emanuil Rusev.
|
||
|
* Markdown invented by John Gruber.
|
||
|
*
|
||
|
* @property int $flavor Markdown flavor (see flavor constants)
|
||
|
* @property bool|int $safeMode Whether or not we are in safe mode (https://github.com/erusev/parsedown#security)
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
class TextformatterMarkdownExtra extends Textformatter implements ConfigurableModule {
|
||
|
|
||
|
public static function getModuleInfo() {
|
||
|
return array(
|
||
|
'title' => 'Markdown/Parsedown Extra',
|
||
|
'version' => 180,
|
||
|
'summary' => "Markdown/Parsedown extra lightweight markup language by Emanuil Rusev. Based on Markdown by John Gruber.",
|
||
|
);
|
||
|
}
|
||
|
|
||
|
const flavorDefault = 2;
|
||
|
const flavorParsedownExtra = 2;
|
||
|
const flavorParsedown = 4;
|
||
|
const flavorMarkdownExtra = 2;
|
||
|
const flavorRCD = 16; // add RCD extensions to one of above, @see markdownExtensions() method (@deprecated)
|
||
|
|
||
|
public function __construct() {
|
||
|
parent::__construct();
|
||
|
$this->set('flavor', self::flavorDefault);
|
||
|
$this->set('safeMode', false);
|
||
|
}
|
||
|
|
||
|
public function format(&$str) {
|
||
|
$str = $this->markdown($str, (int) $this->flavor);
|
||
|
}
|
||
|
|
||
|
public function formatValue(Page $page, Field $field, &$value) {
|
||
|
$value = $this->markdown($value, (int) $this->flavor);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get or set safe mode
|
||
|
*
|
||
|
* “Parsedown is capable of escaping user-input within the HTML that it generates.
|
||
|
* Additionally Parsedown will apply sanitisation to additional scripting vectors
|
||
|
* (such as scripting link destinations) that are introduced by the markdown syntax
|
||
|
* itself. To tell Parsedown that it is processing untrusted user-input, use the
|
||
|
* following...” See: <https://github.com/erusev/parsedown#security>
|
||
|
*
|
||
|
* @param bool|null $safeMode
|
||
|
* @return bool
|
||
|
*
|
||
|
*/
|
||
|
public function safeMode($safeMode = null) {
|
||
|
if($safeMode !== null) $this->safeMode = $safeMode ? true : false;
|
||
|
return (bool) $this->safeMode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Given a string, return a version processed with markdown
|
||
|
*
|
||
|
* @param $str String to process
|
||
|
* @param int|null $flavor Flavor of markdown (null=use module configured setting)
|
||
|
* @param bool|null $safeMode Safe mode ON or OFF (null=use module configured setting)
|
||
|
* @return string Processed string
|
||
|
*
|
||
|
*/
|
||
|
public function markdown($str, $flavor = null, $safeMode = null) {
|
||
|
$parsedown = $this->getParsedown($flavor);
|
||
|
if(is_bool($safeMode)) $parsedown->setSafeMode($safeMode);
|
||
|
$str = $parsedown->text($str);
|
||
|
if($flavor & self::flavorRCD) $this->markdownExtensions($str);
|
||
|
return $str;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Given a string, return a version processed with markdown in safe mode
|
||
|
*
|
||
|
* https://github.com/erusev/parsedown#security
|
||
|
*
|
||
|
* @param $str String to process
|
||
|
* @param int $flavor Flavor of markdown (default=parsedown extra)
|
||
|
* @return string Processed string
|
||
|
*
|
||
|
*/
|
||
|
public function markdownSafe($str, $flavor = 0) {
|
||
|
return $this->markdown($str, $flavor, true);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param int|null $flavor
|
||
|
* @return \ParsedownExtra|\Parsedown
|
||
|
*
|
||
|
*/
|
||
|
public function getParsedown($flavor = null) {
|
||
|
if($flavor === null) $flavor = (int) $this->flavor;
|
||
|
if(!class_exists("\\Parsedown")) require_once(dirname(__FILE__) . "/parsedown/Parsedown.php");
|
||
|
if(!class_exists("\\ParsedownExtra")) require_once(dirname(__FILE__) . "/parsedown-extra/ParsedownExtra.php");
|
||
|
if($flavor & self::flavorParsedown) {
|
||
|
// regular parsedown
|
||
|
$parsedown = new \Parsedown();
|
||
|
} else {
|
||
|
// parsedown extra
|
||
|
$parsedown = new \ParsedownExtra();
|
||
|
}
|
||
|
if($this->safeMode) $parsedown->setSafeMode(true);
|
||
|
return $parsedown;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* A few RCD extentions to MarkDown syntax, to be executed after Markdown has already had it's way with the text
|
||
|
*
|
||
|
* @param string $str
|
||
|
* @deprecated
|
||
|
*
|
||
|
*/
|
||
|
public function markdownExtensions(&$str) {
|
||
|
|
||
|
// add id attribute to a tag, when followed by a #myid
|
||
|
if(strpos($str, '>#')) $str = preg_replace('/<([a-z][a-z0-9]*)([^>]*>.*?)(<\/\\1>)#([a-z][-_a-z0-9]*)\b/', '<$1 id="$4"$2$3', $str);
|
||
|
|
||
|
// add class attribute to tag when followed by a .myclass
|
||
|
if(strpos($str, '>.')) $str = preg_replace('/<([a-z][a-z0-9]*)([^>]*>.*?)(<\/\\1>)\.([a-z][-_a-z0-9]*)\b/', '<$1 class="$4"$2$3', $str);
|
||
|
|
||
|
// href links open in new window when followed by a plus sign, i.e. [google](http://google.com)+
|
||
|
if(strpos($str, '</a>+')) $str = preg_replace('/<a ([^>]+>.+?<\/a>)\+/', '<a target="_blank" $1', $str);
|
||
|
|
||
|
// strip out comments
|
||
|
if(strpos($str, '/*') !== false) $str = preg_replace('{/\*.*?\*/}s', '', $str);
|
||
|
if(strpos($str, '//') !== false) $str = preg_replace('{^//.*$}m', '', $str);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @param InputfieldWrapper $inputfields
|
||
|
*
|
||
|
*/
|
||
|
public function getModuleConfigInputfields(InputfieldWrapper $inputfields) {
|
||
|
$f = $inputfields->InputfieldRadios;
|
||
|
$f->attr('name', 'flavor');
|
||
|
$f->label = $this->_('Markdown flavor to use');
|
||
|
$f->addOption(self::flavorParsedownExtra, 'Parsedown Extra');
|
||
|
$f->addOption(self::flavorParsedown, 'Parsedown');
|
||
|
$f->attr('value', (int) $this->flavor);
|
||
|
$inputfields->add($f);
|
||
|
|
||
|
$f = $inputfields->InputfieldToggle;
|
||
|
$f->attr('name', 'safeMode');
|
||
|
$f->label = $this->_('Safe mode?');
|
||
|
$f->description = $this->_('Enable this to Markdown that it is processing untrusted user-input.') . ' ' .
|
||
|
"[" . $this->_('Details') . "](https://github.com/erusev/parsedown#security)";
|
||
|
$f->val((int) $this->safeMode);
|
||
|
$inputfields->add($f);
|
||
|
|
||
|
$f = $inputfields->InputfieldTextarea;
|
||
|
$f->attr('name', '_test_markdown');
|
||
|
$f->label = $this->_('Test markdown');
|
||
|
$f->description = $this->_('Enter some markdown and click submit to test the result with your current settings.');
|
||
|
$f->collapsed = Inputfield::collapsedBlank;
|
||
|
$f->icon = 'flask';
|
||
|
$inputfields->add($f);
|
||
|
|
||
|
$session = $this->wire()->session;
|
||
|
$text = $this->wire()->input->post('_test_markdown');
|
||
|
if($text) {
|
||
|
$session->setFor($this, 'text', $text);
|
||
|
} else {
|
||
|
$text = $session->getFor($this, 'text');
|
||
|
$session->removeFor($this, 'text');
|
||
|
$f->val($text);
|
||
|
if($text) {
|
||
|
$markup = $this->markdown($text);
|
||
|
$this->message("<strong>$f->label results:</strong><br />" .
|
||
|
"<pre>" . $this->wire()->sanitizer->entities($markup) . '</pre>',
|
||
|
Notice::allowMarkup | Notice::noGroup
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
}
|