getAliasContaining($page, true); if (! $page_info && count($aliases) > 0) { $error_title = tra("Cannot create aliased page"); $error_msg = tra("You attempted to create the following page:") . " " . "$page.\n

\n"; $error_msg .= tra("That page is an alias for the following pages") . ": "; foreach ($aliases as $an_alias) { $error_msg .= '' . $an_alias['fromPage'] . ', '; } $error_msg .= "\n

\n"; $error_msg .= tra("If you want to create the page, you must first edit each of the pages above to remove the alias link they may contain. This link should look something like this"); $error_msg .= ": (alias($page))"; $access->display_error(page, $error_title, "", true, $error_msg); } } public function user_needs_to_specify_language_of_page_to_be_created($page, $page_info, $new_page_inherited_attributes = null) { global $prefs; if (isset($_REQUEST['need_lang']) && $_REQUEST['need_lang'] == 'n') { return false; } if ($prefs['feature_multilingual'] == 'n') { return false; } if ($page_info && isset($page_info['lang']) && $page_info['lang'] != '') { return false; } if (isset($_REQUEST['lang']) && $_REQUEST['lang'] != '') { return false; } if ( $new_page_inherited_attributes != null && isset($new_page_inherited_attributes['lang']) && $new_page_inherited_attributes['lang'] != '' ) { return false; } return true; } // translation functions public function isTranslationMode() { return $this->isUpdateTranslationMode() || $this->isNewTranslationMode(); } public function isNewTranslationMode() { global $prefs; if ($prefs['feature_multilingual'] != 'y') { return false; } if ( isset($_REQUEST['translationOf']) && ! empty($_REQUEST['translationOf']) ) { return true; } if ( isset($_REQUEST['is_new_translation']) && $_REQUEST['is_new_translation'] == 'y' ) { return true; } return false; } public function isUpdateTranslationMode() { return isset($_REQUEST['source_page']) && isset($_REQUEST['oldver']) && (! isset($_REQUEST['is_new_translation']) || $_REQUEST['is_new_translation'] == 'n') && isset($_REQUEST['newver']); } public function prepareTranslationData() { $this->setTranslationSourceAndTargetPageNames(); $this->setTranslationSourceAndTargetVersions(); } private function setTranslationSourceAndTargetPageNames() { $smarty = TikiLib::lib('smarty'); if (! $this->isTranslationMode()) { return; } $this->targetPageName = null; if (isset($_REQUEST['target_page'])) { $this->targetPageName = $_REQUEST['target_page']; } elseif (isset($_REQUEST['page'])) { $this->targetPageName = $_REQUEST['page']; } $smarty->assign('target_page', $this->targetPageName); $this->sourcePageName = null; if (isset($_REQUEST['translationOf']) && $_REQUEST['translationOf']) { $this->sourcePageName = $_REQUEST['translationOf']; } elseif (isset($_REQUEST['source_page'])) { $this->sourcePageName = $_REQUEST['source_page']; } $smarty->assign('source_page', $this->sourcePageName); if ($this->isNewTranslationMode()) { $smarty->assign('translationIsNew', 'y'); } else { $smarty->assign('translationIsNew', 'n'); } } private function setTranslationSourceAndTargetVersions() { global $_REQUEST, $tikilib; if (isset($_REQUEST['oldver'])) { $this->oldSourceVersion = $_REQUEST['oldver']; } else { // Note: -1 means a "virtual" empty version. $this->oldsourceVersion = -1; } if (isset($_REQUEST['newver'])) { $this->newSourceVersion = $_REQUEST['newver']; } else { // Note: version number of 0 means the most recent version. $this->newSourceVersion = 0; } } public function aTranslationWasSavedAs($complete_or_partial) { if ( ! $this->isTranslationMode() || ! isset($_REQUEST['save']) ) { return false; } // We are saving a translation. Is it partial or complete? if ($complete_or_partial == 'complete' && isset($_REQUEST['partial_save'])) { return false; } elseif ($complete_or_partial == 'partial' && ! isset($_REQUEST['partial_save'])) { return false; } return true; } /** * Convert rgb() color definiton to hex color definiton * * @param unknown_type $col * @return The hex representation */ public function parseColor(&$col) { if (preg_match("/^rgb\( *(\d+) *, *(\d+) *, *(\d+) *\)$/", $col, $parts)) { $hex = str_pad(dechex($parts[1]), 2, '0', STR_PAD_LEFT) . str_pad(dechex($parts[2]), 2, '0', STR_PAD_LEFT) . str_pad(dechex($parts[3]), 2, '0', STR_PAD_LEFT); $hex = '#' . TikiLib::strtoupper($hex); } else { $hex = $col; } return $hex; } /** * Utility for walk_and_parse to process links * * @param array $args the attributes of the link * @param array $text the link text * @param string $src output string * @param array $p ['stack'] = closing strings stack */ private function parseLinkTag(&$args, &$text, &$src, &$p) { global $prefs; $link = ''; $link_open = ''; $link_close = ''; /* * parse the link classes */ $cl_wiki = false; $cl_wiki_page = false; // Wiki page $cl_ext_page = false; // external Wiki page $cl_external = false; // external web page $cl_semantic = ''; // semantic link if ($prefs['feature_semantic'] === 'y') { $semantic_tokens = TikiLib::lib('semantic')->getAllTokens(); } else { $semantic_tokens = []; } $ext_wiki_name = ''; if (isset($args['class']) && isset($args['href'])) { $matches = []; preg_match_all('/([^ ]+)/', $args['class']['value'], $matches); $classes = $matches[0]; for ($i = 0, $count_classes = count($classes); $i < $count_classes; $i++) { $cl = $classes[$i]; switch ($cl) { case 'wiki': $cl_wiki = true; break; case 'wiki_page': $cl_wiki_page = true; break; case 'ext_page': $cl_ext_page = true; break; case 'external': $cl_external = true; break; default: // if the preceding class was 'ext_page', then we have the name of the external Wiki if ($i > 0 && $classes[$i - 1] == 'ext_page') { $ext_wiki_name = $cl; } if (in_array($cl, $semantic_tokens)) { $cl_semantic = $cl; } } } } /* * extract the target and the anchor from the href */ if (isset($args['href'])) { $href = urldecode($args['href']['value']); // replace pipe chars as that's the delimiter in a wiki/external link $href = str_replace('|', '%7C', $href); $matches = explode('#', $href); if (count($matches) == 2) { $target = $matches[0]; $anchor = '#' . $matches[1]; } else { $target = $href; $anchor = ''; } } else { $target = ''; $anchor = ''; } /* * treat invalid external Wikis as web links */ if ($cl_ext_page) { // retrieve the definitions from the database if ($this->external_wikis == null) { global $tikilib; $query = 'SELECT `name`, `extwiki` FROM `tiki_extwiki`'; $this->external_wikis = $tikilib->fetchMap($query); } // name must be set and defined if (! $ext_wiki_name || ! isset($this->external_wikis[$ext_wiki_name])) { $cl_ext_page = false; $cl_wiki_page = false; } }; /* * construct the links according to the defined classes */ if ($cl_wiki_page) { /* * link to wiki page -> (( )) */ $link_open = "($cl_semantic("; $link_close = '))'; // remove the html part of the target $target = preg_replace('/tiki\-index\.php\?page\=/', '', $target); // construct the link $link = $target; if ($anchor) { $link .= '|' . $anchor; } } elseif ($cl_ext_page) { /* * link to external Wiki page ((:)) */ $link_open = '(('; $link_close = '))'; // remove the extwiki definition from the target $def = preg_replace('/\$page/', '', $this->external_wikis[$ext_wiki_name]); $def = preg_quote($def, '/'); $target = preg_replace('/^' . $def . '/', '', $target); // construct the link $link = $ext_wiki_name . ':' . $target; if ($anchor) { $link .= '|' . $anchor; } } elseif ($cl_wiki && ! $cl_external && ! $target && strlen($anchor) > 0 && substr($anchor, 0, 1) == '#') { /* * inpage links [#] */ $link_open = '['; $link_close = ']'; // construct the link $link = $target = $anchor; $anchor = ''; } elseif ($cl_wiki && ! $cl_external) { /* * other tiki resources [] * -> articles, ... */ $link_open = '['; $link_close = ']'; // construct the link $link = $target; } elseif (! $cl_wiki && ! $cl_external && ! $text && isset($args['id']) && isset($args['id']['value'])) { /* * anchor */ $link_open = '{ANAME()}'; $link_close = '{ANAME}'; $link = $args['id']['value']; } else { /* * other links [] */ $link_open = '['; $link_close = ']'; /* * parse the rel attribute */ $box = ''; if (isset($args['class']) && isset($args['rel'])) { $matches = []; preg_match_all('/([^ ]+)/', $args['rel']['value'], $matches); $rels = $matches[0]; for ($i = 0, $count_rels = count($rels); $i < $count_rels; $i++) { $r = $rels[$i]; if (preg_match('/^box/', $r)) { $box = $r; } } } // construct the link $link = $target; if ($anchor) { $link .= $anchor; } // the box must be appended to the text if ($box) { $text .= '|' . $box; } } // convert links /* * flush the constructed link */ if ($link_open && $link_close) { $p['wiki_lbr']++; // force wiki line break mode // does the link text match the target? if ($target == trim($text)) { $text = ''; // make sure walk_and_parse() does not append any text. } else { $link .= '|'; // the text will be appended by walk_and_parse() } // process the tag and update the output $this->processWikiTag('a', $src, $p, $link_open, $link_close, true); $src .= $link; } } /** * Utility for walk_and_parse to process p and div tags * * @param bool $isPar True if we process a

, false if a

* @param array $args the attributes of the tag * @param string $src output string * @param array $p ['stack'] = closing strings stack */ private function parseParDivTag($isPar, &$args, &$src, &$p) { global $prefs; if (isset($args['style']) || isset($args['align'])) { $tag_name = $isPar ? 'p' : 'div'; // key for the $p[stack] $type = $isPar ? 'type="p", ' : ''; // used for {DIV()} $style = []; $this->parseStyleAttribute($args['style']['value'], $style); /* * convert 'align' to 'style' definitions */ if (isset($args['align'])) { $style['text-align'] = $args['align']['value']; } /* * process all the defined styles */ foreach (array_keys($style) as $format) { switch ($format) { case 'text-align': if ($style[$format] == 'left') { $src .= "{DIV(${type}align=\"left\")}"; $p['stack'][] = ['tag' => $tag_name, 'string' => '{DIV}']; } elseif ($style[$format] == 'center') { $markup = ($prefs['feature_use_three_colon_centertag'] == 'y') ? ':::' : '::'; $this->processWikiTag($tag_name, $src, $p, $markup, $markup, false); } elseif ($style[$format] == 'right') { $src .= "{DIV(${type}align=\"right\")}"; $p['stack'][] = ['tag' => $tag_name, 'string' => '{DIV}']; } elseif ($style[$format] == 'justify') { $src .= "{DIV(${type}align=\"justify\")}"; $p['stack'][] = ['tag' => $tag_name, 'string' => '{DIV}']; } break; } // switch format } // foreach style } } /** * Utility for walk_and_parse to process span arguments * * @param array $args the attributes of the span * @param string $src output string * @param array $p ['stack'] = closing strings stack */ private function parseSpanTag(&$args, &$src, &$p) { if (isset($args['style'])) { $style = []; $this->parseStyleAttribute($args['style']['value'], $style); $this->processWikiTag('span', $src, $p, '', '', true); // prepare the stacks, later we will append /* * The colors need to be handeled separatly; two style definitions become * one single wiki markup. */ $fcol = ''; $bcol = ''; if (isset($style['color'])) { $fcol = $this->parseColor($style['color']); unset($style['color']); } if (isset($style['background-color'])) { // background: color-def have been converted to background-color $bcol = $this->parseColor($style['background-color']); unset($style['background-color']); } if ($fcol || $bcol) { $col = "~~"; $col .= ($fcol ? $fcol : ' '); $col .= ($bcol ? ',' . $bcol : ''); $col .= ':'; $this->processWikiTag('span', $src, $p, $col, '~~', true, true); } /* * Process the remaining format definitions */ foreach (array_keys($style) as $format) { switch ($format) { case 'font-weight': if ($style[$format] == 'bold') { $this->processWikiTag('span', $src, $p, '__', '__', true, true); } break; case 'font-style': if ($style[$format] == 'italic') { $this->processWikiTag('span', $src, $p, '\'\'', '\'\'', true, true); } // no break case 'text-decoration': if ($style[$format] == 'line-through') { $this->processWikiTag('span', $src, $p, '--', '--', true, true); } elseif ($style[$format] == 'underline') { $this->processWikiTag('span', $src, $p, '===', '===', true, true); } // no break } // switch format } // foreach style } // style } /** * Parse a html style definition into an array * * This method tries to expand the shorthand definitions, such as 'background:', * to the correspoinding key/value paris. If a definition is unknown, it is kept. * * @param string $style The value of the style attribute * @param array $parsed key/value pairs */ public function parseStyleAttribute(&$style, &$parsed) { $matches = []; preg_match_all('/ *([^ :]+) *: *([^;]+) *;?/', $style ?? '', $matches); for ($i = 0, $count_matches = count($matches[0]); $i < $count_matches; $i++) { $key = $matches[1][$i]; $value = trim($matches[2][$i]); /* * shortand list 'background:' * - set 'background-color' */ if ($key == 'background') { $unprocessed = ''; $shorthand = []; $this->parseStyleList($value, $shorthand); foreach ($shorthand as $s) { switch ($s) { case preg_match('/^#(\w{3,6})$/', $s) > 0: $parsed['background-color'] = $s; break; case preg_match('/^rgb\(.*\)$/', $s) > 0: $parsed['background-color'] = $s; break; default: $unprocessed .= ' ' . $s; } } // foreach shorthand // keep unprocessed list entries $value = trim($unprocessed); } // background: // save the result if ($value) { $parsed[$key] = $value; } } // style definitions } /** * Parse a space separated list of html styles * * Example: "rgb( 1, 2, 3) url(background.gif)" * * @param string $list List of styles * @param array $parsed The parsed list */ public function parseStyleList(&$list, &$parsed) { $matches = []; preg_match_all('/(?:[[:graph:]]+\([^\)]*\))|(?:[^ ]+)/', $list, $matches); $parsed = $matches[0]; } /** * Utility of walk_and_parse to process a wiki tag * * Wiki tags need a special treatment: In html, a tag may contain * several line breaks. In Wiki however, line breaks are often not allowed * and sometimes additional line breaks are required. * * This method saves Wiki tags an line break information in separate stacks. * These stackes are used in walk_and_parse to: * - Output the required markup before and after the linebreaks (
). * - To ensure that linebreaks are, if required, inserted at the correct place (\n). * * @param string $tag The name of the html tag * @param string $src Th Output string * @param array $p ['stack'] = closing strings stack, * @param string $begin The wiki markup that begins the tag * @param string $end The wiki markup that ends the tag * @param bool $is_inline True if the tag is inline, false if the tag must span exacly one line. * @param bool $append True = append to the topmost element on the stack, false = create a new element on the stack */ private function processWikiTag($tag, &$src, &$p, $begin, $end, $is_inline, $append = false) { // append=false, create new entries on the stack if (! $append) { $p['stack'][] = ['tag' => $tag, 'string' => '', 'wikitags' => 0 ]; $p['wikistack'][] = [ 'begin' => [], 'end' => [] ]; }; // get the entry points on the stacks $keys = array_keys($p['wikistack']); $key = end($keys); $wiki = &$p['wikistack'][$key]; $keys = array_keys($p['stack']); $key = end($keys); $stack = &$p['stack'][$key]; $string = &$stack['string']; // append to the stacks $wiki['begin'][] = $begin; $wiki['end'][] = $end; $string = $end . $string; $stack['wikitags']++; // update the output string if (! $is_inline) { $this->startNewLine($src); } $src .= $begin; } public function saveCompleteTranslation() { $multilinguallib = TikiLib::lib('multilingual'); $tikilib = TikiLib::lib('tiki'); $sourceInfo = $tikilib->get_page_info($this->sourcePageName); $targetInfo = $tikilib->get_page_info($this->targetPageName); $multilinguallib->propagateTranslationBits( 'wiki page', $sourceInfo['page_id'], $targetInfo['page_id'], $sourceInfo['version'], $targetInfo['version'] ); $multilinguallib->deleteTranslationInProgressFlags($targetInfo['page_id'], $sourceInfo['lang']); } public function savePartialTranslation() { $tikilib = TikiLib::lib('tiki'); $multilinguallib = TikiLib::lib('multilingual'); $sourceInfo = $tikilib->get_page_info($this->sourcePageName); $targetInfo = $tikilib->get_page_info($this->targetPageName); $multilinguallib->addTranslationInProgressFlags($targetInfo['page_id'], $sourceInfo['lang']); } /** * Function to take html from ckeditor and parse back to wiki markup * Used by "switch editor" and when saving in wysiwyg_htmltowiki mode * When saving in mixed "html" mode the "unparsing" is done in JavaScript client-side * * @param $inData string editor content * @return string wiki markup */ public function parseToWiki($inData) { global $prefs; $parsed = $this->partialParseWysiwygToWiki($inData); // remove cke type plugin wrappers $parsed = html_entity_decode($parsed, ENT_QUOTES, 'UTF-8'); $parsed = preg_replace('/\t/', '', $parsed); // remove all tabs inserted by the CKE $parsed = preg_replace_callback('/
(.*?)<\/pre>/ims', [$this, 'parseToWikiPlugin'], $parsed);   // rempve plugin wrappers

        $parsed = $this->parse_html($parsed);
        $parsed = preg_replace('/\{img\(? src=.*?img\/smiles\/icon_([\w\-]*?)\..*\}/im', '(:$1:)', $parsed);    // "unfix" smilies
        $parsed = preg_replace('/ /m', ' ', $parsed);                                              // spaces
        $parsed = preg_replace('/!(?:\d\.)+/', '!#', $parsed); // numbered headings
        if ($prefs['feature_use_three_colon_centertag'] == 'y') { // numbered and centerd headings
            $parsed = preg_replace('/!:::(?:\d\.)+ *(.*):::/', '!#:::\1:::', $parsed);
        } else {
            $parsed = preg_replace('/!::(?:\d\.)+ *(.*)::/', '!#::\1::', $parsed);
        }

        // remove empty center tags
        if ($prefs['feature_use_three_colon_centertag'] == 'y') { // numbered and centerd headings
            $parsed = preg_replace('/::: *:::\n/', '', $parsed);
        } else {
            $parsed = preg_replace('/:: *::\n/', '', $parsed);
        }

        // Put back htmlentities as normal char
        $parsed = htmlspecialchars_decode($parsed, ENT_QUOTES);
        return $parsed;
    }

    public function parseToWikiPlugin($matches)
    {
        if (count($matches) > 1) {
            return nl2br($matches[1]);
        }
    }

    /**
     * Render html to send to ckeditor, including parsing plugins for wysiwyg editing
     * From both wiki page source (for wysiwyg_htmltowiki) and "html" modes
     *
     * @param $inData string    page data, can be wiki or mixed html/wiki
     * @param bool $fromWiki    set if converting from wiki page using "switch editor"
     * @param bool $isHtml      true if $inData is HTML, false if wiki
     * @return string           html to send to ckeditor
     */

    public function parseToWysiwyg($inData, $fromWiki = false, $isHtml = false, $options = [])
    {
        global $tikiroot, $prefs;

        $allowImageLazyLoad = $prefs['allowImageLazyLoad'];
        $prefs['allowImageLazyLoad'] = 'n';

        // Parsing page data for wysiwyg editor
        $inData = $this->partialParseWysiwygToWiki($inData);    // remove any wysiwyg plugins so they don't get double parsed
        $parsed = preg_replace('/(!!*)[\+\-]/m', '$1', $inData);        // remove show/hide headings
        $parsed = preg_replace('/'/', '\'', $parsed);          // catch single quotes at html entities

        if (preg_match('/^\|\|.*\|\|$/', $parsed)) {    // new tables get newlines converted to 
then to %%% $parsed = str_replace('
', "\n", $parsed); } $parsed = TikiLib::lib('parser')->parse_data( $parsed, array_merge([ 'absolute_links' => true, 'noheaderinc' => true, 'suppress_icons' => true, 'ck_editor' => true, 'is_html' => ($isHtml && ! $fromWiki), 'process_wiki_paragraphs' => (! $isHtml || $fromWiki), 'process_double_brackets' => 'y' ], $options) ); if ($fromWiki) { $parsed = preg_replace('/^\s*

 [\s]*<\/p>\s*/iu', '', $parsed); // remove added empty

} $parsed = preg_replace('/(.*?)<\/span>/im', '$1', $parsed); // remove spans round img's // Workaround Wysiwyg Image Plugin Editor in IE7 erases image on insert http://dev.tiki.org/item3615 $parsed2 = preg_replace('/(<\/span>)<\/p>/is', '$1 

', $parsed); if ($parsed2 !== null) { $parsed = $parsed2; } // Fix IE7 wysiwyg editor always adding absolute path if (isset($_SERVER['HTTP_HOST'])) { $search = '/(]+href=\")https?\:\/\/' . preg_quote($_SERVER['HTTP_HOST'] . $tikiroot, '/') . '([^>]+_cke_saved_href)/i'; } else { $search = '/(]+href=\")https?\:\/\/' . preg_quote($_SERVER['SERVER_NAME'] . $tikiroot, '/') . '([^>]+_cke_saved_href)/i'; } $parsed = preg_replace($search, '$1$2', $parsed); if (! $isHtml) { // Fix for plugin being the last item in a page making it impossible to add new lines (new text ends up inside the plugin) $parsed = preg_replace('/<\/(span|div)>(<\/p>)?$/', ' $2', $parsed); // also if first $parsed = preg_replace('/^<(div|span) class="tiki_plugin"/', ' <$1 class="tiki_plugin"', $parsed); } $prefs['allowImageLazyLoad'] = $allowImageLazyLoad; return $parsed; } /** * Converts wysiwyg plugins into wiki. * Also processes headings by removing surrounding

* Also used by ajax preview in Services_Edit_Controller * * @param string $inData page data - mostly html but can have a bit of wiki in it * @return string html with wiki plugins */ public static function partialParseWysiwygToWiki($inData) { if (empty($inData)) { return ''; } // de-protect ck_protected comments $ret = preg_replace('//i', '', $inData); if (! $ret) { $ret = $inData; trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 1, preg_last_error())); } // remove the wysiwyg plugin elements leaving the syntax only remaining $ret2 = preg_replace('/<(?:div|span)[^>]*syntax="(.*)".*end tiki_plugin --><\/(?:div|span)>/Umis', "$1", $ret); // preg_replace blows up here with a PREG_BACKTRACK_LIMIT_ERROR on pages with "corrupted" plugins if (! $ret2) { trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 2, preg_last_error())); } else { $ret = $ret2; } // take away the

that f/ck introduces around wiki heading ! to have maketoc/edit section working $ret2 = preg_replace('/

\s*!(.*)<\/p>/Umis', "!$1\n", $ret); if (! $ret2) { trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 3, preg_last_error())); } else { $ret = $ret2; } // strip the last empty

tag generated somewhere (ckeditor 3.6, Tiki 10) $ret2 = preg_replace('/\s*

\s*<\/p>\s*$/Umis', "$1\n", $ret); if (! $ret2) { trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 4, preg_last_error())); } else { $ret = $ret2; } // convert tikicomment tags back to ~tc~tiki comments~/tc~ $ret2 = preg_replace('/(.*)<\/tikicomment>/Umis', '~tc~$1~/tc~', $ret); if (! $ret2) { trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 5, preg_last_error())); } else { $ret = $ret2; } return $ret; } // parse HTML functions /** * \brief Parsed HTML tree walker (used by HTML sucker) * * This is initial implementation (stupid... w/o any intellegence (almost :)) * It is rapidly designed version... just for test: 'can this feature be useful'. * Later it should be replaced by well designed one :) don't bash me now :) * * \param &$c array -- parsed HTML * \param &$src string -- output string * \param &$p array -- ['stack'] = closing strings stack, ['listack'] = stack of list types currently opened ['first_td'] = flag: 'is was just before this ' ['first_tr'] = flag: 'is was just before this ' */ public function walk_and_parse(&$c, &$src, &$p, $head_url) { global $prefs; // If no string if (! $c) { return; } $parserlib = TikiLib::lib('parser'); for ($i = 0; $i <= $c['contentpos']; $i++) { $node = $c[$i]; // If content type 'text' output it to destination... if ($node['type'] == 'text') { if (! ctype_space($node['data'])) { $add = $node['data']; $noparsed = []; $parserlib->plugins_remove($add, $noparsed); $add = str_replace(["\r","\n"], '', $add); $add = str_replace(' ', ' ', $add); $add = str_replace('[', '[[', $add); // escape square brackets to prevent accidental wiki links $parserlib->plugins_replace($add, $noparsed, true); $src .= $add; } else { $src .= str_replace(["\n", "\r"], '', $node['data']); // keep the spaces } } elseif ($node['type'] == 'comment') { $src .= preg_replace('//', "~/hc~\n", $node['data'])); } elseif ($node['type'] == 'tag') { if ($node['data']['type'] == 'open') { // Open tag type // deal with plugins - could be either span of div so process before the switch statement if (isset($node['pars']['plugin']) && isset($node['pars']['syntax'])) { // handling for tiki plugins $src .= html_entity_decode($node['pars']['syntax']['value']); $more_spans = 1; $elem_type = $node['data']['name']; $other_elements = 0; $j = $i + 1; while ($j < $c['contentpos']) { // loop through contents of this span and discard everything if ($c[$j]['data']['name'] == $elem_type && $c[$j]['data']['type'] == 'close') { $more_spans--; if ($more_spans === 0) { break; } // } else if ($c[$j]['data']['name'] == 'br' && $more_spans === 1 && $other_elements === 0) { } elseif ($c[$j]['data']['name'] == $elem_type && $c[$j]['data']['type'] == 'open') { $more_spans++; } elseif ($c[$j]['data']['type'] == 'open' && $c[$j]['data']['name'] != 'br' && $c[$j]['data']['name'] != 'img' && $c[$j]['data']['name'] != 'input') { $other_elements++; } elseif ($c[$j]['data']['type'] == 'close') { $other_elements--; } $j++; } $i = $j; // skip everything that was inside this span } $isPar = false; // assuming "div" when calling parseParDivTag() switch ($node['data']['name']) { // Tags we don't want at all. case 'meta': case 'link': $node['content'] = ''; break; case 'script': $node['content'] = ''; if (! isset($node['pars']['src'])) { $i++; while ($node['type'] === 'text' || ($node['data']['name'] !== 'script' && $node['data']['type'] !== 'close' && $i <= $c['contentpos'])) { $i++; // skip contents of script tag } } break; case 'style': $node['content'] = ''; $i++; while ($node['data']['name'] !== 'style' && $node['data']['type'] !== 'close' && $i <= $c['contentpos']) { $i++; // skip contents of script tag } break; // others we do want case 'br': if ($p['wiki_lbr']) { // "%%%" or "\n" ? $src .= ' %%% '; } else { // close all wiki tags foreach (array_reverse($p['wikistack']) as $wiki_arr) { foreach (array_reverse($wiki_arr['end']) as $end) { $src .= $end; } } $src .= "\n"; // for lists, we must prepend '+' to keep the indentation if ($p['listack']) { $src .= str_repeat('+', count($p['listack'])); } // reopen all wikitags foreach ($p['wikistack'] as $wiki_arr) { foreach ($wiki_arr['begin'] as $begin) { $src .= $begin; } } } break; case 'hr': $src .= $this->startNewLine($src) . '---'; break; case 'title': $src .= "\n!"; $p['stack'][] = ['tag' => 'title', 'string' => "\n"]; break; case 'p': $isPar = true; if ($src && $prefs['feature_wiki_paragraph_formatting'] !== 'y') { $src .= "\n"; } // no break case 'div': // Wiki parsing creates divs for center if (isset($node['pars'])) { $this->parseParDivTag($isPar, $node['pars'], $src, $p); } elseif (! empty($p['table'])) { $src .= '%%%'; } else { // normal para or div $src .= $this->startNewLine($src); $p['stack'][] = ['tag' => $node['data']['name'], 'string' => "\n\n"]; } break; case 'span': if (isset($node['pars'])) { $this->parseSpanTag($node['pars'], $src, $p); } break; case 'b': $this->processWikiTag('b', $src, $p, '__', '__', true); break; case 'i': $this->processWikiTag('i', $src, $p, '\'\'', '\'\'', true); break; case 'em': $this->processWikiTag('em', $src, $p, '\'\'', '\'\'', true); break; case 'strong': $this->processWikiTag('strong', $src, $p, '__', '__', true); break; case 'u': $this->processWikiTag('u', $src, $p, '===', '===', true); break; case 'strike': $this->processWikiTag('strike', $src, $p, '--', '--', true); break; case 'del': $this->processWikiTag('del', $src, $p, '--', '--', true); break; case 'center': if ($prefs['feature_use_three_colon_centertag'] == 'y') { $src .= ':::'; $p['stack'][] = ['tag' => 'center', 'string' => ':::']; } else { $src .= '::'; $p['stack'][] = ['tag' => 'center', 'string' => '::']; } break; case 'code': $src .= '-+'; $p['stack'][] = ['tag' => 'code', 'string' => '+-']; break; case 'dd': $src .= ':'; $p['stack'][] = ['tag' => 'dd', 'string' => "\n"]; break; case 'dt': $src .= ';'; $p['stack'][] = ['tag' => 'dt', 'string' => '']; break; case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': $p['wiki_lbr']++; // force wiki line break mode $hlevel = (int) $node['data']['name'][1]; if (isset($node['pars']['style']['value']) && strpos($node['pars']['style']['value'], 'text-align: center;') !== false) { if ($prefs['feature_use_three_colon_centertag'] == 'y') { $src .= $this->startNewLine($src) . str_repeat('!', $hlevel) . ':::'; $p['stack'][] = ['tag' => $node['data']['name'], 'string' => ":::\n"]; } else { $src .= $this->startNewLine($src) . str_repeat('!', $hlevel) . '::'; $p['stack'][] = ['tag' => $node['data']['name'], 'string' => "::\n"]; } } else { // normal para or div $src .= $this->startNewLine($src) . str_repeat('!', $hlevel); $p['stack'][] = ['tag' => $node['data']['name'], 'string' => "\n"]; } break; case 'pre': $src .= "~pre~\n"; $p['stack'][] = ['tag' => 'pre', 'string' => "~/pre~\n"]; break; case 'sub': $src .= '{SUB()}'; $p['stack'][] = ['tag' => 'sub', 'string' => '{SUB}']; break; case 'sup': $src .= '{SUP()}'; $p['stack'][] = ['tag' => 'sup', 'string' => '{SUP}']; break; case 'tt': $src .= '{DIV(type="tt")}'; $p['stack'][] = ['tag' => 'tt', 'string' => '{DIV}']; break; case 's': $src .= $this->processWikiTag('s', $src, $p, '--', '--', true); break; // Table parser case 'table': $src .= $this->startNewLine($src) . '||'; $p['stack'][] = ['tag' => 'table', 'string' => '||']; $p['first_tr'] = true; $p['table'] = true; break; case 'tr': if (! $p['first_tr']) { $this->startNewLine($src); } $p['first_tr'] = false; $p['first_td'] = true; $p['wiki_lbr']++; // force wiki line break mode break; case 'td': if ($p['first_td']) { $src .= ''; } else { $src .= '|'; } $p['first_td'] = false; break; // Lists parser case 'ul': $p['listack'][] = '*'; break; case 'ol': $p['listack'][] = '#'; break; case 'li': // Generate wiki list item according to current list depth. $src .= $this->startNewLine($src) . str_repeat(end($p['listack']), count($p['listack'])); break; case 'font': // If color attribute present in tag if (isset($node['pars']['color']['value'])) { $src .= '~~' . $node['pars']['color']['value'] . ':'; $p['stack'][] = ['tag' => 'font', 'string' => '~~']; } break; case 'img': // If src attribute present in tag if (isset($node['pars']['src']['value'])) { // Note what it produce (img) not {img}! Will fix this below... if (strstr($node['pars']['src']['value'], 'http:')) { $src .= '{img src="' . $node['pars']['src']['value'] . '"}'; } else { $src .= '{img src="' . $head_url . $node['pars']['src']['value'] . '"}'; } } break; case 'a': if (isset($node['pars'])) { // get the link text $text = ''; if ($i < count($c)) { $next_token = &$c[$i + 1]; if (isset($next_token['type']) && $next_token['type'] == 'text' && isset($next_token['data'])) { $text = &$next_token['data']; } } // parse the link $this->parseLinkTag($node['pars'], $text, $src, $p); } // deactivated by mauriz, will be replaced by the routine above // If href attribute present in tag /* if (isset($c[$i]['pars']['href']['value'])) { if ( strstr( $c[$i]['pars']['href']['value'], 'http:' )) { $src .= '['.$c[$i]['pars']['href']['value'].'|'; } else { $src .= '['.$head_url.$c[$i]['pars']['href']['value'].'|'; } $p['stack'][] = array('tag' => 'a', 'string' => ']'); } if ( isset($c[$i]['pars']['name']['value'])) { $src .= '{ANAME()}'.$c[$i]['pars']['name']['value'].'{ANAME}'; } */ break; } // end switch on tag name } else { // This is close tag type. Is that smth we r waiting for? switch ($node['data']['name']) { case 'ul': if (end($p['listack']) == '*') { array_pop($p['listack']); } if (empty($p['listack'])) { $src .= "\n"; } break; case 'ol': if (end($p['listack']) == '#') { array_pop($p['listack']); } if (empty($p['listack'])) { $src .= "\n"; } break; default: $e = end($p['stack']); if (isset($e['tag']) && $node['data']['name'] == $e['tag']) { $src .= $e['string']; array_pop($p['stack']); } if ($node['data']['name'] === 'table') { $p['table'] = false; } break; } // update the wiki stack if (isset($e['wikitags']) && $e['wikitags']) { for ($i_wiki = 0; $i_wiki < $e['wikitags']; $i_wiki++) { array_pop($p['wikistack']); } } // can we leave wiki line break mode ? switch ($node['data']['name']) { case 'a': case 'h1': case 'h2': case 'h3': case 'h4': case 'h5': case 'h6': case 'tr': $p['wiki_lbr']--; break; } } } // Recursive call on tags with content... if (! empty($node['content'])) { if (substr($src, -1) != ' ') { $src .= ' '; } $this->walk_and_parse($node['content'], $src, $p, $head_url); } } if (substr($src, -2) == "\n\n") { // seem to always get too many line ends $src = substr($src, 0, -2); } } public function startNewLine(&$str) { if (strlen($str) && substr($str, -1) != "\n") { $str .= "\n"; } } /** * wrapper around zaufi's HTML sucker code just to use the html to wiki bit * * @param string $inHtml -- HTML in * @return null|string * @throws Exception */ public function parse_html(&$inHtml) { include(__DIR__ . '/../htmlparser/htmlparser.inc'); // Read compiled (serialized) grammar $grammarfile = TIKI_PATH . '/lib/htmlparser/htmlgrammar.cmp'; if (! $fp = @fopen($grammarfile, 'r')) { $smarty = TikiLib::lib('smarty'); $smarty->assign('msg', tra("Can't parse HTML data - no grammar file")); $smarty->display("error.tpl"); die; } $grammar = unserialize(fread($fp, filesize($grammarfile))); fclose($fp); // process a few ckeditor artifacts $inHtml = str_replace('

', '', $inHtml); // empty p tags are invisible // create parser object, insert html code and parse it $htmlparser = new HtmlParser($inHtml, $grammar, '', 0); $htmlparser->Parse(); // Should I try to convert HTML to wiki? $out_data = ''; /* * ['stack'] = array * Speacial keys introduced to convert to Wiki * - ['wikitags'] = the number of 'wikistack' entries produced by the html tag * * ['wikistack'] = array(), is used to save the wiki markup for the linebreak handling (1 array = 1 html tag) * Each array entry contains the following keys: * - ['begin'] = array() of begin markups (1 style definition = 1 array entry) * - ['end'] = array() of end markups * * wiki_lbr = true if we must use '%%%' for linebreaks instead of '\n' */ $p = ['stack' => [], 'listack' => [], 'wikistack' => [], 'wiki_lbr' => 0, 'first_td' => false, 'first_tr' => false]; $this->walk_and_parse($htmlparser->content, $out_data, $p, ''); // Is some tags still opened? (It can be if HTML not valid, but this is not reason // to produce invalid wiki :) while (count($p['stack'])) { $e = end($p['stack']); $out_data .= $e['string']; array_pop($p['stack']); } // Unclosed lists r ignored... wiki have no special start/end lists syntax.... // OK. Things remains to do: // 1) fix linked images $out_data = preg_replace(',\[(.*)\|\(img src=(.*)\)\],mU', '{img src=$2 link=$1}', $out_data); // 2) fix remains images (not in links) $out_data = preg_replace(',\(img src=(.*)\),mU', '{img src=$1}', $out_data); // 3) remove empty lines $out_data = preg_replace(",[\n]+,mU", "\n", $out_data); // 4) remove nbsp's $out_data = preg_replace(", ,mU", " ", $out_data); return $out_data; } public function get_new_page_attributes_from_parent_pages($page, $page_info) { $tikilib = TikiLib::lib('tiki'); $wikilib = TikiLib::lib('wiki'); $new_page_attrs = []; $parent_pages = $wikilib->get_parent_pages($page); $parent_pages_info = []; foreach ($parent_pages as $a_parent_page_name) { $this_parent_page_info = $tikilib->get_page_info($a_parent_page_name); $parent_pages_info[] = $this_parent_page_info; } $new_page_attrs = $this->get_newpage_language_from_parent_page($page, $page_info, $parent_pages_info, $new_page_attrs); // Note: in the future, may add some methods below to guess things like // categories, workspaces, etc... return $new_page_attrs; } public function get_newpage_language_from_parent_page($page, $page_info, $parent_pages_info, $new_page_attrs) { if (! isset($page_info['lang'])) { $lang = null; foreach ($parent_pages_info as $this_parent_page_info) { if (isset($this_parent_page_info['lang'])) { if ($lang != null and $lang != $this_parent_page_info['lang']) { // If more than one parent pages and they have different languages // then we can't guess which is the right one. $lang = null; break; } else { $lang = $this_parent_page_info['lang']; } } } if ($lang != null) { $new_page_attrs['lang'] = $lang; } } return $new_page_attrs; } /** * Bound to tiki.save to find and notifiy users of any mentions * * @param array $args * * @throws Exception */ final public function process_mentions(array $arguments): void { global $prefs; // Notify users/usergroups if ($prefs['feature_notify_users_mention'] === 'y' && $prefs['feature_tag_users'] === 'y') { if ($arguments['type'] === 'wiki page') { $oldData = $arguments['old_data'] ?? ''; $newData = $arguments['data'] ?? ''; } elseif ($arguments['type'] === 'trackeritem' && isset($arguments['values_by_permname'])) { $oldData = implode("\n", array_values(array_diff($arguments['old_values_by_permname'], $arguments['values_by_permname']))); $newData = implode("\n", array_values(array_diff($arguments['values_by_permname'], $arguments['old_values_by_permname']))); } elseif (isset($arguments['content'])) { // 'forum post', 'blog post', comments $oldData = ''; $newData = $arguments['content'] ?? ''; } else { $oldData = ''; $newData = ''; } if ($oldData || $newData) { $oldData = explode("\n", $oldData); $newData = explode("\n", $newData); $sections = []; require_once('lib/diff/difflib.php'); $textDiff = new Text_Diff($oldData, $newData, true); if (! $textDiff->isEmpty()) { foreach ($textDiff->_edits as $edit) { if (is_a($edit, 'Text_Diff_Op_add')) { // new content $sections[] = findMentions($edit->final, 'new'); } elseif (is_a($edit, 'Text_Diff_Op_change')) { // change or new content $sections[] = findMentionsOnChange($edit); } elseif (is_a($edit, 'Text_Diff_Op_copy')) { // no diffs on content $sections[] = findMentions($edit->final, 'old'); } } } $count = 1; $tikiLibUser = TikiLib::lib('user'); $smarty = TikiLib::lib('smarty'); $smarty->loadPlugin('smarty_function_object_link'); $objectUrl = smarty_function_object_link( ['type' => $arguments['type'], 'id' => $arguments['object']], $smarty->getEmptyInternalTemplate() ); // strip out html to get the plain url and title? if (preg_match('/(.*?)<\/a>/', $objectUrl, $matches)) { $objectUrl = $matches[1]; $title = html_entity_decode($matches[2], ENT_QUOTES); } foreach (array_filter($sections) as $sec) { $notifiedUsers = []; foreach ($sec as $possibleUser) { if (isset($possibleUser) && $possibleUser['state'] == 'new') { $mentionedBy = $tikiLibUser->clean_user($arguments['user']); // gets realName $mentionedUser = substr($possibleUser['mention'], strpos($possibleUser['mention'], "@") + 1); if ($mentionedUser) { $userInfo = $tikiLibUser->get_user_info($mentionedUser); if (is_array($userInfo) && $userInfo['userId'] > 0) { if (! in_array($mentionedUser, $notifiedUsers)) { $url = $objectUrl .= '#mentioned-' . $mentionedUser . '-section-' . $count; $emailData = [ 'siteName' => TikiLib::lib('tiki')->get_preference('browsertitle'), 'mentionedBy' => $mentionedBy, 'url' => $url, 'type' => $arguments['type'], 'title' => $title ?? $arguments['object'], ]; Tiki\Notifications\Email::sendMentionNotification( 'mention_notification_subject.tpl', 'mention_notification.tpl', $emailData, [$userInfo] ); $notifiedUsers[] = $mentionedUser; } $count++; } } } } } } } } }