<?php
|
|
|
|
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
|
|
//
|
|
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
|
|
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
|
|
// $Id$
|
|
|
|
/****
|
|
* Initially just a collection of the functions dotted around tiki-editpage.php for v4.0
|
|
* Started in the edit_fixup experimental branch - jonnybradley Aug 2009
|
|
*
|
|
*/
|
|
|
|
class EditLib
|
|
{
|
|
private $tracesOn = false;
|
|
|
|
// Fields for translation related methods.
|
|
public $sourcePageName = null;
|
|
public $targetPageName = null;
|
|
public $oldSourceVersion = null;
|
|
public $newSourceVersion = null;
|
|
|
|
// Fields for handling links to external wiki pages
|
|
private $external_wikis = null;
|
|
|
|
|
|
// general
|
|
|
|
public function make_sure_page_to_be_created_is_not_an_alias($page, $page_info)
|
|
{
|
|
$access = TikiLib::lib('access');
|
|
$tikilib = TikiLib::lib('tiki');
|
|
$wikilib = TikiLib::lib('wiki');
|
|
$semanticlib = TikiLib::lib('semantic');
|
|
|
|
$aliases = $semanticlib->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:") . " " .
|
|
"<b>$page</b>.\n<p>\n";
|
|
$error_msg .= tra("That page is an alias for the following pages") . ": ";
|
|
foreach ($aliases as $an_alias) {
|
|
$error_msg .= '<a href="' . $wikilib->editpage_url($an_alias['fromPage'], false) . '">' . $an_alias['fromPage'] . '</a>, ';
|
|
}
|
|
$error_msg .= "\n<p>\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 .= ": <b>(alias($page))</b>";
|
|
|
|
$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 <p>, false if a <div>
|
|
* @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 (<br />).
|
|
* - 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 class=["\']tiki_plugin["\']>(.*?)<\/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 <br> then to %%%
|
|
$parsed = str_replace('<br>', "\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*<p> [\s]*<\/p>\s*/iu', '', $parsed); // remove added empty <p>
|
|
}
|
|
$parsed = preg_replace('/<span class=\"img\">(.*?)<\/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 class=\"tiki_plugin\".*?plugin=\"img\".*?><\/span>)<\/p>/is', '$1<span> </span></p>', $parsed);
|
|
if ($parsed2 !== null) {
|
|
$parsed = $parsed2;
|
|
}
|
|
// Fix IE7 wysiwyg editor always adding absolute path
|
|
if (isset($_SERVER['HTTP_HOST'])) {
|
|
$search = '/(<a[^>]+href=\")https?\:\/\/' . preg_quote($_SERVER['HTTP_HOST'] . $tikiroot, '/') . '([^>]+_cke_saved_href)/i';
|
|
} else {
|
|
$search = '/(<a[^>]+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('/<!-- end tiki_plugin --><\/(span|div)>(<\/p>)?$/', '<!-- end tiki_plugin --></$1> $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 <p>
|
|
* 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('/<!--{cke_protected}{C}%3C!%2D%2D%20end%20tiki_plugin%20%2D%2D%3E-->/i', '<!-- end tiki_plugin -->', $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 <p> that f/ck introduces around wiki heading ! to have maketoc/edit section working
|
|
$ret2 = preg_replace('/<p>\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 <p> tag generated somewhere (ckeditor 3.6, Tiki 10)
|
|
$ret2 = preg_replace('/\s*<p>\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>(.*)<\/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 <tr> was just before this <td>'
|
|
['first_tr'] = flag: 'is <table> was just before this <tr>'
|
|
*/
|
|
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('/<!--/', "\n~hc~", 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 <font> 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 <img> 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 <a> 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('<p></p>', '', $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.* href=["\'](.*?)["\'].*>(.*?)<\/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++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|