/libraries/src/Microdata/Microdata.php
https://github.com/joomla/joomla-cms · PHP · 835 lines · 340 code · 100 blank · 395 comment · 58 complexity · c0454cd4e181b9219cdb586008b6728e MD5 · raw file
- <?php
- /**
- * Joomla! Content Management System
- *
- * @copyright (C) 2013 Open Source Matters, Inc. <https://www.joomla.org>
- * @license GNU General Public License version 2 or later; see LICENSE.txt
- */
- namespace Joomla\CMS\Microdata;
- /**
- * Joomla Platform class for interacting with Microdata semantics.
- *
- * @since 3.2
- */
- class Microdata
- {
- /**
- * Array with all available Types and Properties from the http://schema.org vocabulary
- *
- * @var array
- * @since 3.2
- */
- protected static $types = null;
- /**
- * The Type
- *
- * @var string
- * @since 3.2
- */
- protected $type = null;
- /**
- * The Property
- *
- * @var string
- * @since 3.2
- */
- protected $property = null;
- /**
- * The Human content
- *
- * @var string
- * @since 3.2
- */
- protected $content = null;
- /**
- * The Machine content
- *
- * @var string
- * @since 3.2
- */
- protected $machineContent = null;
- /**
- * The Fallback Type
- *
- * @var string
- * @since 3.2
- */
- protected $fallbackType = null;
- /**
- * The Fallback Property
- *
- * @var string
- * @since 3.2
- */
- protected $fallbackProperty = null;
- /**
- * Used for checking if the library output is enabled or disabled
- *
- * @var boolean
- * @since 3.2
- */
- protected $enabled = true;
- /**
- * Initialize the class and setup the default $Type
- *
- * @param string $type Optional, fallback to 'Thing' Type
- * @param boolean $flag Enable or disable the library output
- *
- * @since 3.2
- */
- public function __construct($type = '', $flag = true)
- {
- if ($this->enabled = (bool) $flag) {
- // Fallback to 'Thing' Type
- if (!$type) {
- $type = 'Thing';
- }
- $this->setType($type);
- }
- }
- /**
- * Load all available Types and Properties from the http://schema.org vocabulary contained in the types.json file
- *
- * @return void
- *
- * @since 3.2
- */
- protected static function loadTypes()
- {
- // Load the JSON
- if (!static::$types) {
- $path = __DIR__ . '/types.json';
- static::$types = json_decode(file_get_contents($path), true);
- }
- }
- /**
- * Reset all params
- *
- * @return void
- *
- * @since 3.2
- */
- protected function resetParams()
- {
- $this->content = null;
- $this->machineContent = null;
- $this->property = null;
- $this->fallbackProperty = null;
- $this->fallbackType = null;
- }
- /**
- * Enable or Disable the library output
- *
- * @param boolean $flag Enable or disable the library output
- *
- * @return Microdata Instance of $this
- *
- * @since 3.2
- */
- public function enable($flag = true)
- {
- $this->enabled = (bool) $flag;
- return $this;
- }
- /**
- * Return 'true' if the library output is enabled
- *
- * @return boolean
- *
- * @since 3.2
- */
- public function isEnabled()
- {
- return $this->enabled;
- }
- /**
- * Set a new http://schema.org Type
- *
- * @param string $type The $Type to be setup
- *
- * @return Microdata Instance of $this
- *
- * @since 3.2
- */
- public function setType($type)
- {
- if (!$this->enabled) {
- return $this;
- }
- // Sanitize the Type
- $this->type = static::sanitizeType($type);
- // If the given $Type isn't available, fallback to 'Thing' Type
- if (!static::isTypeAvailable($this->type)) {
- $this->type = 'Thing';
- }
- return $this;
- }
- /**
- * Return the current $Type name
- *
- * @return string
- *
- * @since 3.2
- */
- public function getType()
- {
- return $this->type;
- }
- /**
- * Setup a $Property
- *
- * @param string $property The Property
- *
- * @return Microdata Instance of $this
- *
- * @since 3.2
- */
- public function property($property)
- {
- if (!$this->enabled) {
- return $this;
- }
- // Sanitize the $Property
- $property = static::sanitizeProperty($property);
- // Control if the $Property exists in the given $Type and setup it, otherwise leave it 'NULL'
- if (static::isPropertyInType($this->type, $property)) {
- $this->property = $property;
- }
- return $this;
- }
- /**
- * Return the current $Property name
- *
- * @return string
- *
- * @since 3.2
- */
- public function getProperty()
- {
- return $this->property;
- }
- /**
- * Setup a Human content or content for the Machines
- *
- * @param string $content The human content or machine content to be used
- * @param string $machineContent The machine content
- *
- * @return Microdata Instance of $this
- *
- * @since 3.2
- */
- public function content($content, $machineContent = null)
- {
- $this->content = $content;
- $this->machineContent = $machineContent;
- return $this;
- }
- /**
- * Return the current $content
- *
- * @return string
- *
- * @since 3.2
- */
- public function getContent()
- {
- return $this->content;
- }
- /**
- * Return the current $machineContent
- *
- * @return string
- *
- * @since 3.3
- */
- public function getMachineContent()
- {
- return $this->machineContent;
- }
- /**
- * Setup a Fallback Type and Property
- *
- * @param string $type The Fallback Type
- * @param string $property The Fallback Property
- *
- * @return Microdata Instance of $this
- *
- * @since 3.2
- */
- public function fallback($type, $property)
- {
- if (!$this->enabled) {
- return $this;
- }
- // Sanitize the $Type
- $this->fallbackType = static::sanitizeType($type);
- // If the given $Type isn't available, fallback to 'Thing' Type
- if (!static::isTypeAvailable($this->fallbackType)) {
- $this->fallbackType = 'Thing';
- }
- // Control if the $Property exist in the given $Type and setup it, otherwise leave it 'NULL'
- if (static::isPropertyInType($this->fallbackType, $property)) {
- $this->fallbackProperty = $property;
- } else {
- $this->fallbackProperty = null;
- }
- return $this;
- }
- /**
- * Return the current $fallbackType
- *
- * @return string
- *
- * @since 3.2
- */
- public function getFallbackType()
- {
- return $this->fallbackType;
- }
- /**
- * Return the current $fallbackProperty
- *
- * @return string
- *
- * @since 3.2
- */
- public function getFallbackProperty()
- {
- return $this->fallbackProperty;
- }
- /**
- * This function handles the display logic.
- * It checks if the Type, Property are available, if not check for a Fallback,
- * then reset all params for the next use and return the HTML.
- *
- * @param string $displayType Optional, 'inline', available options ['inline'|'span'|'div'|meta]
- * @param boolean $emptyOutput Return an empty string if the library output is disabled and there is a $content value
- *
- * @return string
- *
- * @since 3.2
- */
- public function display($displayType = '', $emptyOutput = false)
- {
- // Initialize the HTML to output
- $html = ($this->content !== null && !$emptyOutput) ? $this->content : '';
- // Control if the library output is enabled, otherwise return the $content or an empty string
- if (!$this->enabled) {
- // Reset params
- $this->resetParams();
- return $html;
- }
- // If the $property is wrong for the current $Type check if a Fallback is available, otherwise return an empty HTML
- if ($this->property) {
- // Process and return the HTML the way the user expects to
- if ($displayType) {
- switch ($displayType) {
- case 'span':
- $html = static::htmlSpan($html, $this->property);
- break;
- case 'div':
- $html = static::htmlDiv($html, $this->property);
- break;
- case 'meta':
- $html = $this->machineContent ?? $html;
- $html = static::htmlMeta($html, $this->property);
- break;
- default:
- // Default $displayType = 'inline'
- $html = static::htmlProperty($this->property);
- break;
- }
- } else {
- /*
- * Process and return the HTML in an automatic way,
- * with the $Property expected Types and display everything in the right way,
- * check if the $Property is 'normal', 'nested' or must be rendered in a metadata tag
- */
- switch (static::getExpectedDisplayType($this->type, $this->property)) {
- case 'nested':
- // Retrieve the expected 'nested' Type of the $Property
- $nestedType = static::getExpectedTypes($this->type, $this->property);
- $nestedProperty = '';
- // If there is a Fallback Type then probably it could be the expectedType
- if (\in_array($this->fallbackType, $nestedType)) {
- $nestedType = $this->fallbackType;
- if ($this->fallbackProperty) {
- $nestedProperty = $this->fallbackProperty;
- }
- } else {
- $nestedType = $nestedType[0];
- }
- // Check if a $content is available, otherwise fallback to an 'inline' display type
- if ($this->content !== null) {
- if ($nestedProperty) {
- $html = static::htmlSpan(
- $this->content,
- $nestedProperty
- );
- }
- $html = static::htmlSpan(
- $html,
- $this->property,
- $nestedType,
- true
- );
- } else {
- $html = static::htmlProperty($this->property) . ' ' . static::htmlScope($nestedType);
- if ($nestedProperty) {
- $html .= ' ' . static::htmlProperty($nestedProperty);
- }
- }
- break;
- case 'meta':
- // Check if a $content is available, otherwise fallback to an 'inline' display type
- if ($this->content !== null) {
- $html = $this->machineContent ?? $this->content;
- $html = static::htmlMeta($html, $this->property) . $this->content;
- } else {
- $html = static::htmlProperty($this->property);
- }
- break;
- default:
- /*
- * Default expected display type = 'normal'
- * Check if a $content is available,
- * otherwise fallback to an 'inline' display type
- */
- if ($this->content !== null) {
- $html = static::htmlSpan($this->content, $this->property);
- } else {
- $html = static::htmlProperty($this->property);
- }
- break;
- }
- }
- } elseif ($this->fallbackProperty) {
- // Process and return the HTML the way the user expects to
- if ($displayType) {
- switch ($displayType) {
- case 'span':
- $html = static::htmlSpan($html, $this->fallbackProperty, $this->fallbackType);
- break;
- case 'div':
- $html = static::htmlDiv($html, $this->fallbackProperty, $this->fallbackType);
- break;
- case 'meta':
- $html = $this->machineContent ?? $html;
- $html = static::htmlMeta($html, $this->fallbackProperty, $this->fallbackType);
- break;
- default:
- // Default $displayType = 'inline'
- $html = static::htmlScope($this->fallbackType) . ' ' . static::htmlProperty($this->fallbackProperty);
- break;
- }
- } else {
- /*
- * Process and return the HTML in an automatic way,
- * with the $Property expected Types and display everything in the right way,
- * check if the Property is 'nested' or must be rendered in a metadata tag
- */
- switch (static::getExpectedDisplayType($this->fallbackType, $this->fallbackProperty)) {
- case 'meta':
- // Check if a $content is available, otherwise fallback to an 'inline' display Type
- if ($this->content !== null) {
- $html = $this->machineContent ?? $this->content;
- $html = static::htmlMeta($html, $this->fallbackProperty, $this->fallbackType);
- } else {
- $html = static::htmlScope($this->fallbackType) . ' ' . static::htmlProperty($this->fallbackProperty);
- }
- break;
- default:
- /*
- * Default expected display type = 'normal'
- * Check if a $content is available,
- * otherwise fallback to an 'inline' display Type
- */
- if ($this->content !== null) {
- $html = static::htmlSpan($this->content, $this->fallbackProperty);
- $html = static::htmlSpan($html, '', $this->fallbackType);
- } else {
- $html = static::htmlScope($this->fallbackType) . ' ' . static::htmlProperty($this->fallbackProperty);
- }
- break;
- }
- }
- } elseif (!$this->fallbackProperty && $this->fallbackType !== null) {
- $html = static::htmlScope($this->fallbackType);
- }
- // Reset params
- $this->resetParams();
- return $html;
- }
- /**
- * Return the HTML of the current Scope
- *
- * @return string
- *
- * @since 3.2
- */
- public function displayScope()
- {
- // Control if the library output is enabled, otherwise return the $content or empty string
- if (!$this->enabled) {
- return '';
- }
- return static::htmlScope($this->type);
- }
- /**
- * Return the sanitized $Type
- *
- * @param string $type The Type to sanitize
- *
- * @return string
- *
- * @since 3.2
- */
- public static function sanitizeType($type)
- {
- return ucfirst(trim($type));
- }
- /**
- * Return the sanitized $Property
- *
- * @param string $property The Property to sanitize
- *
- * @return string
- *
- * @since 3.2
- */
- public static function sanitizeProperty($property)
- {
- return lcfirst(trim($property));
- }
- /**
- * Return an array with all available Types and Properties from the http://schema.org vocabulary
- *
- * @return array
- *
- * @since 3.2
- */
- public static function getTypes()
- {
- static::loadTypes();
- return static::$types;
- }
- /**
- * Return an array with all available Types from the http://schema.org vocabulary
- *
- * @return array
- *
- * @since 3.2
- */
- public static function getAvailableTypes()
- {
- static::loadTypes();
- return array_keys(static::$types);
- }
- /**
- * Return the expected Types of the given Property
- *
- * @param string $type The Type to process
- * @param string $property The Property to process
- *
- * @return array
- *
- * @since 3.2
- */
- public static function getExpectedTypes($type, $property)
- {
- static::loadTypes();
- $tmp = static::$types[$type]['properties'];
- // Check if the $Property is in the $Type
- if (isset($tmp[$property])) {
- return $tmp[$property]['expectedTypes'];
- }
- // Check if the $Property is inherit
- $extendedType = static::$types[$type]['extends'];
- // Recursive
- if (!empty($extendedType)) {
- return static::getExpectedTypes($extendedType, $property);
- }
- return array();
- }
- /**
- * Return the expected display type: [normal|nested|meta]
- * In which way to display the Property:
- * normal -> itemprop="name"
- * nested -> itemprop="director" itemscope itemtype="https://schema.org/Person"
- * meta -> `<meta itemprop="datePublished" content="1991-05-01">`
- *
- * @param string $type The Type where to find the Property
- * @param string $property The Property to process
- *
- * @return string
- *
- * @since 3.2
- */
- protected static function getExpectedDisplayType($type, $property)
- {
- $expectedTypes = static::getExpectedTypes($type, $property);
- // Retrieve the first expected type
- $type = $expectedTypes[0];
- // Check if it's a 'meta' display
- if ($type === 'Date' || $type === 'DateTime' || $property === 'interactionCount') {
- return 'meta';
- }
- // Check if it's a 'normal' display
- if ($type === 'Text' || $type === 'URL' || $type === 'Boolean' || $type === 'Number') {
- return 'normal';
- }
- // Otherwise it's a 'nested' display
- return 'nested';
- }
- /**
- * Recursive function, control if the given Type has the given Property
- *
- * @param string $type The Type where to check
- * @param string $property The Property to check
- *
- * @return boolean
- *
- * @since 3.2
- */
- public static function isPropertyInType($type, $property)
- {
- if (!static::isTypeAvailable($type)) {
- return false;
- }
- // Control if the $Property exists, and return 'true'
- if (\array_key_exists($property, static::$types[$type]['properties'])) {
- return true;
- }
- // Recursive: Check if the $Property is inherit
- $extendedType = static::$types[$type]['extends'];
- if (!empty($extendedType)) {
- return static::isPropertyInType($extendedType, $property);
- }
- return false;
- }
- /**
- * Control if the given Type class is available
- *
- * @param string $type The Type to check
- *
- * @return boolean
- *
- * @since 3.2
- */
- public static function isTypeAvailable($type)
- {
- static::loadTypes();
- return \array_key_exists($type, static::$types);
- }
- /**
- * Return Microdata semantics in a `<meta>` tag with content for machines.
- *
- * @param string $content The machine content to display
- * @param string $property The Property
- * @param string $scope Optional, the Type scope to display
- * @param boolean $invert Optional, default = false, invert the $scope with the $property
- *
- * @return string
- *
- * @since 3.2
- */
- public static function htmlMeta($content, $property, $scope = '', $invert = false)
- {
- return static::htmlTag('meta', $content, $property, $scope, $invert);
- }
- /**
- * Return Microdata semantics in a `<span>` tag.
- *
- * @param string $content The human content
- * @param string $property Optional, the human content to display
- * @param string $scope Optional, the Type scope to display
- * @param boolean $invert Optional, default = false, invert the $scope with the $property
- *
- * @return string
- *
- * @since 3.2
- */
- public static function htmlSpan($content, $property = '', $scope = '', $invert = false)
- {
- return static::htmlTag('span', $content, $property, $scope, $invert);
- }
- /**
- * Return Microdata semantics in a `<div>` tag.
- *
- * @param string $content The human content
- * @param string $property Optional, the human content to display
- * @param string $scope Optional, the Type scope to display
- * @param boolean $invert Optional, default = false, invert the $scope with the $property
- *
- * @return string
- *
- * @since 3.2
- */
- public static function htmlDiv($content, $property = '', $scope = '', $invert = false)
- {
- return static::htmlTag('div', $content, $property, $scope, $invert);
- }
- /**
- * Return Microdata semantics in a specified tag.
- *
- * @param string $tag The HTML tag
- * @param string $content The human content
- * @param string $property Optional, the human content to display
- * @param string $scope Optional, the Type scope to display
- * @param boolean $invert Optional, default = false, invert the $scope with the $property
- *
- * @return string
- *
- * @since 3.3
- */
- public static function htmlTag($tag, $content, $property = '', $scope = '', $invert = false)
- {
- // Control if the $Property has already the 'itemprop' prefix
- if (!empty($property) && stripos($property, 'itemprop') !== 0) {
- $property = static::htmlProperty($property);
- }
- // Control if the $Scope have already the 'itemscope' prefix
- if (!empty($scope) && stripos($scope, 'itemscope') !== 0) {
- $scope = static::htmlScope($scope);
- }
- // Depending on the case, the $scope must precede the $property, or otherwise
- if ($invert) {
- $tmp = implode(' ', array($property, $scope));
- } else {
- $tmp = implode(' ', array($scope, $property));
- }
- $tmp = trim($tmp);
- $tmp = ($tmp) ? ' ' . $tmp : '';
- // Control if it is an empty element without a closing tag
- if ($tag === 'meta') {
- return "<meta$tmp content='$content'>";
- }
- return '<' . $tag . $tmp . '>' . $content . '</' . $tag . '>';
- }
- /**
- * Return the HTML Scope
- *
- * @param string $scope The Scope to process
- *
- * @return string
- *
- * @since 3.2
- */
- public static function htmlScope($scope)
- {
- return "itemscope itemtype='https://schema.org/" . static::sanitizeType($scope) . "'";
- }
- /**
- * Return the HTML Property
- *
- * @param string $property The Property to process
- *
- * @return string
- *
- * @since 3.2
- */
- public static function htmlProperty($property)
- {
- return "itemprop='$property'";
- }
- }