PageRenderTime 61ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/extension/ezoe/ezxmltext/handlers/input/ezoeinputparser.php

http://github.com/ezsystems/ezpublish
PHP | 1553 lines | 1076 code | 143 blank | 334 comment | 193 complexity | 194304ab8ddaccbfe9628c1f32e2d4fd MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. //
  3. // Created on: <27-Mar-2006 15:28:40 ks>
  4. // Forked on: <20-Des-2007 13:02:06 ar> from eZDHTMLInputParser class
  5. //
  6. // ## BEGIN COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  7. // SOFTWARE NAME: eZ Online Editor extension for eZ Publish
  8. // SOFTWARE RELEASE: 5.0
  9. // COPYRIGHT NOTICE: Copyright (C) 1999-2014 eZ Systems AS
  10. // SOFTWARE LICENSE: GNU General Public License v2.0
  11. // NOTICE: >
  12. // This program is free software; you can redistribute it and/or
  13. // modify it under the terms of version 2.0 of the GNU General
  14. // Public License as published by the Free Software Foundation.
  15. //
  16. // This program is distributed in the hope that it will be useful,
  17. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  19. // GNU General Public License for more details.
  20. //
  21. // You should have received a copy of version 2.0 of the GNU General
  22. // Public License along with this program; if not, write to the Free
  23. // Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  24. // MA 02110-1301, USA.
  25. //
  26. //
  27. // ## END COPYRIGHT, LICENSE AND WARRANTY NOTICE ##
  28. //
  29. /*! \file ezoeinputparser.php
  30. */
  31. /*!
  32. \class eZOEInputParser
  33. \brief The class eZOEInputParser does
  34. */
  35. class eZOEInputParser extends eZXMLInputParser
  36. {
  37. /**
  38. * Used to strip out ezoe, tinymce & browser specific classes
  39. */
  40. const HTML_CLASS_REGEX = "/(webkit-[\w\-]+|Apple-[\w\-]+|mceItem\w+|ezoeItem\w+|ezoeAlign\w+|mceVisualAid)/i";
  41. /**
  42. * Maps input tags (html) to a output tag or a hander to
  43. * decide what kind of ezxml tag to use.
  44. *
  45. * @var array $InputTags
  46. */
  47. public $InputTags = array(
  48. 'section' => array( 'name' => 'section' ),
  49. 'b' => array( 'name' => 'strong' ),
  50. 'bold' => array( 'name' => 'strong' ),
  51. 'strong' => array( 'name' => 'strong' ),
  52. 'i' => array( 'name' => 'emphasize' ),
  53. 'em' => array( 'name' => 'emphasize' ),
  54. 'pre' => array( 'name' => 'literal' ),
  55. 'div' => array( 'nameHandler' => 'tagNameDivnImg' ),
  56. 'u' => array( 'nameHandler' => 'tagNameCustomHelper' ),
  57. 'sub' => array( 'nameHandler' => 'tagNameCustomHelper' ),
  58. 'sup' => array( 'nameHandler' => 'tagNameCustomHelper' ),
  59. 'img' => array( 'nameHandler' => 'tagNameDivnImg',
  60. 'noChildren' => true ),
  61. 'h1' => array( 'nameHandler' => 'tagNameHeader' ),
  62. 'h2' => array( 'nameHandler' => 'tagNameHeader' ),
  63. 'h3' => array( 'nameHandler' => 'tagNameHeader' ),
  64. 'h4' => array( 'nameHandler' => 'tagNameHeader' ),
  65. 'h5' => array( 'nameHandler' => 'tagNameHeader' ),
  66. 'h6' => array( 'nameHandler' => 'tagNameHeader' ),
  67. 'p' => array( 'name' => 'paragraph' ),
  68. 'br' => array( 'name' => 'br',
  69. 'noChildren' => true ),
  70. 'span' => array( 'nameHandler' => 'tagNameSpan' ),
  71. 'table' => array( 'nameHandler' => 'tagNameTable' ),
  72. 'td' => array( 'name' => 'td' ),
  73. 'tr' => array( 'name' => 'tr' ),
  74. 'th' => array( 'name' => 'th' ),
  75. 'ol' => array( 'name' => 'ol' ),
  76. 'ul' => array( 'name' => 'ul' ),
  77. 'li' => array( 'name' => 'li' ),
  78. 'a' => array( 'nameHandler' => 'tagNameLink' ),
  79. 'link' => array( 'nameHandler' => 'tagNameLink' ),
  80. // Stubs for not supported tags.
  81. 'tbody' => array( 'name' => '' ),
  82. 'thead' => array( 'name' => '' ),
  83. 'tfoot' => array( 'name' => '' )
  84. );
  85. /**
  86. * Maps output tags (ezxml) to varius handlers at different stages
  87. * decide what kind of ezxml tag to use.
  88. *
  89. * @var array $OutputTags
  90. */
  91. public $OutputTags = array(
  92. 'section' => array(),
  93. 'embed' => array( 'initHandler' => 'transformStyles',
  94. 'structHandler' => 'appendLineParagraph',
  95. 'publishHandler' => 'publishHandlerEmbed',
  96. 'attributes' => array( 'alt' => 'size',
  97. 'html_id' => 'xhtml:id' ) ),
  98. 'embed-inline' => array( 'initHandler' => 'transformStyles',
  99. 'structHandler' => 'appendLineParagraph',
  100. 'publishHandler' => 'publishHandlerEmbed',
  101. 'attributes' => array( 'alt' => 'size',
  102. 'html_id' => 'xhtml:id' ) ),
  103. 'table' => array( 'initHandler' => 'transformStyles',
  104. 'structHandler' => 'appendParagraph',
  105. 'attributes' => array( 'border' => false,
  106. 'ezborder' => 'border' ) ),
  107. 'tr' => array(),
  108. 'td' => array( 'initHandler' => 'transformStyles',
  109. 'attributes' => array( 'width' => 'xhtml:width',
  110. 'colspan' => 'xhtml:colspan',
  111. 'rowspan' => 'xhtml:rowspan' ) ),
  112. 'th' => array( 'initHandler' => 'transformStyles',
  113. 'attributes' => array( 'width' => 'xhtml:width',
  114. 'colspan' => 'xhtml:colspan',
  115. 'rowspan' => 'xhtml:rowspan' ) ),
  116. 'ol' => array( 'structHandler' => 'structHandlerLists' ),
  117. 'ul' => array( 'structHandler' => 'structHandlerLists' ),
  118. 'li' => array( 'autoCloseOn' => array( 'li' ) ),
  119. 'header' => array( 'initHandler' => 'initHandlerHeader',
  120. 'autoCloseOn' => array( 'paragraph' ),
  121. 'structHandler' => 'structHandlerHeader' ),
  122. 'paragraph' => array( 'parsingHandler' => 'parsingHandlerParagraph',
  123. 'autoCloseOn' => array( 'paragraph' ),
  124. 'initHandler' => 'transformStyles',
  125. 'structHandler' => 'structHandlerParagraph' ),
  126. 'line' => array(),
  127. 'br' => array( 'parsingHandler' => 'breakInlineFlow',
  128. 'structHandler' => 'structHandlerBr',
  129. 'attributes' => false ),
  130. 'literal' => array( 'parsingHandler' => 'parsingHandlerLiteral',
  131. 'structHandler' => 'appendParagraph',
  132. 'attributes' => array( 'class' => 'class' ) ),
  133. 'strong' => array( 'structHandler' => 'appendLineParagraph' ),
  134. 'emphasize' => array( 'structHandler' => 'appendLineParagraph' ),
  135. 'link' => array( 'structHandler' => 'appendLineParagraph',
  136. 'publishHandler' => 'publishHandlerLink',
  137. 'attributes' => array( 'title' => 'xhtml:title',
  138. 'id' => 'xhtml:id' ) ),
  139. 'anchor' => array( 'structHandler' => 'appendLineParagraph' ),
  140. 'custom' => array( 'initHandler' => 'initHandlerCustom',
  141. 'structHandler' => 'structHandlerCustom' ),
  142. '#text' => array( 'structHandler' => 'structHandlerText' )
  143. );
  144. /**
  145. * Constructor
  146. * For more info see {@link eZXMLInputParser::eZXMLInputParser()}
  147. *
  148. * @param int $validateErrorLevel
  149. * @param int $detectErrorLevel
  150. * @param bool $parseLineBreaks flag if line breaks should be given meaning or not
  151. * @param bool $removeDefaultAttrs signal if attributes of default value should not be saved.
  152. */
  153. public function __construct( $validateErrorLevel = eZXMLInputParser::ERROR_NONE,
  154. $detectErrorLevel = eZXMLInputParser::ERROR_NONE,
  155. $parseLineBreaks = false,
  156. $removeDefaultAttrs = false )
  157. {
  158. parent::__construct( $validateErrorLevel,
  159. $detectErrorLevel,
  160. $parseLineBreaks,
  161. $removeDefaultAttrs );
  162. $ini = eZINI::instance( 'content.ini' );
  163. if ( $ini->hasVariable( 'header', 'AnchorAsAttribute' ) )
  164. $this->anchorAsAttribute = $ini->variable( 'header', 'AnchorAsAttribute' ) !== 'disabled';
  165. }
  166. /**
  167. * Process html text and transform it to xml.
  168. *
  169. * @param string $text
  170. * @param bool $createRootNode
  171. * @return false|DOMDocument
  172. */
  173. public function process( $text, $createRootNode = true )
  174. {
  175. $text = preg_replace( '#<!--.*?-->#s', '', $text ); // remove HTML comments
  176. $text = str_replace( array( '&nbsp;', '&#160;', '&#xa0;' ), "\xC2\xA0", $text );
  177. return parent::process( $text, $createRootNode );
  178. }
  179. /**
  180. * Sets the attributes for the given element. This method overrides the
  181. * eZXMLInputParser::setAttributes to make sure the parser correctly
  182. * recognizes attributes with different case variations (for IE8 which adds
  183. * "colSpan" instead of "colspan" for instance).
  184. *
  185. * @param DOMElement $element
  186. * @param array $attributes
  187. */
  188. function setAttributes( $element, $attributes )
  189. {
  190. $thisOutputTag = $this->OutputTags[$element->nodeName];
  191. foreach( $attributes as $key => $value )
  192. {
  193. // Convert attribute names
  194. $qualifiedName = $key;
  195. if ( isset( $thisOutputTag['attributes'] ) )
  196. {
  197. foreach ( $thisOutputTag['attributes'] as $outputKey => $outputAttr )
  198. {
  199. // make sure to recognize attributes with different case
  200. // variations. for instance IE8 generate "colSpan" instead
  201. // of "colspan" in Online Editor...
  202. if ( strcasecmp( $key, $outputKey ) === 0 )
  203. {
  204. $qualifiedName = $outputAttr;
  205. break;
  206. }
  207. }
  208. }
  209. // Filter classes
  210. if ( $qualifiedName == 'class' )
  211. {
  212. $classesList = $this->XMLSchema->getClassesList( $element->nodeName );
  213. if ( !in_array( $value, $classesList ) )
  214. {
  215. $this->handleError( self::ERROR_DATA,
  216. ezpI18n::tr( 'kernel/classes/datatypes/ezxmltext', "Class '%1' is not allowed for element &lt;%2&gt; (check content.ini).",
  217. false, array( $value, $element->nodeName ) ) );
  218. continue;
  219. }
  220. }
  221. // Create attribute nodes
  222. if ( $qualifiedName )
  223. {
  224. if ( strpos( $qualifiedName, ':' ) )
  225. {
  226. list( $prefix, $name ) = explode( ':', $qualifiedName );
  227. if ( isset( $this->Namespaces[$prefix] ) )
  228. {
  229. $URI = $this->Namespaces[$prefix];
  230. $element->setAttributeNS( $URI, $qualifiedName, htmlspecialchars_decode( $value ) );
  231. }
  232. else
  233. {
  234. eZDebug::writeWarning( "No namespace defined for prefix '$prefix'.", 'eZXML input parser' );
  235. }
  236. }
  237. else
  238. {
  239. $element->setAttribute( $qualifiedName, htmlspecialchars_decode( $value ) );
  240. }
  241. }
  242. }
  243. // Check for required attrs are present
  244. if ( isset( $this->OutputTags[$element->nodeName]['requiredInputAttributes'] ) )
  245. {
  246. foreach( $this->OutputTags[$element->nodeName]['requiredInputAttributes'] as $reqAttrName )
  247. {
  248. $presented = false;
  249. foreach( $attributes as $key => $value )
  250. {
  251. if ( $key == $reqAttrName )
  252. {
  253. $presented = true;
  254. break;
  255. }
  256. }
  257. if ( !$presented )
  258. {
  259. $this->handleError( self::ERROR_SCHEMA,
  260. ezpI18n::tr( 'kernel/classes/datatypes/ezxmltext', "Required attribute '%1' is not presented in tag &lt;%2&gt;.",
  261. false, array( $reqAttrName, $element->nodeName ) ) );
  262. }
  263. }
  264. }
  265. }
  266. /**
  267. * tagNameSpan (tag mapping handler)
  268. * Handles span tag and maps it to embed|custom|strong|emphasize|custom.underline
  269. * Reuses {@link eZOEInputParser::tagNameDivnImg()} for embed and custom tag mapping.
  270. *
  271. * @param string $tagName name of input (xhtml) tag
  272. * @param array $attributes byref value of tag attributes
  273. * @return string name of ezxml tag or blank (then tag is removed, but not it's content)
  274. */
  275. function tagNameSpan( $tagName, &$attributes )
  276. {
  277. // embed / custom tag detection code in tagNameDivnImg
  278. $name = $this->tagNameDivnImg( $tagName, $attributes );
  279. if ( $name === '' && isset( $attributes['style'] ) )
  280. {
  281. if ( strpos( $attributes['style'], 'font-weight: bold' ) !== false )
  282. {
  283. $name = 'strong';
  284. unset( $attributes['style'] );
  285. }
  286. elseif ( strpos( $attributes['style'], 'font-style: italic' ) !== false )
  287. {
  288. $name = 'emphasize';
  289. unset( $attributes['style'] );
  290. }
  291. elseif ( strpos( $attributes['style'], 'text-decoration: underline' ) !== false
  292. && self::customTagIsEnabled('underline') )
  293. {
  294. $name = 'custom';
  295. unset( $attributes['style'] );
  296. $attributes['name'] = 'underline';
  297. $attributes['children_required'] = 'true';
  298. }
  299. }
  300. return $name;
  301. }
  302. /**
  303. * tagNameHeader (tag mapping handler)
  304. * Handles H[1-6] tags and maps them to header tag
  305. *
  306. * @param string $tagName name of input (xhtml) tag
  307. * @param array $attributes byref value of tag attributes
  308. * @return string name of ezxml tag or blank (then tag is removed, but not it's content)
  309. */
  310. function tagNameHeader( $tagName, &$attributes )
  311. {
  312. $attributes['level'] = $tagName[1];
  313. return 'header';
  314. }
  315. /**
  316. * tagNameTable (tag mapping handler)
  317. * Handles table tag and cleanups some attributes for it
  318. *
  319. * @param string $tagName name of input (xhtml) tag
  320. * @param array $attributes byref value of tag attributes
  321. * @return string name of ezxml tag or blank (then tag is removed, but not it's content)
  322. */
  323. function tagNameTable( $tagName, &$attributes )
  324. {
  325. $name = 'table';
  326. if ( isset( $attributes['border'] ) && !isset( $attributes['ezborder'] ) )
  327. {
  328. $attributes['ezborder'] = $attributes['border'];
  329. }
  330. if ( isset( $attributes['class'] ) )
  331. $attributes['class'] = self::tagClassNamesCleanup( $attributes['class'] );
  332. return $name;
  333. }
  334. /**
  335. * tagNameDivnImg (tag mapping handler)
  336. * Handles div|img tags and maps them to embed|embed-inline|custom tag
  337. *
  338. * @param string $tagName name of input (xhtml) tag
  339. * @param array $attributes byref value of tag attributes
  340. * @return string name of ezxml tag or blank (then tag is removed, but not it's content)
  341. */
  342. function tagNameDivnImg( $tagName, &$attributes )
  343. {
  344. $name = '';
  345. if ( isset( $attributes['id'] ) )
  346. {
  347. if ( strpos( $attributes['id'], 'eZObject_' ) !== false
  348. || strpos( $attributes['id'], 'eZNode_' ) !== false )
  349. {
  350. // decide if inline or block embed tag
  351. if ( isset( $attributes['inline'] ) && $attributes['inline'] === 'true' )
  352. $name = 'embed-inline';
  353. else
  354. $name = 'embed';
  355. unset( $attributes['inline'] );// unset internal stuff to make sure custom attr with same name works
  356. if ( isset( $attributes['class'] ) )
  357. {
  358. $attributes['class'] = self::tagClassNamesCleanup( $attributes['class'] );
  359. }
  360. }
  361. }
  362. if ( $name === '' && isset( $attributes['type'] ) && $attributes['type'] === 'custom' )
  363. {
  364. $name = 'custom';
  365. unset( $attributes['type'] );// unset internal stuff to make sure custom attr with same name works
  366. if ( $tagName === 'div' )
  367. $attributes['children_required'] = 'true';
  368. $attributes['name'] = self::tagClassNamesCleanup( $attributes['class'] );
  369. unset( $attributes['class'] );// unset internal stuff to make sure custom attr with same name works
  370. }
  371. return $name;
  372. }
  373. /**
  374. * tagNameLink (tag mapping handler)
  375. * Handles a|link tags and maps them to link|anchor tag
  376. *
  377. * @param string $tagName name of input (xhtml) tag
  378. * @param array $attributes byref value of tag attributes
  379. * @return string name of ezxml tag or blank (then tag is removed, but not it's content)
  380. */
  381. function tagNameLink( $tagName, &$attributes )
  382. {
  383. $name = '';
  384. if ( $tagName === 'link'
  385. && isset( $attributes['href'] )
  386. && isset( $attributes['rel'] )
  387. && ( $attributes['rel'] === 'File-List'
  388. || $attributes['rel'] === 'themeData'
  389. || $attributes['rel'] === 'colorSchemeMapping' )
  390. && ( strpos( $attributes['href'], '.xml' ) !== false
  391. || strpos( $attributes['href'], '.thmx' ) !== false) )
  392. {
  393. // empty check to not store buggy links created
  394. // by pasting content from ms word 2007
  395. }
  396. else if ( isset( $attributes['href'] ) )
  397. {
  398. // normal link tag
  399. $name = 'link';
  400. if ( isset( $attributes['name'] ) && !isset( $attributes['anchor_name'] ) )
  401. {
  402. $attributes['anchor_name'] = $attributes['name'];
  403. unset( $attributes['name'] );// unset internal stuff to make sure custom attr with same name works
  404. }
  405. }
  406. else if ( isset( $attributes['name'] ) )
  407. {
  408. // anchor in regular sense
  409. $name = 'anchor';
  410. }
  411. else if ( isset( $attributes['class'] ) && $attributes['class'] === 'mceItemAnchor' )
  412. {
  413. // anchor in TinyMCE sense (was valid up until TinyMCE 3.2)
  414. $name = 'anchor';
  415. // ie bug with name attribute, workaround using id instead
  416. if ( isset( $attributes['id'] ) ) $attributes['name'] = $attributes['id'];
  417. unset( $attributes['class'] );// unset internal stuff to make sure custom attr with same name works
  418. unset( $attributes['id'] );
  419. }
  420. return $name;
  421. }
  422. /**
  423. * tagNameCustomHelper (tag mapping handler)
  424. * Handles u|sub|sup tags and maps them to custom tag if they are enabled
  425. *
  426. * @param string $tagName name of input (xhtml) tag
  427. * @param array $attributes byref value of tag attributes
  428. * @return string name of ezxml tag or blank (then tag is removed, but not it's content)
  429. */
  430. function tagNameCustomHelper( $tagName, &$attributes )
  431. {
  432. $name = '';
  433. if ( $tagName === 'u' && self::customTagIsEnabled('underline') )
  434. {
  435. $name = 'custom';
  436. $attributes['name'] = 'underline';
  437. $attributes['children_required'] = 'true';
  438. }
  439. else if ( ( $tagName === 'sub' || $tagName === 'sup' ) && self::customTagIsEnabled( $tagName ) )
  440. {
  441. $name = 'custom';
  442. $attributes['name'] = $tagName;
  443. $attributes['children_required'] = 'true';
  444. }
  445. return $name;
  446. }
  447. /**
  448. * tagClassNamesCleanup
  449. * Used by init handlers, removes any oe/tinMCE/browser specific classes and trims the result.
  450. *
  451. * @static
  452. * @param string $className 'Dirty' class name as provided by TinyMCE
  453. * @return string Cleaned and trimmed class name
  454. */
  455. public static function tagClassNamesCleanup( $className )
  456. {
  457. return trim( preg_replace( self::HTML_CLASS_REGEX, '', $className ) );
  458. }
  459. /**
  460. * parsingHandlerLiteral (parsing handler, pass 1)
  461. * parse content of literal tag so tags are threated like text.
  462. *
  463. * @param DOMElement $element
  464. * @param array $param parameters for xml element
  465. * @return bool|null
  466. */
  467. function parsingHandlerLiteral( $element, &$param )
  468. {
  469. $ret = null;
  470. $data = $param[0];
  471. $pos = $param[1];
  472. $prePos = strpos( $data, '</pre>', $pos );
  473. if ( $prePos === false )
  474. $prePos = strpos( $data, '</PRE>', $pos );
  475. if ( $prePos === false )
  476. return $ret;
  477. $text = substr( $data, $pos, $prePos - $pos );
  478. $text = preg_replace( "/^<p.*?>/i", '', $text );
  479. $text = preg_replace( "/<\/\s?p>/i", '', $text );
  480. $text = preg_replace( "/<p.*?>/i", "\n\n", $text );
  481. $text = preg_replace( "/<\/?\s?br.*?>/i", "\n", $text );
  482. $text = $this->entitiesDecode( $text );
  483. $text = $this->convertNumericEntities( $text );
  484. $textNode = $this->Document->createTextNode( $text );
  485. $element->appendChild( $textNode );
  486. $param[1] = $prePos + strlen( '</pre>' );
  487. $ret = false;
  488. return $ret;
  489. }
  490. /**
  491. * parsingHandlerParagraph (parsing handler, pass 1)
  492. * parse content of paragraph tag to fix empty paragraphs issues.
  493. *
  494. * @param DOMElement $element
  495. * @param array $param parameters for xml element
  496. * @return bool|null
  497. */
  498. function parsingHandlerParagraph( $element, &$param )
  499. {
  500. $data = $param[0];
  501. $pos = $param[1];
  502. $prePos = strpos( $data, '</p>', $pos );
  503. if ( $prePos === false )
  504. $prePos = strpos( $data, '</P>', $pos );
  505. if ( $prePos === false )
  506. return null;
  507. $text = substr( $data, $pos, $prePos - $pos );
  508. // Fix empty paragraphs in Gecko (<p><br></p>)
  509. if ( $text === '<br>' || $text === '<BR>' || $text === '<br />' )
  510. {
  511. if ( !$this->XMLSchema->Schema['paragraph']['childrenRequired'] )
  512. {
  513. $textNode = $this->Document->createTextNode( $this->entitiesDecode( '&nbsp;' ) );
  514. $element->appendChild( $textNode );
  515. }
  516. }
  517. // Fix empty paragraphs in IE (<P>&nbsp;</P>)
  518. else if ( $text === '&nbsp;' && $this->XMLSchema->Schema['paragraph']['childrenRequired'] )
  519. {
  520. $parent = $element->parentNode;
  521. $parent->removeChild( $element );
  522. }
  523. return true;
  524. }
  525. /**
  526. * breakInlineFlow (parsing handler, pass 1)
  527. * handle flow around <br> tags, legazy from oe 4.x
  528. *
  529. * @param DOMElement $element
  530. * @param array $param parameters for xml element
  531. * @return bool|null
  532. */
  533. function breakInlineFlow( $element, &$param )
  534. {
  535. // Breaks the flow of inline tags. Used for non-inline tags caught within inline.
  536. // Works for tags with no children only.
  537. $ret = null;
  538. $data =& $param[0];
  539. $pos =& $param[1];
  540. $tagBeginPos = $param[2];
  541. $parent = $element->parentNode;
  542. $wholeTagString = substr( $data, $tagBeginPos, $pos - $tagBeginPos );
  543. if ( $parent &&
  544. //!$this->XMLSchema->isInline( $element ) &&
  545. $this->XMLSchema->isInline( $parent ) //&&
  546. //!$this->XMLSchema->check( $parent, $element )
  547. )
  548. {
  549. $insertData = '';
  550. $currentParent = $parent;
  551. end( $this->ParentStack );
  552. do
  553. {
  554. $stackData = current( $this->ParentStack );
  555. $currentParentName = $stackData[0];
  556. $insertData .= '</' . $currentParentName . '>';
  557. $currentParent = $currentParent->parentNode;
  558. prev( $this->ParentStack );
  559. }
  560. while( $this->XMLSchema->isInline( $currentParent ) );
  561. $insertData .= $wholeTagString;
  562. $currentParent = $parent;
  563. end( $this->ParentStack );
  564. $appendData = '';
  565. do
  566. {
  567. $stackData = current( $this->ParentStack );
  568. $currentParentName = $stackData[0];
  569. $currentParentAttrString = '';
  570. if ( $stackData[2] )
  571. $currentParentAttrString = ' ' . $stackData[2];
  572. $appendData = '<' . $currentParentName . $currentParentAttrString . '>' . $appendData;
  573. $currentParent = $currentParent->parentNode;
  574. prev( $this->ParentStack );
  575. }
  576. while( $this->XMLSchema->isInline( $currentParent ) );
  577. $insertData .= $appendData;
  578. $data = $insertData . substr( $data, $pos );
  579. $pos = 0;
  580. $element = $parent->removeChild( $element );
  581. $ret = false;
  582. }
  583. return $ret;
  584. }
  585. /**
  586. * initHandlerCustom (init handler, pass 2 before childre tags)
  587. * seesm to be doing nothing
  588. *
  589. * @param DOMElement $element
  590. * @param array $param parameters for xml element
  591. * @return bool|null
  592. */
  593. function initHandlerCustom( $element, &$params )
  594. {
  595. if ( $this->XMLSchema->isInline( $element ) )
  596. return null;
  597. $this->elementStylesToAttribute( $element );
  598. return null;
  599. }
  600. /**
  601. * initHandlerHeader (init handler, pass 2 before childre tags)
  602. * sets anchor as attribute if setting is enabled
  603. *
  604. * @param DOMElement $element
  605. * @param array $param parameters for xml element
  606. * @return bool|null
  607. */
  608. function initHandlerHeader( $element, &$params )
  609. {
  610. if ( $this->anchorAsAttribute )
  611. {
  612. $anchorElement = $element->firstChild;
  613. if ( $anchorElement->nodeName === 'anchor' )
  614. {
  615. $element->setAttribute( 'anchor_name', $anchorElement->getAttribute( 'name' ) );
  616. $anchorElement = $element->removeChild( $anchorElement );
  617. }
  618. }
  619. $this->elementStylesToAttribute( $element );
  620. return null;
  621. }
  622. /**
  623. * transformStyles (init handler, pass 2 before childre tags)
  624. * tryes to convert css styles to attributes.
  625. *
  626. * @param DOMElement $element
  627. * @param array $params
  628. * @return null|array changes structure if it contains 'result' key
  629. */
  630. function transformStyles( $element, &$params )
  631. {
  632. $this->elementStylesToAttribute( $element );
  633. return null;
  634. }
  635. /**
  636. * appendLineParagraph (Structure handler, pass 2 after childre tags)
  637. * Structure handler for inline nodes.
  638. *
  639. * @param DOMElement $element
  640. * @param DOMElement $newParent node that are going to become new parent.
  641. * @return array changes structure if it contains 'result' key
  642. */
  643. function appendLineParagraph( $element, $newParent )
  644. {
  645. $ret = array();
  646. $parent = $element->parentNode;
  647. if ( !$parent instanceof DOMElement )
  648. {
  649. return $ret;
  650. }
  651. $parentName = $parent->nodeName;
  652. $next = $element->nextSibling;
  653. $newParentName = $newParent != null ? $newParent->nodeName : '';
  654. // Correct schema by adding <line> and <paragraph> tags.
  655. if ( $parentName === 'line' || $this->XMLSchema->isInline( $parent ) )
  656. {
  657. return $ret;
  658. }
  659. if ( $newParentName === 'line' )
  660. {
  661. $element = $parent->removeChild( $element );
  662. $newParent->appendChild( $element );
  663. $ret['result'] = $newParent;
  664. }
  665. else if (
  666. $parentName === 'header'
  667. && (
  668. $parent->getElementsByTagName( 'line' )->length
  669. || $parent->getElementsByTagName( 'br' )->length
  670. )
  671. )
  672. {
  673. // by default the header element does not need a line element
  674. // unless it contains a <br> or a previously created <line>
  675. $newLine = $this->createAndPublishElement( 'line', $ret );
  676. $element = $parent->replaceChild( $newLine, $element );
  677. $newLine->appendChild( $element );
  678. $ret['result'] = $newLine;
  679. }
  680. elseif ( $parentName === 'paragraph' )
  681. {
  682. $newLine = $this->createAndPublishElement( 'line', $ret );
  683. $element = $parent->replaceChild( $newLine, $element );
  684. $newLine->appendChild( $element );
  685. $ret['result'] = $newLine;
  686. }
  687. elseif ( $newParentName === 'paragraph' )
  688. {
  689. $newLine = $this->createAndPublishElement( 'line', $ret );
  690. $element = $parent->removeChild( $element );
  691. $newParent->appendChild( $newLine );
  692. $newLine->appendChild( $element );
  693. $ret['result'] = $newLine;
  694. }
  695. elseif ( $this->XMLSchema->check( $parent, 'paragraph' ) )
  696. {
  697. $newLine = $this->createAndPublishElement( 'line', $ret );
  698. $newPara = $this->createAndPublishElement( 'paragraph', $ret );
  699. $parent->replaceChild( $newPara, $element );
  700. $newPara->appendChild( $newLine );
  701. $newLine->appendChild( $element );
  702. $ret['result'] = $newLine;
  703. }
  704. return $ret;
  705. }
  706. /**
  707. * structHandlerBr (Structure handler, pass 2 after childre tags)
  708. * Structure handler for temporary <br> elements
  709. *
  710. * @param DOMElement $element
  711. * @param DOMElement $newParent node that are going to become new parent.
  712. * @return array changes structure if it contains 'result' key
  713. */
  714. function structHandlerBr( $element, $newParent )
  715. {
  716. $ret = array();
  717. if ( $newParent && $newParent->nodeName === 'line' )
  718. {
  719. $ret['result'] = $newParent->parentNode;
  720. }
  721. return $ret;
  722. }
  723. /**
  724. * appendParagraph (Structure handler, pass 2 after childre tags)
  725. * Structure handler for in-paragraph nodes.
  726. *
  727. * @param DOMElement $element
  728. * @param DOMElement $newParent node that are going to become new parent.
  729. * @return array changes structure if it contains 'result' key
  730. */
  731. function appendParagraph( $element, $newParent )
  732. {
  733. $ret = array();
  734. $parent = $element->parentNode;
  735. if ( !$parent )
  736. return $ret;
  737. $parentName = $parent->nodeName;
  738. if ( $parentName !== 'paragraph' )
  739. {
  740. if ( $newParent && $newParent->nodeName === 'paragraph' )
  741. {
  742. $element = $parent->removeChild( $element );
  743. $newParent->appendChild( $element );
  744. $ret['result'] = $newParent;
  745. return $ret;
  746. }
  747. if ( $newParent
  748. && $newParent->parentNode
  749. && $newParent->parentNode->nodeName === 'paragraph' )
  750. {
  751. $para = $newParent->parentNode;
  752. $element = $parent->removeChild( $element );
  753. $para->appendChild( $element );
  754. $ret['result'] = $newParent->parentNode;
  755. return $ret;
  756. }
  757. if ( $this->XMLSchema->check( $parentName, 'paragraph' ) )
  758. {
  759. $newPara = $this->createAndPublishElement( 'paragraph', $ret );
  760. $parent->replaceChild( $newPara, $element );
  761. $newPara->appendChild( $element );
  762. $ret['result'] = $newPara;
  763. }
  764. }
  765. return $ret;
  766. }
  767. /**
  768. * structHandlerText (Structure handler, pass 2 after childre tags)
  769. * Structure handler for #text.
  770. *
  771. * @param DOMElement $element
  772. * @param DOMElement $newParent node that are going to become new parent.
  773. * @return array changes structure if it contains 'result' key
  774. */
  775. function structHandlerText( $element, $newParent )
  776. {
  777. $ret = array();
  778. $parent = $element->parentNode;
  779. // Remove empty text elements
  780. if ( $element->textContent == '' )
  781. {
  782. $element = $parent->removeChild( $element );
  783. return $ret;
  784. }
  785. $ret = $this->appendLineParagraph( $element, $newParent );
  786. // Fix for italic/bold styles in Mozilla.
  787. $addStrong = $addEmph = null;
  788. $myParent = $element->parentNode;
  789. while( $myParent )
  790. {
  791. $style = $myParent->getAttribute( 'style' );
  792. if ( $style && $addStrong !== false && strpos( $style, 'font-weight: bold;' ) !== false )
  793. {
  794. $addStrong = true;
  795. }
  796. if ( $style && $addEmph !== false && strpos( $style, 'font-style: italic;' ) !== false )
  797. {
  798. $addEmph = true;
  799. }
  800. if ( $myParent->nodeName === 'strong' )
  801. {
  802. $addStrong = false;
  803. }
  804. elseif ( $myParent->nodeName === 'emphasize' )
  805. {
  806. $addEmph = false;
  807. }
  808. elseif ( $myParent->nodeName === 'td'
  809. || $myParent->nodeName === 'th'
  810. || $myParent->nodeName === 'section' )
  811. {
  812. break;
  813. }
  814. $tmp = $myParent;
  815. $myParent = $tmp->parentNode;
  816. }
  817. $parent = $element->parentNode;
  818. if ( $addEmph === true )
  819. {
  820. $emph = $this->Document->createElement( 'emphasize' );
  821. $emph = $parent->insertBefore( $emph, $element );
  822. $element = $parent->removeChild( $element );
  823. $emph->appendChild( $element );
  824. }
  825. if ( $addStrong === true )
  826. {
  827. $strong = $this->Document->createElement( 'strong' );
  828. $strong = $parent->insertBefore( $strong, $element );
  829. $element = $parent->removeChild( $element );
  830. $strong->appendChild( $element );
  831. }
  832. // Left trim spaces:
  833. if ( $this->TrimSpaces )
  834. {
  835. $trim = false;
  836. $currentElement = $element;
  837. // Check if it is the first element in line
  838. do
  839. {
  840. if ( $currentElement->previousSibling )
  841. {
  842. break;
  843. }
  844. $currentElement = $currentElement->parentNode;
  845. if ( $currentElement instanceof DOMElement &&
  846. ( $currentElement->nodeName === 'line' ||
  847. $currentElement->nodeName === 'paragraph' ) )
  848. {
  849. $trim = true;
  850. break;
  851. }
  852. } while ( $currentElement instanceof DOMElement );
  853. if ( $trim )
  854. {
  855. // Trim and remove if empty
  856. $parent = $element->parentNode;
  857. $trimmedElement = new DOMText( ltrim( $element->textContent ) );
  858. if ( $trimmedElement->textContent == '' )
  859. {
  860. $parent->removeChild( $element );
  861. }
  862. else
  863. {
  864. $parent->replaceChild( $trimmedElement, $element );
  865. }
  866. }
  867. }
  868. return $ret;
  869. }
  870. /**
  871. * structHandlerHeader (Structure handler, pass 2 after childre tags)
  872. * Structure handler for header tag.
  873. *
  874. * @param DOMElement $element
  875. * @param DOMElement $newParent node that are going to become new parent.
  876. * @return array changes structure if it contains 'result' key
  877. */
  878. function structHandlerHeader( $element, $newParent )
  879. {
  880. $ret = array();
  881. $parent = $element->parentNode;
  882. $level = $element->getAttribute( 'level' );
  883. if ( !$level )
  884. {
  885. $level = 1;
  886. }
  887. $element->removeAttribute( 'level' );
  888. if ( $level )
  889. {
  890. $sectionLevel = -1;
  891. $current = $element;
  892. while ( $current->parentNode )
  893. {
  894. $tmp = $current;
  895. $current = $tmp->parentNode;
  896. if ( $current->nodeName === 'section' )
  897. {
  898. ++$sectionLevel;
  899. }
  900. elseif ( $current->nodeName === 'td' )
  901. {
  902. ++$sectionLevel;
  903. break;
  904. }
  905. }
  906. if ( $level > $sectionLevel )
  907. {
  908. $newTempParent = $parent;
  909. for ( $i = $sectionLevel; $i < $level; $i++ )
  910. {
  911. $newSection = $this->Document->createElement( 'section' );
  912. if ( $i == $sectionLevel )
  913. {
  914. $newSection = $newTempParent->insertBefore( $newSection, $element );
  915. }
  916. else
  917. {
  918. $newTempParent->appendChild( $newSection );
  919. }
  920. // Schema check
  921. if ( !$this->processBySchemaTree( $newSection ) )
  922. {
  923. return $ret;
  924. }
  925. $newTempParent = $newSection;
  926. unset( $newSection );
  927. }
  928. $elementToMove = $element;
  929. while( $elementToMove &&
  930. $elementToMove->nodeName !== 'section' )
  931. {
  932. $next = $elementToMove->nextSibling;
  933. $elementToMove = $parent->removeChild( $elementToMove );
  934. $newTempParent->appendChild( $elementToMove );
  935. $elementToMove = $next;
  936. if ( !$elementToMove ||
  937. ( $elementToMove->nodeName === 'header' &&
  938. $elementToMove->getAttribute( 'level' ) <= $level ) )
  939. break;
  940. }
  941. }
  942. elseif ( $level < $sectionLevel )
  943. {
  944. $newLevel = $sectionLevel + 1;
  945. $current = $element;
  946. while( $level < $newLevel )
  947. {
  948. $tmp = $current;
  949. $current = $tmp->parentNode;
  950. if ( $current->nodeName === 'section' )
  951. --$newLevel;
  952. }
  953. $elementToMove = $element;
  954. while ( $elementToMove->parentNode->nodeName === 'custom' )
  955. {
  956. $elementToMove = $elementToMove->parentNode;
  957. $parent = $elementToMove->parentNode;
  958. }
  959. while( $elementToMove &&
  960. $elementToMove->nodeName !== 'section' )
  961. {
  962. $next = $elementToMove->nextSibling;
  963. $parent->removeChild( $elementToMove );
  964. $current->appendChild( $elementToMove );
  965. $elementToMove = $next;
  966. if ( !$elementToMove ||
  967. ( $elementToMove->nodeName === 'header' &&
  968. $elementToMove->getAttribute( 'level' ) <= $level ) )
  969. break;
  970. }
  971. }
  972. }
  973. return $ret;
  974. }
  975. /**
  976. * structHandlerCustom (Structure handler, pass 2 after childre tags)
  977. * Structure handler for custom tag.
  978. *
  979. * @param DOMElement $element
  980. * @param DOMElement $newParent node that are going to become new parent.
  981. * @return array changes structure if it contains 'result' key
  982. */
  983. function structHandlerCustom( $element, $newParent )
  984. {
  985. $ret = array();
  986. $isInline = $this->XMLSchema->isInline( $element );
  987. if ( $isInline )
  988. {
  989. $ret = $this->appendLineParagraph( $element, $newParent );
  990. $value = $element->getAttribute( 'value' );
  991. if ( $value )
  992. {
  993. $value = $this->washText( $value );
  994. $textNode = $this->Document->createTextNode( $value );
  995. $element->appendChild( $textNode );
  996. }
  997. }
  998. else
  999. {
  1000. $ret = $this->appendParagraph( $element, $newParent );
  1001. }
  1002. return $ret;
  1003. }
  1004. /**
  1005. * structHandlerLists (Structure handler, pass 2 after childre tags)
  1006. * Structure handler for ul|ol tags.
  1007. *
  1008. * @param DOMElement $element
  1009. * @param DOMElement $newParent node that are going to become new parent.
  1010. * @return array changes structure if it contains 'result' key
  1011. */
  1012. function structHandlerLists( $element, $newParent )
  1013. {
  1014. $ret = array();
  1015. $parent = $element->parentNode;
  1016. $parentName = $parent->nodeName;
  1017. if ( $parentName === 'paragraph' )
  1018. return $ret;
  1019. // If we are inside a list
  1020. if ( $parentName === 'ol' || $parentName === 'ul' )
  1021. {
  1022. // If previous 'li' doesn't exist, create it,
  1023. // else append to the previous 'li' element.
  1024. $prev = $element->previousSibling;
  1025. if ( !$prev )
  1026. {
  1027. $li = $this->Document->createElement( 'li' );
  1028. $li = $parent->insertBefore( $li, $element );
  1029. $element = $parent->removeChild( $element );
  1030. $li->appendChild( $element );
  1031. }
  1032. else
  1033. {
  1034. $lastChild = $prev->lastChild;
  1035. if ( $lastChild->nodeName !== 'paragraph' )
  1036. {
  1037. $para = $this->Document->createElement( 'paragraph' );
  1038. $element = $parent->removeChild( $element );
  1039. $prev->appendChild( $element );
  1040. $ret['result'] = $para;
  1041. }
  1042. else
  1043. {
  1044. $element = $parent->removeChild( $element );
  1045. $lastChild->appendChild( $element );
  1046. $ret['result'] = $lastChild;
  1047. }
  1048. return $ret;
  1049. }
  1050. }
  1051. else if ( $parentName === 'li' )
  1052. {
  1053. $prev = $element->previousSibling;
  1054. if ( $prev )
  1055. {
  1056. $element = $parent->removeChild( $element );
  1057. $prev->appendChild( $element );
  1058. $ret['result'] = $prev;
  1059. return $ret;
  1060. }
  1061. }
  1062. $ret = $this->appendParagraph( $element, $newParent );
  1063. return $ret;
  1064. }
  1065. /**
  1066. * structHandlerParagraph (Structure handler, pass 2 after childre tags)
  1067. * Structure handler for paragraph tag.
  1068. *
  1069. * @param DOMElement $element
  1070. * @param DOMElement $newParent node that are going to become new parent.
  1071. * @return array changes structure if it contains 'result' key
  1072. */
  1073. function structHandlerParagraph( $element, $newParent )
  1074. {
  1075. $ret = array();
  1076. $parentNode = $element->parentNode;
  1077. if ( $parentNode->nodeName === 'custom' &&
  1078. !$this->XMLSchema->isInline( $parentNode ) &&
  1079. $parentNode->childNodes->length === 1 &&
  1080. $parentNode->getAttribute( 'name' ) === $element->textContent )
  1081. {
  1082. // removing the paragraph as it is there only to handle the custom
  1083. // in the rich text editor
  1084. $parentNode->removeAttribute( 'children_required' );
  1085. $parentNode->removeChild( $element );
  1086. return $ret;
  1087. }
  1088. if ( $element->getAttribute( 'ezparser-new-element' ) === 'true' &&
  1089. !$element->hasChildren() )
  1090. {
  1091. $element = $element->parentNode->removeChild( $element );
  1092. return $ret;
  1093. }
  1094. // Removes single line tag
  1095. $line = $element->lastChild;
  1096. if ( $element->childNodes->length == 1 && $line->nodeName === 'line' )
  1097. {
  1098. $lineChildren = array();
  1099. $lineChildNodes = $line->childNodes;
  1100. foreach ( $lineChildNodes as $lineChildNode )
  1101. {
  1102. $lineChildren[] = $lineChildNode;
  1103. }
  1104. $line = $element->removeChild( $line );
  1105. foreach ( $lineChildren as $lineChild )
  1106. {
  1107. $element->appendChild( $lineChild );
  1108. }
  1109. }
  1110. return $ret;
  1111. }
  1112. /**
  1113. * publishHandlerLink (Publish handler, pass 2 after schema validation)
  1114. * Publish handler for link element, converts href to [object|node|link]_id.
  1115. *
  1116. * @param DOMElement $element
  1117. * @param array $param parameters for xml element
  1118. * @return null|array changes structure if it contains 'result' key
  1119. */
  1120. function publishHandlerLink( $element, &$params )
  1121. {
  1122. $ret = null;
  1123. $href = $element->getAttribute( 'href' );
  1124. if ( $href )
  1125. {
  1126. $objectID = false;
  1127. if ( strpos( $href, 'ezobject' ) === 0
  1128. && preg_match( "@^ezobject://([0-9]+)/?(#.+)?@i", $href, $matches ) )
  1129. {
  1130. $objectID = $matches[1];
  1131. if ( isset( $matches[2] ) )
  1132. $anchorName = substr( $matches[2], 1 );
  1133. $element->setAttribute( 'object_id', $objectID );
  1134. if ( !eZContentObject::exists( $objectID ))
  1135. {
  1136. $this->Messages[] = ezpI18n::tr( 'design/standard/ezoe/handler',
  1137. 'Invalid link: "%1". Target object does not exist.',
  1138. false,
  1139. array( $matches[0] ) );
  1140. }
  1141. }
  1142. /*
  1143. * rfc2396: ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?
  1144. * ezdhtml: "@^eznode://([^/#]+)/?(#[^/]*)?/?@i"
  1145. */
  1146. elseif ( strpos( $href, 'eznode' ) === 0
  1147. && preg_match( "@^eznode://([^#]+)(#.+)?@i", $href, $matches ) )
  1148. {
  1149. $nodePath = trim( $matches[1], '/' );
  1150. if ( isset( $matches[2] ) )
  1151. $anchorName = substr( $matches[2], 1 );
  1152. if ( is_numeric( $nodePath ) )
  1153. {
  1154. $nodeID = $nodePath;
  1155. $node = eZContentObjectTreeNode::fetch( $nodeID );
  1156. if ( !$node instanceOf eZContentObjectTreeNode )
  1157. {
  1158. $this->Messages[] = ezpI18n::tr( 'design/standard/ezoe/handler',
  1159. 'Invalid link: "%1". Target node does not exist.',
  1160. false,
  1161. array( $matches[0] ) );
  1162. }
  1163. }
  1164. else
  1165. {
  1166. $node = eZContentObjectTreeNode::fetchByURLPath( $nodePath );
  1167. if ( !$node instanceOf eZContentObjectTreeNode )
  1168. {
  1169. $this->Messages[] = ezpI18n::tr( 'design/standard/ezoe/handler',
  1170. 'Invalid link: "%1". Target node does not exist.',
  1171. false,
  1172. array( $matches[0] ) );
  1173. }
  1174. else
  1175. {
  1176. $nodeID = $node->attribute( 'node_id' );
  1177. }
  1178. $element->setAttribute( 'show_path', 'true' );
  1179. }
  1180. if ( isset( $nodeID ) && $nodeID )
  1181. {
  1182. $element->setAttribute( 'node_id', $nodeID );
  1183. }
  1184. if ( isset( $node ) && $node instanceOf eZContentObjectTreeNode )
  1185. {
  1186. $objectID = $node->attribute( 'contentobject_id' );
  1187. }
  1188. }
  1189. elseif ( strpos( $href, '#' ) === 0 )
  1190. {
  1191. $anchorName = substr( $href, 1 );
  1192. }
  1193. else
  1194. {
  1195. $temp = explode( '#', $href );
  1196. $url = $temp[0];
  1197. if ( isset( $temp[1] ) )
  1198. {
  1199. $anchorName = $temp[1];
  1200. }
  1201. if ( $url )
  1202. {
  1203. // Protection from XSS attack
  1204. if ( preg_match( "/^(java|vb)script:.*/i" , $url ) )
  1205. {
  1206. $this->isInputValid = false;
  1207. $this->Messages[] = "Using scripts in links is not allowed, '$url' has been removed";
  1208. $element->removeAttribute( 'href' );
  1209. return $ret;
  1210. }
  1211. // Check mail address validity following RFC 5322 and RFC 5321
  1212. if ( preg_match( "/^mailto:([^.][a-z0-9!#\$%&'*+-\/=?`{|}~^]+@([a-z0-9.-]+))/i" , $url, $mailAddr ) )
  1213. {
  1214. if ( !eZMail::validate( $mailAddr[1] ) )
  1215. {
  1216. $this->isInputValid = false;
  1217. if ( $this->errorLevel >= 0

Large files files are truncated, but you can click here to view the full file