FormatHelper.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695
  1. <?php
  2. namespace Tools\View\Helper;
  3. use Cake\Core\Configure;
  4. use Cake\Utility\Inflector;
  5. use Cake\View\Helper;
  6. use Cake\View\StringTemplate;
  7. use Cake\View\View;
  8. /**
  9. * Format helper with basic html snippets
  10. *
  11. * TODO: make snippets more "css and background image" (instead of inline img links)
  12. *
  13. * @author Mark Scherer
  14. * @license MIT
  15. * @property \Cake\View\Helper\HtmlHelper $Html
  16. */
  17. class FormatHelper extends Helper {
  18. /**
  19. * Other helpers used by FormHelper
  20. *
  21. * @var array
  22. */
  23. public $helpers = ['Html'];
  24. /**
  25. * @var \Cake\View\StringTemplate
  26. */
  27. public $template;
  28. /**
  29. * @var array
  30. */
  31. protected $_defaultIcons = [
  32. 'yes' => 'fa fa-check',
  33. 'no' => 'fa fa-times',
  34. 'view' => 'fa fa-eye',
  35. 'edit' => 'fa fa-pencil',
  36. 'add' => 'fa fa-plus',
  37. 'delete' => 'fa fa-trash',
  38. 'prev' => 'fa fa-prev',
  39. 'next' => 'fa fa-next',
  40. 'pro' => 'fa fa-thumbs-up',
  41. 'contra' => 'fa fa-thumbs-down',
  42. 'male' => 'fa fa-mars',
  43. 'female' => 'fa fa-venus',
  44. 'config' => 'fa fa-cogs'
  45. //'genderless' => 'fa fa-genderless'
  46. ];
  47. /**
  48. * @var array
  49. */
  50. protected $_defaults = [
  51. 'fontIcons' => null,
  52. 'iconNamespace' => 'fa', // Used to be icon,
  53. 'autoPrefix' => true, // For custom icons "prev" becomes "fa-prev" when iconNamespace is "fa"
  54. 'templates' => [
  55. 'icon' => '<i class="{{class}}"{{attributes}}></i>',
  56. 'ok' => '<span class="ok-{{type}}" style="color:{{color}}"{{attributes}}>{{content}}</span>'
  57. ]
  58. ];
  59. /**
  60. * FormatHelper constructor.
  61. *
  62. * @param \Cake\View\View $View
  63. * @param array $config
  64. */
  65. public function __construct(View $View, array $config = []) {
  66. $defaults = (array)Configure::read('Format') + $this->_defaults;
  67. $config += $defaults;
  68. $config['fontIcons'] = (array)$config['fontIcons'] + $this->_defaultIcons;
  69. $this->template = new StringTemplate($config['templates']);
  70. parent::__construct($View, $config);
  71. }
  72. /**
  73. * jqueryAccess: {id}Pro, {id}Contra
  74. *
  75. * @param mixed $value Boolish value
  76. * @param array $options
  77. * @param array $attributes
  78. * @return string
  79. */
  80. public function thumbs($value, array $options = [], array $attributes = []) {
  81. $icon = !empty($value) ? 'pro' : 'contra';
  82. return $this->icon($icon, $options, $attributes);
  83. }
  84. /**
  85. * Display neighbor quicklinks
  86. *
  87. * @param array $neighbors (containing prev and next)
  88. * @param string $field : just field or Model.field syntax
  89. * @param array $options :
  90. * - name: title name: next{Record} (if none is provided, "record" is used - not translated!)
  91. * - slug: true/false (defaults to false)
  92. * - titleField: field or Model.field
  93. * @return string
  94. */
  95. public function neighbors(array $neighbors, $field, array $options = []) {
  96. if (mb_strpos($field, '.') !== false) {
  97. $fieldArray = explode('.', $field, 2);
  98. $alias = $fieldArray[0];
  99. $field = $fieldArray[1];
  100. }
  101. if (empty($alias)) {
  102. if (!empty($neighbors['prev'])) {
  103. $modelNames = array_keys($neighbors['prev']);
  104. $alias = $modelNames[0];
  105. } elseif (!empty($neighbors['next'])) {
  106. $modelNames = array_keys($neighbors['next']);
  107. $alias = $modelNames[0];
  108. }
  109. }
  110. if (empty($field)) {
  111. }
  112. $name = 'Record'; // Translation further down!
  113. if (!empty($options['name'])) {
  114. $name = ucfirst($options['name']);
  115. }
  116. $prevSlug = $nextSlug = null;
  117. if (!empty($options['slug'])) {
  118. if (!empty($neighbors['prev'])) {
  119. $prevSlug = Inflector::slug($neighbors['prev'][$alias][$field], '-');
  120. }
  121. if (!empty($neighbors['next'])) {
  122. $nextSlug = Inflector::slug($neighbors['next'][$alias][$field], '-');
  123. }
  124. }
  125. $titleAlias = $alias;
  126. $titleField = $field;
  127. if (!empty($options['titleField'])) {
  128. if (mb_strpos($options['titleField'], '.') !== false) {
  129. $fieldArray = explode('.', $options['titleField'], 2);
  130. $titleAlias = $fieldArray[0];
  131. $titleField = $fieldArray[1];
  132. } else {
  133. $titleField = $options['titleField'];
  134. }
  135. }
  136. if (!isset($options['escape']) || $options['escape'] === false) {
  137. $titleField = h($titleField);
  138. }
  139. $ret = '<div class="next-prev-navi nextPrevNavi">';
  140. if (!empty($neighbors['prev'])) {
  141. $url = [$neighbors['prev'][$alias]['id'], $prevSlug];
  142. if (!empty($options['url'])) {
  143. $url += $options['url'];
  144. }
  145. // ICON_PREV, false
  146. $ret .= $this->Html->link(
  147. $this->icon('prev') . '&nbsp;' . __d('tools', 'prev' . $name),
  148. $url,
  149. ['escape' => false, 'title' => $neighbors['prev'][$titleAlias][$titleField]]
  150. );
  151. } else {
  152. //ICON_PREV_DISABLED, __d('tools', 'noPrev' . $name)) . '&nbsp;' . __d('tools', 'prev' . $name
  153. $ret .= $this->icon('prev');
  154. }
  155. $ret .= '&nbsp;&nbsp;';
  156. if (!empty($neighbors['next'])) {
  157. $url = [$neighbors['next'][$alias]['id'], $prevSlug];
  158. if (!empty($options['url'])) {
  159. $url += $options['url'];
  160. }
  161. // ICON_NEXT, false
  162. $ret .= $this->Html->link(
  163. $this->icon('next') . '&nbsp;' . __d('tools', 'next' . $name),
  164. $url,
  165. ['escape' => false, 'title' => $neighbors['next'][$titleAlias][$titleField]]
  166. );
  167. } else {
  168. // ICON_NEXT_DISABLED, __d('tools', 'noNext' . $name)
  169. $ret .= $this->icon('next') . '&nbsp;' . __d('tools', 'next' . $name);
  170. }
  171. $ret .= '</div>';
  172. return $ret;
  173. }
  174. const GENDER_FEMALE = 2;
  175. const GENDER_MALE = 1;
  176. /**
  177. * Displays gender icon
  178. *
  179. * @param mixed $value
  180. * @return string
  181. */
  182. public function genderIcon($value) {
  183. $value = (int)$value;
  184. if ($value == static::GENDER_FEMALE) {
  185. $icon = $this->icon('female');
  186. } elseif ($value == static::GENDER_MALE) {
  187. $icon = $this->icon('male');
  188. } else {
  189. $icon = $this->icon('genderless', [], ['title' => 'Unknown']);
  190. }
  191. return $icon;
  192. }
  193. /**
  194. * Display a font icon (fast and resource-efficient).
  195. * Uses http://fontawesome.io/icons/
  196. *
  197. * Options:
  198. * - size (int|string: 1...5 or large)
  199. * - rotate (integer: 90, 270, ...)
  200. * - spin (booelan: true/false)
  201. * - extra (array: muted, light, dark, border)
  202. * - pull (string: left, right)
  203. *
  204. * @param string|array $icon
  205. * @param array $options
  206. * @param array $attributes
  207. * @return string
  208. */
  209. public function fontIcon($icon, array $options = [], array $attributes = []) {
  210. $defaults = [
  211. 'namespace' => $this->_config['iconNamespace']
  212. ];
  213. $options += $defaults;
  214. $icon = (array)$icon;
  215. $class = [$options['namespace']];
  216. foreach ($icon as $i) {
  217. $class[] = $options['namespace'] . '-' . $i;
  218. }
  219. if (!empty($options['extra'])) {
  220. foreach ($options['extra'] as $i) {
  221. $class[] = $options['namespace'] . '-' . $i;
  222. }
  223. }
  224. if (!empty($options['size'])) {
  225. $class[] = $options['namespace'] . '-' . ($options['size'] === 'large' ? 'large' : $options['size'] . 'x');
  226. }
  227. if (!empty($options['pull'])) {
  228. $class[] = 'pull-' . $options['pull'];
  229. }
  230. if (!empty($options['rotate'])) {
  231. $class[] = $options['namespace'] . '-rotate-' . (int)$options['rotate'];
  232. }
  233. if (!empty($options['spin'])) {
  234. $class[] = $options['namespace'] . '-spin';
  235. }
  236. return '<i class="' . implode(' ', $class) . '"></i>';
  237. }
  238. /**
  239. * Icons using the default namespace
  240. *
  241. * @param string $icon (constant or filename)
  242. * @param array $options :
  243. * - translate, title, ...
  244. * @param array $attributes :
  245. * - class, ...
  246. * @return string
  247. */
  248. public function icon($icon, array $options = [], array $attributes = []) {
  249. $defaults = [
  250. 'translate' => true,
  251. ];
  252. $options += $defaults;
  253. if (!isset($attributes['title'])) {
  254. if (isset($options['title'])) {
  255. $attributes['title'] = $options['title'];
  256. } else {
  257. $attributes['title'] = Inflector::humanize($icon);
  258. }
  259. }
  260. return $this->_fontIcon($icon, $options, $attributes);
  261. }
  262. /**
  263. * Img Icons
  264. *
  265. * @param string $icon (constant or filename)
  266. * @param array $options :
  267. * - translate, title, ...
  268. * @param array $attributes :
  269. * - class, ...
  270. * @return string
  271. */
  272. public function cIcon($icon, array $options = [], array $attributes = []) {
  273. return $this->_customIcon($icon, $options, $attributes);
  274. }
  275. /**
  276. * Deprecated img icons, font icons should be used instead, but sometimes
  277. * we still need a custom img icon.
  278. *
  279. * @param string $icon (constant or filename)
  280. * @param array $options :
  281. * - translate, title, ...
  282. * @param array $attributes :
  283. * - class, ...
  284. * @return string
  285. */
  286. protected function _customIcon($icon, array $options = [], array $attributes = []) {
  287. $translate = isset($options['translate']) ? $options['translate'] : true;
  288. $type = pathinfo($icon, PATHINFO_FILENAME);
  289. $title = isset($t) ? $t : ucfirst($type);
  290. $alt = (isset($a) ? $a : Inflector::slug($title));
  291. if ($translate !== false) {
  292. $title = __($title);
  293. $alt = __($alt);
  294. }
  295. $alt = '[' . $alt . ']';
  296. $defaults = ['title' => $title, 'alt' => $alt, 'class' => 'icon'];
  297. $options = $attributes + $options;
  298. $options += $defaults;
  299. if (substr($icon, 0, 1) !== '/') {
  300. $icon = 'icons/' . $icon;
  301. }
  302. return $this->Html->image($icon, $options);
  303. }
  304. /**
  305. * Renders a font icon.
  306. *
  307. * @param string $type
  308. * @param array $options
  309. * @param array $attributes
  310. * @return string
  311. */
  312. protected function _fontIcon($type, $options, $attributes) {
  313. $iconClass = $type;
  314. if ($this->_config['autoPrefix'] && $this->_config['iconNamespace']) {
  315. $iconClass = $this->_config['iconNamespace'] . '-' . $iconClass;
  316. }
  317. if ($this->_config['iconNamespace']) {
  318. $iconClass = $this->_config['iconNamespace'] . ' ' . $iconClass;
  319. }
  320. if (isset($this->_config['fontIcons'][$type])) {
  321. $iconClass = $this->_config['fontIcons'][$type];
  322. }
  323. $defaults = [
  324. 'class' => 'icon icon-' . $type . ' ' . $iconClass
  325. ];
  326. $options += $defaults;
  327. if (!isset($attributes['title'])) {
  328. $attributes['title'] = ucfirst($type);
  329. if (!isset($options['translate']) || $options['translate'] !== false) {
  330. $attributes['title'] = __($attributes['title']);
  331. }
  332. }
  333. $attributes += [
  334. 'data-placement' => 'bottom',
  335. 'data-toggle' => 'tooltip'
  336. ];
  337. $options['attributes'] = $this->template->formatAttributes($attributes);
  338. return $this->template->format('icon', $options);
  339. }
  340. /**
  341. * Displays yes/no symbol.
  342. *
  343. * @param int|bool $value Value
  344. * @param array $options
  345. * - on (defaults to 1/true)
  346. * - onTitle
  347. * - offTitle
  348. * @param array $attributes
  349. * - title, ...
  350. * @return string HTML icon Yes/No
  351. */
  352. public function yesNo($value, array $options = [], array $attributes = []) {
  353. $defaults = [
  354. 'on' => 1,
  355. 'onTitle' => __d('tools', 'Yes'),
  356. 'offTitle' => __d('tools', 'No'),
  357. ];
  358. $options += $defaults;
  359. if ($value == $options['on']) {
  360. $icon = 'yes';
  361. $value = 'on';
  362. } else {
  363. $icon = 'no';
  364. $value = 'off';
  365. }
  366. $attributes += ['title' => $options[$value . 'Title']];
  367. return $this->icon($icon, $options, $attributes);
  368. }
  369. /**
  370. * Gets URL of a png img of a website (16x16 pixel).
  371. *
  372. * @param string $domain
  373. * @return string
  374. */
  375. public function siteIconUrl($domain) {
  376. if (strpos($domain, 'http') === 0) {
  377. // Strip protocol
  378. $pieces = parse_url($domain);
  379. $domain = $pieces['host'];
  380. }
  381. return 'http://www.google.com/s2/favicons?domain=' . $domain;
  382. }
  383. /**
  384. * Display a png img of a website (16x16 pixel)
  385. * if not available, will return a fallback image (a globe)
  386. *
  387. * @param string $domain (preferably without protocol, e.g. "www.site.com")
  388. * @param array $options
  389. * @return string
  390. */
  391. public function siteIcon($domain, array $options = []) {
  392. $url = $this->siteIconUrl($domain);
  393. $options['width'] = 16;
  394. $options['height'] = 16;
  395. if (!isset($options['alt'])) {
  396. $options['alt'] = $domain;
  397. }
  398. if (!isset($options['title'])) {
  399. $options['title'] = $domain;
  400. }
  401. return $this->Html->image($url, $options);
  402. }
  403. /**
  404. * Display a disabled link tag
  405. *
  406. * @param string $text
  407. * @param array $options
  408. * @return string
  409. */
  410. public function disabledLink($text, array $options = []) {
  411. $defaults = ['class' => 'disabledLink', 'title' => __d('tools', 'notAvailable')];
  412. $options += $defaults;
  413. return $this->Html->tag('span', $text, $options);
  414. }
  415. /**
  416. * Generates a pagination count: #1 etc for each pagination record
  417. * respects order (ASC/DESC)
  418. *
  419. * @param array $paginator
  420. * @param int $count (current post count on this page)
  421. * @param string|null $dir (ASC/DESC)
  422. * @return int
  423. * @deprecated
  424. */
  425. public function absolutePaginateCount(array $paginator, $count, $dir = null) {
  426. if ($dir === null) {
  427. $dir = 'ASC';
  428. }
  429. $currentPage = $paginator['page'];
  430. $pageCount = $paginator['pageCount'];
  431. $totalCount = $paginator['count'];
  432. $limit = $paginator['limit'];
  433. $step = isset($paginator['step']) ? $paginator['step'] : 1;
  434. if ($dir === 'DESC') {
  435. $currentCount = $count + ($pageCount - $currentPage) * $limit * $step;
  436. if ($currentPage != $pageCount && $pageCount > 1) {
  437. $currentCount -= $pageCount * $limit * $step - $totalCount;
  438. }
  439. } else {
  440. $currentCount = $count + ($currentPage - 1) * $limit * $step;
  441. }
  442. return $currentCount;
  443. }
  444. /**
  445. * Fixes utf8 problems of native php str_pad function
  446. * //TODO: move to textext helper? Also note there is Text::wrap() now.
  447. *
  448. * @param string $input
  449. * @param int $padLength
  450. * @param string $padString
  451. * @param mixed $padType
  452. * @return string input
  453. */
  454. public function pad($input, $padLength, $padString, $padType = STR_PAD_RIGHT) {
  455. $length = mb_strlen($input);
  456. if ($padLength - $length > 0) {
  457. switch ($padType) {
  458. case STR_PAD_LEFT:
  459. $input = str_repeat($padString, $padLength - $length) . $input;
  460. break;
  461. case STR_PAD_RIGHT:
  462. $input .= str_repeat($padString, $padLength - $length);
  463. break;
  464. }
  465. }
  466. return $input;
  467. }
  468. /**
  469. * Returns red colored if not ok
  470. *
  471. * @param string $value
  472. * @param mixed $ok Boolish value
  473. * @return string Value in HTML tags
  474. */
  475. public function warning($value, $ok = false) {
  476. if (!$ok) {
  477. return $this->ok($value, false);
  478. }
  479. return $value;
  480. }
  481. /**
  482. * Returns green on ok, red otherwise
  483. *
  484. * @todo Remove inline css and make classes better: green=>ok red=>not-ok
  485. * Maybe use templating
  486. *
  487. * @param mixed $content Output
  488. * @param bool $ok Boolish value
  489. * @param array $attributes
  490. * @return string Value nicely formatted/colored
  491. */
  492. public function ok($content, $ok = false, array $attributes = []) {
  493. if ($ok) {
  494. $type = 'yes';
  495. $color = 'green';
  496. } else {
  497. $type = 'no';
  498. $color = 'red';
  499. }
  500. $options = [
  501. 'type' => $type,
  502. 'color' => $color
  503. ];
  504. $options['content'] = $content;
  505. $options['attributes'] = $this->template->formatAttributes($attributes);
  506. return $this->template->format('ok', $options);
  507. }
  508. /**
  509. * Useful for displaying tabbed (code) content when the default of 8 spaces
  510. * inside <pre> is too much. This converts it to spaces for better output.
  511. *
  512. * Inspired by the tab2space function found at:
  513. *
  514. * @see http://aidan.dotgeek.org/lib/?file=function.tab2space.php
  515. * @param string $text
  516. * @param int $spaces
  517. * @return string
  518. */
  519. public function tab2space($text, $spaces = 4) {
  520. $spaces = str_repeat(' ', $spaces);
  521. $text = preg_split("/\r\n|\r|\n/", trim($text));
  522. $wordLengths = [];
  523. $wArray = [];
  524. // Store word lengths
  525. foreach ($text as $line) {
  526. $words = preg_split("/(\t+)/", $line, -1, PREG_SPLIT_DELIM_CAPTURE);
  527. foreach (array_keys($words) as $i) {
  528. $strlen = strlen($words[$i]);
  529. $add = isset($wordLengths[$i]) && ($wordLengths[$i] < $strlen);
  530. if ($add || !isset($wordLengths[$i])) {
  531. $wordLengths[$i] = $strlen;
  532. }
  533. }
  534. $wArray[] = $words;
  535. }
  536. $text = '';
  537. // Apply padding when appropriate and rebuild the string
  538. foreach (array_keys($wArray) as $i) {
  539. foreach (array_keys($wArray[$i]) as $ii) {
  540. if (preg_match("/^\t+$/", $wArray[$i][$ii])) {
  541. $wArray[$i][$ii] = str_pad($wArray[$i][$ii], $wordLengths[$ii], "\t");
  542. } else {
  543. $wArray[$i][$ii] = str_pad($wArray[$i][$ii], $wordLengths[$ii]);
  544. }
  545. }
  546. $text .= str_replace("\t", $spaces, implode('', $wArray[$i])) . "\n";
  547. }
  548. return $text;
  549. }
  550. /**
  551. * Translate a result array into a HTML table
  552. *
  553. * @todo Move to Text Helper etc.
  554. *
  555. * Options:
  556. * - recursive: Recursively generate tables for multi-dimensional arrays
  557. * - heading: Display the first as heading row (th)
  558. * - escape: Defaults to true
  559. * - null: Null value
  560. *
  561. * @author Aidan Lister <aidan@php.net>
  562. * @version 1.3.2
  563. * @link http://aidanlister.com/2004/04/converting-arrays-to-human-readable-tables/
  564. * @param array $array The result (numericaly keyed, associative inner) array.
  565. * @param array $options
  566. * @param array $attributes For the table
  567. * @return string
  568. */
  569. public function array2table(array $array, array $options = [], array $attributes = []) {
  570. $defaults = [
  571. 'null' => '&nbsp;',
  572. 'recursive' => false,
  573. 'heading' => true,
  574. 'escape' => true
  575. ];
  576. $options += $defaults;
  577. // Sanity check
  578. if (empty($array)) {
  579. return '';
  580. }
  581. if (!isset($array[0]) || !is_array($array[0])) {
  582. $array = [$array];
  583. }
  584. $attributes += [
  585. 'class' => 'table'
  586. ];
  587. $attributes = $this->template->formatAttributes($attributes);
  588. // Start the table
  589. $table = "<table$attributes>\n";
  590. if ($options['heading']) {
  591. // The header
  592. $table .= "\t<tr>";
  593. // Take the keys from the first row as the headings
  594. foreach (array_keys($array[0]) as $heading) {
  595. $table .= '<th>' . ($options['escape'] ? h($heading) : $heading) . '</th>';
  596. }
  597. $table .= "</tr>\n";
  598. }
  599. // The body
  600. foreach ($array as $row) {
  601. $table .= "\t<tr>";
  602. foreach ($row as $cell) {
  603. $table .= '<td>';
  604. // Cast objects
  605. if (is_object($cell)) {
  606. $cell = (array)$cell;
  607. }
  608. if ($options['recursive'] && is_array($cell) && !empty($cell)) {
  609. // Recursive mode
  610. $table .= "\n" . static::array2table($cell, $options) . "\n";
  611. } else {
  612. $table .= (!is_array($cell) && strlen($cell) > 0) ? ($options['escape'] ? h(
  613. $cell
  614. ) : $cell) : $options['null'];
  615. }
  616. $table .= '</td>';
  617. }
  618. $table .= "</tr>\n";
  619. }
  620. $table .= '</table>';
  621. return $table;
  622. }
  623. }