diff --git a/library/Opus/DocumentFinder/DefaultDocumentFinder.php b/library/Opus/DocumentFinder/DefaultDocumentFinder.php index 73f93d5e..8e0ac0a6 100644 --- a/library/Opus/DocumentFinder/DefaultDocumentFinder.php +++ b/library/Opus/DocumentFinder/DefaultDocumentFinder.php @@ -31,13 +31,15 @@ namespace Opus\DocumentFinder; -use Opus\DocumentFinder; +use Doctrine\DBAL\Connection; +use Opus\Db2\Database; use Opus\DocumentFinderInterface; +use function array_unique; use function is_array; /** - * Wrapper implementing DocumentFinderInterface around old DocumentFinder using Zend_Db. + * Implementing DocumentFinderInterface using Doctrine DBAL. * * Mit diesem Zwischenschritt kann die Application von der DocumentFinder Klasse abgekoppelt werden ohne sofort einen * neuen DocumentFinder entwickeln zu müssen. @@ -46,11 +48,20 @@ */ class DefaultDocumentFinder implements DocumentFinderInterface { - private $finder; + /** @var int */ + private $namedParameterCounter = 0; + + /** @var Connection */ + private $connection; + + /** @var Doctrine\DBAL\Query\QueryBuilder */ + private $select; public function __construct() { - $this->finder = new DocumentFinder(); + $this->connection = Database::getConnection(); + $queryBuilder = $this->connection->createQueryBuilder(); + $this->select = $queryBuilder->select('*')->from('documents', 'd'); } /** @@ -58,7 +69,10 @@ public function __construct() */ public function getIds() { - return $this->finder->ids(); + $this->select->select('d.id'); + $result = $this->select->execute()->fetchFirstColumn(); + + return array_unique($result); } /** @@ -66,7 +80,8 @@ public function getIds() */ public function getCount() { - return $this->finder->count(); + $this->select->select("count(d.id)")->distinct(); + return $this->select->execute()->fetchOne(); } /** @@ -76,21 +91,47 @@ public function getCount() */ public function setOrder($criteria, $ascending = true) { + $order = $ascending ? 'ASC' : 'DESC'; + switch ($criteria) { case self::ORDER_ID: - $this->finder->orderById($ascending); + // Order by id + $this->select->orderBy('d.id', $order); break; case self::ORDER_AUTHOR: - $this->finder->orderByAuthorLastname($ascending); + // Order by author lastname + $this->select + ->leftJoin( + 'd', + 'link_persons_documents', + 'pd', + 'd.id = pd.document_id AND pd.role = "author"' + ) + ->leftJoin('pd', 'persons', 'p', 'pd.person_id = p.id') + ->groupBy('d.id') + ->addGroupBy('p.last_name') + ->orderBy('p.last_name ', $order); break; case self::ORDER_TITLE: - $this->finder->orderByTitleMain($ascending); + // Order by title main + $this->select + ->leftJoin( + 'd', + 'document_title_abstracts', + 't', + 't.document_id = d.id AND t.type = "main"' + ) + ->groupBy('d.id') + ->addGroupBy('t.value') + ->orderBy('t.value', $order); break; case self::ORDER_DOCUMENT_TYPE: - $this->finder->orderByType($ascending); + // Order by type + $this->select->orderBy('d.type ', $order); break; case self::ORDER_SERVER_DATE_PUBLISHED: - $this->finder->orderByServerDatePublished($ascending); + // Order by server date published + $this->select->orderBy('d.server_date_published', $order); break; default: break; @@ -104,7 +145,11 @@ public function setOrder($criteria, $ascending = true) */ public function setDocumentIds($documentIds) { - $this->finder->setIdSubset($documentIds); + $queryParam = $this->createQueryParameterName('setDocumentIdsParam'); + + $this->select->andWhere('d.id IN (:' . $queryParam . ')') + ->setParameter($queryParam, $documentIds, Connection::PARAM_STR_ARRAY); + return $this; } @@ -115,27 +160,37 @@ public function setDocumentIds($documentIds) */ public function setDocumentIdRange($start = null, $end = null) { + $queryParamStart = $this->createQueryParameterName('setDocumentIdRangeStart'); + $queryParamEnd = $this->createQueryParameterName('setDocumentIdRangeEnd'); + if ($start !== null) { - $this->finder->setIdRangeStart($start); + $this->select->andWhere('d.id >= :' . $queryParamStart) + ->setParameter($queryParamStart, $start); } if ($end !== null) { - $this->finder->setIdRangeEnd($end); + $this->select->andWhere('d.id <= :' . $queryParamEnd) + ->setParameter($queryParamEnd, $end); } return $this; } /** - * @param string $serverState + * @param string|string[] $serverState * @return $this */ public function setServerState($serverState) { + $queryParam = $this->createQueryParameterName('setServerStateParam'); + if (is_array($serverState)) { - $this->finder->setServerStateInList($serverState); + $this->select->andWhere('server_state IN (:' . $queryParam . ')', $serverState) + ->setParameter($queryParam, $serverState, Connection::PARAM_STR_ARRAY); } else { - $this->finder->setServerState($serverState); + $this->select->andWhere('server_state = :' . $queryParam) + ->setParameter($queryParam, $serverState); } + return $this; } @@ -145,7 +200,11 @@ public function setServerState($serverState) */ public function setBelongsToBibliography($partOfBibliography = true) { - $this->finder->setBelongsToBibliography($partOfBibliography); + $queryParam = $this->createQueryParameterName('setBelongsToBibliographyParam'); + + $this->select->andWhere('d.belongs_to_bibliography = :' . $queryParam) + ->setParameter($queryParam, $partOfBibliography); + return $this; } @@ -155,7 +214,23 @@ public function setBelongsToBibliography($partOfBibliography = true) */ public function setCollectionId($collectionId) { - $this->finder->setCollectionId($collectionId); + $queryParam = $this->createQueryParameterName('setCollectionIdParam'); + + $queryBuilder = $this->connection->createQueryBuilder(); + + $subSelect = $queryBuilder->select('l.document_id') + ->from('link_documents_collections', 'l') + ->where('l.document_id = d.id') + ->andWhere('l.collection_id IN (:' . $queryParam . ')'); + + $this->select->andWhere("EXISTS (" . $subSelect->getSQL() . ")"); + + if (is_array($collectionId)) { + $this->select->setParameter($queryParam, $collectionId, Connection::PARAM_STR_ARRAY); + } else { + $this->select->setParameter($queryParam, $collectionId); + } + return $this; } @@ -165,7 +240,20 @@ public function setCollectionId($collectionId) */ public function setCollectionRoleId($roleId) { - $this->finder->setCollectionRoleId($roleId); + $queryParam = $this->createQueryParameterName('setCollectionRoleIdParam'); + + $queryBuilder = $this->connection->createQueryBuilder(); + + $subSelect = $queryBuilder->select('l.document_id') + ->from('collections', 'c') + ->from('link_documents_collections', 'l') + ->where('l.document_id = d.id') + ->andWhere('l.collection_id = c.id') + ->andWhere('c.role_id = :' . $queryParam); + + $this->select->andWhere("EXISTS (" . $subSelect->getSQL() . ")") + ->setParameter($queryParam, $roleId); + return $this; } @@ -175,7 +263,18 @@ public function setCollectionRoleId($roleId) */ public function setIdentifierExists($name) { - $this->finder->setIdentifierTypeExists($name); + $queryParam = $this->createQueryParameterName('setIdentifierExistsParam'); + + $queryBuilder = $this->connection->createQueryBuilder(); + + $subSelect = $queryBuilder->select('i.id') + ->from('document_identifiers', 'i') + ->where('i.document_id = d.id') + ->andWhere('type = :' . $queryParam); + + $this->select->andWhere("EXISTS (" . $subSelect->getSQL() . ")") + ->setParameter($queryParam, $name); + return $this; } @@ -186,7 +285,21 @@ public function setIdentifierExists($name) */ public function setIdentifierValue($name, $value) { - $this->finder->setIdentifierTypeValue($name, $value); + $queryParamName = $this->createQueryParameterName('setIdentifierValueName'); + $queryParamValue = $this->createQueryParameterName('setIdentifierValue'); + + $queryBuilder = $this->connection->createQueryBuilder(); + + $subSelect = $queryBuilder->select('id') + ->from('document_identifiers', 'i') + ->where('i.document_id = d.id') + ->andWhere('type = :' . $queryParamName) + ->andWhere('value = :' . $queryParamValue); + + $this->select->andWhere("EXISTS (" . $subSelect->getSQL() . ")") + ->setParameter($queryParamName, $name) + ->setParameter($queryParamValue, $value); + return $this; } @@ -196,10 +309,14 @@ public function setIdentifierValue($name, $value) */ public function setDocumentType($type) { + $queryParam = $this->createQueryParameterName('setDocumentTypeParam'); + if (is_array($type)) { - $this->finder->setTypeInList($type); + $this->select->andWhere('type IN (:' . $queryParam . ')') + ->setParameter($queryParam, $type, Connection::PARAM_STR_ARRAY); } else { - $this->finder->setType($type); + $this->select->andWhere('type = :' . $queryParam) + ->setParameter($queryParam, $type); } return $this; } @@ -210,7 +327,18 @@ public function setDocumentType($type) */ public function setEnrichmentExists($name) { - $this->finder->setEnrichmentKeyExists($name); + $queryParam = $this->createQueryParameterName('setEnrichmentExistsParam'); + + $queryBuilder = $this->connection->createQueryBuilder(); + + $subSelect = $queryBuilder->select('id') + ->from('document_enrichments', 'e') + ->where('e.document_id = d.id') + ->andWhere('key_name = :' . $queryParam); + + $this->select->andWhere('EXISTS (' . $subSelect->getSQL() . ')') + ->setParameter($queryParam, $name); + return $this; } @@ -221,7 +349,21 @@ public function setEnrichmentExists($name) */ public function setEnrichmentValue($key, $value) { - $this->finder->setEnrichmentKeyValue($key, $value); + $queryParamKey = $this->createQueryParameterName('setEnrichmentValueKey'); + $queryParamValue = $this->createQueryParameterName('setEnrichmentValue'); + + $queryBuilder = $this->connection->createQueryBuilder(); + + $subSelect = $queryBuilder->select('d.id') + ->from('document_enrichments', 'e') + ->where('document_id = d.id') + ->andWhere('key_name = :' . $queryParamKey) + ->andWhere('value = :' . $queryParamValue); + + $this->select->andWhere("EXISTS (" . $subSelect->getSQL() . ")") + ->setParameter($queryParamKey, $key) + ->setParameter($queryParamValue, $value); + return $this; } @@ -231,7 +373,11 @@ public function setEnrichmentValue($key, $value) */ public function setServerDatePublishedBefore($date) { - $this->finder->setServerDatePublishedBefore($date); + $queryParam = $this->createQueryParameterName('setServerDatePublishedBeforeParam'); + + $this->select->andWhere('d.server_date_published < :' . $queryParam) + ->setParameter($queryParam, $date); + return $this; } @@ -242,7 +388,14 @@ public function setServerDatePublishedBefore($date) */ public function setServerDatePublishedRange($from, $until) { - $this->finder->setServerDatePublishedRange($from, $until); + $queryParamFrom = $this->createQueryParameterName('setServerDatePublishedRangeFrom'); + $queryParamUntil = $this->createQueryParameterName('setServerDatePublishedRangeUntil'); + + $this->select->andWhere('d.server_date_published >= :' . $queryParamFrom) + ->andWhere('d.server_date_published < :' . $queryParamUntil) + ->setParameter($queryParamFrom, $from) + ->setParameter($queryParamUntil, $until); + return $this; } @@ -252,7 +405,10 @@ public function setServerDatePublishedRange($from, $until) */ public function setServerDateModifiedBefore($date) { - $this->finder->setServerDateModifiedBefore($date); + $queryParam = $this->createQueryParameterName('setServerDateModifiedBeforeParam'); + + $this->select->andWhere('d.server_date_modified < :' . $queryParam) + ->setParameter($queryParam, $date); return $this; } @@ -262,7 +418,11 @@ public function setServerDateModifiedBefore($date) */ public function setServerDateModifiedAfter($date) { - $this->finder->setServerDateModifiedAfter($date); + $queryParam = $this->createQueryParameterName('setServerDateModifiedAfterParam'); + + $this->select->andWhere('d.server_date_modified >= :' . $queryParam) + ->setParameter($queryParam, $date); + return $this; } @@ -272,7 +432,11 @@ public function setServerDateModifiedAfter($date) */ public function setEmbargoDateBefore($date) { - $this->finder->setEmbargoDateBefore($date); + $queryParam = $this->createQueryParameterName('setEmbargoDateBeforeDate'); + + $this->select->andWhere('d.embargo_date < :' . $queryParam) + ->setParameter($queryParam, $date); + return $this; } @@ -282,7 +446,11 @@ public function setEmbargoDateBefore($date) */ public function setNotEmbargoedOn($date) { - $this->finder->setNotEmbargoedOn($date); + $queryParam = $this->createQueryParameterName('setNotEmbargoedOnDate'); + + $this->select->andWhere('d.embargo_date < :' . $queryParam . ' or d.embargo_date IS NULL') + ->setParameter($queryParam, $date); + return $this; } @@ -291,7 +459,7 @@ public function setNotEmbargoedOn($date) */ public function setNotModifiedAfterEmbargoDate() { - $this->finder->setNotModifiedAfterEmbargoDate(); + $this->select->andWhere('d.server_date_modified < d.embargo_date'); return $this; } @@ -300,7 +468,15 @@ public function setNotModifiedAfterEmbargoDate() */ public function setHasFilesVisibleInOai() { - $this->finder->setFilesVisibleInOai(); + $queryBuilder = $this->connection->createQueryBuilder(); + + $subSelect = $queryBuilder->select('f.document_id') + ->from('document_files', 'f') + ->where('f.document_id = d.id') + ->andWhere('f.visible_in_oai=1'); + + $this->select->andWhere('d.id IN (' . $subSelect->getSQL() . ')'); + return $this; } @@ -309,7 +485,14 @@ public function setHasFilesVisibleInOai() */ public function setNotInXmlCache() { - $this->finder->setNotInXmlCache(); + $queryBuilder = $this->connection->createQueryBuilder(); + + // get all IDs in XML cache + $subSelect = $queryBuilder->select('dxc.document_id') + ->from('document_xml_cache', 'dxc'); + + $this->select->andWhere(' NOT d.id IN (' . $subSelect->getSQL() . ')'); + return $this; } @@ -320,9 +503,12 @@ public function setNotInXmlCache() public function getDocumentTypes($includeCount = false) { if ($includeCount) { - return $this->finder->groupedTypesPlusCount(); + $this->select->select('type AS type', 'count(DISTINCT id) AS count') + ->groupBy('type'); + return $this->select->execute()->fetchAllAssociative(); } else { - return $this->finder->groupedTypes(); + $this->select->select("type")->distinct(); + return $this->select->execute()->fetchFirstColumn(); } } @@ -331,6 +517,18 @@ public function getDocumentTypes($includeCount = false) */ public function getYearsPublished() { - return $this->finder->groupedServerYearPublished(); + $this->select->select("substr(server_date_published, 1, 4)")->distinct(); + return $this->select->execute()->fetchFirstColumn(); + } + + /** + * Creates a unique named query parameter. + * + * @param string $prefix + * @return string + */ + protected function createQueryParameterName($prefix) + { + return $prefix . $this->namedParameterCounter++; } } diff --git a/tests/Opus/DocumentFinder/DefaultDocumentFinderTest.php b/tests/Opus/DocumentFinder/DefaultDocumentFinderTest.php new file mode 100644 index 00000000..8b91790f --- /dev/null +++ b/tests/Opus/DocumentFinder/DefaultDocumentFinderTest.php @@ -0,0 +1,1051 @@ +clearTables(false, [ + 'documents', + 'persons', + 'link_persons_documents', + 'document_title_abstracts', + 'document_identifiers', + 'document_enrichments', + 'enrichmentkeys', + 'collections_roles', + 'collections', + 'link_documents_collections', + 'document_xml_cache', + ]); + } + + /** + * @return DefaultDocumentFinder + */ + private function createDocumentFinder() + { + return new DefaultDocumentFinder(); + } + + private function prepareDocuments() + { + $publishedDoc1 = Document::new(); + $publishedDoc1->setType("preprint") + ->setServerState('published') + ->setBelongsToBibliography(true) + ->store(); + + $title = $publishedDoc1->addTitleMain(); + $title->setValue('Title 1'); + $title->setLanguage('deu'); + + $title = $publishedDoc1->addTitleMain(); + $title->setValue('Title 2'); + $title->setLanguage('eng'); + + $publishedDoc1->store(); + + $publishedDoc2 = Document::new(); + $publishedDoc2->setType("article") + ->setServerState('published') + ->store(); + + $title = $publishedDoc2->addTitleMain(); + $title->setValue('A Title 1'); + $title->setLanguage('deu'); + + $publishedDoc2->store(); + + $unpublishedDoc1 = Document::new(); + $unpublishedDoc1->setType("doctoral_thesis") + ->setServerState('unpublished') + ->store(); + + $person = new Person(); + $person->setLastName('B'); + $unpublishedDoc1->addPersonAuthor($person); + + $unpublishedDoc1->store(); + + $unpublishedDoc2 = Document::new(); + $unpublishedDoc2->setType("preprint") + ->setServerState('unpublished') + ->store(); + + $person = new Person(); + $person->setLastName('C'); + $unpublishedDoc2->addPersonAuthor($person); + + $person = new Person(); + $person->setLastName('A'); + $unpublishedDoc2->addPersonAuthor($person); + + $unpublishedDoc2->store(); + + $deletedDoc1 = Document::new(); + $deletedDoc1->setType("article") + ->setServerState('deleted') + ->store(); + + $deletedDoc2 = Document::new(); + $deletedDoc2->setType("doctoral_thesis") + ->setServerState('deleted') + ->store(); + } + + /** + * @param int[] $ids + * @param string $state + * @throws ModelException + */ + private function checkServerState($ids, $state) + { + foreach ($ids as $id) { + $doc = new Document($id); + $this->assertEquals($state, $doc->getServerState()); + } + } + + /** + * Basic functionality + */ + public function testCountOnEmptyDb() + { + $finder = $this->createDocumentFinder(); + $this->assertEquals(0, $finder->getCount()); + } + + /** + * Basic functionality + */ + public function testIdsOnEmptyDb() + { + $finder = $this->createDocumentFinder(); + $this->assertEquals([], $finder->getIds()); + } + + /** + * Basic functionality + */ + public function testAllEntriesNoConstraints() + { + $this->prepareDocuments(); + + // published + $finder = $this->createDocumentFinder(); + $this->assertEquals(6, $finder->getCount()); + $this->assertEquals(6, count($finder->getIds())); + } + + /** + * Basic functionality + */ + public function testAllConstraints() + { + $this->markTestSkipped('TODO DOCTRINE DBAL Issue #129: Does this test even make sense?'); + + // published + $finder = $this->createDocumentFinder(); + $finder->setEnrichmentKeyExists('foobar') + ->setEnrichmentKeyValue('foo', 'bar') + ->setIdRange(1, 2) + ->setIdRangeStart(2) + ->setIdRangeEnd(1) + ->setIdentifierTypeValue('opus-3', 23) + ->setServerState('published') + ->setServerStateInList(['published']) + ->setType('fooprintbar') + ->setTypeInList(['fooprintbar']) + ->setServerDateModifiedRange('2010-01-01', '2000-01-01') + ->setServerDatePublishedRange('1999-12-31', '1900-01-01') + ->setIdSubset(null) + ->setIdSubset([]) + ->setIdSubset([1]) + ->setIdSubset([-1]) + ->setIdSubset([1, 2]) + ->setIdSubset(['foo']); + + $this->assertEquals(0, $finder->getCount()); + $this->assertEquals(0, count($finder->getIds())); + } + + public function testIdsByState() + { + $this->prepareDocuments(); + + // published + $finder = $this->createDocumentFinder(); + $finder->setServerState('published'); + $this->assertEquals(2, $finder->getCount()); + + $publishedDocs = $finder->getIds(); + $this->assertEquals(2, count($publishedDocs)); + $this->checkServerState($publishedDocs, 'published'); + + // unpublished + $finder = $this->createDocumentFinder(); + $finder->setServerState('unpublished'); + $this->assertEquals(2, count($finder->getIds())); + + $unpublishedDocs = $finder->getIds(); + $this->assertEquals(2, count($unpublishedDocs)); + $this->checkServerState($unpublishedDocs, 'unpublished'); + + // deleted + $finder = $this->createDocumentFinder(); + $finder->setServerState('deleted'); + $this->assertEquals(2, count($finder->getIds())); + + $deletedDocs = $finder->getIds(); + $this->assertEquals(2, count($deletedDocs)); + $this->checkServerState($deletedDocs, 'deleted'); + } + + public function testSubsetOfDocumentIds() + { + for ($i = 0; $i < 10; $i++) { + $document = Document::new(); + $document->setType('book'); + $title = $document->addTitleMain(); + $title->setValue('Title' . $i); + $title->setLanguage('de'); + $document->store(); + } + + $finder = $this->createDocumentFinder(); + $finder->setDocumentIds([1, 3, 5, 7, 9]); + $this->assertEquals([1, 3, 5, 7, 9], $finder->getIds()); + + $finder = $this->createDocumentFinder(); + $finder->setDocumentIdRange(3, 7); + $this->assertEquals([3, 4, 5, 6, 7], $finder->getIds()); + } + + public function testIdentifierExists() + { + $document = Document::new(); + $isbn = $document->addIdentifierIsbn(); + $isbn->setValue('1234-1234-1234'); + $document->store(); + + $document = Document::new(); + $issn = $document->addIdentifierIssn(); + $issn->setValue('2345-2345-2345'); + $doi = $document->addIdentifierDoi(); + $doi->setValue('3576934857'); + $document->store(); + + $document = Document::new(); + $doi = $document->addIdentifierDoi(); + $doi->setValue('1234567890'); + $document->store(); + + $document = Document::new(); + $issn = $document->addIdentifierIssn(); + $issn->setValue('5678-5678-5678'); + $document->store(); + + $finder = $this->createDocumentFinder(); + $finder->setIdentifierExists('issn'); + $this->assertEquals(2, $finder->getCount()); + + $finder = $this->createDocumentFinder(); + $finder->setIdentifierExists('issn'); + $finder->setIdentifierExists('doi'); + $this->assertEquals(1, $finder->getCount()); + + $finder = $this->createDocumentFinder(); + $finder->setIdentifierExists('isbn'); + $finder->setIdentifierExists('doi'); + $this->assertEquals(0, $finder->getCount()); + } + + public function testIdentifierValue() + { + $document = Document::new(); + $document->setType("article"); + + $title = $document->addTitleMain(); + $title->setValue('Title'); + $title->setLanguage('de'); + + $isbn = $document->addIdentifierIsbn(); + $isbn->setValue('111-111-111'); + + $issn1 = $document->addIdentifierIssn(); + $issn1->setValue('1000-1000-1000'); + + $issn2 = $document->addIdentifierIssn(); + $issn2->setValue('2000-2000-2000'); + + $document->store(); + + $finder = $this->createDocumentFinder(); + $finder->setIdentifierValue('isbn', '111-111-111'); + $this->assertEquals(1, count($finder->getIds())); + + $finder = $this->createDocumentFinder(); + $finder->setDocumentType('article'); + $finder->setIdentifierValue('issn', '123-123-123'); + $finder->setIdentifierValue('issn', '2000-2000-2000'); + $this->assertEquals(0, count($finder->getIds())); + } + + public function testEnrichments() + { + $enrichment1 = new Enrichment(); + $enrichment1->setKeyName('enrichmentKey1'); + $enrichment1->setValue('enrichment-value1'); + + $enrichment2 = new Enrichment(); + $enrichment2->setKeyName('enrichmentKey2'); + $enrichment2->setValue('enrichment-value2'); + + $doc1 = Document::new(); + $doc1->addEnrichment($enrichment1); + $doc1->addEnrichment($enrichment2); + $doc1Id = $doc1->store(); + + $enrichment3 = new Enrichment(); + $enrichment3->setKeyName('enrichmentKey1'); + $enrichment3->setValue('enrichment-value1'); + + $doc2 = Document::new(); + $doc2->addEnrichment($enrichment3); + $doc2->store(); + + $finder = $this->createDocumentFinder(); + $finder->setEnrichmentExists('enrichmentKey1'); + $this->assertEquals(2, $finder->getCount()); + $finder->setEnrichmentExists('enrichmentKey2'); + $this->assertEquals(1, $finder->getCount()); + + $finder = $this->createDocumentFinder(); + $finder->setEnrichmentValue('enrichmentKey2', 'enrichment-value2'); + $this->assertEquals([$doc1Id], $finder->getIds()); + } + + public function testServerDatePublished() + { + $doc = Document::new(); + $doc->setServerDatePublished('2022-01-01'); + $doc->store(); + + $doc = Document::new(); + $doc->setServerDatePublished('2021-10-20'); + $doc->store(); + + $doc = Document::new(); + $doc->setServerDatePublished('2021-08-10'); + $doc->store(); + + $doc = Document::new(); + $doc->setServerDatePublished('2021-07-08'); + $doc->store(); + + $doc = Document::new(); + $doc->setServerDatePublished('2021-01-01'); + $doc->store(); + + $finder = $this->createDocumentFinder(); + $finder->setServerDatePublishedBefore('2021-08-30'); + $this->assertEquals(3, $finder->getCount()); + + $finder = $this->createDocumentFinder(); + $finder->setServerDatePublishedRange('2021-07-01', '2021-10-30'); + $this->assertEquals(3, $finder->getCount()); + + $finder = $this->createDocumentFinder(); + $this->assertEquals(['2022', '2021'], $finder->getYearsPublished()); + } + + public function testServerDateModified() + { + $doc = Document::new(); + $id = $doc->store(); + Document::setServerDateModifiedByIds(new Date('2022-01-01'), [$id]); + + $doc = Document::new(); + $id = $doc->store(); + Document::setServerDateModifiedByIds(new Date('2021-10-20'), [$id]); + + $doc = Document::new(); + $id = $doc->store(); + Document::setServerDateModifiedByIds(new Date('2021-08-10'), [$id]); + + $doc = Document::new(); + $id = $doc->store(); + Document::setServerDateModifiedByIds(new Date('2021-07-08'), [$id]); + + $doc = Document::new(); + $id = $doc->store(); + Document::setServerDateModifiedByIds(new Date('2021-01-01'), [$id]); + + $finder = $this->createDocumentFinder(); + $finder->setServerDateModifiedBefore('2021-08-30'); + $this->assertEquals(3, $finder->getCount()); + + $finder = $this->createDocumentFinder(); + $finder->setServerDateModifiedAfter('2021-08-01'); + $this->assertEquals(3, $finder->getCount()); + } + + public function testBelongsToBibliography() + { + $this->prepareDocuments(); + + $finder = $this->createDocumentFinder(); + $finder->setServerState('published'); + $finder->setBelongsToBibliography(true); + $this->assertEquals(1, $finder->getCount()); + } + + /** + * Extended functionality: Grouping + */ + public function testGroupedDocumentTypes() + { + $this->prepareDocuments(); + + // all + $finder = $this->createDocumentFinder(); + $types = $finder->getDocumentTypes(); + $this->assertEquals(3, count($types)); + + // published + $finder = $this->createDocumentFinder(); + $finder->setServerState('published'); + $types = $finder->getDocumentTypes(); + $this->assertEquals(2, count($types)); + + // unpublished + $finder = $this->createDocumentFinder(); + $finder->setServerState('unpublished'); + $types = $finder->getDocumentTypes(); + $this->assertEquals(2, count($types)); + + // deleted + $finder = $this->createDocumentFinder(); + $finder->setServerState('deleted'); + $types = $finder->getDocumentTypes(); + $this->assertEquals(2, count($types)); + } + + /** + * Extended functionality: Sorting + */ + public function testSortByAuthorLastName() + { + $this->prepareDocuments(); + + // By Author + $finder = $this->createDocumentFinder(); + + $finder->setOrder(DocumentFinderInterface::ORDER_AUTHOR); + + $docs = $finder->getIds(); + + $this->assertEquals(6, count($docs)); + + $this->assertEquals(4, $docs[4]); + $this->assertEquals(3, $docs[5]); + } + + public function testCollections() + { + $collectionRoles = []; + $collections = []; + + for ($i = 0; $i < 4; $i++) { + $collectionRole = new CollectionRole(); + $collectionRole->setName("role-name-" . rand()); + $collectionRole->setOaiName("role-oainame-" . rand()); + $collectionRole->setVisible(1); + $collectionRole->setVisibleBrowsingStart(1); + $collectionRole->store(); + + $collectionRoles[$i] = $collectionRole; + + $collection = $collectionRole->addRootCollection(); + $collection->setTheme('dummy'); + $collection->store(); + + $collections[] = $collection; + } + + $doc1 = Document::new(); + $doc1->addCollection($collections[1]); + $doc1->store(); + + $doc2 = Document::new(); + $doc2->addCollection($collections[0]); + $doc2->store(); + + $doc3 = Document::new(); + $doc3->setType('article'); + $doc3->addCollection($collections[2]); + $doc3->addCollection($collections[1]); + $doc3->store(); + + $doc4 = Document::new(); + $doc4->setType('article'); + $doc4->addCollection($collections[2]); + $doc4->store(); + + $finder = $this->createDocumentFinder(); + $finder->setCollectionId($collections[2]->getId()); + $this->assertEquals(2, $finder->getCount()); + $finder->setCollectionId($collections[1]->getId()); + $this->assertEquals(1, $finder->getCount()); + + $finder = $this->createDocumentFinder(); + $finder->setDocumentType('article'); + $finder->setCollectionId($collections[1]->getId()); + $this->assertEquals(1, $finder->getCount()); + + $finder = $this->createDocumentFinder(); + $finder->setCollectionId($collectionRoles[1]->getId()); + $this->assertEquals(2, $finder->getCount()); + $finder->setCollectionId($collectionRoles[2]->getId()); + $this->assertEquals(1, $finder->getCount()); + + $finder = $this->createDocumentFinder(); + $finder->setDocumentType('article'); + $finder->setCollectionId($collectionRoles[1]->getId()); + $this->assertEquals(1, $finder->getCount()); + } + + public function testNotInXmlCache() + { + $documentIds = []; + + for ($i = 0; $i < 4; $i++) { + $doc = Document::new(); + $documentIds[$i] = $doc->store(); + } + + $xmlCache = new DocumentXmlCache(); + $xmlCache->delete('document_id = ' . $documentIds[2]); + + $finder = $this->createDocumentFinder(); + $finder->setNotInXmlCache(); + + $this->assertEquals(1, $finder->getCount()); + } + + public function testSortById() + { + $this->prepareDocuments(); + + // By Id + $finder = $this->createDocumentFinder(); + + $finder->setOrder(DocumentFinderInterface::ORDER_ID); + + $docs = $finder->getIds(); + + $this->assertEquals(6, count($docs)); + + $lastDoc = $docs[0]; + + foreach ($docs as $docId) { + if ($lastDoc > $docId) { + $this->fail('documents are not sorted by id'); + } + } + } + + /** + * @throws ModelException + * @throws SecurityException + * + * TODO the testdata for this text is not meaningfull + */ + public function testSortByServerDatePublished() + { + $this->prepareDocuments(); + + // By ServerDatePublished + $finder = $this->createDocumentFinder(); + + $finder->setOrder(DocumentFinderInterface::ORDER_SERVER_DATE_PUBLISHED); + + $docs = $finder->getIds(); + + $this->assertEquals(6, count($docs)); + + $lastDate = null; + + foreach ($docs as $docId) { + $doc = new Document($docId); + if ($lastDate === null) { + $lastDate = $doc->getServerDatePublished(); + } + + if ($lastDate !== null && $lastDate->compare($doc->getServerDatePublished()) === 1) { + $this->fail('documents are not sorted properly'); + } + } + } + + public function testSortByTitleMain() + { + $this->prepareDocuments(); + + // By TitleMain + $finder = $this->createDocumentFinder(); + + $finder->setOrder(DocumentFinderInterface::ORDER_TITLE); + + $docs = $finder->getIds(); + + $this->assertEquals(6, count($docs)); + + // documents without title come first (0-3) + $this->assertEquals(2, $docs[4]); + $this->assertEquals(1, $docs[5]); + } + + public function testSortByType() + { + $this->prepareDocuments(); + + // By DocumentType + $finder = $this->createDocumentFinder(); + + $finder->setOrder(DocumentFinderInterface::ORDER_DOCUMENT_TYPE); + + $docs = $finder->getIds(); + + $this->assertEquals(6, count($docs)); + + $expectedOrder = [2, 5, 3, 6, 1, 4]; + + foreach ($docs as $index => $docId) { + if ((int) $docId !== $expectedOrder[$index]) { + $this->fail('documents are not in expected order'); + } + } + } + + /** + * test for added functionality setServerDateCreated[Before|After]() + */ + public function testFindByDateCreated() + { + $this->markTestSkipped( + 'TODO DOCTRINE DBAL Issue #129: Function setServerDateCreatedAfter() and setServerDateCreatedBefore()' + . ' are no part of the DocumentFinderInterface' + ); + + $this->prepareDocuments(); + $date = new Date(); + $date->setNow(); + $date->setDay(date('d') - 1); + $date->setHour(date('H') - 1); + + $finder = $this->createDocumentFinder(); + $this->assertEquals(6, $finder->getCount()); + $finder->setServerDateCreatedAfter(date("Y-m-d", time() + (60 * 60 * 24))); + $this->assertEquals(0, $finder->getCount()); + $finder = $this->createDocumentFinder(); + $finder->setServerDateCreatedAfter(date("Y-m-d", time() - (60 * 60 * 24))); + $this->assertEquals(6, $finder->getCount()); + $finder = $this->createDocumentFinder(); + $finder->setServerDateCreatedBefore(date("Y-m-d", time() - (60 * 60 * 24))); + $this->assertEquals(0, $finder->getCount()); + $finder = $this->createDocumentFinder(); + $finder->setServerDateCreatedBefore(date("Y-m-d", time() + (60 * 60 * 24))); + $this->assertEquals(6, $finder->getCount()); + } + + public function testSetDependentModel() + { + $this->markTestSkipped('TODO DOCTRINE DBAL Issue #129: Function setDependentModel() is no part of the DocumentFinderInterface'); + + $docIds = []; + $doc1 = Document::new(); + $docIds[] = $doc1->setType("article") + ->setServerState('published') + ->store(); + + $doc2 = Document::new(); + $docIds[] = $doc2->setType("article") + ->setServerState('unpublished') + ->store(); + + $doc3 = Document::new(); + $docIds[] = $doc3->setType("preprint") + ->setServerState('unpublished') + ->store(); + + // test dependent model + $title = $doc3->addTitleMain(); + $title->setValue('Ein deutscher Titel'); + $title->setLanguage('deu'); + $titleId = $title->store(); + + $title = new Title($titleId); + $docfinder = $this->createDocumentFinder(); + $resultDocIds = $docfinder->setDependentModel($title)->getIds(); + $this->assertEquals(1, count($resultDocIds), 'Excpected 1 ID in result'); + $this->assertTrue(in_array($doc3->getId(), $resultDocIds), 'Expected Document-ID in result set'); + $this->assertFalse(in_array($doc1->getId(), $resultDocIds), 'Expected Document-ID not in result set'); + $this->assertFalse(in_array($doc2->getId(), $resultDocIds), 'Expected Document-ID not in result set'); + + // test linked model + //person + $author = new Person(); + $author->setFirstName('Karl'); + $author->setLastName('Tester'); + $author->setDateOfBirth('1857-11-26'); + $author->setPlaceOfBirth('Genf'); + + $doc2->addPersonAuthor($author); + $doc2->store(); + + $docfinder = $this->createDocumentFinder(); + $resultDocIds = $docfinder->setDependentModel($author)->getIds(); + $this->assertEquals(1, count($resultDocIds), 'Excpected 1 ID in result'); + $this->assertTrue(in_array($doc2->getId(), $resultDocIds), 'Expected Document-ID in result set'); + $this->assertFalse(in_array($doc1->getId(), $resultDocIds), 'Expected Document-ID not in result set'); + $this->assertFalse(in_array($doc3->getId(), $resultDocIds), 'Expected Document-ID not in result set'); + + // licence + $licence = new Licence(); + $licence->setNameLong('LongNameLicence'); + $licence->setLinkLicence('http://licence.link'); + $licenceId = $licence->store(); + $doc1->addLicence($licence); + $doc1->store(); + + $licence = new Licence($licenceId); + $docfinder = $this->createDocumentFinder(); + $resultDocIds = $docfinder->setDependentModel($licence)->getIds(); + + $this->assertEquals(1, count($resultDocIds), 'Excpected 1 ID in result'); + $this->assertTrue(in_array($doc1->getId(), $resultDocIds), 'Expected Document-ID in result set'); + $this->assertFalse(in_array($doc2->getId(), $resultDocIds), 'Expected Document-ID not in result set'); + $this->assertFalse(in_array($doc3->getId(), $resultDocIds), 'Expected Document-ID not in result set'); + + $doc2->addLicence($licence); + $doc2->store(); + + $resultDocIds = $docfinder->getIds(); + + $this->assertEquals(2, count($resultDocIds), 'Excpected 2 IDs in result'); + $this->assertTrue(in_array($doc1->getId(), $resultDocIds), 'Expected Document-ID in result set'); + $this->assertTrue(in_array($doc2->getId(), $resultDocIds), 'Expected Document-ID in result set'); + $this->assertFalse(in_array($doc3->getId(), $resultDocIds), 'Expected Document-ID not in result set'); + + // collections (are implemented differently) + $collectionRole = new CollectionRole(); + $collectionRole->setName("role-name-" . rand()); + $collectionRole->setOaiName("role-oainame-" . rand()); + $collectionRole->setVisible(1); + $collectionRole->setVisibleBrowsingStart(1); + $collectionRoleId = $collectionRole->store(); + + $collection = $collectionRole->addRootCollection(); + $collection->setTheme('dummy'); + $collectionId = $collection->store(); + + $doc1->addCollection($collection); + $doc1->store(); + $doc3->addCollection($collection); + $doc3->store(); + + $collection = new Collection($collectionId); + $docfinder = $this->createDocumentFinder(); + $resultDocIds = $docfinder->setDependentModel($collection)->getIds(); + + $this->assertEquals(2, count($resultDocIds), 'Excpected 2 IDs in result'); + $this->assertTrue(in_array($doc1->getId(), $resultDocIds), 'Expected Document-ID in result set'); + $this->assertFalse(in_array($doc2->getId(), $resultDocIds), 'Expected Document-ID not in result set'); + $this->assertTrue(in_array($doc3->getId(), $resultDocIds), 'Expected Document-ID in result set'); + } + + public function testSetHasFilesVisibleInOai() + { + $visibleFileDoc = Document::new(); + $visibleFile = new File(); + + $visibleFile->setPathName('visible_file.txt'); + $visibleFile->setVisibleInOai(true); + + $visibleFileDoc->addFile($visibleFile); + + $invisibleFileDoc = Document::new(); + $invisibleFile = new File(); + + $invisibleFile->setPathName('invisible_file.txt'); + $invisibleFile->setVisibleInOai(false); + + $invisibleFileDoc->addFile($invisibleFile); + + $visibleFileDocId = $visibleFileDoc->store(); + $invisibleFileDocId = $invisibleFileDoc->store(); + + $mixedFileDoc = Document::new(); + $visibleFile = new File(); + + $visibleFile->setPathName('another_visible_file.txt'); + $visibleFile->setVisibleInOai(true); + + $invisibleFile = new File(); + + $invisibleFile->setPathName('another_invisible_file.txt'); + $invisibleFile->setVisibleInOai(false); + + $mixedFileDoc->addFile($visibleFile); + $mixedFileDoc->addFile($invisibleFile); + + $mixedFileDocId = $mixedFileDoc->store(); + + $docfinder = $this->createDocumentFinder(); + $docfinder->setHasFilesVisibleInOai(); + $foundIds = $docfinder->getIds(); + + $this->assertTrue(in_array($visibleFileDocId, $foundIds), 'Expected id of Document with visible file in OAI'); + $this->assertTrue(in_array($mixedFileDocId, $foundIds), 'Expected id of Document with visible and invisible file in OAI'); + $this->assertFalse(in_array($invisibleFileDocId, $foundIds), 'Expected no id of Document with invisible file in OAI'); + } + + public function testSetEmbargoDateBefore() + { + $doc = Document::new(); + $doc->setEmbargoDate('2016-10-16'); + $doc1Id = $doc->store(); + + $doc = Document::new(); + $doc->setEmbargoDate('2016-10-14'); + $doc2Id = $doc->store(); + + $docfinder = $this->createDocumentFinder(); + $docfinder->setEmbargoDateBefore('2016-10-15'); + $foundIds = $docfinder->getIds(); + + $this->assertCount(1, $foundIds); + $this->assertContains($doc2Id, $foundIds); + $this->assertNotContains($doc1Id, $foundIds); + } + + public function testSetEmbargoDateAfter() + { + $this->markTestSkipped('TODO DOCTRINE DBAL Issue #129: Function setEmbargoDateAfter() is no part of the DocumentFinderInterface'); + + $doc = Document::new(); + $doc->setEmbargoDate('2016-10-16'); + $doc1Id = $doc->store(); + + $doc = Document::new(); + $doc->setEmbargoDate('2016-10-14'); + $doc2Id = $doc->store(); + + $doc = Document::new(); + $doc->setEmbargoDate('2016-10-15'); + $doc3Id = $doc->store(); + + $docfinder = $this->createDocumentFinder(); + $docfinder->setEmbargoDateAfter('2016-10-15'); + $foundIds = $docfinder->getIds(); + + $this->assertCount(2, $foundIds); + $this->assertContains($doc1Id, $foundIds); + $this->assertContains($doc3Id, $foundIds); + $this->assertNotContains($doc2Id, $foundIds); + } + + public function testSetEmbargoDateRange() + { + $this->markTestSkipped('TODO DOCTRINE DBAL Issue #129: Function setEmbargoDateRange() is no part of the DocumentFinderInterface'); + + $doc = Document::new(); + $doc->setEmbargoDate('2016-10-16'); // not in range + $doc1Id = $doc->store(); + + $doc = Document::new(); + $doc->setEmbargoDate('2016-10-13'); // not in range + $doc2Id = $doc->store(); + + $doc = Document::new(); + $doc->setEmbargoDate('2016-10-14'); // in range + $doc3Id = $doc->store(); + + $docfinder = $this->createDocumentFinder(); + $docfinder->setEmbargoDateRange('2016-10-14', '2016-10-16'); + $foundIds = $docfinder->getIds(); + + $this->assertCount(1, $foundIds); + $this->assertContains($doc3Id, $foundIds); + $this->assertNotContains($doc1Id, $foundIds); + $this->assertNotContains($doc2Id, $foundIds); + } + + /** + * Tests from a perspective of two days in the future to avoid the need to manipulate ServerDateModified. + */ + public function testFindDocumentsWithExpiredEmbargoDateForUpdatingServerDateModified() + { + $tomorrow = date('Y-m-d', time() + (60 * 60 * 24)); + $dayaftertomorrow = date('Y-m-d', time() + (2 * 60 * 60 * 24)); + $today = date('Y-m-d', time()); + $yesterday = date('Y-m-d', time() - (60 * 60 * 24)); + + $doc = Document::new(); + $doc->setEmbargoDate($dayaftertomorrow); + $notExpiredId = $doc->store(); // not in result - not yet expired embargo + + $doc = Document::new(); + $doc->setEmbargoDate($yesterday); + $expiredUpdatedId = $doc->store(); // not in result - expired and saved after expiration + + $doc = Document::new(); + $doc->setEmbargoDate($tomorrow); + $expiredNotUpdatedId = $doc->store(); // in result - expired and saved before expiration + + $docfinder = $this->createDocumentFinder(); + $docfinder->setEmbargoDateBefore($dayaftertomorrow); + $docfinder->setNotModifiedAfterEmbargoDate(); + $foundIds = $docfinder->getIds(); + + $this->assertContains($expiredNotUpdatedId, $foundIds); + $this->assertNotContains($expiredUpdatedId, $foundIds); + $this->assertNotContains($notExpiredId, $foundIds); + } + + public function testSetEmbargoDateBeforeWithTime() + { + $now = new Date(); + $now->setNow(); + + $past = new Date(); + $past->setDateTime(new DateTime(date('Y-m-d H:i:s', strtotime('-1 hour')))); + + $future = new Date(); + $future->setDateTime(new DateTime(date('Y-m-d H:i:s', strtotime('+1 hour')))); + + $doc = Document::new(); + $doc->setEmbargoDate($past); + $pastId = $doc->store(); + + $doc = Document::new(); + $doc->setEmbargoDate($now); + $nowId = $doc->store(); + + $doc = Document::new(); + $doc->setEmbargoDate($future); + $futureId = $doc->store(); + + $docfinder = $this->createDocumentFinder(); + $docfinder->setEmbargoDateBefore($now); + $foundIds = $docfinder->getIds(); + + $this->assertContains($pastId, $foundIds); + $this->assertNotContains($nowId, $foundIds); + $this->assertNotContains($futureId, $foundIds); + } + + public function testFindDocumentsForXMetaDissPlus() + { + $today = date('Y-m-d', time()); + + $doc = Document::new(); + $doc->setServerState('published'); + $doc->setType('article'); + $publishedId = $doc->store(); + + $doc = Document::new(); + $doc->setServerState('published'); + $doc->setType('periodical'); + $periodicalId = $doc->store(); + + $doc = Document::new(); + $doc->setServerState('published'); + $doc->setType('article'); + $doc->setEmbargoDate($today); // today still in embargo until tomorrow + $embargoedId = $doc->store(); + + $doc = Document::new(); + $doc->setServerState('unpublished'); + $unpublishedId = $doc->store(); + + $docfinder = $this->createDocumentFinder(); + + $docfinder->setServerState('published'); + $docfinder->setDocumentType('article'); + $docfinder->setNotEmbargoedOn($today); + + $foundIds = $docfinder->getIds(); + + $this->assertCount(1, $foundIds); + $this->assertContains($publishedId, $foundIds); + } +}