first commit
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file classes/metadata/MetadataDataObjectAdapter.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 MetadataDataObjectAdapter
|
||||
*
|
||||
* @ingroup metadata
|
||||
*
|
||||
* @see DataObject
|
||||
* @see MetadataSchema
|
||||
* @see MetadataDescription
|
||||
*
|
||||
* @brief Class that injects/extracts a meta-data description
|
||||
* into/from an application entity object (DataObject).
|
||||
*
|
||||
* These adapters have to be persistable as they'll be provided
|
||||
* by plug-ins via the filter registry.
|
||||
*/
|
||||
|
||||
namespace PKP\metadata;
|
||||
|
||||
use PKP\core\DataObject;
|
||||
use PKP\filter\ClassTypeDescription;
|
||||
use PKP\filter\FilterGroup;
|
||||
use PKP\filter\PersistableFilter;
|
||||
|
||||
class MetadataDataObjectAdapter extends PersistableFilter
|
||||
{
|
||||
public const METADATA_DOA_INJECTION_MODE = 1;
|
||||
public const METADATA_DOA_EXTRACTION_MODE = 2;
|
||||
|
||||
/** @var int */
|
||||
public $_mode;
|
||||
|
||||
/** @var MetadataSchema */
|
||||
public $_metadataSchema;
|
||||
|
||||
/** @var string */
|
||||
public $_dataObjectClass;
|
||||
|
||||
/** @var array */
|
||||
public $_metadataFieldNames;
|
||||
|
||||
/** @var string */
|
||||
public $_metadataSchemaName;
|
||||
|
||||
/** @var int */
|
||||
public $_assocType;
|
||||
|
||||
/** @var string */
|
||||
public $_dataObjectName;
|
||||
|
||||
/** @var DataObject */
|
||||
public $_targetDataObject;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param FilterGroup $filterGroup
|
||||
* @param null|mixed $mode
|
||||
*/
|
||||
public function __construct($filterGroup, $mode = null)
|
||||
{
|
||||
// Initialize the adapter.
|
||||
parent::__construct($filterGroup);
|
||||
|
||||
// Extract information from the input/output types.
|
||||
|
||||
// Find out whether this filter is injecting or
|
||||
// extracting meta-data.
|
||||
$metadataTypeDescription = null; /** @var MetadataTypeDescription $metadataTypeDescription */
|
||||
$dataObjectTypeDescription = null; /** @var ClassTypeDescription $dataObjectTypeDescription */
|
||||
$inputType = & $this->getInputType();
|
||||
$outputType = & $this->getOutputType();
|
||||
if (is_null($mode)) {
|
||||
if ($inputType instanceof \PKP\metadata\MetadataTypeDescription) {
|
||||
$mode = self::METADATA_DOA_INJECTION_MODE;
|
||||
} else {
|
||||
$mode = self::METADATA_DOA_EXTRACTION_MODE;
|
||||
}
|
||||
}
|
||||
$this->_mode = $mode;
|
||||
|
||||
if ($mode == self::METADATA_DOA_INJECTION_MODE) {
|
||||
// We are in meta-data injection mode (or both input and output are meta-data descriptions).
|
||||
$metadataTypeDescription = & $inputType; /** @var MetadataTypeDescription $metadataTypeDescription */
|
||||
assert($outputType instanceof ClassTypeDescription);
|
||||
$dataObjectTypeDescription = & $outputType; /** @var ClassTypeDescription $dataObjectTypeDescription */
|
||||
} else {
|
||||
// We are in meta-data extraction mode.
|
||||
assert($outputType instanceof \PKP\metadata\MetadataTypeDescription);
|
||||
$metadataTypeDescription = & $outputType;
|
||||
assert($inputType instanceof ClassTypeDescription);
|
||||
$dataObjectTypeDescription = & $inputType;
|
||||
}
|
||||
|
||||
// Extract information from the input/output types.
|
||||
$this->_metadataSchemaName = $metadataTypeDescription->getMetadataSchemaClass();
|
||||
$this->_assocType = $metadataTypeDescription->getAssocType();
|
||||
$this->_dataObjectName = $dataObjectTypeDescription->getTypeName();
|
||||
|
||||
// Set the display name.
|
||||
if ($mode == self::METADATA_DOA_INJECTION_MODE) {
|
||||
$this->setDisplayName('Inject metadata into a(n) ' . $this->getDataObjectClass());
|
||||
} else {
|
||||
$this->setDisplayName('Extract metadata from a(n) ' . $this->getDataObjectClass());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Getters and setters
|
||||
//
|
||||
/**
|
||||
* One of the METADATA_DOA_*_MODE constants.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMode()
|
||||
{
|
||||
return $this->_mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the fully qualified class name of
|
||||
* the supported meta-data schema.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMetadataSchemaName()
|
||||
{
|
||||
return $this->_metadataSchemaName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supported meta-data schema (lazy load)
|
||||
*
|
||||
* @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();
|
||||
assert(!is_null($metadataSchemaName));
|
||||
if (preg_match('/^[a-zA-Z0-9_.]+$/', $metadataSchemaName)) {
|
||||
// DEPRECATED as of 3.4.0: Schema class names should be fully qualified pkp/pkp-lib#8186
|
||||
$this->_metadataSchema = & instantiate($metadataSchemaName, (string) \PKP\metadata\MetadataSchema::class);
|
||||
} elseif (class_exists($metadataSchemaName)) {
|
||||
$this->_metadataSchema = new $metadataSchemaName();
|
||||
}
|
||||
if (!$this->_metadataSchema instanceof \PKP\metadata\MetadataSchema) {
|
||||
throw new \Exception('Metadata schema is unexpected class!');
|
||||
}
|
||||
assert(is_object($this->_metadataSchema));
|
||||
}
|
||||
return $this->_metadataSchema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method that returns the
|
||||
* meta-data name space.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getMetadataNamespace()
|
||||
{
|
||||
$metadataSchema = & $this->getMetadataSchema();
|
||||
return $metadataSchema->getNamespace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the supported application entity (class) name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDataObjectName()
|
||||
{
|
||||
return $this->_dataObjectName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the data object class name
|
||||
* (without the package prefix)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDataObjectClass()
|
||||
{
|
||||
if (is_null($this->_dataObjectClass)) {
|
||||
$dataObjectName = $this->getDataObjectName();
|
||||
assert(!is_null($dataObjectName));
|
||||
$dataObjectNameParts = explode('.', $dataObjectName);
|
||||
$this->_dataObjectClass = array_pop($dataObjectNameParts);
|
||||
}
|
||||
return $this->_dataObjectClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the association type corresponding to the data
|
||||
* object type.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAssocType()
|
||||
{
|
||||
return $this->_assocType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the target data object for meta-data injection.
|
||||
*
|
||||
* @param DataObject $targetDataObject
|
||||
*/
|
||||
public function setTargetDataObject(&$targetDataObject)
|
||||
{
|
||||
$this->_targetDataObject = & $targetDataObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target data object for meta-data injection.
|
||||
*/
|
||||
public function &getTargetDataObject()
|
||||
{
|
||||
return $this->_targetDataObject;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Abstract template methods
|
||||
//
|
||||
/**
|
||||
* Inject a MetadataDescription into the target DataObject
|
||||
*
|
||||
* @param MetadataDescription $metadataDescription
|
||||
* @param DataObject $targetDataObject
|
||||
*
|
||||
* @return DataObject
|
||||
*/
|
||||
public function &injectMetadataIntoDataObject(&$metadataDescription, &$targetDataObject)
|
||||
{
|
||||
// Must be implemented by sub-classes
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a MetadataDescription from a source DataObject.
|
||||
*
|
||||
* @param DataObject $sourceDataObject
|
||||
*
|
||||
* @return MetadataDescription
|
||||
*/
|
||||
public function extractMetadataFromDataObject(&$sourceDataObject)
|
||||
{
|
||||
// Must be implemented by sub-classes
|
||||
assert(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the additional field names introduced by the
|
||||
* meta-data schema that need to be persisted in the
|
||||
* ..._settings table corresponding to the DataObject
|
||||
* which is supported by this adapter.
|
||||
* NB: The field names must be prefixed with the meta-data
|
||||
* schema namespace identifier.
|
||||
*
|
||||
* @param bool $translated if true, return localized field
|
||||
* names, otherwise return additional field names.
|
||||
*
|
||||
* @return array an array of field names to be persisted.
|
||||
*/
|
||||
public function getDataObjectMetadataFieldNames($translated = true)
|
||||
{
|
||||
// By default return all field names
|
||||
return $this->getMetadataFieldNames($translated);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Implement template methods from Filter
|
||||
//
|
||||
/**
|
||||
* Convert a MetadataDescription to an application
|
||||
* object or vice versa.
|
||||
*
|
||||
* @see Filter::process()
|
||||
*
|
||||
* @param mixed $input either a MetadataDescription or an application object
|
||||
*
|
||||
* @return mixed either a MetadataDescription or an application object
|
||||
*/
|
||||
public function &process(&$input)
|
||||
{
|
||||
// Do we inject or extract metadata?
|
||||
switch ($this->getMode()) {
|
||||
case self::METADATA_DOA_INJECTION_MODE:
|
||||
$targetDataObject = & $this->getTargetDataObject();
|
||||
|
||||
// Instantiate a new data object if none was given.
|
||||
if (is_null($targetDataObject)) {
|
||||
$targetDataObject = & $this->instantiateDataObject();
|
||||
assert(is_a($targetDataObject, $this->getDataObjectName()));
|
||||
}
|
||||
|
||||
// Inject meta-data into the data object.
|
||||
$output = & $this->injectMetadataIntoDataObject($input, $targetDataObject);
|
||||
break;
|
||||
|
||||
case self::METADATA_DOA_EXTRACTION_MODE:
|
||||
$output = $this->extractMetadataFromDataObject($input);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Input should be validated by now.
|
||||
assert(false);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Protected helper methods
|
||||
//
|
||||
/**
|
||||
* Instantiate a new data object of the
|
||||
* correct type.
|
||||
*
|
||||
* NB: This can be overridden by sub-classes for more complex
|
||||
* data objects. The standard implementation assumes there are
|
||||
* no constructor args to be set or configurations to be made.
|
||||
*
|
||||
* @return DataObject
|
||||
*/
|
||||
public function &instantiateDataObject()
|
||||
{
|
||||
$dataObjectName = $this->getDataObjectName();
|
||||
assert(!is_null($dataObjectName));
|
||||
$dataObject = & instantiate($dataObjectName, $this->getDataObjectClass());
|
||||
return $dataObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantiate a meta-data description that conforms to the
|
||||
* settings of this adapter.
|
||||
*
|
||||
* @return MetadataDescription
|
||||
*/
|
||||
public function &instantiateMetadataDescription()
|
||||
{
|
||||
$metadataDescription = new MetadataDescription($this->getMetadataSchemaName(), $this->getAssocType());
|
||||
return $metadataDescription;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all field names introduced by the
|
||||
* meta-data schema that might have to be persisted.
|
||||
*
|
||||
* @param bool $translated if true, return localized field
|
||||
* names, otherwise return additional field names.
|
||||
*
|
||||
* @return array an array of field names to be persisted.
|
||||
*/
|
||||
public function getMetadataFieldNames($translated = true)
|
||||
{
|
||||
// Do we need to build the field name cache first?
|
||||
if (is_null($this->_metadataFieldNames)) {
|
||||
// Initialize the cache array
|
||||
$this->_metadataFieldNames = [];
|
||||
|
||||
// Retrieve all properties and add
|
||||
// their names to the cache
|
||||
$metadataSchema = & $this->getMetadataSchema();
|
||||
$metadataSchemaNamespace = $metadataSchema->getNamespace();
|
||||
$properties = & $metadataSchema->getProperties();
|
||||
foreach ($properties as $property) {
|
||||
$propertyAssocTypes = $property->getAssocTypes();
|
||||
if (in_array($this->_assocType, $propertyAssocTypes)) {
|
||||
// Separate translated and non-translated property names
|
||||
// and add the name space so that field names are unique
|
||||
// across various meta-data schemas.
|
||||
$this->_metadataFieldNames[$property->getTranslated()][] = $metadataSchemaNamespace . ':' . $property->getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the field names
|
||||
return $this->_metadataFieldNames[$translated];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set several localized statements in a meta-data schema.
|
||||
*
|
||||
* @param MetadataDescription $metadataDescription
|
||||
* @param string $propertyName
|
||||
* @param array $localizedValues (keys: locale, values: localized values)
|
||||
*/
|
||||
public function addLocalizedStatements(&$metadataDescription, $propertyName, $localizedValues)
|
||||
{
|
||||
if (is_array($localizedValues)) {
|
||||
foreach ($localizedValues as $locale => $values) {
|
||||
// Handle cardinality "many" and "one" in the same way.
|
||||
if (is_scalar($values)) {
|
||||
$values = [$values];
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
$metadataDescription->addStatement($propertyName, $value, $locale);
|
||||
unset($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly inject all fields that are not mapped to the
|
||||
* data object into the data object's data array for
|
||||
* automatic persistence by the meta-data framework.
|
||||
*
|
||||
* @param MetadataDescription $metadataDescription
|
||||
* @param DataObject $dataObject
|
||||
*/
|
||||
public function injectUnmappedDataObjectMetadataFields(&$metadataDescription, &$dataObject)
|
||||
{
|
||||
// Handle translated and non-translated statements separately.
|
||||
foreach ([true, false] as $translated) {
|
||||
// Retrieve the unmapped fields.
|
||||
foreach ($this->getDataObjectMetadataFieldNames($translated) as $unmappedProperty) {
|
||||
// Identify the corresponding property name.
|
||||
[$namespace, $propertyName] = explode(':', $unmappedProperty);
|
||||
|
||||
// Find out whether we have a statement for this unmapped property.
|
||||
if ($metadataDescription->hasStatement($propertyName)) {
|
||||
// Add the unmapped statement directly to the
|
||||
// data object.
|
||||
if ($translated) {
|
||||
$dataObject->setData($unmappedProperty, $metadataDescription->getStatementTranslations($propertyName));
|
||||
} else {
|
||||
$dataObject->setData($unmappedProperty, $metadataDescription->getStatement($propertyName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly extract all fields that are not mapped to the
|
||||
* data object from the data object's data array.
|
||||
*
|
||||
* @param DataObject $dataObject
|
||||
* @param MetadataDescription $metadataDescription
|
||||
*/
|
||||
public function extractUnmappedDataObjectMetadataFields(&$dataObject, &$metadataDescription)
|
||||
{
|
||||
$metadataSchema = & $this->getMetadataSchema();
|
||||
$handledNamespace = $metadataSchema->getNamespace();
|
||||
|
||||
// Handle translated and non-translated statements separately.
|
||||
foreach ([true, false] as $translated) {
|
||||
// Retrieve the unmapped fields.
|
||||
foreach ($this->getDataObjectMetadataFieldNames($translated) as $unmappedProperty) {
|
||||
// Find out whether we have a statement for this unmapped property.
|
||||
if ($dataObject->hasData($unmappedProperty)) {
|
||||
// Identify the corresponding property name and namespace.
|
||||
[$namespace, $propertyName] = explode(':', $unmappedProperty);
|
||||
|
||||
// Only extract data if the namespace of the property
|
||||
// is the same as the one handled by this adapter and the
|
||||
// property is within the current description.
|
||||
if ($namespace == $handledNamespace && $metadataSchema->hasProperty($propertyName)) {
|
||||
// Add the unmapped statement to the metadata description.
|
||||
if ($translated) {
|
||||
$this->addLocalizedStatements($metadataDescription, $propertyName, $dataObject->getData($unmappedProperty));
|
||||
} else {
|
||||
$metadataDescription->addStatement($propertyName, $dataObject->getData($unmappedProperty));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!PKP_STRICT_MODE) {
|
||||
class_alias('\PKP\metadata\MetadataDataObjectAdapter', '\MetadataDataObjectAdapter');
|
||||
define('METADATA_DOA_INJECTION_MODE', MetadataDataObjectAdapter::METADATA_DOA_INJECTION_MODE);
|
||||
define('METADATA_DOA_EXTRACTION_MODE', MetadataDataObjectAdapter::METADATA_DOA_EXTRACTION_MODE);
|
||||
}
|
||||
Reference in New Issue
Block a user