first commit
This commit is contained in:
@@ -0,0 +1,663 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/metadata/MetadataDescription.php
|
||||
*
|
||||
* Copyright (c) 2014-2021 Simon Fraser University
|
||||
* Copyright (c) 2000-2021 John Willinsky
|
||||
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
||||
*
|
||||
* @class MetadataDescription
|
||||
*
|
||||
* @ingroup metadata
|
||||
*
|
||||
* @see MetadataProperty
|
||||
* @see MetadataRecord
|
||||
* @see MetadataSchema
|
||||
*
|
||||
* @brief Class modeling a description (DCMI abstract model) or subject-
|
||||
* predicate-object graph (RDF). This class and its children provide
|
||||
* meta-data (DCMI abstract model: statements of property-value pairs,
|
||||
* RDF: assertions of predicate-object pairs) about a given PKP application
|
||||
* entity instance (DCMI abstract model: described resource, RDF: subject).
|
||||
*
|
||||
* This class has primarily been designed to describe journals, journal
|
||||
* issues, articles, conferences, conference proceedings (conference papers),
|
||||
* monographs (books), monograph components (book chapters) or citations.
|
||||
*
|
||||
* It is, however, flexible enough to be extended to describe any
|
||||
* application entity in the future. Descriptions can be retrieved from
|
||||
* any application object that implements the MetadataProvider interface.
|
||||
*
|
||||
* Special attention has been paid to the compatibility of the class
|
||||
* implementation with the implementation of several meta-data standards
|
||||
* that we consider especially relevant to our use cases.
|
||||
*
|
||||
* We distinguish two main use cases for meta-data: discovery and delivery
|
||||
* of described resources. We have chosen the element-citation tag from the
|
||||
* NLM standard <http://dtd.nlm.nih.gov/publishing/tag-library/3.0/n-8xa0.html>
|
||||
* as our primary representation of delivery meta-data and dcterms
|
||||
* <http://dublincore.org/documents/dcmi-terms/> as our primary
|
||||
* representation of discovery meta-data.
|
||||
*
|
||||
* Our specific use of meta-data has important implications and determines
|
||||
* our design goals:
|
||||
* * Neither NLM-citation nor dcterms have been designed with an object
|
||||
* oriented encoding in mind. NLM-citation is usually XML encoded
|
||||
* while typical dcterms encodings are HTML meta-tags, RDF or XML.
|
||||
* * We believe that trying to implement a super-set of meta-data
|
||||
* standards ("least common denominator" or super-schema approach)
|
||||
* is fundamentally flawed as meta-data standards are always
|
||||
* developed with specific use-cases in mind that require potentially
|
||||
* incompatible data properties or encodings.
|
||||
* * Although we think that NLM-citation and dcterms are sensible default
|
||||
* meta-data schemes our design should remain flexible enough for
|
||||
* users to implement and use other schemes as an internal meta-data
|
||||
* standard.
|
||||
* * We have to make sure that we can easily extract/inject meta-data
|
||||
* from/to PKP application objects.
|
||||
* * We have to avoid code duplication to keep maintenance cost under
|
||||
* control.
|
||||
* * We have to minimize the "impedance mismatch" between our own
|
||||
* object oriented encoding and fully standard compliant external
|
||||
* encodings (i.e. XML, RDF, HTML meta-tags, ...) to allow for easy
|
||||
* conversion between encodings.
|
||||
* * We have to make sure that we can switch between internal and
|
||||
* external encodings without any data loss.
|
||||
* * We have to make sure that crosswalks to and from other important
|
||||
* meta-data standards (e.g. OpenURL variants, MODS, MARC) can be
|
||||
* performed in a well-defined and easy way while minimizing data
|
||||
* loss.
|
||||
* * We have to make sure that we can support qualified fields (e.g.
|
||||
* qualified DC).
|
||||
* * We have to make sure that we can support RDF triples.
|
||||
*
|
||||
* We took the following design decisions to achieve these goals:
|
||||
* * We only implement properties that are justified by strong real-world
|
||||
* use-cases. We recognize that the limiting factor is not the data that
|
||||
* we could represent but the data we actually have. This is not determined
|
||||
* by the chosen standard but by the PKP application objects we want to
|
||||
* represent. Additional meta-data properties/predicates can be added as
|
||||
* required.
|
||||
* * We do adapt data structures as long as we can make sure that a
|
||||
* fully standard compliant encoding can always be re-constructed. This
|
||||
* is especially true for NLM-citation which is designed with
|
||||
* XML in mind and therefore uses hierarchical constructs that are
|
||||
* difficult to represent in an OO class model.
|
||||
* This means that our meta-data framework only supports (nested) key/
|
||||
* value-based schemas which can however be converted to hierarchical
|
||||
* representations.
|
||||
* * We borrow class and property names from the DCMI abstract model as
|
||||
* the terms used there provide better readability for developers less
|
||||
* acquainted with formal model theory. We'll, however, make sure that
|
||||
* data can easily be RDF encoded within our data model.
|
||||
* * Data validation must ensure that meta-data always complies with a
|
||||
* specific meta-data standard. As we are speaking about an object
|
||||
* oriented encoding that is not defined in the original standard, we
|
||||
* define compliance as "roundtripability". This means we must be able
|
||||
* to convert our object oriented data encoding to a fully standard
|
||||
* compliant encoding and back without any data loss.
|
||||
*/
|
||||
|
||||
namespace PKP\metadata;
|
||||
|
||||
use APP\core\Application;
|
||||
use PKP\facades\Locale;
|
||||
|
||||
class MetadataDescription extends \PKP\core\DataObject
|
||||
{
|
||||
public const METADATA_DESCRIPTION_REPLACE_ALL = 1;
|
||||
public const METADATA_DESCRIPTION_REPLACE_PROPERTIES = 2;
|
||||
public const METADATA_DESCRIPTION_REPLACE_NOTHING = 3;
|
||||
|
||||
public const METADATA_DESCRIPTION_UNKNOWN_LOCALE = 'unknown';
|
||||
|
||||
/** @var string fully qualified class name of the meta-data schema this description complies to */
|
||||
public $_metadataSchemaName;
|
||||
|
||||
/** @var MetadataSchema the schema this description complies to */
|
||||
public $_metadataSchema;
|
||||
|
||||
/** @var int association type (the type of the described resource) */
|
||||
public $_assocType;
|
||||
|
||||
/** @var int association id (the identifier of the described resource) */
|
||||
public $_assocId;
|
||||
|
||||
/**
|
||||
* @var string an (optional) display name that describes the contents
|
||||
* of this meta-data description to the end user.
|
||||
*/
|
||||
public $_displayName;
|
||||
|
||||
/**
|
||||
* @var int sequence id used when saving several descriptions
|
||||
* of the same subject.
|
||||
*/
|
||||
public $_seq;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public function __construct($metadataSchemaName, $assocType)
|
||||
{
|
||||
assert(is_string($metadataSchemaName) && is_integer($assocType));
|
||||
parent::__construct();
|
||||
$this->_metadataSchemaName = $metadataSchemaName;
|
||||
$this->_assocType = $assocType;
|
||||
}
|
||||
|
||||
//
|
||||
// Get/set methods
|
||||
//
|
||||
/**
|
||||
* Get the fully qualified class name of
|
||||
* the supported meta-data schema.
|
||||
*/
|
||||
public function getMetadataSchemaName()
|
||||
{
|
||||
return $this->_metadataSchemaName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the metadata schema
|
||||
*
|
||||
* @return MetadataSchema
|
||||
*/
|
||||
public function &getMetadataSchema()
|
||||
{
|
||||
// Lazy-load the meta-data schema if this has
|
||||
// not been done before.
|
||||
if (is_null($this->_metadataSchema)) {
|
||||
$metadataSchemaName = $this->getMetadataSchemaName();
|
||||
if (preg_match('/^[a-zA-Z0-9_.]+$/', $metadataSchemaName)) {
|
||||
// DEPRECATED as of 3.4.0: non-PSR classloading pkp/pkp-lib#8186
|
||||
$this->_metadataSchema = & instantiate($metadataSchemaName, \PKP\metadata\MetadataSchema::class);
|
||||
} elseif (class_exists($metadataSchemaName)) {
|
||||
$this->_metadataSchema = new $metadataSchemaName();
|
||||
}
|
||||
if (! $this->_metadataSchema instanceof \PKP\metadata\MetadataSchema) {
|
||||
throw new \Exception('Unexpected metadata schema class!');
|
||||
}
|
||||
}
|
||||
return $this->_metadataSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the association type (described resource type)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAssocType()
|
||||
{
|
||||
return $this->_assocType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the association id (described resource identifier)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAssocId()
|
||||
{
|
||||
return $this->_assocId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the association id (described resource identifier)
|
||||
*
|
||||
* @param int $assocId
|
||||
*/
|
||||
public function setAssocId($assocId)
|
||||
{
|
||||
$this->_assocId = $assocId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a meta-data application entity id
|
||||
* (described resource id / subject id) for
|
||||
* this meta-data description object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAssoc()
|
||||
{
|
||||
$assocType = $this->getAssocType();
|
||||
$assocId = $this->getAssocId();
|
||||
assert(isset($assocType) && isset($assocId));
|
||||
return $assocType . ':' . $assocId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the (optional) display name
|
||||
*
|
||||
* @param string $displayName
|
||||
*/
|
||||
public function setDisplayName($displayName)
|
||||
{
|
||||
$this->_displayName = $displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the (optional) display name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDisplayName()
|
||||
{
|
||||
return $this->_displayName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sequence id
|
||||
*
|
||||
* @param int $seq
|
||||
*/
|
||||
public function setSequence($seq)
|
||||
{
|
||||
$this->_seq = $seq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sequence id
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getSequence()
|
||||
{
|
||||
return $this->_seq;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a meta-data statement. Statements can only be added
|
||||
* for properties that are part of the meta-data schema. This
|
||||
* method will also check the validity of the value for the
|
||||
* given property before adding the statement.
|
||||
*
|
||||
* @param string $propertyName The name of the property
|
||||
* @param mixed $value The value to be assigned to the property
|
||||
* @param string $locale
|
||||
* @param bool $replace whether to replace an existing statement
|
||||
*
|
||||
* @return bool true if a valid statement was added, otherwise false
|
||||
*/
|
||||
public function addStatement($propertyName, $value, $locale = null, $replace = false)
|
||||
{
|
||||
// Check the property
|
||||
$property = & $this->getProperty($propertyName);
|
||||
if (is_null($property)) {
|
||||
return false;
|
||||
}
|
||||
assert($property instanceof \PKP\metadata\MetadataProperty);
|
||||
|
||||
// Check that the property is allowed for the described resource
|
||||
if (!in_array($this->_assocType, $property->getAssocTypes())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle translation
|
||||
$translated = $property->getTranslated();
|
||||
if (isset($locale) && !$translated) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($locale) && $translated) {
|
||||
// Retrieve the current locale
|
||||
$locale = Locale::getLocale();
|
||||
}
|
||||
|
||||
// Check that the value is compliant with the property specification
|
||||
if ($property->isValid($value, $locale) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle cardinality
|
||||
$existingValue = & $this->getStatement($propertyName, $locale);
|
||||
switch ($property->getCardinality()) {
|
||||
case MetadataProperty::METADATA_PROPERTY_CARDINALITY_ONE:
|
||||
if (isset($existingValue) && !$replace) {
|
||||
return false;
|
||||
}
|
||||
$newValue = $value;
|
||||
break;
|
||||
|
||||
case MetadataProperty::METADATA_PROPERTY_CARDINALITY_MANY:
|
||||
if (isset($existingValue) && !$replace) {
|
||||
assert(is_array($existingValue));
|
||||
$newValue = $existingValue;
|
||||
array_push($newValue, $value);
|
||||
} else {
|
||||
$newValue = [$value];
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Add the value
|
||||
$this->setData($propertyName, $newValue, $locale);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove statement. If the property has cardinality 'many'
|
||||
* then all statements for the property will be removed at once.
|
||||
* If the property is translated and the locale is null then
|
||||
* the statements for all locales will be removed.
|
||||
*
|
||||
* @param string $propertyName
|
||||
* @param string $locale
|
||||
*
|
||||
* @return bool true if the statement was found and removed, otherwise false
|
||||
*/
|
||||
public function removeStatement($propertyName, $locale = null)
|
||||
{
|
||||
// Remove the statement if it exists
|
||||
if (isset($propertyName) && $this->hasData($propertyName, $locale)) {
|
||||
$this->setData($propertyName, null, $locale);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all statements
|
||||
*
|
||||
* @return array statements
|
||||
*/
|
||||
public function &getStatements()
|
||||
{
|
||||
// Do not retrieve the data by-ref
|
||||
// otherwise the following unset()
|
||||
// will change internal state.
|
||||
$allData = $this->getAllData();
|
||||
|
||||
// Unset data variables that are not statements
|
||||
unset($allData['id']);
|
||||
return $allData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific statement
|
||||
*
|
||||
* @param string $propertyName
|
||||
* @param string $locale
|
||||
*
|
||||
* @return mixed a scalar property value or an array of property values
|
||||
* if the cardinality of the property is 'many'.
|
||||
*/
|
||||
public function &getStatement($propertyName, $locale = null)
|
||||
{
|
||||
// Check the property
|
||||
$property = & $this->getProperty($propertyName);
|
||||
assert(isset($property) && $property instanceof \PKP\metadata\MetadataProperty);
|
||||
|
||||
// Handle translation
|
||||
$translated = $property->getTranslated();
|
||||
if (!$translated) {
|
||||
assert(is_null($locale));
|
||||
}
|
||||
if ($translated && !isset($locale)) {
|
||||
// Retrieve the current locale
|
||||
$locale = Locale::getLocale();
|
||||
}
|
||||
|
||||
// Retrieve the value
|
||||
return $this->getData($propertyName, $locale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all translations of a translated property
|
||||
*
|
||||
* @param string $propertyName
|
||||
*
|
||||
* @return array all translations of a given property; if the
|
||||
* property has cardinality "many" then this returns a two-dimensional
|
||||
* array whereby the first key represents the locale and the second
|
||||
* the translated values.
|
||||
*/
|
||||
public function &getStatementTranslations($propertyName)
|
||||
{
|
||||
assert($this->isTranslatedProperty($propertyName));
|
||||
return $this->getData($propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add several statements at once. If one of the statements
|
||||
* is invalid then the meta-data description will remain in its
|
||||
* initial state.
|
||||
* * Properties with a cardinality of 'many' must be passed in as
|
||||
* sub-arrays.
|
||||
* * Translated properties with a cardinality of 'one' must be
|
||||
* passed in as sub-arrays with the locale as a key.
|
||||
* * Translated properties with a cardinality of 'many' must be
|
||||
* passed in as sub-sub-arrays with the locale as the first key.
|
||||
*
|
||||
* @param array $statements statements
|
||||
* @param int $replace one of the allowed replace levels.
|
||||
*
|
||||
* @return bool true if all statements could be added, false otherwise
|
||||
*/
|
||||
public function setStatements(&$statements, $replace = self::METADATA_DESCRIPTION_REPLACE_PROPERTIES)
|
||||
{
|
||||
assert(in_array($replace, $this->_allowedReplaceLevels()));
|
||||
|
||||
// Make a backup copy of all existing statements.
|
||||
$statementsBackup = $this->getAllData();
|
||||
|
||||
if ($replace == self::METADATA_DESCRIPTION_REPLACE_ALL) {
|
||||
// Delete existing statements
|
||||
$emptyArray = [];
|
||||
$this->setAllData($emptyArray);
|
||||
}
|
||||
|
||||
// Add statements one by one to detect invalid values.
|
||||
foreach ($statements as $propertyName => $content) {
|
||||
assert(!empty($content));
|
||||
|
||||
// Transform scalars or translated fields to arrays so that
|
||||
// we can handle properties with different cardinalities in
|
||||
// the same way.
|
||||
if (is_scalar($content) || is_string(key($content))) {
|
||||
$values = [&$content];
|
||||
} else {
|
||||
$values = & $content;
|
||||
}
|
||||
|
||||
if ($replace == self::METADATA_DESCRIPTION_REPLACE_PROPERTIES) {
|
||||
$replaceProperty = true;
|
||||
} else {
|
||||
$replaceProperty = false;
|
||||
}
|
||||
|
||||
$valueIndex = 0;
|
||||
foreach ($values as $value) {
|
||||
$firstValue = ($valueIndex == 0) ? true : false;
|
||||
// Is this a translated property?
|
||||
if (is_array($value)) {
|
||||
foreach ($value as $locale => $translation) {
|
||||
// Handle cardinality many and one in the same way
|
||||
if (is_scalar($translation)) {
|
||||
$translationValues = [&$translation];
|
||||
} else {
|
||||
$translationValues = & $translation;
|
||||
}
|
||||
$translationIndex = 0;
|
||||
foreach ($translationValues as $translationValue) {
|
||||
$firstTranslation = ($translationIndex == 0) ? true : false;
|
||||
// Add a statement (replace existing statement if any)
|
||||
if (!($this->addStatement($propertyName, $translationValue, $locale, $firstTranslation && $replaceProperty))) {
|
||||
$this->setAllData($statementsBackup);
|
||||
return false;
|
||||
}
|
||||
unset($translationValue);
|
||||
$translationIndex++;
|
||||
}
|
||||
unset($translationValues);
|
||||
}
|
||||
unset($translation);
|
||||
} else {
|
||||
// Add a statement (replace existing statement if any)
|
||||
if (!($this->addStatement($propertyName, $value, null, $firstValue && $replaceProperty))) {
|
||||
$this->setAllData($statementsBackup);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
unset($value);
|
||||
$valueIndex++;
|
||||
}
|
||||
unset($values);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that returns the properties of
|
||||
* the underlying meta-data schema.
|
||||
*
|
||||
* @return array an array of MetadataProperties
|
||||
*/
|
||||
public function &getProperties()
|
||||
{
|
||||
$metadataSchema = & $this->getMetadataSchema();
|
||||
return $metadataSchema->getProperties();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that returns a property from
|
||||
* the underlying meta-data schema.
|
||||
*
|
||||
* @param string $propertyName
|
||||
*
|
||||
* @return MetadataProperty
|
||||
*/
|
||||
public function &getProperty($propertyName)
|
||||
{
|
||||
$metadataSchema = & $this->getMetadataSchema();
|
||||
return $metadataSchema->getProperty($propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that returns a property id
|
||||
* the underlying meta-data schema.
|
||||
*
|
||||
* @param string $propertyName
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getNamespacedPropertyId($propertyName)
|
||||
{
|
||||
$metadataSchema = & $this->getMetadataSchema();
|
||||
return $metadataSchema->getNamespacedPropertyId($propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that returns the valid
|
||||
* property names of the underlying meta-data schema.
|
||||
*
|
||||
* @return array an array of string values representing valid property names
|
||||
*/
|
||||
public function getPropertyNames()
|
||||
{
|
||||
$metadataSchema = & $this->getMetadataSchema();
|
||||
return $metadataSchema->getPropertyNames();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that returns the names of properties with a
|
||||
* given data type of the underlying meta-data schema.
|
||||
*
|
||||
* @param string $propertyType
|
||||
*
|
||||
* @return array an array of string values representing valid property names
|
||||
*/
|
||||
public function getPropertyNamesByType($propertyType)
|
||||
{
|
||||
$metadataSchema = & $this->getMetadataSchema();
|
||||
return $metadataSchema->getPropertyNamesByType($propertyType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of property names for
|
||||
* which statements exist.
|
||||
*
|
||||
* @return array an array of string values representing valid property names
|
||||
*/
|
||||
public function getSetPropertyNames()
|
||||
{
|
||||
return array_keys($this->getStatements());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that checks the existence
|
||||
* of a property in the underlying meta-data schema.
|
||||
*
|
||||
* @param string $propertyName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasProperty($propertyName)
|
||||
{
|
||||
$metadataSchema = & $this->getMetadataSchema();
|
||||
return $metadataSchema->hasProperty($propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the existence of a statement for the given property.
|
||||
*
|
||||
* @param string $propertyName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasStatement($propertyName)
|
||||
{
|
||||
$statements = & $this->getStatements();
|
||||
return (isset($statements[$propertyName]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that checks whether a given property
|
||||
* is translated.
|
||||
*
|
||||
* @param string $propertyName
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isTranslatedProperty($propertyName)
|
||||
{
|
||||
$property = $this->getProperty($propertyName);
|
||||
assert($property instanceof \PKP\metadata\MetadataProperty);
|
||||
return $property->getTranslated();
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Private helper methods
|
||||
//
|
||||
/**
|
||||
* The allowed replace levels for the
|
||||
* setStatements() method.
|
||||
*/
|
||||
public static function _allowedReplaceLevels()
|
||||
{
|
||||
static $allowedReplaceLevels = [
|
||||
self::METADATA_DESCRIPTION_REPLACE_ALL,
|
||||
self::METADATA_DESCRIPTION_REPLACE_PROPERTIES,
|
||||
self::METADATA_DESCRIPTION_REPLACE_NOTHING
|
||||
];
|
||||
return $allowedReplaceLevels;
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\metadata\MetadataDescription', '\MetadataDescription');
|
||||
foreach ([
|
||||
'METADATA_DESCRIPTION_REPLACE_ALL',
|
||||
'METADATA_DESCRIPTION_REPLACE_PROPERTIES',
|
||||
'METADATA_DESCRIPTION_REPLACE_NOTHING',
|
||||
'METADATA_DESCRIPTION_UNKNOWN_LOCALE'
|
||||
] as $constantName) {
|
||||
define($constantName, constant('\MetadataDescription::' . $constantName));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user