[ 'start' => '^(!{1,4})([^\n]+)\n*', 'callback' => 'StudipCoreFormat::markupHeading' ], // horizontal rule 'hrule' => [ 'start' => '^--(\d?)$', 'callback' => 'StudipCoreFormat::markupHorizontalRule' ], // list and table 'list' => [ 'start' => '(^[=-]+ [^\n]+\n?)+', 'callback' => 'StudipCoreFormat::markupList' ], 'table' => [ 'start' => '(^\|[^\n]*\|[^\n]*\n?)+', 'callback' => 'StudipCoreFormat::markupTable' ], // block indent 'indent' => [ 'start' => '(^ [^\n]+\n?)+', 'callback' => 'StudipCoreFormat::markupIndent' ], // basic text formatting 'bold' => [ 'start' => '\*\*', 'end' => '\*\*', 'callback' => 'StudipCoreFormat::markupText' ], 'italics' => [ 'start' => '%%', 'end' => '%%', 'callback' => 'StudipCoreFormat::markupText' ], 'underline' => [ 'start' => '__', 'end' => '__', 'callback' => 'StudipCoreFormat::markupText' ], 'verb' => [ 'start' => '##', 'end' => '##', 'callback' => 'StudipCoreFormat::markupText' ], 'big' => [ 'start' => '(\+\+)+', 'end' => '(\+\+)+', 'callback' => 'StudipCoreFormat::markupTextSize' ], 'small' => [ 'start' => '(--)+', 'end' => '(--)+', 'callback' => 'StudipCoreFormat::markupTextSize' ], 'super' => [ 'start' => '>>', 'end' => '>>', 'callback' => 'StudipCoreFormat::markupText' ], 'sub' => [ 'start' => '<<', 'end' => '<<', 'callback' => 'StudipCoreFormat::markupText' ], 'strike' => [ 'start' => '\{-', 'end' => '-\}', 'callback' => 'StudipCoreFormat::markupText' ], // basic text formatting (simple form) 'simple_bold' => [ 'start' => '(?<=\s|^)\*(\S+)\*(?=\s|$)', 'callback' => 'StudipCoreFormat::markupTextSimple' ], 'simple_italics' => [ 'start' => '(?<=\s|^)%(\S+)%(?=\s|$)', 'callback' => 'StudipCoreFormat::markupTextSimple' ], 'simple_underline' => [ 'start' => '(?<=\s|^)_(\S+)_(?=\s|$)', 'callback' => 'StudipCoreFormat::markupTextSimple' ], 'simple_verb' => [ 'start' => '(?<=\s|^)#(\S+)#(?=\s|$)', 'callback' => 'StudipCoreFormat::markupTextSimple' ], 'simple_big' => [ 'start' => '(?<=\s|^)\+(\S+)\+(?=\s|$)', 'callback' => 'StudipCoreFormat::markupTextSimple' ], 'simple_small' => [ 'start' => '(?<=\s|^)-(\S+)-(?=\s|$)', 'callback' => 'StudipCoreFormat::markupTextSimple' ], 'simple_super' => [ 'start' => '(?<=\s|^)>(\S+)>(?=\s|$)', 'callback' => 'StudipCoreFormat::markupTextSimple' ], 'simple_sub' => [ 'start' => '(?<=\s|^)<(\S+)<(?=\s|$)', 'callback' => 'StudipCoreFormat::markupTextSimple' ], // preformatted text, quote, nop and code 'pre' => [ 'start' => '\[pre\]', 'end' => '\[\/pre\]', 'callback' => 'StudipCoreFormat::markupPreformat' ], 'quote' => [ 'start' => '\[quote(=.*?)?\]', 'end' => '\[\/quote\]\s*', 'callback' => 'StudipCoreFormat::markupQuote' ], 'nop' => [ 'start' => '\[nop\](.*?)\[\/nop\]', 'callback' => 'StudipCoreFormat::markupNoFormat' ], 'code' => [ 'start' => '\[code(=.*?)?\](.*?)\[\/code\]', 'callback' => 'StudipCoreFormat::markupCode' ], 'media' => [ 'start' => '\[(img|flash|audio|video)(.*?)\](.*?)(?=\s|$)', 'callback' => 'StudipCoreFormat::markupMedia' ], 'emails' => [ 'start' => '(?xi: # ensure block is preceded by whitspace, line start or closing # tag (?<=\s|^|\>) # capture displayed text (?:\[([^\n\f\]]+?)\])? # capture actual email address ([\w.!#%+\-]+@([[:alnum:]\-.]+\.[[:alnum:]\-.]{2,})) # ensure block is succeeded by white space or line end (?=\s|$) )', 'callback' => 'StudipCoreFormat::markupEmails' ], 'htmlAnchor' => [ # avoid replacing html links by studip's links-markup # match ... 'start' => '(?xi: <\s* a (?:\s(?:\s* \w+ \s*=\s* "[^"]*" )*)? \s*>)', 'end' => '(?xi: <\s* \/\s* a \s*>)', 'callback' => 'StudipCoreFormat::htmlAnchor' ], 'htmlImg' => [ # avoid replacing img links by studip's links-markup # match 'start' => '(?xi: # match tag start: \s*\/?\s*> )', 'callback' => 'StudipCoreFormat::htmlImg' ], 'links' => [ // markup: [text]url // // To set URLs apart from normal text, scheme and host are // obligatory. URLs are based on, but allow more than RFC3986 in // order to simplify the regular expression. // // http://tools.ietf.org/html/rfc3986 'start' => '(?xi: # capture 1: displayed text (?:\[( [^\n\f\]]+ )\])? # capture 2: URL \b( # scheme [a-z][a-z0-9\+\-\.]*:\/\/ # user:password@host:port\/path?query#fragment [a-zA-Z0-9\x{00a0}-\x{d7ff}\x{f900}-\x{fdcf}\x{fdf0}-\x{ffef}_\-\.~!$&\'\(\)\[\]*+,;=%:@\/?#]+ ) )', 'callback' => 'StudipCoreFormat::markupLinks' ], 'tex' => [ 'start' => '\[tex\]', 'end' => '\[\/tex\]', 'callback' => 'StudipCoreFormat::markupTexFormat' ], ]; /** * Returns the list of global Stud.IP markup rules as an array. * Each entry has the following attributes: 'start', 'end' and * 'callback'. The rule name is used as the entry's array key. * * @return array list of all markup rules */ public static function getStudipMarkups() { return self::$studip_rules; } /** * Adds a new markup rule to the global Stud.IP markup set. This can * also be used to replace an existing markup rule. The end regular * expression is optional (i.e. may be NULL) to indicate that this * rule has an empty content model. The callback is called whenever * the rule matches and is passed the following arguments: * * - $markup the markup parser object * - $matches match results of preg_match for $start * - $contents (parsed) contents of this markup rule * * Sometimes you may want your rule to apply before another specific rule * will apply. For this case the parameter $before defines a rulename of * existing markup, before which your rule should apply. * * @param string $name name of this rule * @param string $start start regular expression * @param string $end end regular expression (optional) * @param callback $callback function generating output of this rule * @param string $before mark before which rule this rule should be appended */ public static function addStudipMarkup($name, $start, $end, $callback, $before = null) { $inserted = false; foreach (self::$studip_rules as $rule_name => $rule) { if ($rule_name === $before) { self::$studip_rules[$name] = compact('start', 'end', 'callback'); $inserted = true; } if ($inserted) { unset(self::$studip_rules[$rule_name]); self::$studip_rules[$rule_name] = $rule; } } if (!$inserted) { self::$studip_rules[$name] = $end ? compact('start', 'end', 'callback') : compact('start', 'callback'); } } /** * Returns a single markup-rule if it exists. * @return array: array('start' => "...", 'end' => "...", 'callback' => "...") */ public static function getStudipMarkup($name) { return self::$studip_rules[$name]; } /** * Removes a markup rule from the global Stud.IP markup set. * * @param string $name name of the rule */ public static function removeStudipMarkup($name) { unset(self::$studip_rules[$name]); } /** * Initializes a new StudipCoreFormat instance. */ public function __construct() { parent::__construct(self::getStudipMarkups()); } /** * Stud.IP markup for headings */ protected static function markupHeading($markup, $matches) { $level = max(1, 5 - mb_strlen($matches[1])); $text = $markup->format($matches[2]); return sprintf('%s', $level, $text, $level); } /** * Stud.IP markup for horizontal rule */ protected static function markupHorizontalRule($markup, $matches) { return sprintf('
', $matches[1] ? ' style="height: '.((int) $matches[1]).'px"' : "" ); } /** * Basic text formatting: bold, italics, underline, sup, sub etc. */ protected static function markupText($markup, $matches, $contents) { static $tag = [ '**' => 'strong', '%%' => 'em', '__' => 'u', '##' => 'tt', '>>' => 'sup', '<<' => 'sub', '{-' => 's' ]; $key = $matches[0]; return sprintf('<%s>%s', $tag[$key], $contents, $tag[$key]); } /** * Text size formatting: small and small */ protected static function markupTextSize($markup, $matches, $contents) { static $tag = [ '++' => 'big', '--' => 'small', ]; $key = $matches[1]; $level = mb_strlen($matches[0]) / 2; $open = str_repeat('<' . $tag[$key] . '>', $level); $close = str_repeat('', $level); return $open . $contents . $close; } /** * Basic text formatting: bold, italics, underline etc. (simple form) */ protected static function markupTextSimple($markup, $matches) { static $tag = [ '*' => 'strong', '%' => 'em', '_' => 'u', '#' => 'tt', '+' => 'big', '-' => 'small', '>' => 'sup', '<' => 'sub' ]; $key = $matches[0][0]; $text = str_replace($key, ' ', $matches[1]); return sprintf('<%s>%s', $tag[$key], $markup->quote($text), $tag[$key]); } /** * Stud.IP markup for lists (may be nested) */ protected static function markupList($markup, $matches) { $rows = explode("\n", rtrim($matches[0])); $indent = 0; $result = ''; foreach ($rows as $row) { list($level, $text) = explode(' ', $row, 2); $level = mb_strlen($level); if ($indent < $level) { for (; $indent < $level; ++$indent) { $type = $row[$indent] == '=' ? 'ol' : 'ul'; $result .= sprintf('<%s>
  • ', $type); $types[] = $type; } } else { for (; $indent > $level; --$indent) { $result .= sprintf('
  • ', array_pop($types)); } $result .= '
  • '; } $result .= $markup->format($text); } for (; $indent > 0; --$indent) { $result .= sprintf('
  • ', array_pop($types)); } return $result; } /** * Stud.IP markup for tables */ protected static function markupTable($markup, $matches) { $rows = explode("\n", rtrim($matches[0])); $result = ''; foreach ($rows as $row) { $cells = explode('|', trim(trim($row), '|')); $result .= ''; foreach ($cells as $cell) { $result .= ''; } $result .= ''; } $result .= '
    '; $result .= $markup->format(trim($cell)); $result .= '
    '; return $result; } /** * Stud.IP markup for indented paragraphs */ protected static function markupIndent($markup, $matches) { $text = preg_replace('/^ +/', '', $matches[0]); $text = preg_replace('/^ /m', '', $text); return sprintf('
    %s
    ', $markup->format($text)); } /** * Stud.IP markup for preformatted text */ protected static function markupPreformat($markup, $matches, $contents) { return sprintf('
    %s
    ', trim($contents)); } /** * Stud.IP markup for quoted text */ protected static function markupQuote($markup, $matches, $contents) { if (isset($matches[1]) && mb_strlen($matches[1]) > 1) { $title = sprintf(_('%s hat geschrieben:'), $markup->format(mb_substr($matches[1], 1))); return sprintf('
    %s
    %s
    ', $title, trim($contents)); } else { return sprintf('
    %s
    ', trim($contents)); } } /** * Stud.IP markup for unformatted text */ protected static function markupNoFormat($markup, $matches) { return $markup->quote($matches[1]); } /** * Stud.IP markup for (PHP) source code */ protected static function markupCode($markup, $matches) { $codetype = ""; if (mb_strlen($matches[1])) { $codetype = " ".decodeHTML(trim(mb_substr($matches[1], 1)), ENT_QUOTES); } $code = decodeHTML(trim($matches[2]), ENT_QUOTES); return sprintf('
    %2$s
    ', htmlReady($codetype), htmlReady($code)); } /** * Stud.IP markup for email-adresses */ protected static function markupEmails($markup, $matches) { $link_text = $matches[1] ?: $matches[2]; $email = $matches[2]; $domain = $matches[3]; $intern = $domain === $_SERVER['HTTP_HOST']; return sprintf('%2$s', $email, $link_text ); } /** * Stud.IP markup for images, audio, video and flash-films */ protected static function markupMedia($markup, $matches) { $tag = $matches[1]; $params = explode(":",$matches[2]); $url = $matches[3]; $whitespace = $matches[4] ?? null; $title = null; $link = null; $position = null; $width = null; $virtual_url = null; foreach ($params as $key => $param) { if ($param) { if (is_numeric($param)) { $width = $param; } elseif(in_array($param, words("left center right"))) { $position = $param; } elseif($key === 0 && $param[0] === "=") { $title = mb_substr($param, 1); } elseif($key < count($params) - 1) { $virtual_url = $param.":".$params[$key + 1]; if (isURL($virtual_url)) { $link = $virtual_url; } } } } $format_strings = [ 'img' => '%s', 'audio' => '', 'video' => '' ]; $url = TransformInternalLinks($url); $pu = @parse_url($url); $intern = false; if ( in_array($pu['scheme'], ['http', 'https']) && ( !isset($pu['host']) || $pu['host'] === $_SERVER['HTTP_HOST'] || (isset($pu['port']) && $pu['host'] . ':' . $pu['port'] === $_SERVER['HTTP_HOST']) ) && mb_strpos($pu['path'], $GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP']) === 0 ) { $intern = true; $checkpath = urldecode(mb_substr($pu['path'], mb_strlen($GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP']))); if (mb_strpos($checkpath, '../') === false) { list($pu['first_target']) = explode('/', $checkpath); } else { $pu['first_target'] = false; } } $LOAD_EXTERNAL_MEDIA = Config::GetInstance()->getValue('LOAD_EXTERNAL_MEDIA'); if ($intern && !in_array($pu['first_target'], ['sendfile.php','download','assets','pictures']) && !($pu['first_target'] === 'dispatch.php' && mb_strpos($pu['path'], 'dispatch.php/document/download') !== false)) { return $matches[0]; } elseif ((!$LOAD_EXTERNAL_MEDIA || $LOAD_EXTERNAL_MEDIA === 'deny') && !$intern) { return $matches[0]; } //Mediaproxy? if (!$intern && $LOAD_EXTERNAL_MEDIA === "proxy" && Seminar_Session::is_current_session_authenticated()) { $media_url = $GLOBALS['ABSOLUTE_URI_STUDIP'] . 'dispatch.php/media_proxy?url=' . urlencode(decodeHTML(idna_link($url))); } else { $media_url = idna_link($url); } if ($tag === "flash") { $width = $width ? $width : 200; $height = round($width * 0.75); $flash_config = $width > 200 ? $GLOBALS['FLASHPLAYER_DEFAULT_CONFIG_MAX'] : $GLOBALS['FLASHPLAYER_DEFAULT_CONFIG_MIN']; $media = ' '; } else { $media = sprintf($format_strings[$tag], $media_url, isset($width) ? "width: ".$width."px;" : "", $title, $title ); } if ($tag === 'audio') { $random_id = 'audio-' . mb_substr(md5(uniqid('audio', true)), -8); $media = str_replace('