You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1160 lines
48 KiB

  1. <?php
  2. /**
  3. * (c) Kitodo. Key to digital objects e.V. <contact@kitodo.org>
  4. *
  5. * This file is part of the Kitodo and TYPO3 projects.
  6. *
  7. * @license GNU General Public License version 3 or later.
  8. * For the full copyright and license information, please read the
  9. * LICENSE.txt file that was distributed with this source code.
  10. */
  11. namespace Kitodo\Dlf\Common;
  12. use TYPO3\CMS\Core\Database\ConnectionPool;
  13. use TYPO3\CMS\Core\Database\Query\Restriction\HiddenRestriction;
  14. use TYPO3\CMS\Core\Utility\GeneralUtility;
  15. use Ubl\Iiif\Tools\IiifHelper;
  16. use Ubl\Iiif\Services\AbstractImageService;
  17. /**
  18. * MetsDocument class for the 'dlf' extension.
  19. *
  20. * @author Sebastian Meyer <sebastian.meyer@slub-dresden.de>
  21. * @author Henrik Lochmann <dev@mentalmotive.com>
  22. * @package TYPO3
  23. * @subpackage dlf
  24. * @access public
  25. * @property-write int $cPid This holds the PID for the configuration
  26. * @property-read array $dmdSec This holds the XML file's dmdSec parts with their IDs as array key
  27. * @property-read array $fileGrps This holds the file ID -> USE concordance
  28. * @property-read bool $hasFulltext Are there any fulltext files available?
  29. * @property-read string $location This holds the documents location
  30. * @property-read array $metadataArray This holds the documents' parsed metadata array
  31. * @property-read \SimpleXMLElement $mets This holds the XML file's METS part as \SimpleXMLElement object
  32. * @property-read int $numPages The holds the total number of pages
  33. * @property-read int $parentId This holds the UID of the parent document or zero if not multi-volumed
  34. * @property-read array $physicalStructure This holds the physical structure
  35. * @property-read array $physicalStructureInfo This holds the physical structure metadata
  36. * @property-read int $pid This holds the PID of the document or zero if not in database
  37. * @property-read bool $ready Is the document instantiated successfully?
  38. * @property-read string $recordId The METS file's / IIIF manifest's record identifier
  39. * @property-read int $rootId This holds the UID of the root document or zero if not multi-volumed
  40. * @property-read array $smLinks This holds the smLinks between logical and physical structMap
  41. * @property-read array $tableOfContents This holds the logical structure
  42. * @property-read string $thumbnail This holds the document's thumbnail location
  43. * @property-read string $toplevelId This holds the toplevel structure's @ID (METS) or the manifest's @id (IIIF)
  44. * @property-read mixed $uid This holds the UID or the URL of the document
  45. */
  46. final class MetsDocument extends Document
  47. {
  48. /**
  49. * This holds the whole XML file as string for serialization purposes
  50. * @see __sleep() / __wakeup()
  51. *
  52. * @var string
  53. * @access protected
  54. */
  55. protected $asXML = '';
  56. /**
  57. * This holds the XML file's dmdSec parts with their IDs as array key
  58. *
  59. * @var array
  60. * @access protected
  61. */
  62. protected $dmdSec = [];
  63. /**
  64. * Are the METS file's dmdSecs loaded?
  65. * @see $dmdSec
  66. *
  67. * @var bool
  68. * @access protected
  69. */
  70. protected $dmdSecLoaded = false;
  71. /**
  72. * The extension key
  73. *
  74. * @var string
  75. * @access public
  76. */
  77. public static $extKey = 'dlf';
  78. /**
  79. * This holds the file ID -> USE concordance
  80. * @see _getFileGrps()
  81. *
  82. * @var array
  83. * @access protected
  84. */
  85. protected $fileGrps = [];
  86. /**
  87. * Are the image file groups loaded?
  88. * @see $fileGrps
  89. *
  90. * @var bool
  91. * @access protected
  92. */
  93. protected $fileGrpsLoaded = false;
  94. /**
  95. * This holds the XML file's METS part as \SimpleXMLElement object
  96. *
  97. * @var \SimpleXMLElement
  98. * @access protected
  99. */
  100. protected $mets;
  101. /**
  102. * This holds the whole XML file as \SimpleXMLElement object
  103. *
  104. * @var \SimpleXMLElement
  105. * @access protected
  106. */
  107. protected $xml;
  108. /**
  109. * This adds metadata from METS structural map to metadata array.
  110. *
  111. * @access public
  112. *
  113. * @param array &$metadata: The metadata array to extend
  114. * @param string $id: The @ID attribute of the logical structure node
  115. *
  116. * @return void
  117. */
  118. public function addMetadataFromMets(&$metadata, $id)
  119. {
  120. $details = $this->getLogicalStructure($id);
  121. if (!empty($details)) {
  122. $metadata['mets_order'][0] = $details['order'];
  123. $metadata['mets_label'][0] = $details['label'];
  124. $metadata['mets_orderlabel'][0] = $details['orderlabel'];
  125. }
  126. }
  127. /**
  128. *
  129. * {@inheritDoc}
  130. * @see \Kitodo\Dlf\Common\Document::establishRecordId()
  131. */
  132. protected function establishRecordId($pid)
  133. {
  134. // Check for METS object @ID.
  135. if (!empty($this->mets['OBJID'])) {
  136. $this->recordId = (string) $this->mets['OBJID'];
  137. }
  138. // Get hook objects.
  139. $hookObjects = Helper::getHookObjects('Classes/Common/MetsDocument.php');
  140. // Apply hooks.
  141. foreach ($hookObjects as $hookObj) {
  142. if (method_exists($hookObj, 'construct_postProcessRecordId')) {
  143. $hookObj->construct_postProcessRecordId($this->xml, $this->recordId);
  144. }
  145. }
  146. }
  147. /**
  148. *
  149. * {@inheritDoc}
  150. * @see \Kitodo\Dlf\Common\Document::getDownloadLocation()
  151. */
  152. public function getDownloadLocation($id)
  153. {
  154. $fileMimeType = $this->getFileMimeType($id);
  155. $fileLocation = $this->getFileLocation($id);
  156. if ($fileMimeType === 'application/vnd.kitodo.iiif') {
  157. $fileLocation = (strrpos($fileLocation, 'info.json') === strlen($fileLocation) - 9) ? $fileLocation : (strrpos($fileLocation, '/') === strlen($fileLocation) ? $fileLocation . 'info.json' : $fileLocation . '/info.json');
  158. $conf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][self::$extKey]);
  159. IiifHelper::setUrlReader(IiifUrlReader::getInstance());
  160. IiifHelper::setMaxThumbnailHeight($conf['iiifThumbnailHeight']);
  161. IiifHelper::setMaxThumbnailWidth($conf['iiifThumbnailWidth']);
  162. $service = IiifHelper::loadIiifResource($fileLocation);
  163. if ($service !== null && $service instanceof AbstractImageService) {
  164. return $service->getImageUrl();
  165. }
  166. } elseif ($fileMimeType === 'application/vnd.netfpx') {
  167. $baseURL = $fileLocation . (strpos($fileLocation, '?') === false ? '?' : '');
  168. // TODO CVT is an optional IIP server capability; in theory, capabilities should be determined in the object request with '&obj=IIP-server'
  169. return $baseURL . '&CVT=jpeg';
  170. }
  171. return $fileLocation;
  172. }
  173. /**
  174. * {@inheritDoc}
  175. * @see \Kitodo\Dlf\Common\Document::getFileLocation()
  176. */
  177. public function getFileLocation($id)
  178. {
  179. $location = $this->mets->xpath('./mets:fileSec/mets:fileGrp/mets:file[@ID="' . $id . '"]/mets:FLocat[@LOCTYPE="URL"]');
  180. if (
  181. !empty($id)
  182. && !empty($location)
  183. ) {
  184. return (string) $location[0]->attributes('http://www.w3.org/1999/xlink')->href;
  185. } else {
  186. Helper::devLog('There is no file node with @ID "' . $id . '"', DEVLOG_SEVERITY_WARNING);
  187. return '';
  188. }
  189. }
  190. /**
  191. * {@inheritDoc}
  192. * @see \Kitodo\Dlf\Common\Document::getFileMimeType()
  193. */
  194. public function getFileMimeType($id)
  195. {
  196. $mimetype = $this->mets->xpath('./mets:fileSec/mets:fileGrp/mets:file[@ID="' . $id . '"]/@MIMETYPE');
  197. if (
  198. !empty($id)
  199. && !empty($mimetype)
  200. ) {
  201. return (string) $mimetype[0];
  202. } else {
  203. Helper::devLog('There is no file node with @ID "' . $id . '" or no MIME type specified', DEVLOG_SEVERITY_WARNING);
  204. return '';
  205. }
  206. }
  207. /**
  208. * {@inheritDoc}
  209. * @see \Kitodo\Dlf\Common\Document::getLogicalStructure()
  210. */
  211. public function getLogicalStructure($id, $recursive = false)
  212. {
  213. $details = [];
  214. // Is the requested logical unit already loaded?
  215. if (
  216. !$recursive
  217. && !empty($this->logicalUnits[$id])
  218. ) {
  219. // Yes. Return it.
  220. return $this->logicalUnits[$id];
  221. } elseif (!empty($id)) {
  222. // Get specified logical unit.
  223. $divs = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]');
  224. } else {
  225. // Get all logical units at top level.
  226. $divs = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]/mets:div');
  227. }
  228. if (!empty($divs)) {
  229. if (!$recursive) {
  230. // Get the details for the first xpath hit.
  231. $details = $this->getLogicalStructureInfo($divs[0]);
  232. } else {
  233. // Walk the logical structure recursively and fill the whole table of contents.
  234. foreach ($divs as $div) {
  235. $this->tableOfContents[] = $this->getLogicalStructureInfo($div, $recursive);
  236. }
  237. }
  238. }
  239. return $details;
  240. }
  241. /**
  242. * This gets details about a logical structure element
  243. *
  244. * @access protected
  245. *
  246. * @param \SimpleXMLElement $structure: The logical structure node
  247. * @param bool $recursive: Whether to include the child elements
  248. *
  249. * @return array Array of the element's id, label, type and physical page indexes/mptr link
  250. */
  251. protected function getLogicalStructureInfo(\SimpleXMLElement $structure, $recursive = false)
  252. {
  253. // Get attributes.
  254. foreach ($structure->attributes() as $attribute => $value) {
  255. $attributes[$attribute] = (string) $value;
  256. }
  257. // Load plugin configuration.
  258. $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][self::$extKey]);
  259. // Extract identity information.
  260. $details = [];
  261. $details['id'] = $attributes['ID'];
  262. $details['dmdId'] = (isset($attributes['DMDID']) ? $attributes['DMDID'] : '');
  263. $details['order'] = (isset($attributes['ORDER']) ? $attributes['ORDER'] : '');
  264. $details['label'] = (isset($attributes['LABEL']) ? $attributes['LABEL'] : '');
  265. $details['orderlabel'] = (isset($attributes['ORDERLABEL']) ? $attributes['ORDERLABEL'] : '');
  266. $details['contentIds'] = (isset($attributes['CONTENTIDS']) ? $attributes['CONTENTIDS'] : '');
  267. $details['volume'] = '';
  268. // Set volume information only if no label is set and this is the toplevel structure element.
  269. if (
  270. empty($details['label'])
  271. && $details['id'] == $this->_getToplevelId()
  272. ) {
  273. $metadata = $this->getMetadata($details['id']);
  274. if (!empty($metadata['volume'][0])) {
  275. $details['volume'] = $metadata['volume'][0];
  276. }
  277. }
  278. $details['pagination'] = '';
  279. $details['type'] = $attributes['TYPE'];
  280. $details['thumbnailId'] = '';
  281. // Load smLinks.
  282. $this->_getSmLinks();
  283. // Load physical structure.
  284. $this->_getPhysicalStructure();
  285. // Get the physical page or external file this structure element is pointing at.
  286. $details['points'] = '';
  287. // Is there a mptr node?
  288. if (count($structure->children('http://www.loc.gov/METS/')->mptr)) {
  289. // Yes. Get the file reference.
  290. $details['points'] = (string) $structure->children('http://www.loc.gov/METS/')->mptr[0]->attributes('http://www.w3.org/1999/xlink')->href;
  291. } elseif (
  292. !empty($this->physicalStructure)
  293. && array_key_exists($details['id'], $this->smLinks['l2p'])
  294. ) {
  295. // Link logical structure to the first corresponding physical page/track.
  296. $details['points'] = max(intval(array_search($this->smLinks['l2p'][$details['id']][0], $this->physicalStructure, true)), 1);
  297. $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
  298. while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
  299. if (!empty($this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['files'][$fileGrpThumb])) {
  300. $details['thumbnailId'] = $this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['files'][$fileGrpThumb];
  301. break;
  302. }
  303. }
  304. // Get page/track number of the first page/track related to this structure element.
  305. $details['pagination'] = $this->physicalStructureInfo[$this->smLinks['l2p'][$details['id']][0]]['orderlabel'];
  306. } elseif ($details['id'] == $this->_getToplevelId()) {
  307. // Point to self if this is the toplevel structure.
  308. $details['points'] = 1;
  309. $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
  310. while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
  311. if (
  312. !empty($this->physicalStructure)
  313. && !empty($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb])
  314. ) {
  315. $details['thumbnailId'] = $this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb];
  316. break;
  317. }
  318. }
  319. }
  320. // Get the files this structure element is pointing at.
  321. $details['files'] = [];
  322. $fileUse = $this->_getFileGrps();
  323. // Get the file representations from fileSec node.
  324. foreach ($structure->children('http://www.loc.gov/METS/')->fptr as $fptr) {
  325. // Check if file has valid @USE attribute.
  326. if (!empty($fileUse[(string) $fptr->attributes()->FILEID])) {
  327. $details['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
  328. }
  329. }
  330. // Keep for later usage.
  331. $this->logicalUnits[$details['id']] = $details;
  332. // Walk the structure recursively? And are there any children of the current element?
  333. if (
  334. $recursive
  335. && count($structure->children('http://www.loc.gov/METS/')->div)
  336. ) {
  337. $details['children'] = [];
  338. foreach ($structure->children('http://www.loc.gov/METS/')->div as $child) {
  339. // Repeat for all children.
  340. $details['children'][] = $this->getLogicalStructureInfo($child, true);
  341. }
  342. }
  343. return $details;
  344. }
  345. /**
  346. * {@inheritDoc}
  347. * @see \Kitodo\Dlf\Common\Document::getMetadata()
  348. */
  349. public function getMetadata($id, $cPid = 0)
  350. {
  351. // Make sure $cPid is a non-negative integer.
  352. $cPid = max(intval($cPid), 0);
  353. // If $cPid is not given, try to get it elsewhere.
  354. if (
  355. !$cPid
  356. && ($this->cPid || $this->pid)
  357. ) {
  358. // Retain current PID.
  359. $cPid = ($this->cPid ? $this->cPid : $this->pid);
  360. } elseif (!$cPid) {
  361. Helper::devLog('Invalid PID ' . $cPid . ' for metadata definitions', DEVLOG_SEVERITY_WARNING);
  362. return [];
  363. }
  364. // Get metadata from parsed metadata array if available.
  365. if (
  366. !empty($this->metadataArray[$id])
  367. && $this->metadataArray[0] == $cPid
  368. ) {
  369. return $this->metadataArray[$id];
  370. }
  371. // Initialize metadata array with empty values.
  372. $metadata = [
  373. 'title' => [],
  374. 'title_sorting' => [],
  375. 'author' => [],
  376. 'place' => [],
  377. 'year' => [],
  378. 'prod_id' => [],
  379. 'record_id' => [],
  380. 'opac_id' => [],
  381. 'union_id' => [],
  382. 'urn' => [],
  383. 'purl' => [],
  384. 'type' => [],
  385. 'volume' => [],
  386. 'volume_sorting' => [],
  387. 'license' => [],
  388. 'terms' => [],
  389. 'restrictions' => [],
  390. 'out_of_print' => [],
  391. 'rights_info' => [],
  392. 'collection' => [],
  393. 'owner' => [],
  394. 'mets_label' => [],
  395. 'mets_orderlabel' => [],
  396. 'document_format' => ['METS'],
  397. ];
  398. // Get the logical structure node's @DMDID.
  399. if (!empty($this->logicalUnits[$id])) {
  400. $dmdIds = $this->logicalUnits[$id]['dmdId'];
  401. } else {
  402. $dmdIds = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]/@DMDID');
  403. $dmdIds = (string) $dmdIds[0];
  404. }
  405. if (!empty($dmdIds)) {
  406. // Handle multiple DMDIDs separately.
  407. $dmdIds = explode(' ', $dmdIds);
  408. $hasSupportedMetadata = false;
  409. } else {
  410. // There is no dmdSec for this structure node.
  411. return [];
  412. }
  413. // Load available metadata formats and dmdSecs.
  414. $this->loadFormats();
  415. $this->_getDmdSec();
  416. foreach ($dmdIds as $dmdId) {
  417. // Is this metadata format supported?
  418. if (!empty($this->formats[$this->dmdSec[$dmdId]['type']])) {
  419. if (!empty($this->formats[$this->dmdSec[$dmdId]['type']]['class'])) {
  420. $class = $this->formats[$this->dmdSec[$dmdId]['type']]['class'];
  421. // Get the metadata from class.
  422. if (
  423. class_exists($class)
  424. && ($obj = GeneralUtility::makeInstance($class)) instanceof MetadataInterface
  425. ) {
  426. $obj->extractMetadata($this->dmdSec[$dmdId]['xml'], $metadata);
  427. } else {
  428. Helper::devLog('Invalid class/method "' . $class . '->extractMetadata()" for metadata format "' . $this->dmdSec[$dmdId]['type'] . '"', DEVLOG_SEVERITY_WARNING);
  429. }
  430. }
  431. } else {
  432. Helper::devLog('Unsupported metadata format "' . $this->dmdSec[$dmdId]['type'] . '" in dmdSec with @ID "' . $dmdId . '"', DEVLOG_SEVERITY_NOTICE);
  433. // Continue searching for supported metadata with next @DMDID.
  434. continue;
  435. }
  436. // Get the structure's type.
  437. if (!empty($this->logicalUnits[$id])) {
  438. $metadata['type'] = [$this->logicalUnits[$id]['type']];
  439. } else {
  440. $struct = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $id . '"]/@TYPE');
  441. if (!empty($struct)) {
  442. $metadata['type'] = [(string) $struct[0]];
  443. }
  444. }
  445. // Get the additional metadata from database.
  446. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  447. ->getQueryBuilderForTable('tx_dlf_metadata');
  448. // Get hidden records, too.
  449. $queryBuilder
  450. ->getRestrictions()
  451. ->removeByType(HiddenRestriction::class);
  452. // Get all metadata with configured xpath and applicable format first.
  453. $resultWithFormat = $queryBuilder
  454. ->select(
  455. 'tx_dlf_metadata.index_name AS index_name',
  456. 'tx_dlf_metadataformat_joins.xpath AS xpath',
  457. 'tx_dlf_metadataformat_joins.xpath_sorting AS xpath_sorting',
  458. 'tx_dlf_metadata.is_sortable AS is_sortable',
  459. 'tx_dlf_metadata.default_value AS default_value',
  460. 'tx_dlf_metadata.format AS format'
  461. )
  462. ->from('tx_dlf_metadata')
  463. ->innerJoin(
  464. 'tx_dlf_metadata',
  465. 'tx_dlf_metadataformat',
  466. 'tx_dlf_metadataformat_joins',
  467. $queryBuilder->expr()->eq(
  468. 'tx_dlf_metadataformat_joins.parent_id',
  469. 'tx_dlf_metadata.uid'
  470. )
  471. )
  472. ->innerJoin(
  473. 'tx_dlf_metadataformat_joins',
  474. 'tx_dlf_formats',
  475. 'tx_dlf_formats_joins',
  476. $queryBuilder->expr()->eq(
  477. 'tx_dlf_formats_joins.uid',
  478. 'tx_dlf_metadataformat_joins.encoded'
  479. )
  480. )
  481. ->where(
  482. $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
  483. $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
  484. $queryBuilder->expr()->eq('tx_dlf_metadataformat_joins.pid', intval($cPid)),
  485. $queryBuilder->expr()->eq('tx_dlf_formats_joins.type', $queryBuilder->createNamedParameter($this->dmdSec[$dmdId]['type']))
  486. )
  487. ->execute();
  488. // Get all metadata without a format, but with a default value next.
  489. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  490. ->getQueryBuilderForTable('tx_dlf_metadata');
  491. // Get hidden records, too.
  492. $queryBuilder
  493. ->getRestrictions()
  494. ->removeByType(HiddenRestriction::class);
  495. $resultWithoutFormat = $queryBuilder
  496. ->select(
  497. 'tx_dlf_metadata.index_name AS index_name',
  498. 'tx_dlf_metadata.is_sortable AS is_sortable',
  499. 'tx_dlf_metadata.default_value AS default_value',
  500. 'tx_dlf_metadata.format AS format'
  501. )
  502. ->from('tx_dlf_metadata')
  503. ->where(
  504. $queryBuilder->expr()->eq('tx_dlf_metadata.pid', intval($cPid)),
  505. $queryBuilder->expr()->eq('tx_dlf_metadata.l18n_parent', 0),
  506. $queryBuilder->expr()->eq('tx_dlf_metadata.format', 0),
  507. $queryBuilder->expr()->neq('tx_dlf_metadata.default_value', $queryBuilder->createNamedParameter(''))
  508. )
  509. ->execute();
  510. // Merge both result sets.
  511. $allResults = array_merge($resultWithFormat->fetchAll(), $resultWithoutFormat->fetchAll());
  512. // We need a \DOMDocument here, because SimpleXML doesn't support XPath functions properly.
  513. $domNode = dom_import_simplexml($this->dmdSec[$dmdId]['xml']);
  514. $domXPath = new \DOMXPath($domNode->ownerDocument);
  515. $this->registerNamespaces($domXPath);
  516. // OK, now make the XPath queries.
  517. foreach ($allResults as $resArray) {
  518. // Set metadata field's value(s).
  519. if (
  520. $resArray['format'] > 0
  521. && !empty($resArray['xpath'])
  522. && ($values = $domXPath->evaluate($resArray['xpath'], $domNode))
  523. ) {
  524. if (
  525. $values instanceof \DOMNodeList
  526. && $values->length > 0
  527. ) {
  528. $metadata[$resArray['index_name']] = [];
  529. foreach ($values as $value) {
  530. $metadata[$resArray['index_name']][] = trim((string) $value->nodeValue);
  531. }
  532. } elseif (!($values instanceof \DOMNodeList)) {
  533. $metadata[$resArray['index_name']] = [trim((string) $values)];
  534. }
  535. }
  536. // Set default value if applicable.
  537. if (
  538. empty($metadata[$resArray['index_name']][0])
  539. && strlen($resArray['default_value']) > 0
  540. ) {
  541. $metadata[$resArray['index_name']] = [$resArray['default_value']];
  542. }
  543. // Set sorting value if applicable.
  544. if (
  545. !empty($metadata[$resArray['index_name']])
  546. && $resArray['is_sortable']
  547. ) {
  548. if (
  549. $resArray['format'] > 0
  550. && !empty($resArray['xpath_sorting'])
  551. && ($values = $domXPath->evaluate($resArray['xpath_sorting'], $domNode))
  552. ) {
  553. if (
  554. $values instanceof \DOMNodeList
  555. && $values->length > 0
  556. ) {
  557. $metadata[$resArray['index_name'] . '_sorting'][0] = trim((string) $values->item(0)->nodeValue);
  558. } elseif (!($values instanceof \DOMNodeList)) {
  559. $metadata[$resArray['index_name'] . '_sorting'][0] = trim((string) $values);
  560. }
  561. }
  562. if (empty($metadata[$resArray['index_name'] . '_sorting'][0])) {
  563. $metadata[$resArray['index_name'] . '_sorting'][0] = $metadata[$resArray['index_name']][0];
  564. }
  565. }
  566. }
  567. // Set title to empty string if not present.
  568. if (empty($metadata['title'][0])) {
  569. $metadata['title'][0] = '';
  570. $metadata['title_sorting'][0] = '';
  571. }
  572. // Add collections from database to toplevel element if document is already saved.
  573. if (
  574. \TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($this->uid)
  575. && $id == $this->_getToplevelId()
  576. ) {
  577. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  578. ->getQueryBuilderForTable('tx_dlf_documents');
  579. $result = $queryBuilder
  580. ->select(
  581. 'tx_dlf_collections_join.index_name AS index_name'
  582. )
  583. ->from('tx_dlf_documents')
  584. ->innerJoin(
  585. 'tx_dlf_documents',
  586. 'tx_dlf_relations',
  587. 'tx_dlf_relations_joins',
  588. $queryBuilder->expr()->eq(
  589. 'tx_dlf_relations_joins.uid_local',
  590. 'tx_dlf_documents.uid'
  591. )
  592. )
  593. ->innerJoin(
  594. 'tx_dlf_relations_joins',
  595. 'tx_dlf_collections',
  596. 'tx_dlf_collections_join',
  597. $queryBuilder->expr()->eq(
  598. 'tx_dlf_relations_joins.uid_foreign',
  599. 'tx_dlf_collections_join.uid'
  600. )
  601. )
  602. ->where(
  603. $queryBuilder->expr()->eq('tx_dlf_documents.pid', intval($cPid)),
  604. $queryBuilder->expr()->eq('tx_dlf_documents.uid', intval($this->uid))
  605. )
  606. ->orderBy('tx_dlf_collections_join.index_name', 'ASC')
  607. ->execute();
  608. $allResults = $result->fetchAll();
  609. foreach ($allResults as $resArray) {
  610. if (!in_array($resArray['index_name'], $metadata['collection'])) {
  611. $metadata['collection'][] = $resArray['index_name'];
  612. }
  613. }
  614. }
  615. // Extract metadata only from first supported dmdSec.
  616. $hasSupportedMetadata = true;
  617. break;
  618. }
  619. if ($hasSupportedMetadata) {
  620. return $metadata;
  621. } else {
  622. Helper::devLog('No supported metadata found for logical structure with @ID "' . $id . '"', DEVLOG_SEVERITY_WARNING);
  623. return [];
  624. }
  625. }
  626. /**
  627. * {@inheritDoc}
  628. * @see \Kitodo\Dlf\Common\Document::getRawText()
  629. */
  630. public function getRawText($id)
  631. {
  632. $rawText = '';
  633. // Get text from raw text array if available.
  634. if (!empty($this->rawTextArray[$id])) {
  635. return $this->rawTextArray[$id];
  636. }
  637. // Load fileGrps and check for fulltext files.
  638. $this->_getFileGrps();
  639. if ($this->hasFulltext) {
  640. $rawText = $this->getRawTextFromXml($id);
  641. }
  642. return $rawText;
  643. }
  644. /**
  645. * {@inheritDoc}
  646. * @see Document::getStructureDepth()
  647. */
  648. public function getStructureDepth($logId)
  649. {
  650. $ancestors = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $logId . '"]/ancestor::*');
  651. if (!empty($ancestors)) {
  652. return count($ancestors);
  653. } else {
  654. return 0;
  655. }
  656. }
  657. /**
  658. * {@inheritDoc}
  659. * @see \Kitodo\Dlf\Common\Document::init()
  660. */
  661. protected function init()
  662. {
  663. // Get METS node from XML file.
  664. $this->registerNamespaces($this->xml);
  665. $mets = $this->xml->xpath('//mets:mets');
  666. if (!empty($mets)) {
  667. $this->mets = $mets[0];
  668. // Register namespaces.
  669. $this->registerNamespaces($this->mets);
  670. } else {
  671. Helper::devLog('No METS part found in document with UID ' . $this->uid, DEVLOG_SEVERITY_ERROR);
  672. }
  673. }
  674. /**
  675. * {@inheritDoc}
  676. * @see \Kitodo\Dlf\Common\Document::loadLocation()
  677. */
  678. protected function loadLocation($location)
  679. {
  680. $fileResource = GeneralUtility::getUrl($location);
  681. if ($fileResource !== false) {
  682. // Turn off libxml's error logging.
  683. $libxmlErrors = libxml_use_internal_errors(true);
  684. // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
  685. $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
  686. // Load XML from file.
  687. $xml = simplexml_load_string($fileResource);
  688. // reset entity loader setting
  689. libxml_disable_entity_loader($previousValueOfEntityLoader);
  690. // Reset libxml's error logging.
  691. libxml_use_internal_errors($libxmlErrors);
  692. // Set some basic properties.
  693. if ($xml !== false) {
  694. $this->xml = $xml;
  695. return true;
  696. }
  697. }
  698. Helper::devLog('Could not load XML file from "' . $location . '"', DEVLOG_SEVERITY_ERROR);
  699. return false;
  700. }
  701. /**
  702. * {@inheritDoc}
  703. * @see \Kitodo\Dlf\Common\Document::ensureHasFulltextIsSet()
  704. */
  705. protected function ensureHasFulltextIsSet()
  706. {
  707. // Are the fileGrps already loaded?
  708. if (!$this->fileGrpsLoaded) {
  709. $this->_getFileGrps();
  710. }
  711. }
  712. /**
  713. * {@inheritDoc}
  714. * @see Document::getParentDocumentUid()
  715. */
  716. protected function getParentDocumentUidForSaving($pid, $core)
  717. {
  718. $partof = 0;
  719. // Get the closest ancestor of the current document which has a MPTR child.
  720. $parentMptr = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@ID="' . $this->_getToplevelId() . '"]/ancestor::mets:div[./mets:mptr][1]/mets:mptr');
  721. if (!empty($parentMptr)) {
  722. $parentLocation = (string) $parentMptr[0]->attributes('http://www.w3.org/1999/xlink')->href;
  723. if ($parentLocation != $this->location) {
  724. $parentDoc = self::getInstance($parentLocation, $pid);
  725. if ($parentDoc->ready) {
  726. if ($parentDoc->pid != $pid) {
  727. $parentDoc->save($pid, $core);
  728. }
  729. $partof = $parentDoc->uid;
  730. }
  731. }
  732. }
  733. return $partof;
  734. }
  735. /**
  736. * {@inheritDoc}
  737. * @see Document::setPreloadedDocument()
  738. */
  739. protected function setPreloadedDocument($preloadedDocument)
  740. {
  741. if ($preloadedDocument instanceof \SimpleXMLElement) {
  742. $this->xml = $preloadedDocument;
  743. return true;
  744. }
  745. return false;
  746. }
  747. /**
  748. * {@inheritDoc}
  749. * @see Document::getDocument()
  750. */
  751. protected function getDocument()
  752. {
  753. return $this->mets;
  754. }
  755. /**
  756. * This returns $this->cPid via __get()
  757. *
  758. * @access protected
  759. *
  760. * @return int The PID of the metadata definitions
  761. */
  762. protected function _getCPid()
  763. {
  764. return $this->cPid;
  765. }
  766. /**
  767. * This builds an array of the document's dmdSecs
  768. *
  769. * @access protected
  770. *
  771. * @return array Array of dmdSecs with their IDs as array key
  772. */
  773. protected function _getDmdSec()
  774. {
  775. if (!$this->dmdSecLoaded) {
  776. // Get available data formats.
  777. $this->loadFormats();
  778. // Get dmdSec nodes from METS.
  779. $dmdIds = $this->mets->xpath('./mets:dmdSec/@ID');
  780. if (!empty($dmdIds)) {
  781. foreach ($dmdIds as $dmdId) {
  782. if ($type = $this->mets->xpath('./mets:dmdSec[@ID="' . (string) $dmdId . '"]/mets:mdWrap[not(@MDTYPE="OTHER")]/@MDTYPE')) {
  783. if (!empty($this->formats[(string) $type[0]])) {
  784. $type = (string) $type[0];
  785. $xml = $this->mets->xpath('./mets:dmdSec[@ID="' . (string) $dmdId . '"]/mets:mdWrap[@MDTYPE="' . $type . '"]/mets:xmlData/' . strtolower($type) . ':' . $this->formats[$type]['rootElement']);
  786. }
  787. } elseif ($type = $this->mets->xpath('./mets:dmdSec[@ID="' . (string) $dmdId . '"]/mets:mdWrap[@MDTYPE="OTHER"]/@OTHERMDTYPE')) {
  788. if (!empty($this->formats[(string) $type[0]])) {
  789. $type = (string) $type[0];
  790. $xml = $this->mets->xpath('./mets:dmdSec[@ID="' . (string) $dmdId . '"]/mets:mdWrap[@MDTYPE="OTHER"][@OTHERMDTYPE="' . $type . '"]/mets:xmlData/' . strtolower($type) . ':' . $this->formats[$type]['rootElement']);
  791. }
  792. }
  793. if (!empty($xml)) {
  794. $this->dmdSec[(string) $dmdId]['type'] = $type;
  795. $this->dmdSec[(string) $dmdId]['xml'] = $xml[0];
  796. $this->registerNamespaces($this->dmdSec[(string) $dmdId]['xml']);
  797. }
  798. }
  799. }
  800. $this->dmdSecLoaded = true;
  801. }
  802. return $this->dmdSec;
  803. }
  804. /**
  805. * This builds the file ID -> USE concordance
  806. *
  807. * @access protected
  808. *
  809. * @return array Array of file use groups with file IDs
  810. */
  811. protected function _getFileGrps()
  812. {
  813. if (!$this->fileGrpsLoaded) {
  814. // Get configured USE attributes.
  815. $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][self::$extKey]);
  816. $useGrps = GeneralUtility::trimExplode(',', $extConf['fileGrpImages']);
  817. if (!empty($extConf['fileGrpThumbs'])) {
  818. $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']));
  819. }
  820. if (!empty($extConf['fileGrpDownload'])) {
  821. $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpDownload']));
  822. }
  823. if (!empty($extConf['fileGrpFulltext'])) {
  824. $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']));
  825. }
  826. if (!empty($extConf['fileGrpAudio'])) {
  827. $useGrps = array_merge($useGrps, GeneralUtility::trimExplode(',', $extConf['fileGrpAudio']));
  828. }
  829. // Get all file groups.
  830. $fileGrps = $this->mets->xpath('./mets:fileSec/mets:fileGrp');
  831. if (!empty($fileGrps)) {
  832. // Build concordance for configured USE attributes.
  833. foreach ($fileGrps as $fileGrp) {
  834. if (in_array((string) $fileGrp['USE'], $useGrps)) {
  835. foreach ($fileGrp->children('http://www.loc.gov/METS/')->file as $file) {
  836. $this->fileGrps[(string) $file->attributes()->ID] = (string) $fileGrp['USE'];
  837. }
  838. }
  839. }
  840. }
  841. // Are there any fulltext files available?
  842. if (
  843. !empty($extConf['fileGrpFulltext'])
  844. && array_intersect(GeneralUtility::trimExplode(',', $extConf['fileGrpFulltext']), $this->fileGrps) !== []
  845. ) {
  846. $this->hasFulltext = true;
  847. }
  848. $this->fileGrpsLoaded = true;
  849. }
  850. return $this->fileGrps;
  851. }
  852. /**
  853. * {@inheritDoc}
  854. * @see \Kitodo\Dlf\Common\Document::prepareMetadataArray()
  855. */
  856. protected function prepareMetadataArray($cPid)
  857. {
  858. $ids = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@DMDID]/@ID');
  859. // Get all logical structure nodes with metadata.
  860. if (!empty($ids)) {
  861. foreach ($ids as $id) {
  862. $this->metadataArray[(string) $id] = $this->getMetadata((string) $id, $cPid);
  863. }
  864. }
  865. // Set current PID for metadata definitions.
  866. }
  867. /**
  868. * This returns $this->mets via __get()
  869. *
  870. * @access protected
  871. *
  872. * @return \SimpleXMLElement The XML's METS part as \SimpleXMLElement object
  873. */
  874. protected function _getMets()
  875. {
  876. return $this->mets;
  877. }
  878. /**
  879. * {@inheritDoc}
  880. * @see \Kitodo\Dlf\Common\Document::_getPhysicalStructure()
  881. */
  882. protected function _getPhysicalStructure()
  883. {
  884. // Is there no physical structure array yet?
  885. if (!$this->physicalStructureLoaded) {
  886. // Does the document have a structMap node of type "PHYSICAL"?
  887. $elementNodes = $this->mets->xpath('./mets:structMap[@TYPE="PHYSICAL"]/mets:div[@TYPE="physSequence"]/mets:div');
  888. if (!empty($elementNodes)) {
  889. // Get file groups.
  890. $fileUse = $this->_getFileGrps();
  891. // Get the physical sequence's metadata.
  892. $physNode = $this->mets->xpath('./mets:structMap[@TYPE="PHYSICAL"]/mets:div[@TYPE="physSequence"]');
  893. $physSeq[0] = (string) $physNode[0]['ID'];
  894. $this->physicalStructureInfo[$physSeq[0]]['id'] = (string) $physNode[0]['ID'];
  895. $this->physicalStructureInfo[$physSeq[0]]['dmdId'] = (isset($physNode[0]['DMDID']) ? (string) $physNode[0]['DMDID'] : '');
  896. $this->physicalStructureInfo[$physSeq[0]]['order'] = (isset($physNode[0]['ORDER']) ? (string) $physNode[0]['ORDER'] : '');
  897. $this->physicalStructureInfo[$physSeq[0]]['label'] = (isset($physNode[0]['LABEL']) ? (string) $physNode[0]['LABEL'] : '');
  898. $this->physicalStructureInfo[$physSeq[0]]['orderlabel'] = (isset($physNode[0]['ORDERLABEL']) ? (string) $physNode[0]['ORDERLABEL'] : '');
  899. $this->physicalStructureInfo[$physSeq[0]]['type'] = (string) $physNode[0]['TYPE'];
  900. $this->physicalStructureInfo[$physSeq[0]]['contentIds'] = (isset($physNode[0]['CONTENTIDS']) ? (string) $physNode[0]['CONTENTIDS'] : '');
  901. // Get the file representations from fileSec node.
  902. foreach ($physNode[0]->children('http://www.loc.gov/METS/')->fptr as $fptr) {
  903. // Check if file has valid @USE attribute.
  904. if (!empty($fileUse[(string) $fptr->attributes()->FILEID])) {
  905. $this->physicalStructureInfo[$physSeq[0]]['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
  906. }
  907. }
  908. // Build the physical elements' array from the physical structMap node.
  909. foreach ($elementNodes as $elementNode) {
  910. $elements[(int) $elementNode['ORDER']] = (string) $elementNode['ID'];
  911. $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['id'] = (string) $elementNode['ID'];
  912. $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['dmdId'] = (isset($elementNode['DMDID']) ? (string) $elementNode['DMDID'] : '');
  913. $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['order'] = (isset($elementNode['ORDER']) ? (string) $elementNode['ORDER'] : '');
  914. $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['label'] = (isset($elementNode['LABEL']) ? (string) $elementNode['LABEL'] : '');
  915. $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['orderlabel'] = (isset($elementNode['ORDERLABEL']) ? (string) $elementNode['ORDERLABEL'] : '');
  916. $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['type'] = (string) $elementNode['TYPE'];
  917. $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['contentIds'] = (isset($elementNode['CONTENTIDS']) ? (string) $elementNode['CONTENTIDS'] : '');
  918. // Get the file representations from fileSec node.
  919. foreach ($elementNode->children('http://www.loc.gov/METS/')->fptr as $fptr) {
  920. // Check if file has valid @USE attribute.
  921. if (!empty($fileUse[(string) $fptr->attributes()->FILEID])) {
  922. $this->physicalStructureInfo[$elements[(int) $elementNode['ORDER']]]['files'][$fileUse[(string) $fptr->attributes()->FILEID]] = (string) $fptr->attributes()->FILEID;
  923. }
  924. }
  925. }
  926. // Sort array by keys (= @ORDER).
  927. if (ksort($elements)) {
  928. // Set total number of pages/tracks.
  929. $this->numPages = count($elements);
  930. // Merge and re-index the array to get nice numeric indexes.
  931. $this->physicalStructure = array_merge($physSeq, $elements);
  932. }
  933. }
  934. $this->physicalStructureLoaded = true;
  935. }
  936. return $this->physicalStructure;
  937. }
  938. /**
  939. * {@inheritDoc}
  940. * @see \Kitodo\Dlf\Common\Document::_getSmLinks()
  941. */
  942. protected function _getSmLinks()
  943. {
  944. if (!$this->smLinksLoaded) {
  945. $smLinks = $this->mets->xpath('./mets:structLink/mets:smLink');
  946. if (!empty($smLinks)) {
  947. foreach ($smLinks as $smLink) {
  948. $this->smLinks['l2p'][(string) $smLink->attributes('http://www.w3.org/1999/xlink')->from][] = (string) $smLink->attributes('http://www.w3.org/1999/xlink')->to;
  949. $this->smLinks['p2l'][(string) $smLink->attributes('http://www.w3.org/1999/xlink')->to][] = (string) $smLink->attributes('http://www.w3.org/1999/xlink')->from;
  950. }
  951. }
  952. $this->smLinksLoaded = true;
  953. }
  954. return $this->smLinks;
  955. }
  956. /**
  957. * {@inheritDoc}
  958. * @see \Kitodo\Dlf\Common\Document::_getThumbnail()
  959. */
  960. protected function _getThumbnail($forceReload = false)
  961. {
  962. if (
  963. !$this->thumbnailLoaded
  964. || $forceReload
  965. ) {
  966. // Retain current PID.
  967. $cPid = ($this->cPid ? $this->cPid : $this->pid);
  968. if (!$cPid) {
  969. Helper::devLog('Invalid PID ' . $cPid . ' for structure definitions', DEVLOG_SEVERITY_ERROR);
  970. $this->thumbnailLoaded = true;
  971. return $this->thumbnail;
  972. }
  973. // Load extension configuration.
  974. $extConf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][self::$extKey]);
  975. if (empty($extConf['fileGrpThumbs'])) {
  976. Helper::devLog('No fileGrp for thumbnails specified', DEVLOG_SEVERITY_WARNING);
  977. $this->thumbnailLoaded = true;
  978. return $this->thumbnail;
  979. }
  980. $strctId = $this->_getToplevelId();
  981. $metadata = $this->getTitledata($cPid);
  982. $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
  983. ->getQueryBuilderForTable('tx_dlf_structures');
  984. // Get structure element to get thumbnail from.
  985. $result = $queryBuilder
  986. ->select('tx_dlf_structures.thumbnail AS thumbnail')
  987. ->from('tx_dlf_structures')
  988. ->where(
  989. $queryBuilder->expr()->eq('tx_dlf_structures.pid', intval($cPid)),
  990. $queryBuilder->expr()->eq('tx_dlf_structures.index_name', $queryBuilder->expr()->literal($metadata['type'][0])),
  991. Helper::whereExpression('tx_dlf_structures')
  992. )
  993. ->setMaxResults(1)
  994. ->execute();
  995. $allResults = $result->fetchAll();
  996. if (count($allResults) == 1) {
  997. $resArray = $allResults[0];
  998. // Get desired thumbnail structure if not the toplevel structure itself.
  999. if (!empty($resArray['thumbnail'])) {
  1000. $strctType = Helper::getIndexNameFromUid($resArray['thumbnail'], 'tx_dlf_structures', $cPid);
  1001. // Check if this document has a structure element of the desired type.
  1002. $strctIds = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@TYPE="' . $strctType . '"]/@ID');
  1003. if (!empty($strctIds)) {
  1004. $strctId = (string) $strctIds[0];
  1005. }
  1006. }
  1007. // Load smLinks.
  1008. $this->_getSmLinks();
  1009. // Get thumbnail location.
  1010. $fileGrpsThumb = GeneralUtility::trimExplode(',', $extConf['fileGrpThumbs']);
  1011. while ($fileGrpThumb = array_shift($fileGrpsThumb)) {
  1012. if (
  1013. $this->_getPhysicalStructure()
  1014. && !empty($this->smLinks['l2p'][$strctId])
  1015. && !empty($this->physicalStructureInfo[$this->smLinks['l2p'][$strctId][0]]['files'][$fileGrpThumb])
  1016. ) {
  1017. $this->thumbnail = $this->getFileLocation($this->physicalStructureInfo[$this->smLinks['l2p'][$strctId][0]]['files'][$fileGrpThumb]);
  1018. break;
  1019. } elseif (!empty($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb])) {
  1020. $this->thumbnail = $this->getFileLocation($this->physicalStructureInfo[$this->physicalStructure[1]]['files'][$fileGrpThumb]);
  1021. break;
  1022. }
  1023. }
  1024. } else {
  1025. Helper::devLog('No structure of type "' . $metadata['type'][0] . '" found in database', DEVLOG_SEVERITY_ERROR);
  1026. }
  1027. $this->thumbnailLoaded = true;
  1028. }
  1029. return $this->thumbnail;
  1030. }
  1031. /**
  1032. * {@inheritDoc}
  1033. * @see \Kitodo\Dlf\Common\Document::_getToplevelId()
  1034. */
  1035. protected function _getToplevelId()
  1036. {
  1037. if (empty($this->toplevelId)) {
  1038. // Get all logical structure nodes with metadata, but without associated METS-Pointers.
  1039. $divs = $this->mets->xpath('./mets:structMap[@TYPE="LOGICAL"]//mets:div[@DMDID and not(./mets:mptr)]');
  1040. if (!empty($divs)) {
  1041. // Load smLinks.
  1042. $this->_getSmLinks();
  1043. foreach ($divs as $div) {
  1044. $id = (string) $div['ID'];
  1045. // Are there physical structure nodes for this logical structure?
  1046. if (array_key_exists($id, $this->smLinks['l2p'])) {
  1047. // Yes. That's what we're looking for.
  1048. $this->toplevelId = $id;
  1049. break;
  1050. } elseif (empty($this->toplevelId)) {
  1051. // No. Remember this anyway, but keep looking for a better one.
  1052. $this->toplevelId = $id;
  1053. }
  1054. }
  1055. }
  1056. }
  1057. return $this->toplevelId;
  1058. }
  1059. /**
  1060. * This magic method is executed prior to any serialization of the object
  1061. * @see __wakeup()
  1062. *
  1063. * @access public
  1064. *
  1065. * @return array Properties to be serialized
  1066. */
  1067. public function __sleep()
  1068. {
  1069. // \SimpleXMLElement objects can't be serialized, thus save the XML as string for serialization
  1070. $this->asXML = $this->xml->asXML();
  1071. return ['uid', 'pid', 'recordId', 'parentId', 'asXML'];
  1072. }
  1073. /**
  1074. * This magic method is used for setting a string value for the object
  1075. *
  1076. * @access public
  1077. *
  1078. * @return string String representing the METS object
  1079. */
  1080. public function __toString()
  1081. {
  1082. $xml = new \DOMDocument('1.0', 'utf-8');
  1083. $xml->appendChild($xml->importNode(dom_import_simplexml($this->mets), true));
  1084. $xml->formatOutput = true;
  1085. return $xml->saveXML();
  1086. }
  1087. /**
  1088. * This magic method is executed after the object is deserialized
  1089. * @see __sleep()
  1090. *
  1091. * @access public
  1092. *
  1093. * @return void
  1094. */
  1095. public function __wakeup()
  1096. {
  1097. // Turn off libxml's error logging.
  1098. $libxmlErrors = libxml_use_internal_errors(true);
  1099. // Reload XML from string.
  1100. $xml = @simplexml_load_string($this->asXML);
  1101. // Reset libxml's error logging.
  1102. libxml_use_internal_errors($libxmlErrors);
  1103. if ($xml !== false) {
  1104. $this->asXML = '';
  1105. $this->xml = $xml;
  1106. // Rebuild the unserializable properties.
  1107. $this->init();
  1108. } else {
  1109. Helper::devLog('Could not load XML after deserialization', DEVLOG_SEVERITY_ERROR);
  1110. }
  1111. }
  1112. }