1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8/**** 9 * Initially just a collection of the functions dotted around tiki-editpage.php for v4.0 10 * Started in the edit_fixup experimental branch - jonnybradley Aug 2009 11 * 12 */ 13 14class EditLib 15{ 16 private $tracesOn = false; 17 18 // Fields for translation related methods. 19 public $sourcePageName = null; 20 public $targetPageName = null; 21 public $oldSourceVersion = null; 22 public $newSourceVersion = null; 23 24 // Fields for handling links to external wiki pages 25 private $external_wikis = null; 26 27 28 // general 29 30 function make_sure_page_to_be_created_is_not_an_alias($page, $page_info) 31 { 32 $access = TikiLib::lib('access'); 33 $tikilib = TikiLib::lib('tiki'); 34 $wikilib = TikiLib::lib('wiki'); 35 $semanticlib = TikiLib::lib('semantic'); 36 37 $aliases = $semanticlib->getAliasContaining($page, true); 38 if (! $page_info && count($aliases) > 0) { 39 $error_title = tra("Cannot create aliased page"); 40 $error_msg = tra("You attempted to create the following page:") . " " . 41 "<b>$page</b>.\n<p>\n"; 42 $error_msg .= tra("That page is an alias for the following pages") . ": "; 43 foreach ($aliases as $an_alias) { 44 $error_msg .= '<a href="' . $wikilib->editpage_url($an_alias['fromPage'], false) . '">' . $an_alias['fromPage'] . '</a>, '; 45 } 46 $error_msg .= "\n<p>\n"; 47 $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"); 48 $error_msg .= ": <b>(alias($page))</b>"; 49 50 $access->display_error(page, $error_title, "", true, $error_msg); 51 } 52 } 53 54 function user_needs_to_specify_language_of_page_to_be_created($page, $page_info, $new_page_inherited_attributes = null) 55 { 56 global $prefs; 57 if (isset($_REQUEST['need_lang']) && $_REQUEST['need_lang'] == 'n') { 58 return false; 59 } 60 if ($prefs['feature_multilingual'] == 'n') { 61 return false; 62 } 63 if ($page_info && isset($page_info['lang']) && $page_info['lang'] != '') { 64 return false; 65 } 66 if (isset($_REQUEST['lang']) && $_REQUEST['lang'] != '') { 67 return false; 68 } 69 if ($new_page_inherited_attributes != null && 70 isset($new_page_inherited_attributes['lang']) && 71 $new_page_inherited_attributes['lang'] != '') { 72 return false; 73 } 74 75 return true; 76 } 77 78 // translation functions 79 80 function isTranslationMode() 81 { 82 return $this->isUpdateTranslationMode() || $this->isNewTranslationMode(); 83 } 84 85 function isNewTranslationMode() 86 { 87 global $prefs; 88 89 if ($prefs['feature_multilingual'] != 'y') { 90 return false; 91 } 92 if (isset($_REQUEST['translationOf']) 93 && ! empty($_REQUEST['translationOf'])) { 94 return true; 95 } 96 if (isset($_REQUEST['is_new_translation']) 97 && $_REQUEST['is_new_translation'] == 'y') { 98 return true; 99 } 100 return false; 101 } 102 103 function isUpdateTranslationMode() 104 { 105 return isset($_REQUEST['source_page']) 106 && isset($_REQUEST['oldver']) 107 && (! isset($_REQUEST['is_new_translation']) || $_REQUEST['is_new_translation'] == 'n') 108 && isset($_REQUEST['newver']); 109 } 110 111 function prepareTranslationData() 112 { 113 $this->setTranslationSourceAndTargetPageNames(); 114 $this->setTranslationSourceAndTargetVersions(); 115 } 116 117 private function setTranslationSourceAndTargetPageNames() 118 { 119 $smarty = TikiLib::lib('smarty'); 120 121 if (! $this->isTranslationMode()) { 122 return; 123 } 124 125 $this->targetPageName = null; 126 if (isset($_REQUEST['target_page'])) { 127 $this->targetPageName = $_REQUEST['target_page']; 128 } elseif (isset($_REQUEST['page'])) { 129 $this->targetPageName = $_REQUEST['page']; 130 } 131 $smarty->assign('target_page', $this->targetPageName); 132 133 $this->sourcePageName = null; 134 if (isset($_REQUEST['translationOf']) && $_REQUEST['translationOf']) { 135 $this->sourcePageName = $_REQUEST['translationOf']; 136 } elseif (isset($_REQUEST['source_page'])) { 137 $this->sourcePageName = $_REQUEST['source_page']; 138 } 139 $smarty->assign('source_page', $this->sourcePageName); 140 141 if ($this->isNewTranslationMode()) { 142 $smarty->assign('translationIsNew', 'y'); 143 } else { 144 $smarty->assign('translationIsNew', 'n'); 145 } 146 } 147 148 private function setTranslationSourceAndTargetVersions() 149 { 150 global $_REQUEST, $tikilib; 151 152 if (isset($_REQUEST['oldver'])) { 153 $this->oldSourceVersion = $_REQUEST['oldver']; 154 } else { 155 // Note: -1 means a "virtual" empty version. 156 $this->oldsourceVersion = -1; 157 } 158 159 if (isset($_REQUEST['newver'])) { 160 $this->newSourceVersion = $_REQUEST['newver']; 161 } else { 162 // Note: version number of 0 means the most recent version. 163 $this->newSourceVersion = 0; 164 } 165 } 166 167 function aTranslationWasSavedAs($complete_or_partial) 168 { 169 if (! $this->isTranslationMode() || 170 ! isset($_REQUEST['save'])) { 171 return false; 172 } 173 174 // We are saving a translation. Is it partial or complete? 175 if ($complete_or_partial == 'complete' && isset($_REQUEST['partial_save'])) { 176 return false; 177 } elseif ($complete_or_partial == 'partial' && ! isset($_REQUEST['partial_save'])) { 178 return false; 179 } 180 return true; 181 } 182 183 184 /** 185 * Convert rgb() color definiton to hex color definiton 186 * 187 * @param unknown_type $col 188 * @return The hex representation 189 */ 190 function parseColor(&$col) 191 { 192 193 if (preg_match("/^rgb\( *(\d+) *, *(\d+) *, *(\d+) *\)$/", $col, $parts)) { 194 $hex = str_pad(dechex($parts[1]), 2, '0', STR_PAD_LEFT) 195 . str_pad(dechex($parts[2]), 2, '0', STR_PAD_LEFT) 196 . str_pad(dechex($parts[3]), 2, '0', STR_PAD_LEFT); 197 $hex = '#' . TikiLib::strtoupper($hex); 198 } else { 199 $hex = $col; 200 } 201 202 return $hex; 203 } 204 205 206 /** 207 * Utility for walk_and_parse to process links 208 * 209 * @param array $args the attributes of the link 210 * @param array $text the link text 211 * @param string $src output string 212 * @param array $p ['stack'] = closing strings stack 213 */ 214 private function parseLinkTag(&$args, &$text, &$src, &$p) 215 { 216 217 global $prefs; 218 219 $link = ''; 220 $link_open = ''; 221 $link_close = ''; 222 223 /* 224 * parse the link classes 225 */ 226 $cl_wiki = false; 227 $cl_wiki_page = false; // Wiki page 228 $cl_ext_page = false; // external Wiki page 229 $cl_external = false; // external web page 230 $cl_semantic = ''; // semantic link 231 if ($prefs['feature_semantic'] === 'y') { 232 $semantic_tokens = TikiLib::lib('semantic')->getAllTokens(); 233 } else { 234 $semantic_tokens = []; 235 } 236 237 $ext_wiki_name = ''; 238 239 if (isset($args['class']) && isset($args['href'])) { 240 $matches = []; 241 preg_match_all('/([^ ]+)/', $args['class']['value'], $matches); 242 $classes = $matches[0]; 243 244 for ($i = 0, $count_classes = count($classes); $i < $count_classes; $i++) { 245 $cl = $classes[$i]; 246 247 switch ($cl) { 248 case 'wiki': 249 $cl_wiki = true; 250 break; 251 case 'wiki_page': 252 $cl_wiki_page = true; 253 break; 254 case 'ext_page': 255 $cl_ext_page = true; 256 break; 257 case 'external': 258 $cl_external = true; 259 break; 260 default: 261 // if the preceding class was 'ext_page', then we have the name of the external Wiki 262 if ($i > 0 && $classes[$i - 1] == 'ext_page') { 263 $ext_wiki_name = $cl; 264 } 265 if (in_array($cl, $semantic_tokens)) { 266 $cl_semantic = $cl; 267 } 268 } 269 } 270 } 271 272 273 /* 274 * extract the target and the anchor from the href 275 */ 276 if (isset($args['href'])) { 277 $href = urldecode($args['href']['value']); 278 // replace pipe chars as that's the delimiter in a wiki/external link 279 $href = str_replace('|', '%7C', $href); 280 $matches = explode('#', $href); 281 if (count($matches) == 2) { 282 $target = $matches[0]; 283 $anchor = '#' . $matches[1]; 284 } else { 285 $target = $href; 286 $anchor = ''; 287 } 288 } else { 289 $target = ''; 290 $anchor = ''; 291 } 292 293 294 /* 295 * treat invalid external Wikis as web links 296 */ 297 if ($cl_ext_page) { 298 // retrieve the definitions from the database 299 if ($this->external_wikis == null) { 300 global $tikilib; 301 $query = 'SELECT `name`, `extwiki` FROM `tiki_extwiki`'; 302 $this->external_wikis = $tikilib->fetchMap($query); 303 } 304 305 // name must be set and defined 306 if (! $ext_wiki_name || ! isset($this->external_wikis[$ext_wiki_name])) { 307 $cl_ext_page = false; 308 $cl_wiki_page = false; 309 } 310 }; 311 312 313 /* 314 * construct the links according to the defined classes 315 */ 316 if ($cl_wiki_page) { 317 /* 318 * link to wiki page -> (( )) 319 */ 320 $link_open = "($cl_semantic("; 321 $link_close = '))'; 322 323 // remove the html part of the target 324 $target = preg_replace('/tiki\-index\.php\?page\=/', '', $target); 325 326 // construct the link 327 $link = $target; 328 if ($anchor) { 329 $link .= '|' . $anchor; 330 } 331 } elseif ($cl_ext_page) { 332 /* 333 * link to external Wiki page ((:)) 334 */ 335 $link_open = '(('; 336 $link_close = '))'; 337 338 // remove the extwiki definition from the target 339 $def = preg_replace('/\$page/', '', $this->external_wikis[$ext_wiki_name]); 340 $def = preg_quote($def, '/'); 341 $target = preg_replace('/^' . $def . '/', '', $target); 342 343 // construct the link 344 $link = $ext_wiki_name . ':' . $target; 345 if ($anchor) { 346 $link .= '|' . $anchor; 347 } 348 } elseif ($cl_wiki && ! $cl_external && ! $target && strlen($anchor) > 0 && substr($anchor, 0, 1) == '#') { 349 /* 350 * inpage links [#] 351 */ 352 $link_open = '['; 353 $link_close = ']'; 354 355 // construct the link 356 $link = $target = $anchor; 357 $anchor = ''; 358 } elseif ($cl_wiki && ! $cl_external) { 359 /* 360 * other tiki resources [] 361 * -> articles, ... 362 */ 363 $link_open = '['; 364 $link_close = ']'; 365 366 // construct the link 367 $link = $target; 368 } elseif (! $cl_wiki && ! $cl_external && ! $text && isset($args['id']) && isset($args['id']['value'])) { 369 /* 370 * anchor 371 */ 372 $link_open = '{ANAME()}'; 373 $link_close = '{ANAME}'; 374 $link = $args['id']['value']; 375 } else { 376 /* 377 * other links [] 378 */ 379 $link_open = '['; 380 $link_close = ']'; 381 382 383 /* 384 * parse the rel attribute 385 */ 386 $box = ''; 387 388 if (isset($args['class']) && isset($args['rel'])) { 389 $matches = []; 390 preg_match_all('/([^ ]+)/', $args['rel']['value'], $matches); 391 $rels = $matches[0]; 392 393 for ($i = 0, $count_rels = count($rels); $i < $count_rels; $i++) { 394 $r = $rels[$i]; 395 396 if (preg_match('/^box/', $r)) { 397 $box = $r; 398 } 399 } 400 } 401 402 // construct the link 403 $link = $target; 404 if ($anchor) { 405 $link .= $anchor; 406 } 407 // the box must be appended to the text 408 if ($box) { 409 $text .= '|' . $box; 410 } 411 } // convert links 412 413 414 415 /* 416 * flush the constructed link 417 */ 418 if ($link_open && $link_close) { 419 $p['wiki_lbr']++; // force wiki line break mode 420 421 // does the link text match the target? 422 if ($target == trim($text)) { 423 $text = ''; // make sure walk_and_parse() does not append any text. 424 } else { 425 $link .= '|'; // the text will be appended by walk_and_parse() 426 } 427 428 // process the tag and update the output 429 $this->processWikiTag('a', $src, $p, $link_open, $link_close, true); 430 $src .= $link; 431 } 432 } 433 434 435 /** 436 * Utility for walk_and_parse to process p and div tags 437 * 438 * @param bool $isPar True if we process a <p>, false if a <div> 439 * @param array $args the attributes of the tag 440 * @param string $src output string 441 * @param array $p ['stack'] = closing strings stack 442 */ 443 private function parseParDivTag($isPar, &$args, &$src, &$p) 444 { 445 446 global $prefs; 447 448 if (isset($args['style']) || isset($args['align'])) { 449 $tag_name = $isPar ? 'p' : 'div'; // key for the $p[stack] 450 $type = $isPar ? 'type="p", ' : ''; // used for {DIV()} 451 452 $style = []; 453 $this->parseStyleAttribute($args['style']['value'], $style); 454 455 456 /* 457 * convert 'align' to 'style' definitions 458 */ 459 if (isset($args['align'])) { 460 $style['text-align'] = $args['align']['value']; 461 } 462 463 464 /* 465 * process all the defined styles 466 */ 467 foreach (array_keys($style) as $format) { 468 switch ($format) { 469 case 'text-align': 470 if ($style[$format] == 'left') { 471 $src .= "{DIV(${type}align=\"left\")}"; 472 $p['stack'][] = ['tag' => $tag_name, 'string' => '{DIV}']; 473 } elseif ($style[$format] == 'center') { 474 $markup = ($prefs['feature_use_three_colon_centertag'] == 'y') ? ':::' : '::'; 475 $this->processWikiTag($tag_name, $src, $p, $markup, $markup, false); 476 } elseif ($style[$format] == 'right') { 477 $src .= "{DIV(${type}align=\"right\")}"; 478 $p['stack'][] = ['tag' => $tag_name, 'string' => '{DIV}']; 479 } elseif ($style[$format] == 'justify') { 480 $src .= "{DIV(${type}align=\"justify\")}"; 481 $p['stack'][] = ['tag' => $tag_name, 'string' => '{DIV}']; 482 } 483 break; 484 } // switch format 485 } // foreach style 486 } 487 } 488 489 490 /** 491 * Utility for walk_and_parse to process span arguments 492 * 493 * @param array $args the attributes of the span 494 * @param string $src output string 495 * @param array $p ['stack'] = closing strings stack 496 */ 497 private function parseSpanTag(&$args, &$src, &$p) 498 { 499 500 if (isset($args['style'])) { 501 $style = []; 502 $this->parseStyleAttribute($args['style']['value'], $style); 503 504 $this->processWikiTag('span', $src, $p, '', '', true); // prepare the stacks, later we will append 505 506 507 /* 508 * The colors need to be handeled separatly; two style definitions become 509 * one single wiki markup. 510 */ 511 $fcol = ''; 512 $bcol = ''; 513 514 if (isset($style['color'])) { 515 $fcol = $this->parseColor($style['color']); 516 unset($style['color']); 517 } 518 if (isset($style['background-color'])) { // background: color-def have been converted to background-color 519 $bcol = $this->parseColor($style['background-color']); 520 unset($style['background-color']); 521 } 522 523 if ($fcol || $bcol) { 524 $col = "~~"; 525 $col .= ($fcol ? $fcol : ' '); 526 $col .= ($bcol ? ',' . $bcol : ''); 527 $col .= ':'; 528 529 $this->processWikiTag('span', $src, $p, $col, '~~', true, true); 530 } 531 532 533 /* 534 * Process the remaining format definitions 535 */ 536 foreach (array_keys($style) as $format) { 537 switch ($format) { 538 case 'font-weight': 539 if ($style[$format] == 'bold') { 540 $this->processWikiTag('span', $src, $p, '__', '__', true, true); 541 } 542 break; 543 case 'font-style': 544 if ($style[$format] == 'italic') { 545 $this->processWikiTag('span', $src, $p, '\'\'', '\'\'', true, true); 546 } 547 case 'text-decoration': 548 if ($style[$format] == 'line-through') { 549 $this->processWikiTag('span', $src, $p, '--', '--', true, true); 550 } elseif ($style[$format] == 'underline') { 551 $this->processWikiTag('span', $src, $p, '===', '===', true, true); 552 } 553 } // switch format 554 } // foreach style 555 } // style 556 } 557 558 559 /** 560 * Parse a html style definition into an array 561 * 562 * This method tries to expand the shorthand definitions, such as 'background:', 563 * to the correspoinding key/value paris. If a definition is unknown, it is kept. 564 * 565 * @param string $style The value of the style attribute 566 * @param array $parsed key/value pairs 567 */ 568 function parseStyleAttribute(&$style, &$parsed) 569 { 570 571 $matches = []; 572 preg_match_all('/ *([^ :]+) *: *([^;]+) *;?/', $style, $matches); 573 574 for ($i = 0, $count_matches = count($matches[0]); $i < $count_matches; $i++) { 575 $key = $matches[1][$i]; 576 $value = trim($matches[2][$i]); 577 578 /* 579 * shortand list 'background:' 580 * - set 'background-color' 581 */ 582 if ($key == 'background') { 583 $unprocessed = ''; 584 $shorthand = []; 585 $this->parseStyleList($value, $shorthand); 586 587 foreach ($shorthand as $s) { 588 switch ($s) { 589 case preg_match('/^#(\w{3,6})$/', $s) > 0: 590 $parsed['background-color'] = $s; 591 break; 592 case preg_match('/^rgb\(.*\)$/', $s) > 0: 593 $parsed['background-color'] = $s; 594 break; 595 default: 596 $unprocessed .= ' ' . $s; 597 } 598 } // foreach shorthand 599 600 // keep unprocessed list entries 601 $value = trim($unprocessed); 602 } // background: 603 604 // save the result 605 if ($value) { 606 $parsed[$key] = $value; 607 } 608 } // style definitions 609 } 610 611 612 /** 613 * Parse a space separated list of html styles 614 * 615 * Example: "rgb( 1, 2, 3) url(background.gif)" 616 * 617 * @param string $list List of styles 618 * @param array $parsed The parsed list 619 */ 620 function parseStyleList(&$list, &$parsed) 621 { 622 623 $matches = []; 624 preg_match_all('/(?:[[:graph:]]+\([^\)]*\))|(?:[^ ]+)/', $list, $matches); 625 $parsed = $matches[0]; 626 } 627 628 629 /** 630 * Utility of walk_and_parse to process a wiki tag 631 * 632 * Wiki tags need a special treatment: In html, a tag may contain 633 * several line breaks. In Wiki however, line breaks are often not allowed 634 * and sometimes additional line breaks are required. 635 * 636 * This method saves Wiki tags an line break information in separate stacks. 637 * These stackes are used in walk_and_parse to: 638 * - Output the required markup before and after the linebreaks (<br />). 639 * - To ensure that linebreaks are, if required, inserted at the correct place (\n). 640 * 641 * @param string $tag The name of the html tag 642 * @param string $src Th Output string 643 * @param array $p ['stack'] = closing strings stack, 644 * @param string $begin The wiki markup that begins the tag 645 * @param string $end The wiki markup that ends the tag 646 * @param bool $is_inline True if the tag is inline, false if the tag must span exacly one line. 647 * @param bool $append True = append to the topmost element on the stack, false = create a new element on the stack 648 */ 649 private function processWikiTag($tag, &$src, &$p, $begin, $end, $is_inline, $append = false) 650 { 651 652 // append=false, create new entries on the stack 653 if (! $append) { 654 $p['stack'][] = ['tag' => $tag, 'string' => '', 'wikitags' => 0 ]; 655 $p['wikistack'][] = [ 'begin' => [], 'end' => [] ]; 656 }; 657 658 // get the entry points on the stacks 659 $keys = array_keys($p['wikistack']); 660 $key = end($keys); 661 $wiki = &$p['wikistack'][$key]; 662 663 $keys = array_keys($p['stack']); 664 $key = end($keys); 665 $stack = &$p['stack'][$key]; 666 $string = &$stack['string']; 667 668 // append to the stacks 669 $wiki['begin'][] = $begin; 670 $wiki['end'][] = $end; 671 $string = $end . $string; 672 $stack['wikitags']++; 673 674 // update the output string 675 if (! $is_inline) { 676 $this->startNewLine($src); 677 } 678 $src .= $begin; 679 } 680 681 682 function saveCompleteTranslation() 683 { 684 $multilinguallib = TikiLib::lib('multilingual'); 685 $tikilib = TikiLib::lib('tiki'); 686 687 $sourceInfo = $tikilib->get_page_info($this->sourcePageName); 688 $targetInfo = $tikilib->get_page_info($this->targetPageName); 689 690 $multilinguallib->propagateTranslationBits( 691 'wiki page', 692 $sourceInfo['page_id'], 693 $targetInfo['page_id'], 694 $sourceInfo['version'], 695 $targetInfo['version'] 696 ); 697 $multilinguallib->deleteTranslationInProgressFlags($targetInfo['page_id'], $sourceInfo['lang']); 698 } 699 700 function savePartialTranslation() 701 { 702 $tikilib = TikiLib::lib('tiki'); 703 $multilinguallib = TikiLib::lib('multilingual'); 704 705 $sourceInfo = $tikilib->get_page_info($this->sourcePageName); 706 $targetInfo = $tikilib->get_page_info($this->targetPageName); 707 708 $multilinguallib->addTranslationInProgressFlags($targetInfo['page_id'], $sourceInfo['lang']); 709 } 710 711 /** 712 * Function to take html from ckeditor and parse back to wiki markup 713 * Used by "switch editor" and when saving in wysiwyg_htmltowiki mode 714 * When saving in mixed "html" mode the "unparsing" is done in JavaScript client-side 715 * 716 * @param $inData string editor content 717 * @return string wiki markup 718 */ 719 720 function parseToWiki($inData) 721 { 722 723 global $prefs; 724 725 $parsed = $this->partialParseWysiwygToWiki($inData); // remove cke type plugin wrappers 726 727 $parsed = html_entity_decode($parsed, ENT_QUOTES, 'UTF-8'); 728 $parsed = preg_replace('/\t/', '', $parsed); // remove all tabs inserted by the CKE 729 730 $parsed = preg_replace_callback('/<pre class=["\']tiki_plugin["\']>(.*?)<\/pre>/ims', [$this, 'parseToWikiPlugin'], $parsed); // rempve plugin wrappers 731 732 $parsed = $this->parse_html($parsed); 733 $parsed = preg_replace('/\{img\(? src=.*?img\/smiles\/icon_([\w\-]*?)\..*\}/im', '(:$1:)', $parsed); // "unfix" smilies 734 $parsed = preg_replace('/ /m', ' ', $parsed); // spaces 735 $parsed = preg_replace('/!(?:\d\.)+/', '!#', $parsed); // numbered headings 736 if ($prefs['feature_use_three_colon_centertag'] == 'y') { // numbered and centerd headings 737 $parsed = preg_replace('/!:::(?:\d\.)+ *(.*):::/', '!#:::\1:::', $parsed); 738 } else { 739 $parsed = preg_replace('/!::(?:\d\.)+ *(.*)::/', '!#::\1::', $parsed); 740 } 741 742 // remove empty center tags 743 if ($prefs['feature_use_three_colon_centertag'] == 'y') { // numbered and centerd headings 744 $parsed = preg_replace('/::: *:::\n/', '', $parsed); 745 } else { 746 $parsed = preg_replace('/:: *::\n/', '', $parsed); 747 } 748 749 // Put back htmlentities as normal char 750 $parsed = htmlspecialchars_decode($parsed, ENT_QUOTES); 751 return $parsed; 752 } 753 754 function parseToWikiPlugin($matches) 755 { 756 if (count($matches) > 1) { 757 return nl2br($matches[1]); 758 } 759 } 760 761 /** 762 * Render html to send to ckeditor, including parsing plugins for wysiwyg editing 763 * From both wiki page source (for wysiwyg_htmltowiki) and "html" modes 764 * 765 * @param $inData string page data, can be wiki or mixed html/wiki 766 * @param bool $fromWiki set if converting from wiki page using "switch editor" 767 * @param bool $isHtml true if $inData is HTML, false if wiki 768 * @return string html to send to ckeditor 769 */ 770 771 function parseToWysiwyg($inData, $fromWiki = false, $isHtml = false, $options = []) 772 { 773 global $tikiroot, $prefs; 774 775 $allowImageLazyLoad = $prefs['allowImageLazyLoad']; 776 $prefs['allowImageLazyLoad'] = 'n'; 777 778 // Parsing page data for wysiwyg editor 779 $inData = $this->partialParseWysiwygToWiki($inData); // remove any wysiwyg plugins so they don't get double parsed 780 $parsed = preg_replace('/(!!*)[\+\-]/m', '$1', $inData); // remove show/hide headings 781 $parsed = preg_replace('/'/', '\'', $parsed); // catch single quotes at html entities 782 783 if (preg_match('/^\|\|.*\|\|$/', $parsed)) { // new tables get newlines converted to <br> then to %%% 784 $parsed = str_replace('<br>', "\n", $parsed); 785 } 786 787 788 $parsed = TikiLib::lib('parser')->parse_data( 789 $parsed, 790 array_merge([ 791 'absolute_links' => true, 792 'noheaderinc' => true, 793 'suppress_icons' => true, 794 'ck_editor' => true, 795 'is_html' => ($isHtml && ! $fromWiki), 796 'process_wiki_paragraphs' => (! $isHtml || $fromWiki), 797 'process_double_brackets' => 'n' 798 ], $options) 799 ); 800 801 if ($fromWiki) { 802 $parsed = preg_replace('/^\s*<p> [\s]*<\/p>\s*/iu', '', $parsed); // remove added empty <p> 803 } 804 $parsed = preg_replace('/<span class=\"img\">(.*?)<\/span>/im', '$1', $parsed); // remove spans round img's 805 // Workaround Wysiwyg Image Plugin Editor in IE7 erases image on insert http://dev.tiki.org/item3615 806 $parsed2 = preg_replace('/(<span class=\"tiki_plugin\".*?plugin=\"img\".*?><\/span>)<\/p>/is', '$1<span> </span></p>', $parsed); 807 if ($parsed2 !== null) { 808 $parsed = $parsed2; 809 } 810 // Fix IE7 wysiwyg editor always adding absolute path 811 if (isset($_SERVER['HTTP_HOST'])) { 812 $search = '/(<a[^>]+href=\")https?\:\/\/' . preg_quote($_SERVER['HTTP_HOST'] . $tikiroot, '/') . '([^>]+_cke_saved_href)/i'; 813 } else { 814 $search = '/(<a[^>]+href=\")https?\:\/\/' . preg_quote($_SERVER['SERVER_NAME'] . $tikiroot, '/') . '([^>]+_cke_saved_href)/i'; 815 } 816 $parsed = preg_replace($search, '$1$2', $parsed); 817 818 if (! $isHtml) { 819 // Fix for plugin being the last item in a page making it impossible to add new lines (new text ends up inside the plugin) 820 $parsed = preg_replace('/<!-- end tiki_plugin --><\/(span|div)>(<\/p>)?$/', '<!-- end tiki_plugin --></$1> $2', $parsed); 821 // also if first 822 $parsed = preg_replace('/^<(div|span) class="tiki_plugin"/', ' <$1 class="tiki_plugin"', $parsed); 823 } 824 825 $prefs['allowImageLazyLoad'] = $allowImageLazyLoad; 826 827 return $parsed; 828 } 829 830 /** 831 * Converts wysiwyg plugins into wiki. 832 * Also processes headings by removing surrounding <p> 833 * Also used by ajax preview in Services_Edit_Controller 834 * 835 * @param string $inData page data - mostly html but can have a bit of wiki in it 836 * @return string html with wiki plugins 837 */ 838 839 static function partialParseWysiwygToWiki($inData) 840 { 841 if (empty($inData)) { 842 return ''; 843 } 844 // de-protect ck_protected comments 845 $ret = preg_replace('/<!--{cke_protected}{C}%3C!%2D%2D%20end%20tiki_plugin%20%2D%2D%3E-->/i', '<!-- end tiki_plugin -->', $inData); 846 if (! $ret) { 847 $ret = $inData; 848 trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 1, preg_last_error())); 849 } 850 // remove the wysiwyg plugin elements leaving the syntax only remaining 851 $ret2 = preg_replace('/<(?:div|span)[^>]*syntax="(.*)".*end tiki_plugin --><\/(?:div|span)>/Umis', "$1", $ret); 852 // preg_replace blows up here with a PREG_BACKTRACK_LIMIT_ERROR on pages with "corrupted" plugins 853 if (! $ret2) { 854 trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 2, preg_last_error())); 855 } else { 856 $ret = $ret2; 857 } 858 859 // take away the <p> that f/ck introduces around wiki heading ! to have maketoc/edit section working 860 $ret2 = preg_replace('/<p>\s*!(.*)<\/p>/Umis', "!$1\n", $ret); 861 if (! $ret2) { 862 trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 3, preg_last_error())); 863 } else { 864 $ret = $ret2; 865 } 866 867 // strip the last empty <p> tag generated somewhere (ckeditor 3.6, Tiki 10) 868 $ret2 = preg_replace('/\s*<p>\s*<\/p>\s*$/Umis', "$1\n", $ret); 869 if (! $ret2) { 870 trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 4, preg_last_error())); 871 } else { 872 $ret = $ret2; 873 } 874 875 // convert tikicomment tags back to ~tc~tiki comments~/tc~ 876 $ret2 = preg_replace('/<tikicomment>(.*)<\/tikicomment>/Umis', '~tc~$1~/tc~', $ret); 877 if (! $ret2) { 878 trigger_error(tr('Parse To Wiki %0: preg_replace error #%1', 5, preg_last_error())); 879 } else { 880 $ret = $ret2; 881 } 882 883 return $ret; 884 } 885 886 // parse HTML functions 887 888 /** 889 * \brief Parsed HTML tree walker (used by HTML sucker) 890 * 891 * This is initial implementation (stupid... w/o any intellegence (almost :)) 892 * It is rapidly designed version... just for test: 'can this feature be useful'. 893 * Later it should be replaced by well designed one :) don't bash me now :) 894 * 895 * \param &$c array -- parsed HTML 896 * \param &$src string -- output string 897 * \param &$p array -- ['stack'] = closing strings stack, 898 ['listack'] = stack of list types currently opened 899 ['first_td'] = flag: 'is <tr> was just before this <td>' 900 ['first_tr'] = flag: 'is <table> was just before this <tr>' 901 */ 902 function walk_and_parse(&$c, &$src, &$p, $head_url) 903 { 904 global $prefs; 905 // If no string 906 if (! $c) { 907 return; 908 } 909 $parserlib = TikiLib::lib('parser'); 910 911 for ($i = 0; $i <= $c['contentpos']; $i++) { 912 $node = $c[$i]; 913 // If content type 'text' output it to destination... 914 915 if ($node['type'] == 'text') { 916 if (! ctype_space($node['data'])) { 917 $add = $node['data']; 918 $noparsed = []; 919 $parserlib->plugins_remove($add, $noparsed); 920 $add = str_replace(["\r","\n"], '', $add); 921 $add = str_replace(' ', ' ', $add); 922 $add = str_replace('[', '[[', $add); // escape square brackets to prevent accidental wiki links 923 $parserlib->plugins_replace($add, $noparsed, true); 924 $src .= $add; 925 } else { 926 $src .= str_replace(["\n", "\r"], '', $node['data']); // keep the spaces 927 } 928 } elseif ($node['type'] == 'comment') { 929 $src .= preg_replace('/<!--/', "\n~hc~", preg_replace('/-->/', "~/hc~\n", $node['data'])); 930 } elseif ($node['type'] == 'tag') { 931 if ($node['data']['type'] == 'open') { 932 // Open tag type 933 934 // deal with plugins - could be either span of div so process before the switch statement 935 if (isset($node['pars']['plugin']) && isset($node['pars']['syntax'])) { // handling for tiki plugins 936 $src .= html_entity_decode($node['pars']['syntax']['value']); 937 $more_spans = 1; 938 $elem_type = $node['data']['name']; 939 $other_elements = 0; 940 $j = $i + 1; 941 while ($j < $c['contentpos']) { // loop through contents of this span and discard everything 942 if ($c[$j]['data']['name'] == $elem_type && $c[$j]['data']['type'] == 'close') { 943 $more_spans--; 944 if ($more_spans === 0) { 945 break; 946 } 947// } else if ($c[$j]['data']['name'] == 'br' && $more_spans === 1 && $other_elements === 0) { 948 } elseif ($c[$j]['data']['name'] == $elem_type && $c[$j]['data']['type'] == 'open') { 949 $more_spans++; 950 } elseif ($c[$j]['data']['type'] == 'open' && $c[$j]['data']['name'] != 'br' && $c[$j]['data']['name'] != 'img' && $c[$j]['data']['name'] != 'input') { 951 $other_elements++; 952 } elseif ($c[$j]['data']['type'] == 'close') { 953 $other_elements--; 954 } 955 $j++; 956 } 957 $i = $j; // skip everything that was inside this span 958 } 959 960 $isPar = false; // assuming "div" when calling parseParDivTag() 961 962 switch ($node['data']['name']) { 963 // Tags we don't want at all. 964 case 'meta': 965 case 'link': 966 $node['content'] = ''; 967 break; 968 case 'script': 969 $node['content'] = ''; 970 if (! isset($node['pars']['src'])) { 971 $i++; 972 while ($node['type'] === 'text' || ($node['data']['name'] !== 'script' && $node['data']['type'] !== 'close' && $i <= $c['contentpos'])) { 973 $i++; // skip contents of script tag 974 } 975 } 976 break; 977 case 'style': 978 $node['content'] = ''; 979 $i++; 980 while ($node['data']['name'] !== 'style' && $node['data']['type'] !== 'close' && $i <= $c['contentpos']) { 981 $i++; // skip contents of script tag 982 } 983 break; 984 985 // others we do want 986 case 'br': 987 if ($p['wiki_lbr']) { // "%%%" or "\n" ? 988 $src .= ' %%% '; 989 } else { 990 // close all wiki tags 991 foreach (array_reverse($p['wikistack']) as $wiki_arr) { 992 foreach (array_reverse($wiki_arr['end']) as $end) { 993 $src .= $end; 994 } 995 } 996 $src .= "\n"; 997 998 // for lists, we must prepend '+' to keep the indentation 999 if ($p['listack']) { 1000 $src .= str_repeat('+', count($p['listack'])); 1001 } 1002 1003 // reopen all wikitags 1004 foreach ($p['wikistack'] as $wiki_arr) { 1005 foreach ($wiki_arr['begin'] as $begin) { 1006 $src .= $begin; 1007 } 1008 } 1009 } 1010 break; 1011 case 'hr': 1012 $src .= $this->startNewLine($src) . '---'; 1013 break; 1014 case 'title': 1015 $src .= "\n!"; 1016 $p['stack'][] = ['tag' => 'title', 'string' => "\n"]; 1017 break; 1018 case 'p': 1019 $isPar = true; 1020 if ($src && $prefs['feature_wiki_paragraph_formatting'] !== 'y') { 1021 $src .= "\n"; 1022 } 1023 case 'div': // Wiki parsing creates divs for center 1024 if (isset($node['pars'])) { 1025 $this->parseParDivTag($isPar, $node['pars'], $src, $p); 1026 } elseif (! empty($p['table'])) { 1027 $src .= '%%%'; 1028 } else { // normal para or div 1029 $src .= $this->startNewLine($src); 1030 $p['stack'][] = ['tag' => $node['data']['name'], 'string' => "\n\n"]; 1031 } 1032 break; 1033 case 'span': 1034 if (isset($node['pars'])) { 1035 $this->parseSpanTag($node['pars'], $src, $p); 1036 } 1037 break; 1038 case 'b': 1039 $this->processWikiTag('b', $src, $p, '__', '__', true); 1040 break; 1041 case 'i': 1042 $this->processWikiTag('i', $src, $p, '\'\'', '\'\'', true); 1043 break; 1044 case 'em': 1045 $this->processWikiTag('em', $src, $p, '\'\'', '\'\'', true); 1046 break; 1047 case 'strong': 1048 $this->processWikiTag('strong', $src, $p, '__', '__', true); 1049 break; 1050 case 'u': 1051 $this->processWikiTag('u', $src, $p, '===', '===', true); 1052 break; 1053 case 'strike': 1054 $this->processWikiTag('strike', $src, $p, '--', '--', true); 1055 break; 1056 case 'del': 1057 $this->processWikiTag('del', $src, $p, '--', '--', true); 1058 break; 1059 case 'center': 1060 if ($prefs['feature_use_three_colon_centertag'] == 'y') { 1061 $src .= ':::'; 1062 $p['stack'][] = ['tag' => 'center', 'string' => ':::']; 1063 } else { 1064 $src .= '::'; 1065 $p['stack'][] = ['tag' => 'center', 'string' => '::']; 1066 } 1067 break; 1068 case 'code': 1069 $src .= '-+'; 1070 $p['stack'][] = ['tag' => 'code', 'string' => '+-']; 1071 break; 1072 case 'dd': 1073 $src .= ':'; 1074 $p['stack'][] = ['tag' => 'dd', 'string' => "\n"]; 1075 break; 1076 case 'dt': 1077 $src .= ';'; 1078 $p['stack'][] = ['tag' => 'dt', 'string' => '']; 1079 break; 1080 1081 case 'h1': 1082 case 'h2': 1083 case 'h3': 1084 case 'h4': 1085 case 'h5': 1086 case 'h6': 1087 $p['wiki_lbr']++; // force wiki line break mode 1088 $hlevel = (int) $node['data']['name']{1}; 1089 if (isset($node['pars']['style']['value']) && strpos($node['pars']['style']['value'], 'text-align: center;') !== false) { 1090 if ($prefs['feature_use_three_colon_centertag'] == 'y') { 1091 $src .= $this->startNewLine($src) . str_repeat('!', $hlevel) . ':::'; 1092 $p['stack'][] = ['tag' => $node['data']['name'], 'string' => ":::\n"]; 1093 } else { 1094 $src .= $this->startNewLine($src) . str_repeat('!', $hlevel) . '::'; 1095 $p['stack'][] = ['tag' => $node['data']['name'], 'string' => "::\n"]; 1096 } 1097 } else { // normal para or div 1098 $src .= $this->startNewLine($src) . str_repeat('!', $hlevel); 1099 $p['stack'][] = ['tag' => $node['data']['name'], 'string' => "\n"]; 1100 } 1101 break; 1102 case 'pre': 1103 $src .= "~pre~\n"; 1104 $p['stack'][] = ['tag' => 'pre', 'string' => "~/pre~\n"]; 1105 break; 1106 case 'sub': 1107 $src .= '{SUB()}'; 1108 $p['stack'][] = ['tag' => 'sub', 'string' => '{SUB}']; 1109 break; 1110 case 'sup': 1111 $src .= '{SUP()}'; 1112 $p['stack'][] = ['tag' => 'sup', 'string' => '{SUP}']; 1113 break; 1114 case 'tt': 1115 $src .= '{DIV(type="tt")}'; 1116 $p['stack'][] = ['tag' => 'tt', 'string' => '{DIV}']; 1117 break; 1118 case 's': 1119 $src .= $this->processWikiTag('s', $src, $p, '--', '--', true); 1120 break; 1121 // Table parser 1122 case 'table': 1123 $src .= $this->startNewLine($src) . '||'; 1124 $p['stack'][] = ['tag' => 'table', 'string' => '||']; 1125 $p['first_tr'] = true; 1126 $p['table'] = true; 1127 break; 1128 case 'tr': 1129 if (! $p['first_tr']) { 1130 $this->startNewLine($src); 1131 } 1132 $p['first_tr'] = false; 1133 $p['first_td'] = true; 1134 $p['wiki_lbr']++; // force wiki line break mode 1135 break; 1136 case 'td': 1137 if ($p['first_td']) { 1138 $src .= ''; 1139 } else { 1140 $src .= '|'; 1141 } 1142 $p['first_td'] = false; 1143 break; 1144 // Lists parser 1145 case 'ul': 1146 $p['listack'][] = '*'; 1147 break; 1148 case 'ol': 1149 $p['listack'][] = '#'; 1150 break; 1151 case 'li': 1152 // Generate wiki list item according to current list depth. 1153 $src .= $this->startNewLine($src) . str_repeat(end($p['listack']), count($p['listack'])); 1154 break; 1155 case 'font': 1156 // If color attribute present in <font> tag 1157 if (isset($node['pars']['color']['value'])) { 1158 $src .= '~~' . $node['pars']['color']['value'] . ':'; 1159 $p['stack'][] = ['tag' => 'font', 'string' => '~~']; 1160 } 1161 break; 1162 case 'img': 1163 // If src attribute present in <img> tag 1164 if (isset($node['pars']['src']['value'])) { 1165 // Note what it produce (img) not {img}! Will fix this below... 1166 if (strstr($node['pars']['src']['value'], 'http:')) { 1167 $src .= '{img src="' . $node['pars']['src']['value'] . '"}'; 1168 } else { 1169 $src .= '{img src="' . $head_url . $node['pars']['src']['value'] . '"}'; 1170 } 1171 } 1172 break; 1173 case 'a': 1174 if (isset($node['pars'])) { 1175 // get the link text 1176 $text = ''; 1177 if ($i < count($c)) { 1178 $next_token = &$c[$i + 1]; 1179 if (isset($next_token['type']) && $next_token['type'] == 'text' && isset($next_token['data'])) { 1180 $text = &$next_token['data']; 1181 } 1182 } 1183 // parse the link 1184 $this->parseLinkTag($node['pars'], $text, $src, $p); 1185 } 1186 1187 // deactivated by mauriz, will be replaced by the routine above 1188 // If href attribute present in <a> tag 1189 /* 1190 if (isset($c[$i]['pars']['href']['value'])) { 1191 if ( strstr( $c[$i]['pars']['href']['value'], 'http:' )) { 1192 $src .= '['.$c[$i]['pars']['href']['value'].'|'; 1193 } else { 1194 $src .= '['.$head_url.$c[$i]['pars']['href']['value'].'|'; 1195 } 1196 $p['stack'][] = array('tag' => 'a', 'string' => ']'); 1197 } 1198 if ( isset($c[$i]['pars']['name']['value'])) { 1199 $src .= '{ANAME()}'.$c[$i]['pars']['name']['value'].'{ANAME}'; 1200 } 1201 */ 1202 1203 1204 break; 1205 } // end switch on tag name 1206 } else { 1207 // This is close tag type. Is that smth we r waiting for? 1208 switch ($node['data']['name']) { 1209 case 'ul': 1210 if (end($p['listack']) == '*') { 1211 array_pop($p['listack']); 1212 } 1213 if (empty($p['listack'])) { 1214 $src .= "\n"; 1215 } 1216 break; 1217 case 'ol': 1218 if (end($p['listack']) == '#') { 1219 array_pop($p['listack']); 1220 } 1221 if (empty($p['listack'])) { 1222 $src .= "\n"; 1223 } 1224 break; 1225 default: 1226 $e = end($p['stack']); 1227 if (isset($e['tag']) && $node['data']['name'] == $e['tag']) { 1228 $src .= $e['string']; 1229 array_pop($p['stack']); 1230 } 1231 if ($node['data']['name'] === 'table') { 1232 $p['table'] = false; 1233 } 1234 break; 1235 } 1236 1237 // update the wiki stack 1238 if (isset($e['wikitags']) && $e['wikitags']) { 1239 for ($i_wiki = 0; $i_wiki < $e['wikitags']; $i_wiki++) { 1240 array_pop($p['wikistack']); 1241 } 1242 } 1243 1244 // can we leave wiki line break mode ? 1245 switch ($node['data']['name']) { 1246 case 'a': 1247 case 'h1': 1248 case 'h2': 1249 case 'h3': 1250 case 'h4': 1251 case 'h5': 1252 case 'h6': 1253 case 'tr': 1254 $p['wiki_lbr']--; 1255 break; 1256 } 1257 } 1258 } 1259 // Recursive call on tags with content... 1260 if (! empty($node['content'])) { 1261 if (substr($src, -1) != ' ') { 1262 $src .= ' '; 1263 } 1264 $this->walk_and_parse($node['content'], $src, $p, $head_url); 1265 } 1266 } 1267 if (substr($src, -2) == "\n\n") { // seem to always get too many line ends 1268 $src = substr($src, 0, -2); 1269 } 1270 } // end walk_and_parse 1271 1272 function startNewLine(&$str) 1273 { 1274 if (strlen($str) && substr($str, -1) != "\n") { 1275 $str .= "\n"; 1276 } 1277 } 1278 1279 /** 1280 * wrapper around zaufi's HTML sucker code just to use the html to wiki bit 1281 * 1282 * @param string $inHtml -- HTML in 1283 * @return null|string 1284 * @throws Exception 1285 */ 1286 1287 1288 function parse_html(&$inHtml) 1289 { 1290 include(__DIR__ . '/../htmlparser/htmlparser.inc'); 1291 // Read compiled (serialized) grammar 1292 $grammarfile = TIKI_PATH . '/lib/htmlparser/htmlgrammar.cmp'; 1293 if (! $fp = @fopen($grammarfile, 'r')) { 1294 $smarty = TikiLib::lib('smarty'); 1295 $smarty->assign('msg', tra("Can't parse HTML data - no grammar file")); 1296 $smarty->display("error.tpl"); 1297 die; 1298 } 1299 $grammar = unserialize(fread($fp, filesize($grammarfile))); 1300 fclose($fp); 1301 1302 // process a few ckeditor artifacts 1303 $inHtml = str_replace('<p></p>', '', $inHtml); // empty p tags are invisible 1304 1305 // create parser object, insert html code and parse it 1306 $htmlparser = new HtmlParser($inHtml, $grammar, '', 0); 1307 $htmlparser->Parse(); 1308 // Should I try to convert HTML to wiki? 1309 $out_data = ''; 1310 /* 1311 * ['stack'] = array 1312 * Speacial keys introduced to convert to Wiki 1313 * - ['wikitags'] = the number of 'wikistack' entries produced by the html tag 1314 * 1315 * ['wikistack'] = array(), is used to save the wiki markup for the linebreak handling (1 array = 1 html tag) 1316 * Each array entry contains the following keys: 1317 * - ['begin'] = array() of begin markups (1 style definition = 1 array entry) 1318 * - ['end'] = array() of end markups 1319 * 1320 * wiki_lbr = true if we must use '%%%' for linebreaks instead of '\n' 1321 */ 1322 $p = ['stack' => [], 'listack' => [], 'wikistack' => [], 1323 'wiki_lbr' => 0, 'first_td' => false, 'first_tr' => false]; 1324 $this->walk_and_parse($htmlparser->content, $out_data, $p, ''); 1325 // Is some tags still opened? (It can be if HTML not valid, but this is not reason 1326 // to produce invalid wiki :) 1327 while (count($p['stack'])) { 1328 $e = end($p['stack']); 1329 $out_data .= $e['string']; 1330 array_pop($p['stack']); 1331 } 1332 // Unclosed lists r ignored... wiki have no special start/end lists syntax.... 1333 // OK. Things remains to do: 1334 // 1) fix linked images 1335 $out_data = preg_replace(',\[(.*)\|\(img src=(.*)\)\],mU', '{img src=$2 link=$1}', $out_data); 1336 // 2) fix remains images (not in links) 1337 $out_data = preg_replace(',\(img src=(.*)\),mU', '{img src=$1}', $out_data); 1338 // 3) remove empty lines 1339 $out_data = preg_replace(",[\n]+,mU", "\n", $out_data); 1340 // 4) remove nbsp's 1341 $out_data = preg_replace(", ,mU", " ", $out_data); 1342 1343 return $out_data; 1344 } // end parse_html 1345 1346 1347 function get_new_page_attributes_from_parent_pages($page, $page_info) 1348 { 1349 $tikilib = TikiLib::lib('tiki'); 1350 $wikilib = TikiLib::lib('wiki'); 1351 1352 $new_page_attrs = []; 1353 $parent_pages = $wikilib->get_parent_pages($page); 1354 $parent_pages_info = []; 1355 foreach ($parent_pages as $a_parent_page_name) { 1356 $this_parent_page_info = $tikilib->get_page_info($a_parent_page_name); 1357 $parent_pages_info[] = $this_parent_page_info; 1358 } 1359 $new_page_attrs = $this->get_newpage_language_from_parent_page($page, $page_info, $parent_pages_info, $new_page_attrs); 1360 // Note: in the future, may add some methods below to guess things like 1361 // categories, workspaces, etc... 1362 1363 return $new_page_attrs; 1364 } 1365 1366 function get_newpage_language_from_parent_page($page, $page_info, $parent_pages_info, $new_page_attrs) 1367 { 1368 if (! isset($page_info['lang'])) { 1369 $lang = null; 1370 foreach ($parent_pages_info as $this_parent_page_info) { 1371 if (isset($this_parent_page_info['lang'])) { 1372 if ($lang != null and $lang != $this_parent_page_info['lang']) { 1373 // If more than one parent pages and they have different languages 1374 // then we can't guess which is the right one. 1375 $lang = null; 1376 break; 1377 } else { 1378 $lang = $this_parent_page_info['lang']; 1379 } 1380 } 1381 } 1382 if ($lang != null) { 1383 $new_page_attrs['lang'] = $lang; 1384 } 1385 } 1386 return $new_page_attrs; 1387 } 1388} 1389