diff --git a/src/RecordManager/Base/Command/Records/Deduplicate.php b/src/RecordManager/Base/Command/Records/Deduplicate.php index aa15f9bf4..612ba6106 100644 --- a/src/RecordManager/Base/Command/Records/Deduplicate.php +++ b/src/RecordManager/Base/Command/Records/Deduplicate.php @@ -54,6 +54,21 @@ class Deduplicate extends AbstractBase */ protected $terminate = false; + /** + * Catch the SIGINT signal and signal the main thread to terminate + * + * Note: this needs to be public so that the int handler can call it. + * + * @param int $signal Signal ID + * + * @return void + */ + public function sigIntHandler($signal) + { + $this->terminate = true; + $this->logger->writelnConsole('Termination requested'); + } + /** * Configure the command. * @@ -297,19 +312,4 @@ function ($record) use ( $this->logger->logInfo('deduplicate', 'Deduplication completed'); return Command::SUCCESS; } - - /** - * Catch the SIGINT signal and signal the main thread to terminate - * - * Note: this needs to be public so that the int handler can call it. - * - * @param int $signal Signal ID - * - * @return void - */ - public function sigIntHandler($signal) - { - $this->terminate = true; - $this->logger->writelnConsole('Termination requested'); - } } diff --git a/src/RecordManager/Base/Command/Records/Harvest.php b/src/RecordManager/Base/Command/Records/Harvest.php index 5709c7c10..3f8068c22 100644 --- a/src/RecordManager/Base/Command/Records/Harvest.php +++ b/src/RecordManager/Base/Command/Records/Harvest.php @@ -102,6 +102,29 @@ public function __construct( $this->harvesterPluginManager = $harvesterManager; } + /** + * Mark a record "seen". Used by OAI-PMH harvesting when deletions are not + * supported. + * + * @param string $sourceId Source ID + * @param string $oaiId ID of the record as received from OAI-PMH + * @param bool $deleted Whether the record is to be deleted + * + * @throws \Exception + * @return void + */ + public function markRecordSeen($sourceId, $oaiId, $deleted) + { + if ($deleted) { + // Don't mark deleted records... + return; + } + $this->db->updateRecords( + ['source_id' => $sourceId, 'oai_id' => $oaiId], + ['date' => $this->db->getTimestamp()] + ); + } + /** * Configure the command. * @@ -419,29 +442,6 @@ protected function doExecute(InputInterface $input, OutputInterface $output) return $returnCode; } - /** - * Mark a record "seen". Used by OAI-PMH harvesting when deletions are not - * supported. - * - * @param string $sourceId Source ID - * @param string $oaiId ID of the record as received from OAI-PMH - * @param bool $deleted Whether the record is to be deleted - * - * @throws \Exception - * @return void - */ - public function markRecordSeen($sourceId, $oaiId, $deleted) - { - if ($deleted) { - // Don't mark deleted records... - return; - } - $this->db->updateRecords( - ['source_id' => $sourceId, 'oai_id' => $oaiId], - ['date' => $this->db->getTimestamp()] - ); - } - /** * Set deleted all records that were not "seen" during harvest * diff --git a/src/RecordManager/Base/Database/PDODatabase.php b/src/RecordManager/Base/Database/PDODatabase.php index 7fcfbed15..988772ec1 100644 --- a/src/RecordManager/Base/Database/PDODatabase.php +++ b/src/RecordManager/Base/Database/PDODatabase.php @@ -618,6 +618,27 @@ public function getRecordAttrs(string $collection, string $id): array return $attrs; } + /** + * Get database handle + * + * @return \PDO + */ + public function getDb(): \PDO + { + if (null === $this->db) { + $this->db = new \PDO($this->dsn, $this->username, $this->password); + $this->db + ->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); + $this->pid = getmypid(); + } elseif ($this->pid !== getmypid()) { + throw new \Exception( + 'PID ' . getmypid() . ': database already connected by PID ' + . $this->pid + ); + } + return $this->db; + } + /** * Get a record * @@ -1194,25 +1215,4 @@ protected function getMainFields(string $collection): array } return $this->mainFields[$collection]; } - - /** - * Get database handle - * - * @return \PDO - */ - public function getDb(): \PDO - { - if (null === $this->db) { - $this->db = new \PDO($this->dsn, $this->username, $this->password); - $this->db - ->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC); - $this->pid = getmypid(); - } elseif ($this->pid !== getmypid()) { - throw new \Exception( - 'PID ' . getmypid() . ': database already connected by PID ' - . $this->pid - ); - } - return $this->db; - } } diff --git a/src/RecordManager/Base/Harvest/GeniePlus.php b/src/RecordManager/Base/Harvest/GeniePlus.php index fb138a6c1..109830343 100644 --- a/src/RecordManager/Base/Harvest/GeniePlus.php +++ b/src/RecordManager/Base/Harvest/GeniePlus.php @@ -262,20 +262,6 @@ public function setInitialPosition($pos) $this->startPosition = intval($pos); } - /** - * Reformat a date for use in API queries - * - * @param string $date Date in YYYY-MM-DD format - * - * @return string Date in MM/DD/YYYY format. - * - * @psalm-suppress FalsableReturnStatement - */ - protected function reformatDate($date) - { - return date('n/j/Y', strtotime($date)); - } - /** * Harvest all available documents. * @@ -348,6 +334,20 @@ public function harvest($callback) } } + /** + * Reformat a date for use in API queries + * + * @param string $date Date in YYYY-MM-DD format + * + * @return string Date in MM/DD/YYYY format. + * + * @psalm-suppress FalsableReturnStatement + */ + protected function reformatDate($date) + { + return date('n/j/Y', strtotime($date)); + } + /** * Get server date as a unix timestamp * diff --git a/src/RecordManager/Base/Record/Ead3.php b/src/RecordManager/Base/Record/Ead3.php index e323eb955..5a65427c2 100644 --- a/src/RecordManager/Base/Record/Ead3.php +++ b/src/RecordManager/Base/Record/Ead3.php @@ -236,6 +236,26 @@ public function getRawGeographicTopicIds(): array return []; } + /** + * Get author identifiers + * + * @return array + */ + public function getAuthorIds(): array + { + return []; + } + + /** + * Get corporate author identifiers + * + * @return array + */ + public function getCorporateAuthorIds() + { + return []; + } + /** * Get topic identifiers. * @@ -286,26 +306,6 @@ protected function getAuthors() return $result; } - /** - * Get author identifiers - * - * @return array - */ - public function getAuthorIds(): array - { - return []; - } - - /** - * Get corporate author identifiers - * - * @return array - */ - public function getCorporateAuthorIds() - { - return []; - } - /** * Get corporate authors * diff --git a/src/RecordManager/Base/Record/Forward.php b/src/RecordManager/Base/Record/Forward.php index e6cee4743..e5e4a890b 100644 --- a/src/RecordManager/Base/Record/Forward.php +++ b/src/RecordManager/Base/Record/Forward.php @@ -238,6 +238,38 @@ public function getMainAuthor() return $author; } + /** + * Return record title + * + * @param bool $forFiling Whether the title is to be used in filing + * (e.g. sorting, non-filing characters should be removed) + * + * @return string + * + * @SuppressWarnings(PHPMD.UnusedFormalParameter) + */ + public function getTitle($forFiling = false) + { + $doc = $this->getMainElement(); + $title = (string)$doc->IdentifyingTitle; + + if ($forFiling) { + $title = $this->metadataUtils->createSortTitle($title); + } + + return $title; + } + + /** + * Return format from predefined values + * + * @return string|array + */ + public function getFormat() + { + return 'MotionPicture'; + } + /** * Get the main metadata element * @@ -425,38 +457,6 @@ protected function getDescriptions($language = null) return $results; } - /** - * Return record title - * - * @param bool $forFiling Whether the title is to be used in filing - * (e.g. sorting, non-filing characters should be removed) - * - * @return string - * - * @SuppressWarnings(PHPMD.UnusedFormalParameter) - */ - public function getTitle($forFiling = false) - { - $doc = $this->getMainElement(); - $title = (string)$doc->IdentifyingTitle; - - if ($forFiling) { - $title = $this->metadataUtils->createSortTitle($title); - } - - return $title; - } - - /** - * Return format from predefined values - * - * @return string|array - */ - public function getFormat() - { - return 'MotionPicture'; - } - /** * Return genres * diff --git a/src/RecordManager/Base/Record/Lrmi.php b/src/RecordManager/Base/Record/Lrmi.php index b0122e29c..52cd0fc6e 100644 --- a/src/RecordManager/Base/Record/Lrmi.php +++ b/src/RecordManager/Base/Record/Lrmi.php @@ -111,6 +111,26 @@ public function getMainAuthor() return $authors[0] ?? ''; } + /** + * Get topics. + * + * @return array + */ + public function getTopics() + { + return $this->getTopicData(false); + } + + /** + * Get all topic identifiers (for enrichment) + * + * @return array + */ + public function getRawTopicIds(): array + { + return $this->getTopicData(true); + } + /** * Get primary authors * @@ -159,26 +179,6 @@ protected function getCorporateAuthors() return $result; } - /** - * Get topics. - * - * @return array - */ - public function getTopics() - { - return $this->getTopicData(false); - } - - /** - * Get all topic identifiers (for enrichment) - * - * @return array - */ - public function getRawTopicIds(): array - { - return $this->getTopicData(true); - } - /** * Get topics with value or id. * diff --git a/src/RecordManager/Base/Record/Marc.php b/src/RecordManager/Base/Record/Marc.php index da2701c1a..0ba903e8c 100644 --- a/src/RecordManager/Base/Record/Marc.php +++ b/src/RecordManager/Base/Record/Marc.php @@ -597,26 +597,6 @@ public function getLinkingIDs() return $results; } - /** - * Create a linking id from record id - * - * @param string $id Record id - * - * @return string - */ - protected function createLinkingId($id) - { - if ('' !== $id && $this->getDriverParam('003InLinkingID', false)) { - $source = $this->metadataUtils->stripTrailingPunctuation( - $this->record->getControlField('003') - ); - if ($source) { - $id = "($source)$id"; - } - } - return $id; - } - /** * Return whether the record is a component part * @@ -1113,168 +1093,6 @@ public function addDedupKeyToMetadata($dedupKey) } } - /** - * Get the building field - * - * @return array - */ - protected function getBuilding() - { - $building = []; - $buildingFieldSpec = $this->getDriverParam('buildingFields', false); - if ( - $this->getDriverParam('holdingsInBuilding', true) - || false !== $buildingFieldSpec - ) { - $buildingFieldSpec = $this->getDriverParam('buildingFields', false); - if (false === $buildingFieldSpec) { - $buildingFields = $this->getDefaultBuildingFields(); - } else { - $buildingFields = []; - $parts = explode(':', $buildingFieldSpec); - foreach ($parts as $part) { - $buildingFields[] = [ - 'field' => substr($part, 0, 3), - 'loc' => substr($part, 3, 1), - 'sub' => substr($part, 4, 1), - ]; - } - } - - foreach ($buildingFields as $buildingField) { - foreach ($this->record->getFields($buildingField['field']) as $field) { - $location = $this->record->getSubfield($field, $buildingField['loc']); - if ($location) { - $subLocField = $buildingField['sub']; - if ($subLocField) { - $sub = $this->record->getSubfield($field, $subLocField); - if ($sub) { - $location = [$location, $sub]; - } - } - $building[] = $location; - } - } - } - } - return $building; - } - - /** - * Get default fields used to populate the building field - * - * @return array - */ - protected function getDefaultBuildingFields() - { - $useSub = $this->getDriverParam('subLocationInBuilding', ''); - $fields = [ - [ - 'field' => '852', - 'loc' => 'b', - 'sub' => $useSub, - ], - ]; - if ( - $this->getDriverParam('kohaNormalization', false) - || $this->getDriverParam('almaNormalization', false) - ) { - $itemSub = $this->getDriverParam('itemSubLocationInBuilding', $useSub); - $fields[] = [ - 'field' => '952', - 'loc' => 'b', - 'sub' => $itemSub, - ]; - } - return $fields; - } - - /** - * Get alternate titles - * - * @return array - */ - protected function getAltTitles(): array - { - return array_values( - array_unique( - $this->getFieldsSubfields( - [ - [MarcHandler::GET_ALT, '245', ['a', 'b']], - [MarcHandler::GET_BOTH, '130', [ - 'a', 'd', 'f', 'g', 'k', 'l', 'n', 'p', 's', 't', - ]], - [MarcHandler::GET_BOTH, '240', ['a']], - [MarcHandler::GET_BOTH, '246', ['a', 'b', 'n', 'p']], - [MarcHandler::GET_BOTH, '730', [ - 'a', 'd', 'f', 'g', 'k', 'l', 'n', 'p', 's', 't', - ]], - [MarcHandler::GET_BOTH, '740', ['a']], - ] - ) - ) - ); - } - - /** - * Check if the work is illustrated - * - * @return string - */ - protected function getIllustrated() - { - $leader = $this->record->getLeader(); - if (in_array(substr($leader, 6, 1), ['a', 't'])) { - $illustratedCodes = [ - 'a' => 1, - 'b' => 1, - 'c' => 1, - 'd' => 1, - 'e' => 1, - 'f' => 1, - 'g' => 1, - 'h' => 1, - 'i' => 1, - 'j' => 1, - 'k' => 1, - 'l' => 1, - 'm' => 1, - 'o' => 1, - 'p' => 1, - ]; - - // 008 - $field008 = $this->record->getControlField('008'); - for ($pos = 18; $pos <= 21; $pos++) { - $ch = substr($field008, $pos, 1); - if ('' !== $ch && isset($illustratedCodes[$ch])) { - return 'Illustrated'; - } - } - - // 006 - foreach ($this->record->getControlFields('006') as $field006) { - for ($pos = 1; $pos <= 4; $pos++) { - $ch = substr($field006, $pos, 1); - if ('' !== $ch && isset($illustratedCodes[$ch])) { - return 'Illustrated'; - } - } - } - } - - // Now check for interesting strings in 300 subfield b: - foreach ($this->record->getFields('300') as $field300) { - $sub = strtolower($this->record->getSubfield($field300, 'b')); - foreach ($this->illustrationStrings as $illStr) { - if (str_contains($sub, $illStr)) { - return 'Illustrated'; - } - } - } - return 'Not Illustrated'; - } - /** * Get key data that can be used to identify expressions of a work * @@ -1698,6 +1516,188 @@ public function getShortTitle(): string return $this->metadataUtils->stripTrailingPunctuation($title, '', true); } + /** + * Create a linking id from record id + * + * @param string $id Record id + * + * @return string + */ + protected function createLinkingId($id) + { + if ('' !== $id && $this->getDriverParam('003InLinkingID', false)) { + $source = $this->metadataUtils->stripTrailingPunctuation( + $this->record->getControlField('003') + ); + if ($source) { + $id = "($source)$id"; + } + } + return $id; + } + + /** + * Get the building field + * + * @return array + */ + protected function getBuilding() + { + $building = []; + $buildingFieldSpec = $this->getDriverParam('buildingFields', false); + if ( + $this->getDriverParam('holdingsInBuilding', true) + || false !== $buildingFieldSpec + ) { + $buildingFieldSpec = $this->getDriverParam('buildingFields', false); + if (false === $buildingFieldSpec) { + $buildingFields = $this->getDefaultBuildingFields(); + } else { + $buildingFields = []; + $parts = explode(':', $buildingFieldSpec); + foreach ($parts as $part) { + $buildingFields[] = [ + 'field' => substr($part, 0, 3), + 'loc' => substr($part, 3, 1), + 'sub' => substr($part, 4, 1), + ]; + } + } + + foreach ($buildingFields as $buildingField) { + foreach ($this->record->getFields($buildingField['field']) as $field) { + $location = $this->record->getSubfield($field, $buildingField['loc']); + if ($location) { + $subLocField = $buildingField['sub']; + if ($subLocField) { + $sub = $this->record->getSubfield($field, $subLocField); + if ($sub) { + $location = [$location, $sub]; + } + } + $building[] = $location; + } + } + } + } + return $building; + } + + /** + * Get default fields used to populate the building field + * + * @return array + */ + protected function getDefaultBuildingFields() + { + $useSub = $this->getDriverParam('subLocationInBuilding', ''); + $fields = [ + [ + 'field' => '852', + 'loc' => 'b', + 'sub' => $useSub, + ], + ]; + if ( + $this->getDriverParam('kohaNormalization', false) + || $this->getDriverParam('almaNormalization', false) + ) { + $itemSub = $this->getDriverParam('itemSubLocationInBuilding', $useSub); + $fields[] = [ + 'field' => '952', + 'loc' => 'b', + 'sub' => $itemSub, + ]; + } + return $fields; + } + + /** + * Get alternate titles + * + * @return array + */ + protected function getAltTitles(): array + { + return array_values( + array_unique( + $this->getFieldsSubfields( + [ + [MarcHandler::GET_ALT, '245', ['a', 'b']], + [MarcHandler::GET_BOTH, '130', [ + 'a', 'd', 'f', 'g', 'k', 'l', 'n', 'p', 's', 't', + ]], + [MarcHandler::GET_BOTH, '240', ['a']], + [MarcHandler::GET_BOTH, '246', ['a', 'b', 'n', 'p']], + [MarcHandler::GET_BOTH, '730', [ + 'a', 'd', 'f', 'g', 'k', 'l', 'n', 'p', 's', 't', + ]], + [MarcHandler::GET_BOTH, '740', ['a']], + ] + ) + ) + ); + } + + /** + * Check if the work is illustrated + * + * @return string + */ + protected function getIllustrated() + { + $leader = $this->record->getLeader(); + if (in_array(substr($leader, 6, 1), ['a', 't'])) { + $illustratedCodes = [ + 'a' => 1, + 'b' => 1, + 'c' => 1, + 'd' => 1, + 'e' => 1, + 'f' => 1, + 'g' => 1, + 'h' => 1, + 'i' => 1, + 'j' => 1, + 'k' => 1, + 'l' => 1, + 'm' => 1, + 'o' => 1, + 'p' => 1, + ]; + + // 008 + $field008 = $this->record->getControlField('008'); + for ($pos = 18; $pos <= 21; $pos++) { + $ch = substr($field008, $pos, 1); + if ('' !== $ch && isset($illustratedCodes[$ch])) { + return 'Illustrated'; + } + } + + // 006 + foreach ($this->record->getControlFields('006') as $field006) { + for ($pos = 1; $pos <= 4; $pos++) { + $ch = substr($field006, $pos, 1); + if ('' !== $ch && isset($illustratedCodes[$ch])) { + return 'Illustrated'; + } + } + } + } + + // Now check for interesting strings in 300 subfield b: + foreach ($this->record->getFields('300') as $field300) { + $sub = strtolower($this->record->getSubfield($field300, 'b')); + foreach ($this->illustrationStrings as $illStr) { + if (str_contains($sub, $illStr)) { + return 'Illustrated'; + } + } + } + return 'Not Illustrated'; + } + /** * Get full title * diff --git a/src/RecordManager/Base/Record/Marc/FormatCalculator.php b/src/RecordManager/Base/Record/Marc/FormatCalculator.php index 7591d6e2b..71c2e58c6 100644 --- a/src/RecordManager/Base/Record/Marc/FormatCalculator.php +++ b/src/RecordManager/Base/Record/Marc/FormatCalculator.php @@ -50,6 +50,33 @@ */ class FormatCalculator { + /** + * Determine Record Format(s) + * + * @param Marc $record MARC record + * + * @return array Record formats + */ + public function getFormats(Marc $record): array + { + // Deduplicate list: + return array_values(array_unique($this->getFormatsAsList($record))); + } + + /** + * Determine Record Format + * + * @param Marc $record MARC record + * + * @return array First format + */ + public function getFormat(Marc $record): array + { + $result = $this->getFormatsAsList($record); + // Return the first format as an array: + return [reset($result)]; + } + /** * Determine whether a record cannot be a book due to findings in 007. * @@ -793,31 +820,4 @@ protected function getFormatsAsList(Marc $record) return $result; } - - /** - * Determine Record Format(s) - * - * @param Marc $record MARC record - * - * @return array Record formats - */ - public function getFormats(Marc $record): array - { - // Deduplicate list: - return array_values(array_unique($this->getFormatsAsList($record))); - } - - /** - * Determine Record Format - * - * @param Marc $record MARC record - * - * @return array First format - */ - public function getFormat(Marc $record): array - { - $result = $this->getFormatsAsList($record); - // Return the first format as an array: - return [reset($result)]; - } } diff --git a/src/RecordManager/Base/Record/MarcAuthority.php b/src/RecordManager/Base/Record/MarcAuthority.php index 445e4af1f..1b6887051 100644 --- a/src/RecordManager/Base/Record/MarcAuthority.php +++ b/src/RecordManager/Base/Record/MarcAuthority.php @@ -133,6 +133,16 @@ public function getOccupationIds(): array return $this->record->getFieldsSubfields('374', ['0']); } + /** + * Get use for headings + * + * @return array + */ + public function getUseForHeadings() + { + return $this->getAlternativeNames(['111', '411', '500', '510', '511']); + } + /** * Get fields of activity * @@ -192,16 +202,6 @@ protected function getHeading() return ''; } - /** - * Get use for headings - * - * @return array - */ - public function getUseForHeadings() - { - return $this->getAlternativeNames(['111', '411', '500', '510', '511']); - } - /** * Get related places * diff --git a/src/RecordManager/Base/Record/Qdc.php b/src/RecordManager/Base/Record/Qdc.php index 56699c701..29503aa5a 100644 --- a/src/RecordManager/Base/Record/Qdc.php +++ b/src/RecordManager/Base/Record/Qdc.php @@ -224,46 +224,6 @@ public function getMainAuthor() return trim((string)$this->doc->creator); } - /** - * Get primary authors - * - * @return array - */ - protected function getPrimaryAuthors() - { - $result = []; - foreach ($this->getValues('creator') as $author) { - $result[] - = $this->metadataUtils->stripTrailingPunctuation($author); - } - return $result; - } - - /** - * Get secondary authors - * - * @return array - */ - protected function getSecondaryAuthors() - { - $result = []; - foreach ($this->getValues('contributor') as $contributor) { - $result[] - = $this->metadataUtils->stripTrailingPunctuation($contributor); - } - return $result; - } - - /** - * Get corporate authors - * - * @return array - */ - protected function getCorporateAuthors() - { - return []; - } - /** * Dedup: Return unique IDs (control numbers) * @@ -392,6 +352,94 @@ public function getPageCount() return ''; } + /** + * Get topics. + * + * @return array + */ + public function getTopics() + { + return $this->getValues('subject'); + } + + /** + * Get descriptions as an associative array + * + * @return array + */ + public function getDescriptions(): array + { + $all = []; + $primary = ''; + $lang = $this->getDriverParam('defaultDisplayLanguage', 'en'); + foreach ($this->doc->description as $description) { + $trimmed = trim((string)$description); + if (!preg_match('/(^https?)|(^\d+\.\d+$)/', $trimmed)) { + $all[] = (string)$description; + if (!$primary) { + $descLang = (string)$description->attributes()->{'lang'}; + if ($descLang === $lang) { + $primary = $trimmed; + } + } + } + } + if (!$primary && $all) { + $primary = $all[0]; + } + return compact('primary', 'all'); + } + + /** + * Get series information + * + * @return array + */ + public function getSeries() + { + return []; + } + + /** + * Get primary authors + * + * @return array + */ + protected function getPrimaryAuthors() + { + $result = []; + foreach ($this->getValues('creator') as $author) { + $result[] + = $this->metadataUtils->stripTrailingPunctuation($author); + } + return $result; + } + + /** + * Get secondary authors + * + * @return array + */ + protected function getSecondaryAuthors() + { + $result = []; + foreach ($this->getValues('contributor') as $contributor) { + $result[] + = $this->metadataUtils->stripTrailingPunctuation($contributor); + } + return $result; + } + + /** + * Get corporate authors + * + * @return array + */ + protected function getCorporateAuthors() + { + return []; + } + /** * Get an array of all fields relevant to allfields search * @@ -472,44 +520,6 @@ protected function getLanguages() return $this->metadataUtils->normalizeLanguageStrings($languages); } - /** - * Get topics. - * - * @return array - */ - public function getTopics() - { - return $this->getValues('subject'); - } - - /** - * Get descriptions as an associative array - * - * @return array - */ - public function getDescriptions(): array - { - $all = []; - $primary = ''; - $lang = $this->getDriverParam('defaultDisplayLanguage', 'en'); - foreach ($this->doc->description as $description) { - $trimmed = trim((string)$description); - if (!preg_match('/(^https?)|(^\d+\.\d+$)/', $trimmed)) { - $all[] = (string)$description; - if (!$primary) { - $descLang = (string)$description->attributes()->{'lang'}; - if ($descLang === $lang) { - $primary = $trimmed; - } - } - } - } - if (!$primary && $all) { - $primary = $all[0]; - } - return compact('primary', 'all'); - } - /** * Get xml field values * @@ -532,16 +542,6 @@ protected function getValues($tag, array $attributes = []) return $values; } - /** - * Get series information - * - * @return array - */ - public function getSeries() - { - return []; - } - /** * Get hierarchy fields. Must be called after title is present in the array. * diff --git a/src/RecordManager/Base/ServiceManager/AbstractPluginManagerFactory.php b/src/RecordManager/Base/ServiceManager/AbstractPluginManagerFactory.php index e8a7ea255..ef365a7e7 100644 --- a/src/RecordManager/Base/ServiceManager/AbstractPluginManagerFactory.php +++ b/src/RecordManager/Base/ServiceManager/AbstractPluginManagerFactory.php @@ -46,31 +46,6 @@ */ class AbstractPluginManagerFactory implements FactoryInterface { - /** - * Determine the configuration key for the specified class name. - * - * @param string $requestedName Service being created - * - * @return string - * - * @throws ServiceNotCreatedException if an exception is raised when - * creating a service. - */ - public function getConfigKey($requestedName) - { - // Extract namespace of the plugin manager (chop off leading top-level - // namespace -- e.g. RecordManager\Base -- and trailing PluginManager class): - $parts = explode('\\', $requestedName); - if (count($parts) < 4 || array_pop($parts) !== 'PluginManager') { - throw new ServiceNotCreatedException( - "Cannot determine config key for $requestedName" - ); - } - array_shift($parts); - array_shift($parts); - return strtolower(implode('_', $parts)); - } - /** * Create an object * @@ -112,4 +87,29 @@ public function __invoke( $config['recordmanager']['plugin_managers'][$configKey] ); } + + /** + * Determine the configuration key for the specified class name. + * + * @param string $requestedName Service being created + * + * @return string + * + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + */ + public function getConfigKey($requestedName) + { + // Extract namespace of the plugin manager (chop off leading top-level + // namespace -- e.g. RecordManager\Base -- and trailing PluginManager class): + $parts = explode('\\', $requestedName); + if (count($parts) < 4 || array_pop($parts) !== 'PluginManager') { + throw new ServiceNotCreatedException( + "Cannot determine config key for $requestedName" + ); + } + array_shift($parts); + array_shift($parts); + return strtolower(implode('_', $parts)); + } } diff --git a/src/RecordManager/Base/Solr/SolrUpdater.php b/src/RecordManager/Base/Solr/SolrUpdater.php index bb4647498..1098139fb 100644 --- a/src/RecordManager/Base/Solr/SolrUpdater.php +++ b/src/RecordManager/Base/Solr/SolrUpdater.php @@ -1081,94 +1081,6 @@ function ($record) use ($handler, &$count) { $this->workerPoolManager->destroyWorkerPools(); } - /** - * Determine if processing dedup records is needed for the given source - * specification - * - * @param string $sourceId Source specification - * - * @return bool - */ - protected function needToProcessDedupRecords(string $sourceId): bool - { - if (!$sourceId) { - return true; - } - $sources = explode(',', $sourceId); - foreach ($sources as $source) { - $source = trim($source); - if ('' === $source) { - continue; - } - if (str_starts_with($source, '-')) { - return true; - } - if ($this->settings[$source]['dedup'] ?? false) { - return true; - } - } - - return false; - } - - /** - * Handle records processed by record workers - * - * @param bool $block Whether to block until all requests are completed - * @param bool $noCommit Whether to disable automatic commits - * - * @return void - */ - protected function handleRecords(bool $block, bool $noCommit): void - { - while ( - $this->workerPoolManager->checkForResults('record') - || $this->workerPoolManager->requestsPending('record') - ) { - while ($this->workerPoolManager->checkForResults('record')) { - $result = $this->workerPoolManager->getResult('record'); - $this->mergedComponents += $result['mergedComponents']; - foreach ($result['deleted'] as $id) { - ++$this->deletedRecords; - $this->bufferedDelete((string)$id); - } - foreach ($result['records'] as $record) { - ++$this->updatedRecords; - $this->bufferedUpdate($record, $noCommit); - } - } - if ($block) { - usleep(10); - } else { - break; - } - } - - // Check for results in the deduplicated record pool: - while ( - $this->workerPoolManager->checkForResults('dedup') - || $this->workerPoolManager->requestsPending('dedup') - ) { - while ($this->workerPoolManager->checkForResults('dedup')) { - $result = $this->workerPoolManager->getResult('dedup'); - $this->mergedComponents += $result['mergedComponents']; - foreach ($result['deleted'] as $id) { - ++$this->deletedRecords; - $this->bufferedDelete((string)$id); - } - foreach ($result['records'] as $record) { - ++$this->updatedRecords; - $this->bufferedUpdate($record, $noCommit); - } - } - if ($block) { - usleep(10); - } else { - break; - } - } - } - /** * Toggle field mappings * @@ -1703,6 +1615,169 @@ public function getLastUpdateStateKey(bool $datePerServer): string return $result; } + /** + * Make a JSON request to the Solr server + * + * Public visibility so that the workers can call this + * + * @param string $body The JSON request + * @param integer|null $timeout If specified, the HTTP call timeout in seconds + * + * @return void + */ + public function solrRequest($body, $timeout = null) + { + if (null === $this->request) { + $this->request + = $this->initSolrRequest(\HTTP_Request2::METHOD_POST, $timeout); + } + + if (!$this->waitForClusterStateOk()) { + throw new \Exception('Failed to check that the cluster state is ok'); + } + + $this->request->setHeader('Content-Type', 'application/json'); + $this->request->setBody($body); + + $response = null; + $maxTries = $this->maxUpdateTries; + for ($try = 1; $try <= $maxTries; $try++) { + try { + // @phpstan-ignore-next-line + if (!$this->waitForClusterStateOk()) { + throw new \Exception( + 'Failed to check that the cluster state is ok' + ); + } + $response = $this->request->send(); + } catch (\Exception $e) { + if ($try < $maxTries) { + $this->log->logWarning( + 'solrRequest', + 'Solr server request failed (' . $e->getMessage() + . "), retrying in {$this->updateRetryWait} seconds..." + ); + sleep($this->updateRetryWait); + continue; + } + throw HttpRequestException::fromException($e); + } + if ($try < $maxTries) { + $code = $response->getStatus(); + if ($code >= 300) { + $this->log->logWarning( + 'solrRequest', + "Solr server request failed ($code), retrying in " + . "{$this->updateRetryWait} seconds..." + . "Beginning of response: " + . substr($response->getBody(), 0, 1000) + ); + sleep($this->updateRetryWait); + continue; + } + } + break; + } + $code = null === $response ? 999 : $response->getStatus(); + if ($code >= 300) { + throw new HttpRequestException( + "Solr server request failed ($code). URL:\n" + . $this->config['Solr']['update_url'] + . "\nRequest:\n$body\n\nResponse:\n" + . (null !== $response ? $response->getBody() : ''), + $code + ); + } + } + + /** + * Determine if processing dedup records is needed for the given source + * specification + * + * @param string $sourceId Source specification + * + * @return bool + */ + protected function needToProcessDedupRecords(string $sourceId): bool + { + if (!$sourceId) { + return true; + } + $sources = explode(',', $sourceId); + foreach ($sources as $source) { + $source = trim($source); + if ('' === $source) { + continue; + } + if (str_starts_with($source, '-')) { + return true; + } + if ($this->settings[$source]['dedup'] ?? false) { + return true; + } + } + + return false; + } + + /** + * Handle records processed by record workers + * + * @param bool $block Whether to block until all requests are completed + * @param bool $noCommit Whether to disable automatic commits + * + * @return void + */ + protected function handleRecords(bool $block, bool $noCommit): void + { + while ( + $this->workerPoolManager->checkForResults('record') + || $this->workerPoolManager->requestsPending('record') + ) { + while ($this->workerPoolManager->checkForResults('record')) { + $result = $this->workerPoolManager->getResult('record'); + $this->mergedComponents += $result['mergedComponents']; + foreach ($result['deleted'] as $id) { + ++$this->deletedRecords; + $this->bufferedDelete((string)$id); + } + foreach ($result['records'] as $record) { + ++$this->updatedRecords; + $this->bufferedUpdate($record, $noCommit); + } + } + if ($block) { + usleep(10); + } else { + break; + } + } + + // Check for results in the deduplicated record pool: + while ( + $this->workerPoolManager->checkForResults('dedup') + || $this->workerPoolManager->requestsPending('dedup') + ) { + while ($this->workerPoolManager->checkForResults('dedup')) { + $result = $this->workerPoolManager->getResult('dedup'); + $this->mergedComponents += $result['mergedComponents']; + foreach ($result['deleted'] as $id) { + ++$this->deletedRecords; + $this->bufferedDelete((string)$id); + } + foreach ($result['records'] as $record) { + ++$this->updatedRecords; + $this->bufferedUpdate($record, $noCommit); + } + } + if ($block) { + usleep(10); + } else { + break; + } + } + } + /** * Initialize worker pool manager and the pools for processing records and * Solr updates @@ -2653,81 +2728,6 @@ protected function initSolrRequest($method, $timeout = null) return $request; } - /** - * Make a JSON request to the Solr server - * - * Public visibility so that the workers can call this - * - * @param string $body The JSON request - * @param integer|null $timeout If specified, the HTTP call timeout in seconds - * - * @return void - */ - public function solrRequest($body, $timeout = null) - { - if (null === $this->request) { - $this->request - = $this->initSolrRequest(\HTTP_Request2::METHOD_POST, $timeout); - } - - if (!$this->waitForClusterStateOk()) { - throw new \Exception('Failed to check that the cluster state is ok'); - } - - $this->request->setHeader('Content-Type', 'application/json'); - $this->request->setBody($body); - - $response = null; - $maxTries = $this->maxUpdateTries; - for ($try = 1; $try <= $maxTries; $try++) { - try { - // @phpstan-ignore-next-line - if (!$this->waitForClusterStateOk()) { - throw new \Exception( - 'Failed to check that the cluster state is ok' - ); - } - $response = $this->request->send(); - } catch (\Exception $e) { - if ($try < $maxTries) { - $this->log->logWarning( - 'solrRequest', - 'Solr server request failed (' . $e->getMessage() - . "), retrying in {$this->updateRetryWait} seconds..." - ); - sleep($this->updateRetryWait); - continue; - } - throw HttpRequestException::fromException($e); - } - if ($try < $maxTries) { - $code = $response->getStatus(); - if ($code >= 300) { - $this->log->logWarning( - 'solrRequest', - "Solr server request failed ($code), retrying in " - . "{$this->updateRetryWait} seconds..." - . "Beginning of response: " - . substr($response->getBody(), 0, 1000) - ); - sleep($this->updateRetryWait); - continue; - } - } - break; - } - $code = null === $response ? 999 : $response->getStatus(); - if ($code >= 300) { - throw new HttpRequestException( - "Solr server request failed ($code). URL:\n" - . $this->config['Solr']['update_url'] - . "\nRequest:\n$body\n\nResponse:\n" - . (null !== $response ? $response->getBody() : ''), - $code - ); - } - } - /** * Wait until SolrCloud cluster state is ok * diff --git a/src/RecordManager/Base/Utils/Logger.php b/src/RecordManager/Base/Utils/Logger.php index a98cb3bf7..1264f237f 100644 --- a/src/RecordManager/Base/Utils/Logger.php +++ b/src/RecordManager/Base/Utils/Logger.php @@ -254,6 +254,102 @@ public function logLevelToStr($level) return '???'; } + /** + * Write a message to the console with 'verbose' verbosity + * + * @param string|callable $msg Message or a function that returns the message + * @param bool $escape Whether the output should be escaped + * + * @return void + */ + public function writelnVerbose($msg, bool $escape = true): void + { + $this->writelnConsole($msg, OutputInterface::VERBOSITY_VERBOSE, $escape); + } + + /** + * Write a message to the console with 'very verbose' verbosity + * + * @param string|callable $msg Message or a function that returns the message + * @param bool $escape Whether the output should be escaped + * + * @return void + */ + public function writelnVeryVerbose($msg, bool $escape = true): void + { + $this + ->writelnConsole($msg, OutputInterface::VERBOSITY_VERY_VERBOSE, $escape); + } + + /** + * Write a message to the console with 'debug' verbosity + * + * @param string|callable $msg Message or a function that returns the message + * @param bool $escape Whether the output should be escaped + * + * @return void + */ + public function writelnDebug($msg, bool $escape = true): void + { + $this->writelnConsole($msg, OutputInterface::VERBOSITY_DEBUG, $escape); + } + + /** + * Write a message to the console + * + * @param string|callable $msg Message or a function that returns the + * message + * @param int $verbosity Verbosity level of the message (see + * OutputInterface) + * @param bool $escape Whether the output should be escaped + * + * @return void + */ + public function writelnConsole( + $msg, + $verbosity = OutputInterface::VERBOSITY_NORMAL, + bool $escape = true + ): void { + if (!$this->consoleOutput) { + return; + } + if ($this->consoleOutput->getVerbosity() >= $verbosity) { + $message = is_callable($msg) ? $msg() : $msg; + if ($escape) { + $message = OutputFormatter::escape($message); + } + $this->consoleOutput->writeln($message); + } + } + + /** + * Write a message to the console without a line feed + * + * @param string|callable $msg Message or a function that returns the + * message + * @param int $verbosity Verbosity level of the message (see + * OutputInterface) + * @param bool $escape Whether the output should be escaped + * + * @return void + */ + public function writeConsole( + $msg, + $verbosity = OutputInterface::VERBOSITY_NORMAL, + bool $escape = true + ): void { + if (!$this->consoleOutput) { + return; + } + if ($this->consoleOutput->getVerbosity() >= $verbosity) { + $message = is_callable($msg) ? $msg() : $msg; + if ($escape) { + $message = OutputFormatter::escape($message); + } + $this->consoleOutput->write($message); + } + } + /** * Write a message to the log * @@ -354,100 +450,4 @@ protected function log( ); } } - - /** - * Write a message to the console with 'verbose' verbosity - * - * @param string|callable $msg Message or a function that returns the message - * @param bool $escape Whether the output should be escaped - * - * @return void - */ - public function writelnVerbose($msg, bool $escape = true): void - { - $this->writelnConsole($msg, OutputInterface::VERBOSITY_VERBOSE, $escape); - } - - /** - * Write a message to the console with 'very verbose' verbosity - * - * @param string|callable $msg Message or a function that returns the message - * @param bool $escape Whether the output should be escaped - * - * @return void - */ - public function writelnVeryVerbose($msg, bool $escape = true): void - { - $this - ->writelnConsole($msg, OutputInterface::VERBOSITY_VERY_VERBOSE, $escape); - } - - /** - * Write a message to the console with 'debug' verbosity - * - * @param string|callable $msg Message or a function that returns the message - * @param bool $escape Whether the output should be escaped - * - * @return void - */ - public function writelnDebug($msg, bool $escape = true): void - { - $this->writelnConsole($msg, OutputInterface::VERBOSITY_DEBUG, $escape); - } - - /** - * Write a message to the console - * - * @param string|callable $msg Message or a function that returns the - * message - * @param int $verbosity Verbosity level of the message (see - * OutputInterface) - * @param bool $escape Whether the output should be escaped - * - * @return void - */ - public function writelnConsole( - $msg, - $verbosity = OutputInterface::VERBOSITY_NORMAL, - bool $escape = true - ): void { - if (!$this->consoleOutput) { - return; - } - if ($this->consoleOutput->getVerbosity() >= $verbosity) { - $message = is_callable($msg) ? $msg() : $msg; - if ($escape) { - $message = OutputFormatter::escape($message); - } - $this->consoleOutput->writeln($message); - } - } - - /** - * Write a message to the console without a line feed - * - * @param string|callable $msg Message or a function that returns the - * message - * @param int $verbosity Verbosity level of the message (see - * OutputInterface) - * @param bool $escape Whether the output should be escaped - * - * @return void - */ - public function writeConsole( - $msg, - $verbosity = OutputInterface::VERBOSITY_NORMAL, - bool $escape = true - ): void { - if (!$this->consoleOutput) { - return; - } - if ($this->consoleOutput->getVerbosity() >= $verbosity) { - $message = is_callable($msg) ? $msg() : $msg; - if ($escape) { - $message = OutputFormatter::escape($message); - } - $this->consoleOutput->write($message); - } - } } diff --git a/src/RecordManager/Base/Utils/MetadataUtils.php b/src/RecordManager/Base/Utils/MetadataUtils.php index 2defe12a6..0e25f4a81 100644 --- a/src/RecordManager/Base/Utils/MetadataUtils.php +++ b/src/RecordManager/Base/Utils/MetadataUtils.php @@ -425,24 +425,6 @@ public function normalizeKey($str, $form = 'NFKC') return mb_strtolower(trim($str), 'UTF-8'); } - /** - * Get the transliterator for folding keys - * - * @return ?\Transliterator - */ - protected function getKeyFoldingTransliterator(): ?\Transliterator - { - if (!$this->keyFoldingRules) { - return null; - } - if (null === $this->keyFoldingTransliterator) { - $this->keyFoldingTransliterator = \Transliterator::createFromRules( - $this->keyFoldingRules - ); - } - return $this->keyFoldingTransliterator; - } - /** * Normalize an ISBN to ISBN-13 without dashes * @@ -1301,6 +1283,24 @@ public function getAuthorInitials(string $authorName): string return trim($result); } + /** + * Get the transliterator for folding keys + * + * @return ?\Transliterator + */ + protected function getKeyFoldingTransliterator(): ?\Transliterator + { + if (!$this->keyFoldingRules) { + return null; + } + if (null === $this->keyFoldingTransliterator) { + $this->keyFoldingTransliterator = \Transliterator::createFromRules( + $this->keyFoldingRules + ); + } + return $this->keyFoldingTransliterator; + } + /** * Read a list file into an array * diff --git a/tests/recordmanager.php-cs-fixer.php b/tests/recordmanager.php-cs-fixer.php index b9c6ba8f9..61b463814 100644 --- a/tests/recordmanager.php-cs-fixer.php +++ b/tests/recordmanager.php-cs-fixer.php @@ -73,6 +73,7 @@ 'no_whitespace_in_blank_line' => true, 'non_printable_character' => true, 'ordered_imports' => true, + 'ordered_class_elements' => true, 'phpdoc_no_access' => true, 'php_unit_method_casing' => true, 'pow_to_exponentiation' => true,