diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 25d0c41..ef59d25 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -9,7 +9,7 @@ on: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: matrix: @@ -18,7 +18,7 @@ jobs: name: PHP ${{ matrix.php-versions }} Test steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - name: Setup PHP ${{ matrix.php-versions }} uses: shivammathur/setup-php@v2 diff --git a/composer.json b/composer.json index 0fcd4bc..f3bd7b1 100644 --- a/composer.json +++ b/composer.json @@ -13,6 +13,7 @@ "ext-dom": "*", "ext-fileinfo": "*", "ext-libxml": "*", + "ext-mbstring": "*", "ext-zip": "*", "opus4-repo/opus4-common": "dev-master as 4.8.1", "opus4-repo/opus4-app-common": "dev-main", diff --git a/src/ArrayImport.php b/src/ArrayImport.php index 2b8c689..d060e41 100644 --- a/src/ArrayImport.php +++ b/src/ArrayImport.php @@ -33,6 +33,13 @@ use Opus\Common\Document; +/** + * Imports documents from array. + * + * TODO What is the use case, besides an easy way to test import mechanisms. + * TODO Interface? + * TODO support multiple documents? + */ class ArrayImport { /** diff --git a/src/ImportRuleConditionInterface.php b/src/ImportRuleConditionInterface.php new file mode 100644 index 0000000..b0a9206 --- /dev/null +++ b/src/ImportRuleConditionInterface.php @@ -0,0 +1,43 @@ +getConfig(); + + if ( + ! isset($config->sword->enableImportRules) || + ! filter_var($config->sword->enableImportRules, FILTER_VALIDATE_BOOLEAN) + ) { + // TODO does this belong here? There should not be anything SWORD specific here! + return; // don't load any rules + } + + $rulesConfig = null; + + if (isset($config->import->rulesConfigFile)) { + $rulesConfigFile = $config->import->rulesConfigFile; + if (is_readable($rulesConfigFile)) { + $rulesConfig = new Zend_Config_Ini($rulesConfigFile); + $rulesConfig = $rulesConfig->toArray(); + } + } + + // Get rules from main configuration as fallback + if ($rulesConfig === null && isset($config->import->rules)) { + $rulesConfig = $config->import->rules->toArray(); + } + + if (is_array($rulesConfig)) { + foreach ($rulesConfig as $name => $options) { + $type = $options['type']; + + $rule = $this->createRule($type, $options); + + if ($rule !== null) { + $this->rules[] = $rule; + } + } + } + } + + /** + * @return ImportRuleInterface[] + */ + public function getRules() + { + return $this->rules; + } + + /** + * @param string $type + * @param array $options + * @return ImportRuleInterface|null + */ + public function createRule($type, $options) + { + if (class_exists($type)) { + $ruleClass = $type; + } else { + $ruleClass = self::IMPORT_RULE_CLASS_PREFIX . $type; + if (! class_exists($ruleClass)) { + // TODO throw exception + return null; + } + } + + $rule = new $ruleClass(); + $rule->setOptions($options); + + return $rule; + } + + /** + * @param DocumentInterface $document + */ + public function apply($document) + { + foreach ($this->getRules() as $rule) { + $rule->apply($document); + } + } +} diff --git a/src/Importer.php b/src/Importer.php index a92a2ae..12d1c69 100644 --- a/src/Importer.php +++ b/src/Importer.php @@ -127,6 +127,9 @@ class Importer /** @var XmlDocument */ private $xmlDocument; + /** @var ImportRules */ + private $importRules; + /** @var bool */ private $updateExistingDocuments = true; @@ -319,6 +322,9 @@ public function run() continue; } + $importRules = $this->getImportRules(); + $importRules->apply($doc); + try { // TODO post "import" processing before storing! $newDocId = $doc->store(); @@ -713,9 +719,11 @@ protected function handleKeywords($node, $doc) { foreach ($node->childNodes as $childNode) { if ($childNode instanceof DOMElement) { - $s = Subject::new(); - $s->setLanguage(trim($childNode->getAttribute('language'))); - $s->setType($childNode->getAttribute('type')); + $s = Subject::new(); + $language = $childNode->getAttribute('language'); + $s->setLanguage($language ?: 'deu'); + $type = $childNode->getAttribute('type'); + $s->setType($type ?: 'uncontrolled'); $s->setValue(trim($childNode->textContent)); $doc->addSubject($s); } @@ -1128,6 +1136,19 @@ public function getDocument(): DocumentInterface return $this->document; } + /** + * @return ImportRules + */ + public function getImportRules() + { + if ($this->importRules === null) { + $this->importRules = new ImportRules(); + $this->importRules->init(); + } + + return $this->importRules; + } + protected function setSingleDocImport(bool $singleDoc): self { $this->singleDocImport = $singleDoc; diff --git a/src/Rules/AbstractImportRule.php b/src/Rules/AbstractImportRule.php new file mode 100644 index 0000000..631dc69 --- /dev/null +++ b/src/Rules/AbstractImportRule.php @@ -0,0 +1,80 @@ +condition = new AccountCondition($condition); + } + if (isset($condition['keyword'])) { + $this->condition = new KeywordCondition($condition); + } + } + } + + /** + * @return ImportRuleConditionInterface + */ + public function getCondition() + { + return $this->condition; + } + + /** + * @param DocumentInterface $document + */ + public function apply($document) + { + // TODO condition check in base class? + } +} diff --git a/src/Rules/AddCollection.php b/src/Rules/AddCollection.php new file mode 100644 index 0000000..927c7c3 --- /dev/null +++ b/src/Rules/AddCollection.php @@ -0,0 +1,77 @@ +collection = new CollectionOption($options['collection']); + } + } + + /** + * @return CollectionInterface|null + */ + public function getCollection() + { + return $this->collection->getCollection() ?? null; + } + + /** + * @param DocumentInterface $document + */ + public function apply($document) + { + $condition = $this->getCondition(); + + if ($condition === null || $condition->applies($document)) { + $col = $this->getCollection(); + if ($col !== null) { + $document->addCollection($col); + } + } + } +} diff --git a/src/Rules/AddKeyword.php b/src/Rules/AddKeyword.php new file mode 100644 index 0000000..0515bd2 --- /dev/null +++ b/src/Rules/AddKeyword.php @@ -0,0 +1,104 @@ +subjectType = $config['type'] ?? Subject::TYPE_UNCONTROLLED; + $this->language = $config['lang'] ?? 'deu'; + } else { + $keyword = $config; + } + + if (! empty($keyword)) { + $subject = Subject::new(); + $subject->setValue($keyword); + $subject->setType($this->subjectType); + $subject->setLanguage($this->language); + $this->subject = $subject; + } + } + } + + /** + * @return SubjectInterface|null + */ + public function getSubject() + { + return $this->subject; + } + + /** + * @param DocumentInterface $document + */ + public function apply($document) + { + $condition = $this->getCondition(); + if ($condition === null || $condition->applies($document)) { + $subject = $this->getSubject(); + if ($subject !== null) { + $document->addSubject($subject); + } + } + } +} diff --git a/src/Rules/AddLicence.php b/src/Rules/AddLicence.php new file mode 100644 index 0000000..1fab238 --- /dev/null +++ b/src/Rules/AddLicence.php @@ -0,0 +1,79 @@ +licence = Licence::get($options['licenceId']); + } + } + + /** + * @return LicenceInterface|null + */ + public function getLicence() + { + return $this->licence; + } + + /** + * @param DocumentInterface $document + */ + public function apply($document) + { + $condition = $this->getCondition(); + if ($condition === null || $condition->applies($document)) { + $licence = $this->getLicence(); + if ($licence !== null) { + $document->addLicence($licence); + } + } + } +} diff --git a/src/Rules/Conditions/AccountCondition.php b/src/Rules/Conditions/AccountCondition.php new file mode 100644 index 0000000..141ac07 --- /dev/null +++ b/src/Rules/Conditions/AccountCondition.php @@ -0,0 +1,107 @@ +setOptions($options); + } + + /** + * @param array|null $options + */ + public function setOptions($options) + { + if (isset($options['account'])) { + $this->expectedUser = $options['account']; + } + } + + /** + * @param DocumentInterface $document + * @return bool + */ + public function applies($document) + { + $currentUser = $this->getUserName(); + if ($this->expectedUser !== null && $currentUser !== null) { + return strcasecmp($this->expectedUser, $currentUser) === 0; + } else { + return false; + } + } + + /** + * @return string|null + */ + protected function getUserName() + { + $identity = Zend_Auth::getInstance()->getIdentity(); + if (isset($identity['username'])) { + return $identity['username']; + } else { + return null; + } + } + + /** + * @return string|null + */ + public function getExpectedUser() + { + return $this->expectedUser; + } + + /** + * @param string|null $user + */ + public function setExpectedUser($user) + { + $this->expectedUser = $user; + } +} diff --git a/src/Rules/Conditions/KeywordCondition.php b/src/Rules/Conditions/KeywordCondition.php new file mode 100644 index 0000000..dc9e5ad --- /dev/null +++ b/src/Rules/Conditions/KeywordCondition.php @@ -0,0 +1,184 @@ +setOptions($options); + } + + /** + * @param array|null $options + */ + public function setOptions($options) + { + if (isset($options['keyword'])) { + $keyword = $options['keyword']; + if (is_array($keyword)) { + $this->expectedKeyword = $keyword['value'] ?? null; + $this->keywordType = $keyword['type'] ?? null; + if (isset($keyword['remove'])) { + $this->removeKeyword = filter_var($keyword['remove'], FILTER_VALIDATE_BOOLEAN); + } + if (isset($keyword['caseSensitive'])) { + $this->caseSensitive = filter_var($keyword['caseSensitive'], FILTER_VALIDATE_BOOLEAN); + } + } else { + $this->expectedKeyword = $options['keyword']; + } + } + } + + /** + * @param DocumentInterface $document + * @return bool + * + * TODO remove keywords + */ + public function applies($document) + { + $caseSensitive = $this->isCaseSensitive(); + $expectedKeyword = $this->getExpectedKeyword(); + $keywordType = $this->getKeywordType(); + + if ($document->hasSubject($expectedKeyword, $keywordType, $caseSensitive)) { + if ($this->isRemoveEnabled()) { + $document->removeSubject($expectedKeyword, $keywordType, $caseSensitive); + } + return true; + } + + return false; + } + + /** + * @return string|null + */ + public function getExpectedKeyword() + { + return $this->expectedKeyword; + } + + /** + * @param string|null $keyword + * @return $this + */ + public function setExpectedKeyword($keyword) + { + $this->expectedKeyword = $keyword; + return $this; + } + + /** + * @return bool + */ + public function isRemoveEnabled() + { + return $this->removeKeyword; + } + + /** + * @param bool $enabled + * @return $this + */ + public function setRemoveEnabled($enabled) + { + $this->removeKeyword = $enabled; + return $this; + } + + /** + * @return bool + */ + public function isCaseSensitive() + { + return $this->caseSensitive; + } + + /** + * @param bool $enabled + * @return $this + */ + public function setCaseSensitive($enabled) + { + $this->caseSensitive = $enabled; + return $this; + } + + /** + * @return string|null + */ + public function getKeywordType() + { + return $this->keywordType; + } + + /** + * @param string|null $type + * @return $this + */ + public function setKeywordType($type) + { + $this->keywordType = $type; + return $this; + } +} diff --git a/src/Rules/Options/CollectionOption.php b/src/Rules/Options/CollectionOption.php new file mode 100644 index 0000000..d51f2b3 --- /dev/null +++ b/src/Rules/Options/CollectionOption.php @@ -0,0 +1,126 @@ +setOptions($options); + } + + /** + * @param string[]|null $options + */ + public function setOptions($options) + { + $this->options = $options; + } + + /** + * @return CollectionInterface|null + */ + public function getCollection() + { + if ($this->collection === null) { + $this->collection = $this->loadCollection(); + } + return $this->collection; + } + + /** + * @return CollectionInterface|null + * @throws NotFoundException + */ + protected function loadCollection() + { + // if collection ID is configured, load collection directly + if (isset($this->options['id']) && is_numeric($this->options['id'])) { + return Collection::get($this->options['id']); + } + + $role = null; + + // try to get collection role ('roleName' or 'roleOaiName') + if (isset($this->options['roleName'])) { + $role = CollectionRole::fetchByName($this->options['roleName']); + } elseif (isset($this->options['roleOaiName'])) { + $role = CollectionRole::fetchByOaiName($this->options['roleOaiName']); + } + + if ($role === null) { + // TODO log, throw exception? + return null; + } + + $collections = []; + + // try to get collection ('number' or 'name') + if (isset($this->options['number'])) { + $collections = Collection::fetchCollectionsByRoleNumber($role->getId(), $this->options['number']); + } elseif (isset($this->options['name'])) { + $collections = Collection::fetchCollectionsByRoleName($role->getId(), $this->options['name']); + } + + if (count($collections) > 0) { + return $collections[0]; + } + + return null; + } +} diff --git a/src/Rules/RemoveKeywords.php b/src/Rules/RemoveKeywords.php new file mode 100644 index 0000000..b8cfabc --- /dev/null +++ b/src/Rules/RemoveKeywords.php @@ -0,0 +1,130 @@ +getCondition(); + if ($condition === null || $condition->applies($document)) { + $keywords = $this->getKeywords(); + if ($keywords !== null) { + $caseSensitive = $this->isCaseSensitive(); + $keywordType = $this->getKeywordType(); + foreach ($keywords as $keyword) { + $document->removeSubject($keyword, $keywordType, $caseSensitive); + } + } + } + } + + /** + * @return string[] + */ + public function getKeywords() + { + return $this->keywords; + } + + /** + * @param string|string[] $keywords + * @return $this + */ + public function setKeywords($keywords) + { + if (is_array($keywords) || $keywords === null) { + $this->keywords = $keywords; + } else { + $this->keywords = array_map('trim', mb_split(',', $keywords)); + } + return $this; + } + + /** + * @return null|string + */ + public function getKeywordType() + { + return $this->keywordType; + } + + /** + * @param null|string $type + * @return $this + */ + public function setKeywordType($type) + { + $this->keywordType = $type; + return $this; + } + + /** + * @return bool + */ + public function isCaseSensitive() + { + return $this->caseSensitive; + } + + /** + * @param bool $caseSensitive + * @return $this + */ + public function setCaseSensitive($caseSensitive) + { + $this->caseSensitive = $caseSensitive; + return $this; + } +} diff --git a/src/Sword/ImportCollection.php b/src/Sword/ImportCollection.php index 875805a..694f055 100644 --- a/src/Sword/ImportCollection.php +++ b/src/Sword/ImportCollection.php @@ -41,6 +41,9 @@ use function trim; /** + * TODO rename this class - is is not a collection, it is a helper class for getting the "import collection" - Is this + * class necessary at all? Couldn't it be just a function, somewhere? What is the purpose, the reasoning behind + * it? * TODO make generic or move to opus4-sword (What is the concept here? Is SWORD specific?) */ class ImportCollection diff --git a/src/Xml/opus-import.xsd b/src/Xml/opus-import.xsd index 217c11d..dd71b1a 100644 --- a/src/Xml/opus-import.xsd +++ b/src/Xml/opus-import.xsd @@ -212,8 +212,8 @@ - - + + @@ -224,6 +224,7 @@ + diff --git a/test/ImportRulesTest.php b/test/ImportRulesTest.php new file mode 100644 index 0000000..baba5cf --- /dev/null +++ b/test/ImportRulesTest.php @@ -0,0 +1,156 @@ +prepareCollections(); + + $this->adjustConfiguration([ + 'import' => [ + 'rules' => [ + 'addCol' => [ + 'type' => 'AddCollection', + 'collection' => [ + 'id' => $this->colId, + ], + ], + ], + 'rulesConfigFile' => null, + ], + 'sword' => [ + 'enableImportRules' => true, + ], + ]); + + $importRules = new ImportRules(); + $importRules->init(); + + $rules = $importRules->getRules(); + + $this->assertCount(1, $rules); + + $rule = $rules[0]; + + $this->assertInstanceOf(AddCollection::class, $rule); + + $col = $rule->getCollection(); + + $this->assertInstanceOf(CollectionInterface::class, $col); + $this->assertEquals('col1', $col->getName()); + } + + public function testConfigFullClassname() + { + $this->prepareCollections(); + + $this->adjustConfiguration([ + 'import' => [ + 'rules' => [ + 'addCol' => [ + 'type' => AddCollection::class, + 'collection' => [ + 'id' => $this->colId, + ], + ], + ], + 'rulesConfigFile' => null, + ], + 'sword' => [ + 'enableImportRules' => true, + ], + ]); + + $importRules = new ImportRules(); + $importRules->init(); + + $rules = $importRules->getRules(); + + $this->assertCount(1, $rules); + + $rule = $rules[0]; + + $this->assertInstanceOf(AddCollection::class, $rule); + + $col = $rule->getCollection(); + + $this->assertInstanceOf(CollectionInterface::class, $col); + $this->assertEquals('col1', $col->getName()); + } + + public function testLoadConfigIni() + { + $this->adjustConfiguration([ + 'sword' => ['enableImportRules' => true], + 'import' => ['rulesConfigFile' => APPLICATION_PATH . '/test/_files/import-rules.ini'], + ]); + + $importRules = new ImportRules(); + $importRules->init(); + + $rules = $importRules->getRules(); + + $this->assertCount(2, $rules); + $this->assertInstanceOf(AddCollection::class, $rules[0]); + } + + protected function prepareCollections() + { + $role = CollectionRole::new(); + $role->setName('import'); + $role->setOaiName('oaiImport'); + $role->addRootCollection(); + + $col = Collection::new(); + $col->setName('col1'); + $col->setNumber('col1number'); + $role->getRootCollection()->addFirstChild($col); + $role->store(); + + $this->colId = $col->getId(); + } +} diff --git a/test/ImporterTest.php b/test/ImporterTest.php index dff2644..b9eed48 100644 --- a/test/ImporterTest.php +++ b/test/ImporterTest.php @@ -31,13 +31,21 @@ namespace OpusTest\Import; +use Opus\Common\Collection; +use Opus\Common\CollectionRole; use Opus\Common\Document; use Opus\Common\DocumentInterface; use Opus\Common\EnrichmentKey; +use Opus\Common\Licence; use Opus\Common\Log; +use Opus\Common\Model\ModelException; +use Opus\Common\Security\SecurityException; +use Opus\Common\Subject; use Opus\Import\Importer; +use Opus\Import\Xml\MetadataImportInvalidXmlException; use Opus\Import\Xml\MetadataImportSkippedDocumentsException; use OpusTest\Import\TestAsset\TestCase; +use Zend_Exception; use function file_get_contents; @@ -57,6 +65,8 @@ public function setUp(): void $enrichmentKey = EnrichmentKey::new(); $enrichmentKey->setName('validtestkey'); $enrichmentKey->store(); + + // TODO enable import rules here? } public function tearDown(): void @@ -131,4 +141,213 @@ public function testFromArray() // var_dump($doc->toArray()); } + + /** + * @return DocumentInterface + * @throws MetadataImportSkippedDocumentsException + * @throws ModelException + * @throws SecurityException + * @throws MetadataImportInvalidXmlException + * @throws Zend_Exception + */ + protected function getTestImportDocument() + { + $xml = file_get_contents(APPLICATION_PATH . '/test/_files/import-rules-test.xml'); + + $importer = new Importer($xml, false, Log::get()); + + $importer->run(); + + $document = $importer->getDocument(); + + $this->assertNotNull($document); + $this->assertInstanceOf(DocumentInterface::class, $document); + + return $document; + } + + public function testImport() + { + $document = $this->getTestImportDocument(); + + $authors = $document->getPersonAuthor(); + + $this->assertCount(1, $authors); + + $this->assertEquals('deu', $document->getLanguage()); + $this->assertEquals('Der Titel', $document->getMainTitle()->getValue()); + } + + public function testImportRulesAddCollectionByAccount() + { + $doc = $this->getTestImportDocument(); + } + + public function testImportRulesAddCollectionForKeyword() + { + $this->prepareCollections(); + + $this->adjustConfiguration([ + 'sword' => ['enableImportRules' => true], + 'import' => ['rulesConfigFile' => APPLICATION_PATH . '/test/_files/import-rules-keywords.ini'], + ]); + + $doc = $this->getTestImportDocument(); + + $collections = $doc->getCollection(); + + $this->assertCount(1, $collections); + $this->assertEquals('col1', $collections[0]->getName()); + } + + public function testImportRulesAddLicenceForKeyword() + { + $licence = Licence::new(); + $licence->setName('CC BY'); + $licence->setNameLong('CC BY Test Licence'); + $licence->setLinkLicence('URL'); + $licenceId = $licence->store(); + + $this->adjustConfiguration([ + 'sword' => ['enableImportRules' => true], + 'import' => [ + 'rulesConfigFile' => null, + 'rules' => [ + 'licence' => [ + 'type' => 'AddLicence', + 'condition' => [ + 'keyword' => 'ccby', + ], + 'licenceId' => $licenceId, + ], + ], + ], + ]); + + $doc = $this->getTestImportDocument(); + + $licences = $doc->getLicence(); + + $this->assertCount(1, $licences); + $this->assertEquals('CC BY', $licences[0]->getName()); + } + + public function testImportRulesRemoveKeyword() + { + $this->markTestIncomplete('TODO implement test'); + } + + public function testImportRulesAddCollectionAndRemoveKeyword() + { + $this->prepareCollections(); + + $this->adjustConfiguration([ + 'sword' => ['enableImportRules' => true], + 'import' => ['rulesConfigFile' => APPLICATION_PATH . '/test/_files/import-rules-keywords.ini'], + ]); + + $doc = $this->getTestImportDocument(); + + $collections = $doc->getCollection(); + + $this->assertCount(1, $collections); + + $this->markTestIncomplete('TODO check if collection was added and keyword removed'); + } + + public function testImportRulesDisabled() + { + $this->adjustConfiguration([ + 'sword' => ['enableImportRules' => false], + 'import' => ['rulesConfigFile' => APPLICATION_PATH . '/test/_files/import-rules-keywords.ini'], + ]); + $doc = $this->getTestImportDocument(); + $this->assertFalse($doc->hasSubject('RulesEnabled')); + } + + public function testKeywordTypeDefaultUncontrolled() + { + $doc = $this->getTestImportDocument(); + + $keywords = $doc->getSubject(); + + $this->assertCount(4, $keywords); + + $this->assertTrue($doc->hasSubject('oa-green', Subject::TYPE_UNCONTROLLED)); + $this->assertTrue($doc->hasSubject('oa-gold', Subject::TYPE_UNCONTROLLED)); + } + + public function testAddKeyword() + { + $this->adjustConfiguration([ + 'sword' => ['enableImportRules' => true], + 'import' => ['rulesConfigFile' => APPLICATION_PATH . '/test/_files/import-rules-keywords.ini'], + ]); + + $doc = $this->getTestImportDocument(); + + $this->assertTrue($doc->hasSubject('RulesEnabled')); + } + + protected function prepareCollections() + { + $role = CollectionRole::new(); + $role->setName('import'); + $role->setOaiName('oaiImport'); + $role->addRootCollection(); + $role->store(); + + $rootCol = $role->getRootCollection(); + + $col = Collection::new(); + $col->setName('col1'); + $col->setNumber('col1number'); + $rootCol->addFirstChild($col); + + $col = Collection::new(); + $col->setName('green'); + $col->setNumber('col2number'); + $rootCol->addLastChild($col); + $role->store(); + } + + public function testImportKeywordDefaults() + { + $xml = file_get_contents(APPLICATION_PATH . '/test/_files/import2.xml'); + + $importer = new Importer($xml, false, Log::get()); + + $importer->run(); + + $doc = $importer->getDocument(); + + $this->assertNotNull($doc); + $this->assertInstanceOf(DocumentInterface::class, $doc); + + $subjects = $doc->getSubject(); + + $this->assertCount(3, $subjects); + + // TODO this result check is overly complicated - better way? + foreach ($subjects as $subject) { + switch ($subject->getType()) { + case Subject::TYPE_SWD: + $this->assertEquals('Test', $subject->getValue()); + $this->assertEquals('deu', $subject->getLanguage()); + break; + case Subject::TYPE_UNCONTROLLED: + switch ($subject->getValue()) { + case 'engTest': + $this->assertEquals('eng', $subject->getLanguage()); + break; + case 'testWithDefaults': + $this->assertEquals('deu', $subject->getLanguage()); + break; + } + break; + default: + $this->fail('Unexpected subject type ' . $subject->getType()); + } + } + } } diff --git a/test/Rules/AddCollectionTest.php b/test/Rules/AddCollectionTest.php new file mode 100644 index 0000000..210b361 --- /dev/null +++ b/test/Rules/AddCollectionTest.php @@ -0,0 +1,175 @@ +authStorage = new Zend_Auth_Storage_NonPersistent(); + Zend_Auth::getInstance()->setStorage($this->authStorage); + } + + public function tearDown(): void + { + Zend_Auth::getInstance() + ->setStorage($this->authStorage) + ->clearIdentity(); + parent::tearDown(); + } + + public function testAddCollection() + { + $this->prepareCollections(); + + $this->adjustConfiguration([ + 'sword' => ['enableImportRules' => true], + 'import' => [ + 'rules' => [ + 'addCol' => [ + 'type' => AddCollection::class, + 'collection' => [ + 'id' => $this->colId, + ], + ], + ], + 'rulesConfigFile' => null, + ], + ]); + + $importRules = new ImportRules(); + $importRules->init(); + + $doc = Document::new(); + + $importRules->apply($doc); + + $collections = $doc->getCollection(); + + $this->assertCount(1, $collections); + $this->assertEquals($this->colId, $collections[0]->getId()); + } + + public function testAddCollectionForAccount() + { + $this->prepareCollections(); + + $col1 = Collection::get($this->colId); + $role = $col1->getRole(); + + $col1id = $col1->getId(); + + $col2 = Collection::new(); + $col2->setName('col1'); + $col2->setNumber('col1number'); + $role->getRootCollection()->addFirstChild($col2); + $role->store(); + + $col2id = $col2->getId(); + + $this->adjustConfiguration([ + 'sword' => ['enableImportRules' => true], + 'import' => [ + 'rules' => [ + 'addCol1' => [ + 'type' => 'AddCollection', + 'condition' => [ + 'account' => 'sword1', + ], + 'collection' => [ + 'id' => $this->colId, + ], + ], + 'addCol2' => [ + 'type' => 'AddCollection', + 'condition' => [ + 'account' => 'sword2', + ], + 'collection' => [ + 'id' => $col2id, + ], + ], + ], + 'rulesConfigFile' => null, + ], + ]); + + $rules = new ImportRules(); + $rules->init(); + + Zend_Auth::getInstance()->authenticate(new MockAuthAdapter('sword1')); + + $doc = Document::new(); + + $rules->apply($doc); + + $collections = $doc->getCollection(); + + $this->assertCount(1, $collections); + $this->assertEquals($col1id, $collections[0]->getId()); + } + + protected function prepareCollections() + { + $role = CollectionRole::new(); + $role->setName('import'); + $role->setOaiName('oaiImport'); + $role->addRootCollection(); + + $col = Collection::new(); + $col->setName('col1'); + $col->setNumber('col1number'); + $role->getRootCollection()->addFirstChild($col); + $role->store(); + + $this->colId = $col->getId(); + } +} diff --git a/test/Rules/AddLicenceTest.php b/test/Rules/AddLicenceTest.php new file mode 100644 index 0000000..a9556f5 --- /dev/null +++ b/test/Rules/AddLicenceTest.php @@ -0,0 +1,193 @@ +adjustConfiguration([ + 'sword' => [ + 'enableImportRules' => true, + ], + ]); + } + + public function testAddLicence() + { + $this->prepareLicences(); + + $this->adjustConfiguration([ + 'import' => [ + 'rules' => [ + 'addLicence' => [ + 'type' => AddLicence::class, + 'licenceId' => $this->licenceId1, + ], + ], + ], + ]); + + $importRules = new ImportRules(); + $importRules->init(); + + $doc = Document::new(); + + $importRules->apply($doc); + + $licences = $doc->getLicence(); + + $this->assertCount(1, $licences); + $this->assertEquals($this->licenceId1, $licences[0]->getModel()->getId()); + } + + public function testAddLicenceForKeyword() + { + $this->prepareLicences(); + + $this->adjustConfiguration([ + 'import' => [ + 'rules' => [ + 'addLicence1' => [ + 'type' => 'AddLicence', + 'licenceId' => $this->licenceId1, + 'condition' => [ + 'keyword' => 'ccby', + ], + ], + 'addLicence2' => [ + 'type' => 'AddLicence', + 'licenceId' => $this->licenceId2, + 'condition' => [ + 'keyword' => 'ccbyna', + ], + ], + ], + ], + ]); + + $importRules = new ImportRules(); + $importRules->init(); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccbyna'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $importRules->apply($doc); + + $licences = $doc->getLicence(); + + $this->assertCount(1, $licences); + $this->assertEquals($this->licenceId2, $licences[0]->getModel()->getId()); + } + + public function testAddMultipleLicences() + { + $this->prepareLicences(); + + $this->adjustConfiguration([ + 'import' => [ + 'rules' => [ + 'addLicence1' => [ + 'type' => 'AddLicence', + 'licenceId' => $this->licenceId1, + 'condition' => [ + 'keyword' => 'ccby', + ], + ], + 'addLicence2' => [ + 'type' => 'AddLicence', + 'licenceId' => $this->licenceId2, + 'condition' => [ + 'keyword' => 'ccbyna', + ], + ], + ], + ], + ]); + + $importRules = new ImportRules(); + $importRules->init(); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccbyna'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_PSYNDEX); + + $importRules->apply($doc); + + $licences = $doc->getLicence(); + + $this->assertCount(2, $licences); + $this->assertNotEquals($licences[0]->getModel()->getId(), $licences[1]->getModel()->getId()); + $this->assertContains($licences[0]->getModel()->getId(), [$this->licenceId1, $this->licenceId2]); + $this->assertContains($licences[1]->getModel()->getId(), [$this->licenceId1, $this->licenceId2]); + } + + protected function prepareLicences() + { + $licence = Licence::fromArray([ + 'Name' => 'CC BY', + 'NameLong' => 'Test Licence 1', + 'LinkLicence' => 'https://www.kobv.de/licence1', + ]); + + $this->licenceId1 = $licence->store(); + + $licence = Licence::fromArray([ + 'name' => 'CC BY NA', + 'NameLong' => 'Test Licence 2', + 'LinkLicence' => 'https://www.kobv.de/licence2', + ]); + + $this->licenceId2 = $licence->store(); + } +} diff --git a/test/Rules/Conditions/AccountConditionTest.php b/test/Rules/Conditions/AccountConditionTest.php new file mode 100644 index 0000000..32e451e --- /dev/null +++ b/test/Rules/Conditions/AccountConditionTest.php @@ -0,0 +1,101 @@ +authStorage = new Zend_Auth_Storage_NonPersistent(); + Zend_Auth::getInstance()->setStorage($this->authStorage); + } + + public function tearDown(): void + { + Zend_Auth::getInstance() + ->setStorage($this->authStorage) + ->clearIdentity(); + parent::tearDown(); + } + + public function testConstruct() + { + $condition = new AccountCondition([ + 'account' => 'sword1', + ]); + + $this->assertEquals('sword1', $condition->getExpectedUser()); + } + + public function testAppliesTrue() + { + Zend_Auth::getInstance()->authenticate(new MockAuthAdapter('sword1')); + + $condition = new AccountCondition([ + 'account' => 'sword1', + ]); + + $doc = Document::new(); + + $this->assertTrue($condition->applies($doc)); + } + + public function testAppliesFalse() + { + Zend_Auth::getInstance()->authenticate(new MockAuthAdapter('sword2')); + + $condition = new AccountCondition([ + 'account' => 'sword1', + ]); + + $doc = Document::new(); + + $this->assertFalse($condition->applies($doc)); + + $condition->setExpectedUser('sword2'); + + $this->assertTrue($condition->applies($doc)); + } +} diff --git a/test/Rules/Conditions/KeywordConditionTest.php b/test/Rules/Conditions/KeywordConditionTest.php new file mode 100644 index 0000000..dd5c4a3 --- /dev/null +++ b/test/Rules/Conditions/KeywordConditionTest.php @@ -0,0 +1,257 @@ + [ + 'value' => 'ccby', + 'remove' => true, + 'caseSensitive' => 1, + 'type' => 'uncontrolled', + ], + ]); + + $this->assertEquals('ccby', $condition->getExpectedKeyword()); + $this->assertTrue($condition->isCaseSensitive()); + $this->assertEquals('uncontrolled', $condition->getKeywordType()); + $this->assertTrue($condition->isRemoveEnabled()); + } + + public function testConstructSimpleConfig() + { + $condition = new KeywordCondition([ + 'keyword' => 'ccby', + ]); + + $this->assertEquals('ccby', $condition->getExpectedKeyword()); + $this->assertFalse($condition->isCaseSensitive()); + $this->assertNull($condition->getKeywordType()); + $this->assertFalse($condition->isRemoveEnabled()); + } + + public function testAppliesTrue() + { + $condition = new KeywordCondition([ + 'keyword' => 'ccby', + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $this->assertTrue($condition->applies($doc)); + } + + public function testAppliesFalse() + { + $condition = new KeywordCondition([ + 'keyword' => 'ccbyna', + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $this->assertFalse($condition->applies($doc)); + } + + public function testUsingKeywordType() + { + $condition = new KeywordCondition([ + 'keyword' => [ + 'value' => 'ccby', + 'type' => 'psyndex', + ], + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $this->assertFalse($condition->applies($doc)); + + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_PSYNDEX); + + $this->assertTrue($condition->applies($doc)); + } + + public function testNotCaseSensitive() + { + $condition = new KeywordCondition([ + 'keyword' => 'ccby', + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('CCBY'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $this->assertTrue($condition->applies($doc)); + } + + public function testCaseSensitive() + { + $condition = new KeywordCondition([ + 'keyword' => [ + 'value' => 'ccby', + 'caseSensitive' => true, + ], + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('CCBY'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $this->assertFalse($condition->applies($doc)); + } + + public function testDoNotRemoveKeyword() + { + $condition = new KeywordCondition([ + 'keyword' => 'ccby', + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $this->assertTrue($condition->applies($doc)); + + $this->assertCount(1, $doc->getSubject()); + } + + public function testDoNotRemoveKeywordIfConditionDoesNotApply() + { + $condition = new KeywordCondition([ + 'keyword' => [ + 'value' => 'ccby', + 'remove' => true, + ], + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccbyna'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $this->assertFalse($condition->applies($doc)); + + $this->assertCount(1, $doc->getSubject()); + } + + public function testRemoveKeyword() + { + $condition = new KeywordCondition([ + 'keyword' => [ + 'value' => 'ccby', + 'remove' => true, + ], + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $this->assertTrue($condition->applies($doc)); + + $this->assertCount(0, $doc->getSubject()); + } + + public function testRemoveOnlyKeywordWithMatchingType() + { + $condition = new KeywordCondition([ + 'keyword' => [ + 'value' => 'ccby', + 'type' => Subject::TYPE_PSYNDEX, + 'remove' => true, + ], + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_PSYNDEX); + + $this->assertTrue($condition->applies($doc)); + + $subjects = $doc->getSubject(); + + $this->assertCount(1, $subjects); + $this->assertEquals(Subject::TYPE_UNCONTROLLED, $subjects[0]->getType()); + } + + public function testRemoveAllMatchingKeywords() + { + $condition = new KeywordCondition([ + 'keyword' => [ + 'value' => 'ccby', + 'remove' => true, + ], + ]); + + $doc = Document::new(); + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_UNCONTROLLED); + + $subject = $doc->addSubject(); + $subject->setValue('ccby'); + $subject->setType(Subject::TYPE_PSYNDEX); + + $this->assertTrue($condition->applies($doc)); + + $subjects = $doc->getSubject(); + + $this->assertCount(0, $subjects); + } +} diff --git a/test/Rules/Options/CollectionOptionTest.php b/test/Rules/Options/CollectionOptionTest.php new file mode 100644 index 0000000..a7c4444 --- /dev/null +++ b/test/Rules/Options/CollectionOptionTest.php @@ -0,0 +1,144 @@ +prepareCollections(); + + $colId = $this->colId; + + $option = new CollectionOption(); + $option->setOptions([ + 'id' => $colId, + ]); + + $col = $option->getCollection(); + + $this->assertNotNull($col); + $this->assertEquals($colId, $col->getId()); + } + + public function testConfigRoleNameColNumber() + { + $this->prepareCollections(); + + $option = new CollectionOption(); + $option->setOptions([ + 'roleName' => 'import', + 'number' => 'col1number', + ]); + + $col = $option->getCollection(); + + $this->assertNotNull($col); + $this->assertEquals($this->colId, $col->getId()); + } + + public function testConfigRoleNameColName() + { + $this->prepareCollections(); + + $option = new CollectionOption(); + $option->setOptions([ + 'roleName' => 'import', + 'name' => 'col1', + ]); + + $col = $option->getCollection(); + + $this->assertNotNull($col); + $this->assertEquals($this->colId, $col->getId()); + } + + public function testConfigRoleOaiNameColNumber() + { + $this->prepareCollections(); + + $option = new CollectionOption(); + $option->setOptions([ + 'roleOaiName' => 'oaiImport', + 'number' => 'col1number', + ]); + + $col = $option->getCollection(); + + $this->assertNotNull($col); + $this->assertEquals($this->colId, $col->getId()); + } + + public function testConfigRoleOaiNameColName() + { + $this->prepareCollections(); + + $option = new CollectionOption(); + $option->setOptions([ + 'roleOaiName' => 'oaiImport', + 'name' => 'col1', + ]); + + $col = $option->getCollection(); + + $this->assertNotNull($col); + $this->assertEquals($this->colId, $col->getId()); + } + + protected function prepareCollections() + { + $role = CollectionRole::new(); + $role->setName('import'); + $role->setOaiName('oaiImport'); + $role->addRootCollection(); + $this->roleId = $role->store(); + + $col = Collection::new(); + $col->setName('col1'); + $col->setNumber('col1number'); + $role->getRootCollection()->addFirstChild($col); + $role->store(); + + $this->colId = $col->getId(); + } +} diff --git a/test/Rules/RemoveKeywordsTest.php b/test/Rules/RemoveKeywordsTest.php new file mode 100644 index 0000000..f5b192a --- /dev/null +++ b/test/Rules/RemoveKeywordsTest.php @@ -0,0 +1,217 @@ +adjustConfiguration([ + 'sword' => ['enableImportRules' => true], + ]); + } + + public function testRemoveKeywordUsingCondition() + { + $this->adjustConfiguration([ + 'import' => [ + 'rules' => [ + 'keyword1' => [ + 'type' => 'RemoveKeywords', + 'condition' => [ + 'keyword' => [ + 'value' => 'RemoveMe', + 'remove' => true, + ], + ], + ], + ], + 'rulesConfigFile' => null, + ], + ]); + + $doc = Document::new(); + $keyword = $doc->addSubject(); + $keyword->setValue('RemoveMe'); + $keyword->setType('uncontrolled'); + $keyword->setLanguage('eng'); + + $rules = new ImportRules(); + $rules->init(); + + $rules->apply($doc); + + $keywords = $doc->getSubject(); + + $this->assertCount(0, $keywords); + } + + public function testSetKeywordsSingleValue() + { + $rule = new RemoveKeywords(); + $rule->setKeywords('RemoveMe'); + $this->assertEquals(['RemoveMe'], $rule->getKeywords()); + } + + public function testSetKeywordsNull() + { + $rule = new RemoveKeywords(); + $rule->setKeywords(null); + $this->assertNull($rule->getKeywords()); + } + + public function testSetKeywordsCsv() + { + $rule = new RemoveKeywords(); + $rule->setKeywords('keyword1,keyword2,keyword3'); + $this->assertEquals(['keyword1', 'keyword2', 'keyword3'], $rule->getKeywords()); + } + + public function testSetKeywordsArray() + { + $rule = new RemoveKeywords(); + $rule->setKeywords(['keyword1', 'keyword2', 'keyword3']); + $this->assertEquals(['keyword1', 'keyword2', 'keyword3'], $rule->getKeywords()); + } + + public function testRemoveKeyword() + { + $doc = Document::new(); + $keyword = $doc->addSubject(); + $keyword->setValue('RemoveMe'); + $keyword->setType('uncontrolled'); + $keyword->setLanguage('eng'); + + $rule = new RemoveKeywords(); + $rule->setKeywords('RemoveMe'); + + $rule->apply($doc); + + $keywords = $doc->getSubject(); + + $this->assertCount(0, $keywords); + } + + public function testRemoveMultipleKeywords() + { + $doc = Document::fromArray([ + 'Subject' => [ + [ + 'Language' => 'eng', + 'Value' => 'key1', + 'Type' => 'uncontrolled', + ], + [ + 'Language' => 'eng', + 'Value' => 'key2', + 'Type' => 'psyndex', + ], + ], + ]); + + $this->assertCount(2, $doc->getSubject()); + + $rule = new RemoveKeywords(); + $rule->setCaseSensitive(false); + $rule->setKeywords(['key1', 'KEY2']); + + $rule->apply($doc); + + $this->assertCount(0, $doc->getSubject()); + } + + public function testRemoveKeywordsUsingType() + { + $doc = Document::fromArray([ + 'Subject' => [ + [ + 'Language' => 'eng', + 'Value' => 'key1', + 'Type' => 'uncontrolled', + ], + [ + 'Language' => 'eng', + 'Value' => 'key2', + 'Type' => 'psyndex', + ], + ], + ]); + + $this->assertCount(2, $doc->getSubject()); + + $rule = new RemoveKeywords(); + $rule->setCaseSensitive(false); + $rule->setKeywords(['key1', 'KEY2']); + $rule->setKeywordType('psyndex'); + + $rule->apply($doc); + + $this->assertCount(1, $doc->getSubject()); + $this->assertEquals('key1', $doc->getSubject(0)->getValue()); + } + + public function testRemoveKeywordsCaseSensitive() + { + $doc = Document::fromArray([ + 'Subject' => [ + [ + 'Language' => 'eng', + 'Value' => 'key1', + 'Type' => 'uncontrolled', + ], + [ + 'Language' => 'eng', + 'Value' => 'key2', + 'Type' => 'psyndex', + ], + ], + ]); + + $this->assertCount(2, $doc->getSubject()); + + $rule = new RemoveKeywords(); + $rule->setCaseSensitive(true); + $rule->setKeywords(['key1', 'KEY2']); + + $rule->apply($doc); + + $this->assertCount(1, $doc->getSubject()); + $this->assertEquals('key2', $doc->getSubject(0)->getValue()); + } +} diff --git a/test/TestAsset/MockAuthAdapter.php b/test/TestAsset/MockAuthAdapter.php new file mode 100644 index 0000000..007d387 --- /dev/null +++ b/test/TestAsset/MockAuthAdapter.php @@ -0,0 +1,58 @@ +user = $user; + } + + /** + * @return Zend_Auth_Result + */ + public function authenticate() + { + $identity = ['username' => $this->user]; + return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $identity); + } +} diff --git a/test/Xml/XmlDocumentTest.php b/test/Xml/XmlDocumentTest.php index 32f700c..0cb1bc9 100644 --- a/test/Xml/XmlDocumentTest.php +++ b/test/Xml/XmlDocumentTest.php @@ -48,7 +48,7 @@ public function testValidation() { foreach (new DirectoryIterator(APPLICATION_PATH . '/test/_files') as $fileInfo) { if ( - $fileInfo->getExtension() !== 'xsd' && ! $fileInfo->isDot() + $fileInfo->getExtension() === 'xml' && ! $fileInfo->isDot() && strpos($fileInfo->getBasename(), 'import') === 0 ) { $xml = file_get_contents($fileInfo->getRealPath()); diff --git a/test/_files/import-rules-keywords.ini b/test/_files/import-rules-keywords.ini new file mode 100644 index 0000000..009e519 --- /dev/null +++ b/test/_files/import-rules-keywords.ini @@ -0,0 +1,14 @@ +enabledCheck.type = 'AddKeyword' +enabledCheck.keyword = 'RulesEnabled' + +addCol.type = 'AddCollection' +addCol.condition.keyword = 'oa-gold' +addCol.collection.roleName = 'import' +addCol.collection.name = 'col1' + +; TODO add rules just for specific tests +; addCol2.type = 'AddCollection' +; addCol2.condition.keyword.value = 'oa-green' +; addCol2.condition.keyword.remove = true +; addCol2.collection.roleName = 'import' +; addCol2.collection.name = 'green' diff --git a/test/_files/import-rules-test.xml b/test/_files/import-rules-test.xml new file mode 100644 index 0000000..61f11c1 --- /dev/null +++ b/test/_files/import-rules-test.xml @@ -0,0 +1,29 @@ + + + + + Der Titel + + + The Title + + + Deutsch Zusammenfassung + + + + + + Test + oa-green + oa-gold + ccby + + + + + + + + + \ No newline at end of file diff --git a/test/_files/import-rules.ini b/test/_files/import-rules.ini new file mode 100644 index 0000000..d60e6ff --- /dev/null +++ b/test/_files/import-rules.ini @@ -0,0 +1,6 @@ +institute1.type = 'AddCollection' +institute1.condition.account = 'sword' +institute1.collection.id = [COLLECTION ID] + +enabledCheck.type = 'AddKeyword' +enabledCheck.keyword = 'RulesEnabled' \ No newline at end of file diff --git a/test/_files/import2.xml b/test/_files/import2.xml index a155a68..ffd7320 100644 --- a/test/_files/import2.xml +++ b/test/_files/import2.xml @@ -4,5 +4,10 @@ Der Titel + + Test + engTest + testWithDefaults + \ No newline at end of file diff --git a/test/test.ini b/test/test.ini index 4174084..f0fa4c7 100644 --- a/test/test.ini +++ b/test/test.ini @@ -99,3 +99,6 @@ filetypes.html.mimeType = 'text/html' ; Allow displaying of PDF directly in browser (default for other types 'attachment') filetypes.pdf.contentDisposition = 'inline' + +sword.enableImportRules = 0 +import.rulesConfigFile =