/libraries/src/Microdata/Microdata.php

https://github.com/Hackwar/joomla-cms · PHP · 899 lines · 404 code · 100 blank · 395 comment · 48 complexity · 87f01d5217280ea4e365979e7789aeee MD5 · raw file

  1. <?php
  2. /**
  3. * Joomla! Content Management System
  4. *
  5. * @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
  6. * @license GNU General Public License version 2 or later; see LICENSE.txt
  7. */
  8. namespace Joomla\CMS\Microdata;
  9. \defined('JPATH_PLATFORM') or die;
  10. /**
  11. * Joomla Platform class for interacting with Microdata semantics.
  12. *
  13. * @since 3.2
  14. */
  15. class Microdata
  16. {
  17. /**
  18. * Array with all available Types and Properties from the http://schema.org vocabulary
  19. *
  20. * @var array
  21. * @since 3.2
  22. */
  23. protected static $types = null;
  24. /**
  25. * The Type
  26. *
  27. * @var string
  28. * @since 3.2
  29. */
  30. protected $type = null;
  31. /**
  32. * The Property
  33. *
  34. * @var string
  35. * @since 3.2
  36. */
  37. protected $property = null;
  38. /**
  39. * The Human content
  40. *
  41. * @var string
  42. * @since 3.2
  43. */
  44. protected $content = null;
  45. /**
  46. * The Machine content
  47. *
  48. * @var string
  49. * @since 3.2
  50. */
  51. protected $machineContent = null;
  52. /**
  53. * The Fallback Type
  54. *
  55. * @var string
  56. * @since 3.2
  57. */
  58. protected $fallbackType = null;
  59. /**
  60. * The Fallback Property
  61. *
  62. * @var string
  63. * @since 3.2
  64. */
  65. protected $fallbackProperty = null;
  66. /**
  67. * Used for checking if the library output is enabled or disabled
  68. *
  69. * @var boolean
  70. * @since 3.2
  71. */
  72. protected $enabled = true;
  73. /**
  74. * Initialize the class and setup the default $Type
  75. *
  76. * @param string $type Optional, fallback to 'Thing' Type
  77. * @param boolean $flag Enable or disable the library output
  78. *
  79. * @since 3.2
  80. */
  81. public function __construct($type = '', $flag = true)
  82. {
  83. if ($this->enabled = (boolean) $flag)
  84. {
  85. // Fallback to 'Thing' Type
  86. if (!$type)
  87. {
  88. $type = 'Thing';
  89. }
  90. $this->setType($type);
  91. }
  92. }
  93. /**
  94. * Load all available Types and Properties from the http://schema.org vocabulary contained in the types.json file
  95. *
  96. * @return void
  97. *
  98. * @since 3.2
  99. */
  100. protected static function loadTypes()
  101. {
  102. // Load the JSON
  103. if (!static::$types)
  104. {
  105. $path = __DIR__ . '/types.json';
  106. static::$types = json_decode(file_get_contents($path), true);
  107. }
  108. }
  109. /**
  110. * Reset all params
  111. *
  112. * @return void
  113. *
  114. * @since 3.2
  115. */
  116. protected function resetParams()
  117. {
  118. $this->content = null;
  119. $this->machineContent = null;
  120. $this->property = null;
  121. $this->fallbackProperty = null;
  122. $this->fallbackType = null;
  123. }
  124. /**
  125. * Enable or Disable the library output
  126. *
  127. * @param boolean $flag Enable or disable the library output
  128. *
  129. * @return Microdata Instance of $this
  130. *
  131. * @since 3.2
  132. */
  133. public function enable($flag = true)
  134. {
  135. $this->enabled = (boolean) $flag;
  136. return $this;
  137. }
  138. /**
  139. * Return 'true' if the library output is enabled
  140. *
  141. * @return boolean
  142. *
  143. * @since 3.2
  144. */
  145. public function isEnabled()
  146. {
  147. return $this->enabled;
  148. }
  149. /**
  150. * Set a new http://schema.org Type
  151. *
  152. * @param string $type The $Type to be setup
  153. *
  154. * @return Microdata Instance of $this
  155. *
  156. * @since 3.2
  157. */
  158. public function setType($type)
  159. {
  160. if (!$this->enabled)
  161. {
  162. return $this;
  163. }
  164. // Sanitize the Type
  165. $this->type = static::sanitizeType($type);
  166. // If the given $Type isn't available, fallback to 'Thing' Type
  167. if (!static::isTypeAvailable($this->type))
  168. {
  169. $this->type = 'Thing';
  170. }
  171. return $this;
  172. }
  173. /**
  174. * Return the current $Type name
  175. *
  176. * @return string
  177. *
  178. * @since 3.2
  179. */
  180. public function getType()
  181. {
  182. return $this->type;
  183. }
  184. /**
  185. * Setup a $Property
  186. *
  187. * @param string $property The Property
  188. *
  189. * @return Microdata Instance of $this
  190. *
  191. * @since 3.2
  192. */
  193. public function property($property)
  194. {
  195. if (!$this->enabled)
  196. {
  197. return $this;
  198. }
  199. // Sanitize the $Property
  200. $property = static::sanitizeProperty($property);
  201. // Control if the $Property exists in the given $Type and setup it, otherwise leave it 'NULL'
  202. if (static::isPropertyInType($this->type, $property))
  203. {
  204. $this->property = $property;
  205. }
  206. return $this;
  207. }
  208. /**
  209. * Return the current $Property name
  210. *
  211. * @return string
  212. *
  213. * @since 3.2
  214. */
  215. public function getProperty()
  216. {
  217. return $this->property;
  218. }
  219. /**
  220. * Setup a Human content or content for the Machines
  221. *
  222. * @param string $content The human content or machine content to be used
  223. * @param string $machineContent The machine content
  224. *
  225. * @return Microdata Instance of $this
  226. *
  227. * @since 3.2
  228. */
  229. public function content($content, $machineContent = null)
  230. {
  231. $this->content = $content;
  232. $this->machineContent = $machineContent;
  233. return $this;
  234. }
  235. /**
  236. * Return the current $content
  237. *
  238. * @return string
  239. *
  240. * @since 3.2
  241. */
  242. public function getContent()
  243. {
  244. return $this->content;
  245. }
  246. /**
  247. * Return the current $machineContent
  248. *
  249. * @return string
  250. *
  251. * @since 3.3
  252. */
  253. public function getMachineContent()
  254. {
  255. return $this->machineContent;
  256. }
  257. /**
  258. * Setup a Fallback Type and Property
  259. *
  260. * @param string $type The Fallback Type
  261. * @param string $property The Fallback Property
  262. *
  263. * @return Microdata Instance of $this
  264. *
  265. * @since 3.2
  266. */
  267. public function fallback($type, $property)
  268. {
  269. if (!$this->enabled)
  270. {
  271. return $this;
  272. }
  273. // Sanitize the $Type
  274. $this->fallbackType = static::sanitizeType($type);
  275. // If the given $Type isn't available, fallback to 'Thing' Type
  276. if (!static::isTypeAvailable($this->fallbackType))
  277. {
  278. $this->fallbackType = 'Thing';
  279. }
  280. // Control if the $Property exist in the given $Type and setup it, otherwise leave it 'NULL'
  281. if (static::isPropertyInType($this->fallbackType, $property))
  282. {
  283. $this->fallbackProperty = $property;
  284. }
  285. else
  286. {
  287. $this->fallbackProperty = null;
  288. }
  289. return $this;
  290. }
  291. /**
  292. * Return the current $fallbackType
  293. *
  294. * @return string
  295. *
  296. * @since 3.2
  297. */
  298. public function getFallbackType()
  299. {
  300. return $this->fallbackType;
  301. }
  302. /**
  303. * Return the current $fallbackProperty
  304. *
  305. * @return string
  306. *
  307. * @since 3.2
  308. */
  309. public function getFallbackProperty()
  310. {
  311. return $this->fallbackProperty;
  312. }
  313. /**
  314. * This function handles the display logic.
  315. * It checks if the Type, Property are available, if not check for a Fallback,
  316. * then reset all params for the next use and return the HTML.
  317. *
  318. * @param string $displayType Optional, 'inline', available options ['inline'|'span'|'div'|meta]
  319. * @param boolean $emptyOutput Return an empty string if the library output is disabled and there is a $content value
  320. *
  321. * @return string
  322. *
  323. * @since 3.2
  324. */
  325. public function display($displayType = '', $emptyOutput = false)
  326. {
  327. // Initialize the HTML to output
  328. $html = ($this->content !== null && !$emptyOutput) ? $this->content : '';
  329. // Control if the library output is enabled, otherwise return the $content or an empty string
  330. if (!$this->enabled)
  331. {
  332. // Reset params
  333. $this->resetParams();
  334. return $html;
  335. }
  336. // If the $property is wrong for the current $Type check if a Fallback is available, otherwise return an empty HTML
  337. if ($this->property)
  338. {
  339. // Process and return the HTML the way the user expects to
  340. if ($displayType)
  341. {
  342. switch ($displayType)
  343. {
  344. case 'span':
  345. $html = static::htmlSpan($html, $this->property);
  346. break;
  347. case 'div':
  348. $html = static::htmlDiv($html, $this->property);
  349. break;
  350. case 'meta':
  351. $html = $this->machineContent ?? $html;
  352. $html = static::htmlMeta($html, $this->property);
  353. break;
  354. default:
  355. // Default $displayType = 'inline'
  356. $html = static::htmlProperty($this->property);
  357. break;
  358. }
  359. }
  360. else
  361. {
  362. /*
  363. * Process and return the HTML in an automatic way,
  364. * with the $Property expected Types and display everything in the right way,
  365. * check if the $Property is 'normal', 'nested' or must be rendered in a metadata tag
  366. */
  367. switch (static::getExpectedDisplayType($this->type, $this->property))
  368. {
  369. case 'nested':
  370. // Retrieve the expected 'nested' Type of the $Property
  371. $nestedType = static::getExpectedTypes($this->type, $this->property);
  372. $nestedProperty = '';
  373. // If there is a Fallback Type then probably it could be the expectedType
  374. if (\in_array($this->fallbackType, $nestedType))
  375. {
  376. $nestedType = $this->fallbackType;
  377. if ($this->fallbackProperty)
  378. {
  379. $nestedProperty = $this->fallbackProperty;
  380. }
  381. }
  382. else
  383. {
  384. $nestedType = $nestedType[0];
  385. }
  386. // Check if a $content is available, otherwise fallback to an 'inline' display type
  387. if ($this->content !== null)
  388. {
  389. if ($nestedProperty)
  390. {
  391. $html = static::htmlSpan(
  392. $this->content,
  393. $nestedProperty
  394. );
  395. }
  396. $html = static::htmlSpan(
  397. $html,
  398. $this->property,
  399. $nestedType,
  400. true
  401. );
  402. }
  403. else
  404. {
  405. $html = static::htmlProperty($this->property) . ' ' . static::htmlScope($nestedType);
  406. if ($nestedProperty)
  407. {
  408. $html .= ' ' . static::htmlProperty($nestedProperty);
  409. }
  410. }
  411. break;
  412. case 'meta':
  413. // Check if a $content is available, otherwise fallback to an 'inline' display type
  414. if ($this->content !== null)
  415. {
  416. $html = $this->machineContent ?? $this->content;
  417. $html = static::htmlMeta($html, $this->property) . $this->content;
  418. }
  419. else
  420. {
  421. $html = static::htmlProperty($this->property);
  422. }
  423. break;
  424. default:
  425. /*
  426. * Default expected display type = 'normal'
  427. * Check if a $content is available,
  428. * otherwise fallback to an 'inline' display type
  429. */
  430. if ($this->content !== null)
  431. {
  432. $html = static::htmlSpan($this->content, $this->property);
  433. }
  434. else
  435. {
  436. $html = static::htmlProperty($this->property);
  437. }
  438. break;
  439. }
  440. }
  441. }
  442. elseif ($this->fallbackProperty)
  443. {
  444. // Process and return the HTML the way the user expects to
  445. if ($displayType)
  446. {
  447. switch ($displayType)
  448. {
  449. case 'span':
  450. $html = static::htmlSpan($html, $this->fallbackProperty, $this->fallbackType);
  451. break;
  452. case 'div':
  453. $html = static::htmlDiv($html, $this->fallbackProperty, $this->fallbackType);
  454. break;
  455. case 'meta':
  456. $html = $this->machineContent ?? $html;
  457. $html = static::htmlMeta($html, $this->fallbackProperty, $this->fallbackType);
  458. break;
  459. default:
  460. // Default $displayType = 'inline'
  461. $html = static::htmlScope($this->fallbackType) . ' ' . static::htmlProperty($this->fallbackProperty);
  462. break;
  463. }
  464. }
  465. else
  466. {
  467. /*
  468. * Process and return the HTML in an automatic way,
  469. * with the $Property expected Types and display everything in the right way,
  470. * check if the Property is 'nested' or must be rendered in a metadata tag
  471. */
  472. switch (static::getExpectedDisplayType($this->fallbackType, $this->fallbackProperty))
  473. {
  474. case 'meta':
  475. // Check if a $content is available, otherwise fallback to an 'inline' display Type
  476. if ($this->content !== null)
  477. {
  478. $html = $this->machineContent ?? $this->content;
  479. $html = static::htmlMeta($html, $this->fallbackProperty, $this->fallbackType);
  480. }
  481. else
  482. {
  483. $html = static::htmlScope($this->fallbackType) . ' ' . static::htmlProperty($this->fallbackProperty);
  484. }
  485. break;
  486. default:
  487. /*
  488. * Default expected display type = 'normal'
  489. * Check if a $content is available,
  490. * otherwise fallback to an 'inline' display Type
  491. */
  492. if ($this->content !== null)
  493. {
  494. $html = static::htmlSpan($this->content, $this->fallbackProperty);
  495. $html = static::htmlSpan($html, '', $this->fallbackType);
  496. }
  497. else
  498. {
  499. $html = static::htmlScope($this->fallbackType) . ' ' . static::htmlProperty($this->fallbackProperty);
  500. }
  501. break;
  502. }
  503. }
  504. }
  505. elseif (!$this->fallbackProperty && $this->fallbackType !== null)
  506. {
  507. $html = static::htmlScope($this->fallbackType);
  508. }
  509. // Reset params
  510. $this->resetParams();
  511. return $html;
  512. }
  513. /**
  514. * Return the HTML of the current Scope
  515. *
  516. * @return string
  517. *
  518. * @since 3.2
  519. */
  520. public function displayScope()
  521. {
  522. // Control if the library output is enabled, otherwise return the $content or empty string
  523. if (!$this->enabled)
  524. {
  525. return '';
  526. }
  527. return static::htmlScope($this->type);
  528. }
  529. /**
  530. * Return the sanitized $Type
  531. *
  532. * @param string $type The Type to sanitize
  533. *
  534. * @return string
  535. *
  536. * @since 3.2
  537. */
  538. public static function sanitizeType($type)
  539. {
  540. return ucfirst(trim($type));
  541. }
  542. /**
  543. * Return the sanitized $Property
  544. *
  545. * @param string $property The Property to sanitize
  546. *
  547. * @return string
  548. *
  549. * @since 3.2
  550. */
  551. public static function sanitizeProperty($property)
  552. {
  553. return lcfirst(trim($property));
  554. }
  555. /**
  556. * Return an array with all available Types and Properties from the http://schema.org vocabulary
  557. *
  558. * @return array
  559. *
  560. * @since 3.2
  561. */
  562. public static function getTypes()
  563. {
  564. static::loadTypes();
  565. return static::$types;
  566. }
  567. /**
  568. * Return an array with all available Types from the http://schema.org vocabulary
  569. *
  570. * @return array
  571. *
  572. * @since 3.2
  573. */
  574. public static function getAvailableTypes()
  575. {
  576. static::loadTypes();
  577. return array_keys(static::$types);
  578. }
  579. /**
  580. * Return the expected Types of the given Property
  581. *
  582. * @param string $type The Type to process
  583. * @param string $property The Property to process
  584. *
  585. * @return array
  586. *
  587. * @since 3.2
  588. */
  589. public static function getExpectedTypes($type, $property)
  590. {
  591. static::loadTypes();
  592. $tmp = static::$types[$type]['properties'];
  593. // Check if the $Property is in the $Type
  594. if (isset($tmp[$property]))
  595. {
  596. return $tmp[$property]['expectedTypes'];
  597. }
  598. // Check if the $Property is inherit
  599. $extendedType = static::$types[$type]['extends'];
  600. // Recursive
  601. if (!empty($extendedType))
  602. {
  603. return static::getExpectedTypes($extendedType, $property);
  604. }
  605. return array();
  606. }
  607. /**
  608. * Return the expected display type: [normal|nested|meta]
  609. * In which way to display the Property:
  610. * normal -> itemprop="name"
  611. * nested -> itemprop="director" itemscope itemtype="https://schema.org/Person"
  612. * meta -> `<meta itemprop="datePublished" content="1991-05-01">`
  613. *
  614. * @param string $type The Type where to find the Property
  615. * @param string $property The Property to process
  616. *
  617. * @return string
  618. *
  619. * @since 3.2
  620. */
  621. protected static function getExpectedDisplayType($type, $property)
  622. {
  623. $expectedTypes = static::getExpectedTypes($type, $property);
  624. // Retrieve the first expected type
  625. $type = $expectedTypes[0];
  626. // Check if it's a 'meta' display
  627. if ($type === 'Date' || $type === 'DateTime' || $property === 'interactionCount')
  628. {
  629. return 'meta';
  630. }
  631. // Check if it's a 'normal' display
  632. if ($type === 'Text' || $type === 'URL' || $type === 'Boolean' || $type === 'Number')
  633. {
  634. return 'normal';
  635. }
  636. // Otherwise it's a 'nested' display
  637. return 'nested';
  638. }
  639. /**
  640. * Recursive function, control if the given Type has the given Property
  641. *
  642. * @param string $type The Type where to check
  643. * @param string $property The Property to check
  644. *
  645. * @return boolean
  646. *
  647. * @since 3.2
  648. */
  649. public static function isPropertyInType($type, $property)
  650. {
  651. if (!static::isTypeAvailable($type))
  652. {
  653. return false;
  654. }
  655. // Control if the $Property exists, and return 'true'
  656. if (\array_key_exists($property, static::$types[$type]['properties']))
  657. {
  658. return true;
  659. }
  660. // Recursive: Check if the $Property is inherit
  661. $extendedType = static::$types[$type]['extends'];
  662. if (!empty($extendedType))
  663. {
  664. return static::isPropertyInType($extendedType, $property);
  665. }
  666. return false;
  667. }
  668. /**
  669. * Control if the given Type class is available
  670. *
  671. * @param string $type The Type to check
  672. *
  673. * @return boolean
  674. *
  675. * @since 3.2
  676. */
  677. public static function isTypeAvailable($type)
  678. {
  679. static::loadTypes();
  680. return \array_key_exists($type, static::$types);
  681. }
  682. /**
  683. * Return Microdata semantics in a `<meta>` tag with content for machines.
  684. *
  685. * @param string $content The machine content to display
  686. * @param string $property The Property
  687. * @param string $scope Optional, the Type scope to display
  688. * @param boolean $invert Optional, default = false, invert the $scope with the $property
  689. *
  690. * @return string
  691. *
  692. * @since 3.2
  693. */
  694. public static function htmlMeta($content, $property, $scope = '', $invert = false)
  695. {
  696. return static::htmlTag('meta', $content, $property, $scope, $invert);
  697. }
  698. /**
  699. * Return Microdata semantics in a `<span>` tag.
  700. *
  701. * @param string $content The human content
  702. * @param string $property Optional, the human content to display
  703. * @param string $scope Optional, the Type scope to display
  704. * @param boolean $invert Optional, default = false, invert the $scope with the $property
  705. *
  706. * @return string
  707. *
  708. * @since 3.2
  709. */
  710. public static function htmlSpan($content, $property = '', $scope = '', $invert = false)
  711. {
  712. return static::htmlTag('span', $content, $property, $scope, $invert);
  713. }
  714. /**
  715. * Return Microdata semantics in a `<div>` tag.
  716. *
  717. * @param string $content The human content
  718. * @param string $property Optional, the human content to display
  719. * @param string $scope Optional, the Type scope to display
  720. * @param boolean $invert Optional, default = false, invert the $scope with the $property
  721. *
  722. * @return string
  723. *
  724. * @since 3.2
  725. */
  726. public static function htmlDiv($content, $property = '', $scope = '', $invert = false)
  727. {
  728. return static::htmlTag('div', $content, $property, $scope, $invert);
  729. }
  730. /**
  731. * Return Microdata semantics in a specified tag.
  732. *
  733. * @param string $tag The HTML tag
  734. * @param string $content The human content
  735. * @param string $property Optional, the human content to display
  736. * @param string $scope Optional, the Type scope to display
  737. * @param boolean $invert Optional, default = false, invert the $scope with the $property
  738. *
  739. * @return string
  740. *
  741. * @since 3.3
  742. */
  743. public static function htmlTag($tag, $content, $property = '', $scope = '', $invert = false)
  744. {
  745. // Control if the $Property has already the 'itemprop' prefix
  746. if (!empty($property) && stripos($property, 'itemprop') !== 0)
  747. {
  748. $property = static::htmlProperty($property);
  749. }
  750. // Control if the $Scope have already the 'itemscope' prefix
  751. if (!empty($scope) && stripos($scope, 'itemscope') !== 0)
  752. {
  753. $scope = static::htmlScope($scope);
  754. }
  755. // Depending on the case, the $scope must precede the $property, or otherwise
  756. if ($invert)
  757. {
  758. $tmp = implode(' ', array($property, $scope));
  759. }
  760. else
  761. {
  762. $tmp = implode(' ', array($scope, $property));
  763. }
  764. $tmp = trim($tmp);
  765. $tmp = ($tmp) ? ' ' . $tmp : '';
  766. // Control if it is an empty element without a closing tag
  767. if ($tag === 'meta')
  768. {
  769. return "<meta$tmp content='$content'>";
  770. }
  771. return '<' . $tag . $tmp . '>' . $content . '</' . $tag . '>';
  772. }
  773. /**
  774. * Return the HTML Scope
  775. *
  776. * @param string $scope The Scope to process
  777. *
  778. * @return string
  779. *
  780. * @since 3.2
  781. */
  782. public static function htmlScope($scope)
  783. {
  784. return "itemscope itemtype='https://schema.org/" . static::sanitizeType($scope) . "'";
  785. }
  786. /**
  787. * Return the HTML Property
  788. *
  789. * @param string $property The Property to process
  790. *
  791. * @return string
  792. *
  793. * @since 3.2
  794. */
  795. public static function htmlProperty($property)
  796. {
  797. return "itemprop='$property'";
  798. }
  799. }