You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

809 lines
33 KiB

  1. <?php
  2. /**
  3. * Helper Component for the Wrap Plugin
  4. *
  5. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  6. * @author Anika Henke <anika@selfthinker.org>
  7. */
  8. class helper_plugin_wrap extends DokuWiki_Plugin {
  9. static protected $boxes = array ('wrap_box', 'wrap_danger', 'wrap_warning', 'wrap_caution', 'wrap_notice', 'wrap_safety',
  10. 'wrap_info', 'wrap_important', 'wrap_alert', 'wrap_tip', 'wrap_help', 'wrap_todo',
  11. 'wrap_download', 'wrap_hi', 'wrap_spoiler');
  12. static protected $paragraphs = array ('wrap_leftalign', 'wrap_rightalign', 'wrap_centeralign', 'wrap_justify');
  13. /* list of languages which normally use RTL scripts */
  14. static protected $rtllangs = array('ar','dv','fa','ha','he','ks','ku','ps','ur','yi','arc');
  15. /* list of right-to-left scripts (may override the language defaults to rtl) */
  16. static protected $rtlscripts = array('arab','thaa','hebr','deva','shrd');
  17. /* selection of left-to-right scripts (may override the language defaults to ltr) */
  18. static protected $ltrscripts = array('latn','cyrl','grek','hebr','cyrs','armn');
  19. static $box_left_pos = 0;
  20. static $box_right_pos = 0;
  21. static $box_first = true;
  22. static $table_entr = 0;
  23. protected $column_count = 0;
  24. /**
  25. * get attributes (pull apart the string between '<wrap' and '>')
  26. * and identify classes, width, lang and dir
  27. *
  28. * @author Anika Henke <anika@selfthinker.org>
  29. * @author Christopher Smith <chris@jalakai.co.uk>
  30. * (parts taken from http://www.dokuwiki.org/plugin:box)
  31. */
  32. function getAttributes($data, $useNoPrefix=true) {
  33. $attr = array(
  34. 'lang' => null,
  35. 'class' => null,
  36. 'width' => null,
  37. 'id' => null,
  38. 'dir' => null
  39. );
  40. $tokens = preg_split('/\s+/', $data, 9);
  41. // anonymous function to convert inclusive comma separated items to regex pattern
  42. $pattern = function ($csv) {
  43. return '/^(?:'. str_replace(['?','*',' ',','],
  44. ['.','.*','','|'], $csv) .')$/';
  45. };
  46. // noPrefix: comma separated class names that should be excluded from
  47. // being prefixed with "wrap_",
  48. // each item may contain wildcard (*, ?)
  49. $noPrefix = ($this->getConf('noPrefix') && $useNoPrefix) ? $pattern($this->getConf('noPrefix')) : '';
  50. // restrictedClasses : comma separated class names that should be checked
  51. // based on restriction type (whitelist or blacklist),
  52. // each item may contain wildcard (*, ?)
  53. $restrictedClasses = ($this->getConf('restrictedClasses')) ?
  54. $pattern($this->getConf('restrictedClasses')) : '';
  55. $restrictionType = $this->getConf('restrictionType');
  56. foreach ($tokens as $token) {
  57. //get width
  58. if (preg_match('/^\d*\.?\d+(%|px|em|rem|ex|ch|vw|vh|pt|pc|cm|mm|in)$/', $token)) {
  59. $attr['width'] = $token;
  60. continue;
  61. }
  62. //get lang
  63. if (preg_match('/:([a-z\-]+)/', $token)) {
  64. $attr['lang'] = trim($token,':');
  65. continue;
  66. }
  67. //get id
  68. if (preg_match('/#([A-Za-z0-9_-]+)/', $token)) {
  69. $attr['id'] = trim($token,'#');
  70. continue;
  71. }
  72. //get classes
  73. //restrict token (class names) characters to prevent any malicious data
  74. if (preg_match('/[^A-Za-z0-9_-]/',$token)) continue;
  75. if ($restrictedClasses) {
  76. $classIsInList = preg_match($restrictedClasses, $token);
  77. // either allow only certain classes or disallow certain classes
  78. if ($restrictionType xor $classIsInList) continue;
  79. }
  80. // prefix adjustment of class name
  81. $prefix = (!empty($noPrefix) && preg_match($noPrefix, $token)) ? '' : 'wrap_';
  82. $attr['class'] = (isset($attr['class']) ? $attr['class'].' ' : '').$prefix.$token;
  83. }
  84. if ($this->getConf('darkTpl')) {
  85. $attr['class'] = (isset($attr['class']) ? $attr['class'].' ' : '').'wrap__dark';
  86. }
  87. if ($this->getConf('emulatedHeadings')) {
  88. $attr['class'] = (isset($attr['class']) ? $attr['class'].' ' : '').'wrap__emuhead';
  89. }
  90. /* improved RTL detection to make sure it covers more cases: */
  91. if($attr['lang'] && $attr['lang'] !== '') {
  92. // turn the language code into an array of components:
  93. $arr = explode('-', $attr['lang']);
  94. // is the language iso code (first field) in the list of RTL languages?
  95. $rtl = in_array($arr[0], self::$rtllangs);
  96. // is there a Script specified somewhere which overrides the text direction?
  97. $rtl = ($rtl xor (bool) array_intersect( $rtl ? self::$ltrscripts : self::$rtlscripts, $arr));
  98. $attr['dir'] = ( $rtl ? 'rtl' : 'ltr' );
  99. }
  100. return $attr;
  101. }
  102. /**
  103. * build attributes (write out classes, width, lang and dir)
  104. */
  105. function buildAttributes($data, $addClass='', $mode='xhtml') {
  106. $attr = $this->getAttributes($data);
  107. $out = '';
  108. if ($mode=='xhtml') {
  109. if($attr['class']) $out .= ' class="'.hsc($attr['class']).' '.$addClass.'"';
  110. // if used in other plugins, they might want to add their own class(es)
  111. elseif($addClass) $out .= ' class="'.$addClass.'"';
  112. if($attr['id']) $out .= ' id="'.hsc($attr['id']).'"';
  113. // width on spans normally doesn't make much sense, but in the case of floating elements it could be used
  114. if($attr['width']) {
  115. if (strpos($attr['width'],'%') !== false) {
  116. $out .= ' style="width: '.hsc($attr['width']).';"';
  117. } else {
  118. // anything but % should be 100% when the screen gets smaller
  119. $out .= ' style="width: '.hsc($attr['width']).'; max-width: 100%;"';
  120. }
  121. }
  122. // only write lang if it's a language in lang2dir.conf
  123. if($attr['dir']) $out .= ' lang="'.$attr['lang'].'" xml:lang="'.$attr['lang'].'" dir="'.$attr['dir'].'"';
  124. }
  125. return $out;
  126. }
  127. /**
  128. * render ODT element, Open
  129. * (get Attributes, select ODT element that fits, render it, return element name)
  130. */
  131. function renderODTElementOpen($renderer, $HTMLelement, $data) {
  132. $attr = $this->getAttributes($data, false);
  133. $attr_string = $this->buildAttributes($data);
  134. $classes = explode (' ', $attr['class']);
  135. // Get language
  136. $language = $attr['lang'];
  137. $is_indent = in_array ('wrap_indent', $classes);
  138. $is_outdent = in_array ('wrap_outdent', $classes);
  139. $is_column = in_array ('wrap_column', $classes);
  140. $is_group = in_array ('wrap_group', $classes);
  141. $is_pagebreak = in_array ('wrap_pagebreak', $classes);
  142. // Check for multicolumns
  143. $columns = 0;
  144. preg_match ('/wrap_col\d/', $attr ['class'], $matches);
  145. if ( empty ($matches [0]) === false ) {
  146. $columns = $matches [0] [strlen($matches [0])-1];
  147. }
  148. // Check for boxes
  149. $is_box = false;
  150. foreach (self::$boxes as $box) {
  151. if ( strpos ($attr ['class'], $box) !== false ) {
  152. $is_box = true;
  153. break;
  154. }
  155. }
  156. // Check for paragraphs
  157. $is_paragraph = false;
  158. if ( empty($language) === false ) {
  159. $is_paragraph = true;
  160. } else {
  161. foreach (self::$paragraphs as $paragraph) {
  162. if ( strpos ($attr ['class'], $paragraph) !== false ) {
  163. $is_paragraph = true;
  164. break;
  165. }
  166. }
  167. }
  168. $style = null;
  169. if ( empty($attr['width']) === false ) {
  170. $style = 'width: '.$attr['width'].';';
  171. }
  172. $attr ['class'] = 'dokuwiki '.$attr ['class'];
  173. // Call corresponding functions for current wrap class
  174. if ( $HTMLelement == 'span' ) {
  175. if ( $is_indent === false && $is_outdent === false ) {
  176. $this->renderODTOpenSpan ($renderer, $attr ['class'], $style, $language, $attr_string);
  177. return 'span';
  178. } else {
  179. $this->renderODTOpenParagraph ($renderer, $attr ['class'], $style, $attr ['dir'], $language, $is_indent, $is_outdent, true, $attr_string);
  180. return 'paragraph';
  181. }
  182. } else if ( $HTMLelement == 'div' ) {
  183. if ( $is_box === true ) {
  184. $wrap = $this->loadHelper('wrap');
  185. $fullattr = $wrap->buildAttributes($data, 'plugin_wrap');
  186. if ( method_exists ($renderer, 'getODTPropertiesFromElement') === false ) {
  187. $this->renderODTOpenBox ($renderer, $attr ['class'], $style, $fullattr);
  188. } else {
  189. $this->renderODTOpenTable ($renderer, $attr, $style, $attr_string);
  190. }
  191. return 'box';
  192. } else if ( $columns > 0 ) {
  193. $this->renderODTOpenColumns ($renderer, $attr ['class'], $style);
  194. return 'multicolumn';
  195. } else if ( $is_paragraph === true || $is_indent === true || $is_outdent === true ) {
  196. $this->renderODTOpenParagraph ($renderer, $attr ['class'], $style, $attr ['dir'], $language, $is_indent, $is_outdent, false, $attr_string);
  197. return 'paragraph';
  198. } else if ( $is_pagebreak === true ) {
  199. $renderer->pagebreak ();
  200. // Pagebreak hasn't got a closing stack so we return/push 'other' on the stack
  201. return 'other';
  202. } else if ( $is_column === true ) {
  203. $this->renderODTOpenColumn ($renderer, $attr ['class'], $style, $attr_string);
  204. return 'column';
  205. } else if ( $is_group === true ) {
  206. $this->renderODTOpenGroup ($renderer, $attr ['class'], $style);
  207. return 'group';
  208. } else if (strpos ($attr ['class'], 'wrap_clear') !== false ) {
  209. $renderer->linebreak();
  210. $renderer->p_close();
  211. $renderer->p_open();
  212. self::$box_left_pos = 0;
  213. self::$box_right_pos = 0;
  214. self::$box_first = true;
  215. }
  216. }
  217. return 'other';
  218. }
  219. /**
  220. * render ODT element, Close
  221. */
  222. function renderODTElementClose($renderer, $element) {
  223. switch ($element) {
  224. case 'box':
  225. if ( method_exists ($renderer, 'getODTPropertiesFromElement') === false ) {
  226. $this->renderODTCloseBox ($renderer);
  227. } else {
  228. $this->renderODTCloseTable ($renderer);
  229. }
  230. break;
  231. case 'multicolumn':
  232. $this->renderODTCloseColumns($renderer);
  233. break;
  234. case 'paragraph':
  235. $this->renderODTCloseParagraph($renderer);
  236. break;
  237. case 'column':
  238. $this->renderODTCloseColumn($renderer);
  239. break;
  240. case 'group':
  241. $this->renderODTCloseGroup($renderer);
  242. break;
  243. case 'span':
  244. $this->renderODTCloseSpan($renderer);
  245. break;
  246. // No default by intention.
  247. }
  248. }
  249. function renderODTOpenBox ($renderer, $class, $style, $fullattr) {
  250. $properties = array ();
  251. if ( method_exists ($renderer, 'getODTProperties') === false ) {
  252. // Function is not supported by installed ODT plugin version, return.
  253. return;
  254. }
  255. // Get CSS properties for ODT export.
  256. $renderer->getODTProperties ($properties, 'div', $class, $style);
  257. if ( empty($properties ['background-image']) === false ) {
  258. $properties ['background-image'] =
  259. $renderer->replaceURLPrefix ($properties ['background-image'], DOKU_INC);
  260. }
  261. if ( empty($properties ['float']) === true ) {
  262. // If the float property is not set, set it to 'left' becuase the ODT plugin
  263. // would default to 'center' which is diffeent to the XHTML behaviour.
  264. if ( strpos ($class, 'wrap_center') === false ) {
  265. $properties ['float'] = 'left';
  266. } else {
  267. $properties ['float'] = 'center';
  268. }
  269. }
  270. // The display property has differing usage in CSS. So we better overwrite it.
  271. $properties ['display'] = 'always';
  272. if ( stripos ($class, 'wrap_noprint') !== false ) {
  273. $properties ['display'] = 'screen';
  274. }
  275. if ( stripos ($class, 'wrap_onlyprint') !== false ) {
  276. $properties ['display'] = 'printer';
  277. }
  278. $renderer->_odtDivOpenAsFrameUseProperties ($properties);
  279. }
  280. function renderODTCloseBox ($renderer) {
  281. if ( method_exists ($renderer, '_odtDivCloseAsFrame') === false ) {
  282. // Function is not supported by installed ODT plugin version, return.
  283. return;
  284. }
  285. $renderer->_odtDivCloseAsFrame ();
  286. }
  287. function renderODTOpenColumns ($renderer, $class, $style) {
  288. $properties = array ();
  289. if ( method_exists ($renderer, 'getODTProperties') === false ) {
  290. // Function is not supported by installed ODT plugin version, return.
  291. return;
  292. }
  293. // Get CSS properties for ODT export.
  294. $renderer->getODTProperties ($properties, 'div', $class, $style);
  295. $renderer->_odtOpenMultiColumnFrame($properties);
  296. }
  297. function renderODTCloseColumns ($renderer) {
  298. if ( method_exists ($renderer, '_odtCloseMultiColumnFrame') === false ) {
  299. // Function is not supported by installed ODT plugin version, return.
  300. return;
  301. }
  302. $renderer->_odtCloseMultiColumnFrame();
  303. }
  304. function renderODTOpenParagraph ($renderer, $class, $style, $dir, $language, $is_indent, $is_outdent, $indent_first, $attr=null) {
  305. $properties = array ();
  306. if ( method_exists ($renderer, 'getODTPropertiesFromElement') === true ) {
  307. // Get CSS properties for ODT export.
  308. // Set parameter $inherit=false to prevent changiung the font-size and family!
  309. $renderer->getODTPropertiesNew ($properties, 'p', $attr, null, false);
  310. } else if ( method_exists ($renderer, 'getODTProperties') === true ) {
  311. // Get CSS properties for ODT export (deprecated version).
  312. $renderer->getODTProperties ($properties, 'p', $class, $style);
  313. if ( empty($properties ['background-image']) === false ) {
  314. $properties ['background-image'] =
  315. $renderer->replaceURLPrefix ($properties ['background-image'], DOKU_INC);
  316. }
  317. } else {
  318. // To old ODT plugin version.
  319. return;
  320. }
  321. if ( empty($properties ['text-align']) )
  322. {
  323. if ($dir == 'ltr') {
  324. $properties ['text-align'] = 'left';
  325. $properties ['writing-mode'] = 'lr';
  326. }
  327. if ($dir == 'rtl') {
  328. $properties ['text-align'] = 'right';
  329. $properties ['writing-mode'] = 'rl';
  330. }
  331. }
  332. $name = '';
  333. if ( empty($language) === false ) {
  334. $properties ['lang'] = $language;
  335. $name .= 'Language: '.$language;
  336. }
  337. if ( $indent_first === true ) {
  338. // Eventually indent or outdent first line only...
  339. if ( $is_indent === true ) {
  340. // FIXME: Has to be adjusted if test direction will be supported.
  341. // See all.css
  342. $properties ['text-indent'] = $properties ['padding-left'];
  343. $properties ['padding-left'] = 0;
  344. $name .= 'Indent first';
  345. }
  346. if ( $is_outdent === true ) {
  347. // FIXME: Has to be adjusted if text (RTL, LTR) direction will be supported.
  348. // See all.css
  349. $properties ['text-indent'] = $properties ['margin-left'];
  350. $properties ['margin-left'] = 0;
  351. $name .= 'Outdent first';
  352. }
  353. } else {
  354. // Eventually indent or outdent the whole paragraph...
  355. if ( $is_indent === true ) {
  356. // FIXME: Has to be adjusted if test direction will be supported.
  357. // See all.css
  358. $properties ['margin-left'] = $properties ['padding-left'] ?? null;
  359. $properties ['padding-left'] = 0;
  360. $name .= 'Indent';
  361. }
  362. if ( $is_outdent === true ) {
  363. // Nothing to change: keep left margin property.
  364. // FIXME: Has to be adjusted if text (RTL, LTR) direction will be supported.
  365. // See all.css
  366. $name .= 'Outdent';
  367. }
  368. }
  369. $renderer->p_close();
  370. if ( method_exists ($renderer, 'createParagraphStyle') === false ) {
  371. // Older ODT plugin version.
  372. $renderer->_odtParagraphOpenUseProperties($properties);
  373. } else {
  374. // Newer version create our own common styles.
  375. // Create parent style to group the others beneath it
  376. if (!$renderer->styleExists('Plugin_Wrap_Paragraphs')) {
  377. $parent_properties = array();
  378. $parent_properties ['style-parent'] = null;
  379. $parent_properties ['style-class'] = 'Plugin Wrap Paragraphs';
  380. $parent_properties ['style-name'] = 'Plugin_Wrap_Paragraphs';
  381. $parent_properties ['style-display-name'] = 'Plugin Wrap';
  382. $renderer->createParagraphStyle($parent_properties);
  383. }
  384. $name .= $this->getODTCommonStyleName($class);
  385. $style_name = 'Plugin_Wrap_Paragraph_'.$name;
  386. if (!$renderer->styleExists($style_name)) {
  387. $properties ['style-parent'] = 'Plugin_Wrap_Paragraphs';
  388. $properties ['style-class'] = null;
  389. $properties ['style-name'] = $style_name;
  390. $properties ['style-display-name'] = $name;
  391. $renderer->createParagraphStyle($properties);
  392. }
  393. $renderer->p_open($style_name);
  394. }
  395. }
  396. function renderODTCloseParagraph ($renderer) {
  397. if ( method_exists ($renderer, 'p_close') === false ) {
  398. // Function is not supported by installed ODT plugin version, return.
  399. return;
  400. }
  401. $renderer->p_close();
  402. }
  403. function renderODTOpenColumn ($renderer, $class, $style, $attr) {
  404. $properties = array ();
  405. if ( method_exists ($renderer, 'getODTPropertiesFromElement') === true ) {
  406. // Get CSS properties for ODT export.
  407. $renderer->getODTPropertiesNew ($properties, 'div', $attr);
  408. } else if ( method_exists ($renderer, 'getODTProperties') === true ) {
  409. // Get CSS properties for ODT export (deprecated version).
  410. $renderer->getODTProperties ($properties, null, $class, $style);
  411. } else {
  412. // To old ODT plugin version.
  413. return;
  414. }
  415. // Frames/Textboxes still have some issues with formatting (at least in LibreOffice)
  416. // So as a workaround we implement columns as a table.
  417. // This is why we now use the margin of the div as the padding for the ODT table.
  418. $properties ['padding-left'] = $properties ['margin-left'] ?? null;
  419. $properties ['padding-right'] = $properties ['margin-right'] ?? null;
  420. $properties ['padding-top'] = $properties ['margin-top'] ?? null;
  421. $properties ['padding-bottom'] = $properties ['margin-bottom'] ?? null;
  422. $properties ['margin-left'] = null;
  423. $properties ['margin-right'] = null;
  424. $properties ['margin-top'] = null;
  425. $properties ['margin-bottom'] = null;
  426. // Percentage values are not supported for the padding. Convert to absolute values.
  427. $length = strlen ($properties ['padding-left']);
  428. if ( $length > 0 && $properties ['padding-left'] [$length-1] == '%' ) {
  429. $properties ['padding-left'] = trim ($properties ['padding-left'], '%');
  430. $properties ['padding-left'] = $renderer->_getAbsWidthMindMargins ($properties ['padding-left']).'cm';
  431. }
  432. $length = strlen ($properties ['padding-right']);
  433. if ( $length > 0 && $properties ['padding-right'] [$length-1] == '%' ) {
  434. $properties ['padding-right'] = trim ($properties ['padding-right'], '%');
  435. $properties ['padding-right'] = $renderer->_getAbsWidthMindMargins ($properties ['padding-right']).'cm';
  436. }
  437. $length = strlen ($properties ['padding-top']);
  438. if ( $length > 0 && $properties ['padding-top'] [$length-1] == '%' ) {
  439. $properties ['padding-top'] = trim ($properties ['padding-top'], '%');
  440. $properties ['padding-top'] = $renderer->_getAbsWidthMindMargins ($properties ['padding-top']).'cm';
  441. }
  442. $length = strlen ($properties ['padding-bottom']);
  443. if ( $length > 0 && $properties ['padding-bottom'] [$length-1] == '%' ) {
  444. $properties ['padding-bottom'] = trim ($properties ['padding-bottom'], '%');
  445. $properties ['padding-bottom'] = $renderer->_getAbsWidthMindMargins ($properties ['padding-bottom']).'cm';
  446. }
  447. $this->column_count++;
  448. if ( $this->column_count == 1 ) {
  449. // If this is the first column opened since the group was opened
  450. // then we have to open the table and a (single) row first.
  451. $properties ['width'] = '100%';
  452. $renderer->_odtTableOpenUseProperties($properties);
  453. $renderer->_odtTableRowOpenUseProperties($properties);
  454. }
  455. // We did not specify any max column value when we opened the table.
  456. // So we have to tell the renderer to add a column just now.
  457. unset($properties ['width']);
  458. $renderer->_odtTableAddColumnUseProperties($properties);
  459. // Open the cell.
  460. $renderer->_odtTableCellOpenUseProperties($properties);
  461. }
  462. function renderODTCloseColumn ($renderer) {
  463. if ( method_exists ($renderer, '_odtTableAddColumnUseProperties') === false ) {
  464. // Function is not supported by installed ODT plugin version, return.
  465. return;
  466. }
  467. $renderer->tablecell_close();
  468. }
  469. function renderODTOpenGroup ($renderer, $class, $style) {
  470. // Nothing to do for now.
  471. }
  472. function renderODTCloseGroup ($renderer) {
  473. // If a table has been opened in the group we close it now.
  474. if ( $this->column_count > 0 ) {
  475. // At last we need to close the row and the table!
  476. $renderer->tablerow_close();
  477. //$renderer->table_close();
  478. $renderer->_odtTableClose();
  479. }
  480. $this->column_count = 0;
  481. }
  482. function renderODTOpenSpan ($renderer, $class, $style, $language, $attr) {
  483. $properties = array ();
  484. if ( method_exists ($renderer, 'getODTPropertiesFromElement') === true ) {
  485. // Get CSS properties for ODT export.
  486. // Set parameter $inherit=false to prevent changiung the font-size and family!
  487. $renderer->getODTPropertiesNew ($properties, 'span', $attr, null, false);
  488. } else if ( method_exists ($renderer, 'getODTProperties') === true ) {
  489. // Get CSS properties for ODT export (deprecated version).
  490. $renderer->getODTProperties ($properties, 'span', $class, $style);
  491. if ( empty($properties ['background-image']) === false ) {
  492. $properties ['background-image'] =
  493. $renderer->replaceURLPrefix ($properties ['background-image'], DOKU_INC);
  494. }
  495. } else {
  496. // To old ODT plugin version.
  497. return;
  498. }
  499. $name = '';
  500. if ( empty($language) === false ) {
  501. $properties ['lang'] = $language;
  502. $name .= 'Language: '.$language;
  503. }
  504. if ( method_exists ($renderer, 'getODTPropertiesFromElement') === false ) {
  505. // Older ODT plugin version.
  506. $renderer->_odtSpanOpenUseProperties($properties);
  507. } else {
  508. // Newer version create our own common styles.
  509. $properties ['font-size'] = null;
  510. // Create parent style to group the others beneath it
  511. if (!$renderer->styleExists('Plugin_Wrap_Spans')) {
  512. $parent_properties = array();
  513. $parent_properties ['style-parent'] = null;
  514. $parent_properties ['style-class'] = 'Plugin Wrap Spans';
  515. $parent_properties ['style-name'] = 'Plugin_Wrap_Spans';
  516. $parent_properties ['style-display-name'] = 'Plugin Wrap';
  517. $renderer->createTextStyle($parent_properties);
  518. }
  519. $name .= $this->getODTCommonStyleName($class);
  520. $style_name = 'Plugin_Wrap_Span_'.$name;
  521. if (!$renderer->styleExists($style_name)) {
  522. $properties ['style-parent'] = 'Plugin_Wrap_Spans';
  523. $properties ['style-class'] = null;
  524. $properties ['style-name'] = $style_name;
  525. $properties ['style-display-name'] = $name;
  526. $renderer->createTextStyle($properties);
  527. }
  528. if (!empty($properties ['background-image'])) {
  529. if (method_exists ($renderer, '_odtAddImageUseProperties') === true) {
  530. $size = null;
  531. if (!empty($properties ['font-size'])) {
  532. $size = $properties ['font-size'];
  533. $size = $renderer->addToValue($size, '2pt');
  534. }
  535. $properties ['width'] = $size;
  536. $properties ['height'] = $size;
  537. $properties ['title'] = null;
  538. $renderer->_odtAddImageUseProperties ($properties ['background-image'],$properties);
  539. } else {
  540. $renderer->_odtAddImage ($properties ['background-image'],null,null,null,null,null);
  541. }
  542. }
  543. $renderer->_odtSpanOpen($style_name);
  544. }
  545. }
  546. function renderODTCloseSpan ($renderer) {
  547. if ( method_exists ($renderer, '_odtSpanClose') === false ) {
  548. // Function is not supported by installed ODT plugin version, return.
  549. return;
  550. }
  551. $renderer->_odtSpanClose();
  552. }
  553. function renderODTOpenTable ($renderer, $attr, $style, $attr_string) {
  554. self::$table_entr += 1;
  555. $class = $attr ['class'];
  556. $css_properties = array ();
  557. if ( method_exists ($renderer, 'getODTPropertiesFromElement') === false ) {
  558. // Function is not supported by installed ODT plugin version, return.
  559. return;
  560. }
  561. // Get CSS properties for ODT export.
  562. $renderer->getODTPropertiesNew ($css_properties, 'div', $attr_string, null, true);
  563. if ( empty($css_properties ['float']) === true ) {
  564. // If the float property is not set, set it to 'left' becuase the ODT plugin
  565. // would default to 'center' which is diffeent to the XHTML behaviour.
  566. //$css_properties ['float'] = 'left';
  567. if (strpos ($class, 'wrap_left') !== false ) {
  568. $css_properties ['float'] = 'left';
  569. } else if (strpos ($class, 'wrap_center') !== false ) {
  570. $css_properties ['float'] = 'center';
  571. } else if (strpos ($class, 'wrap_right') !== false) {
  572. $css_properties ['float'] = 'right';
  573. }
  574. }
  575. // The display property has differing usage in CSS. So we better overwrite it.
  576. $css_properties ['display'] = 'always';
  577. if ( stripos ($class, 'wrap_noprint') !== false ) {
  578. $css_properties ['display'] = 'screen';
  579. }
  580. if ( stripos ($class, 'wrap_onlyprint') !== false ) {
  581. $css_properties ['display'] = 'printer';
  582. }
  583. $background_color = $css_properties ['background-color'];
  584. $image = $css_properties ['background-image'] ?? null;
  585. $margin_top = $css_properties ['margin-top'];
  586. $margin_right = $css_properties ['margin-right'];
  587. $margin_bottom = $css_properties ['margin-bottom'];
  588. $margin_left = $css_properties ['margin-left'];
  589. $width = $attr ['width'];
  590. // Open 2x1 table if image is present
  591. // otherwise only a 1x1 table
  592. $properties = array();
  593. $properties ['width'] = '100%';
  594. $properties ['align'] = 'center';
  595. $properties ['margin-top'] = $margin_top;
  596. $properties ['margin-right'] = $margin_right;
  597. $properties ['margin-bottom'] = $margin_bottom;
  598. $properties ['margin-left'] = $margin_left;
  599. $frame_props = array();
  600. if (!empty($css_properties ['border'])) {
  601. $frame_props ['border'] = $css_properties ['border'];
  602. } else {
  603. $frame_props ['border'] = 'none';
  604. }
  605. $frame_props ['min-height'] = '1cm';
  606. $frame_props ['width'] = $attr ['width'];
  607. $frame_props ['float'] = $css_properties ['float'];
  608. if ( self::$table_entr > 1 ) {
  609. $frame_props ['anchor-type'] = 'as-char';
  610. } else {
  611. $frame_props ['anchor-type'] = 'paragraph';
  612. }
  613. $frame_props ['textarea-horizontal-align'] = 'left';
  614. $frame_props ['run-through'] = 'foreground';
  615. $frame_props ['vertical-pos'] = 'from-top';
  616. $frame_props ['vertical-rel'] = 'paragraph';
  617. $frame_props ['horizontal-pos'] = 'from-left';
  618. $frame_props ['horizontal-rel'] = 'paragraph';
  619. $frame_props ['wrap'] = 'parallel';
  620. $frame_props ['number-wrapped-paragraphs'] = 'no-limit';
  621. if (!empty($frame_props ['float']) &&
  622. $frame_props ['float'] != 'center') {
  623. $frame_props ['margin-top'] = '0cm';
  624. $frame_props ['margin-right'] = '0cm';
  625. $frame_props ['margin-bottom'] = '0cm';
  626. $frame_props ['margin-left'] = '0cm';
  627. $frame_props ['padding-top'] = '0cm';
  628. $frame_props ['padding-bottom'] = '0cm';
  629. } else {
  630. // No wrapping on not floating divs
  631. $frame_props ['wrap'] = 'none';
  632. }
  633. switch ($frame_props ['float']) {
  634. case 'left':
  635. if ( self::$table_entr == 1 ) {
  636. $frame_props ['y'] = '0cm';
  637. $frame_props ['x'] = self::$box_left_pos.'cm';
  638. self::$box_left_pos += trim($frame_props ['width'], 'cm');
  639. }
  640. $frame_props ['padding-left'] = '0cm';
  641. break;
  642. case 'right':
  643. $frame_props ['horizontal-rel'] = 'paragraph';
  644. $frame_props ['horizontal-pos'] = 'right';
  645. $frame_props ['padding-right'] = '0cm';
  646. break;
  647. case 'center':
  648. $frame_props ['horizontal-pos'] = 'center';
  649. break;
  650. default:
  651. $frame_props ['padding-left'] = '0cm';
  652. break;
  653. }
  654. $renderer->_odtOpenTextBoxUseProperties($frame_props);
  655. $renderer->_odtTableOpenUseProperties($properties);
  656. if (!empty($image)) {
  657. $properties = array();
  658. $properties ['width'] = '2cm';
  659. $renderer->_odtTableAddColumnUseProperties($properties);
  660. }
  661. $properties = array();
  662. $renderer->_odtTableAddColumnUseProperties($properties);
  663. $renderer->tablerow_open();
  664. if (!empty($image)) {
  665. $properties = array();
  666. $properties ['vertical-align'] = 'middle';
  667. $properties ['text-align'] = 'center';
  668. $properties ['padding'] = '0.1cm';
  669. $properties ['background-color'] = $background_color;
  670. $renderer->_odtTableCellOpenUseProperties($properties);
  671. $renderer->_odtAddImage($image);
  672. $renderer->tablecell_close();
  673. }
  674. $properties = array();
  675. $properties ['vertical-align'] = 'middle';
  676. $properties ['padding'] = '0.3cm';
  677. $properties ['background-color'] = $background_color;
  678. $properties ['border'] = 'none';
  679. $renderer->_odtTableCellOpenUseProperties($properties);
  680. }
  681. function renderODTCloseTable ($renderer) {
  682. $renderer->tablecell_close();
  683. $renderer->tablerow_close();
  684. $renderer->_odtTableClose();
  685. $renderer->_odtCloseTextBox ();
  686. $renderer->p_open();
  687. $renderer->p_close();
  688. self::$table_entr -= 1;
  689. }
  690. protected function getODTCommonStyleName ($class_string) {
  691. static $map = array (
  692. 'wrap_box' => 'Box', 'wrap_danger' => 'Danger', 'wrap_warning' => 'Warning',
  693. 'wrap_caution' => 'Caution', 'wrap_notice' => 'Notice', 'wrap_safety' => 'Safety',
  694. 'wrap_info' => 'Info', 'wrap_important' => 'Important', 'wrap_alert' => 'Alert',
  695. 'wrap_tip' => 'Tip', 'wrap_help' => 'Help', 'wrap_todo' => 'To do',
  696. 'wrap_download' => 'Download', 'wrap_hi' => 'Highlighted', 'wrap_spoiler' => 'Spoiler',
  697. 'wrap_leftalign' => 'Left aligned', 'wrap_rightalign' => 'Right aligned',
  698. 'wrap_centeralign' => 'Centered', 'wrap_justify' => 'Justify', 'wrap_em' => 'Emphasised',
  699. 'wrap_lo' => 'Less significant');
  700. $classes = explode(' ', $class_string);
  701. $name = '';
  702. foreach ($classes as $class) {
  703. if (array_key_exists($class, $map)) {
  704. $name .= $map [$class];
  705. }
  706. }
  707. return ($name);
  708. }
  709. }