diff --git a/.github/phpstan.neon b/.github/phpstan.neon new file mode 100644 index 000000000..fa63a3481 --- /dev/null +++ b/.github/phpstan.neon @@ -0,0 +1,31 @@ +parameters: + ignoreErrors: + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::countByPid\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findByIsListed\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findByIsSortable\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByFeUserId\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByIndexName\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByPid\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByRecordId\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByRoot\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneBySessionId\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByType\(\)\.#' + - '#Call to an undefined method Kitodo\\Dlf\\Domain\\Repository\\[a-zA-Z]+Repository::findOneByUid\(\)\.#' + - '#Call to an undefined method Psr\\Http\\Message\\RequestFactoryInterface::request\(\)\.#' + - '#Call to an undefined method Solarium\\Core\\Query\\DocumentInterface::setField\(\)\.#' + - '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\IiifResourceInterface::getHeight\(\)\.#' + - '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\IiifResourceInterface::getWidth\(\)\.#' + - '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\IiifResourceInterface::getPossibleTextAnnotationContainers\(\)\.#' + - '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\IiifResourceInterface::getTextAnnotations\(\)\.#' + - '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\ManifestInterface::getOriginalJsonArray\(\)\.#' + - '#Call to an undefined method Ubl\\Iiif\\Presentation\\Common\\Model\\Resources\\RangeInterface::getMemberRangesAndRanges\(\)\.#' + - '#Constant LOG_SEVERITY_ERROR not found\.#' + - '#Constant LOG_SEVERITY_NOTICE not found\.#' + - '#Constant LOG_SEVERITY_WARNING not found\.#' + - '#Constant TYPO3_MODE not found\.#' + level: 5 + paths: + - ../Classes/ + excludePaths: + - ../Classes/Controller/OaiPmhController.php + treatPhpDocTypesAsCertain: false diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 000000000..8d8861501 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,25 @@ +name: PHPStan + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + phpstan: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Install dependencies + uses: php-actions/composer@v6 + with: + command: update + php_version: "7.4" + + - name: PHPStan Static Analysis + uses: php-actions/phpstan@v3 + with: + configuration: ./.github/phpstan.neon diff --git a/Classes/Api/Orcid/Client.php b/Classes/Api/Orcid/Client.php index b2cb38c10..3c814b70c 100644 --- a/Classes/Api/Orcid/Client.php +++ b/Classes/Api/Orcid/Client.php @@ -31,12 +31,12 @@ class Client /** * @var string constant for API hostname **/ - const HOSTNAME = 'orcid.org'; + const HOSTNAME = 'orcid.org'; /** * @var string constant for API version **/ - const VERSION = '3.0'; + const VERSION = '3.0'; /** * @access protected diff --git a/Classes/Api/Viaf/Client.php b/Classes/Api/Viaf/Client.php index b4f82e01f..dbe13163c 100644 --- a/Classes/Api/Viaf/Client.php +++ b/Classes/Api/Viaf/Client.php @@ -35,8 +35,6 @@ class Client protected Logger $logger; /** - * The VIAF API endpoint - * * @access private * @var string The VIAF API endpoint **/ @@ -110,7 +108,7 @@ public function getData() * * @return string **/ - private function getApiEndpoint(): string + private function getApiEndpoint(): string { return $this->viafUrl . '/' . $this->endpoint; } diff --git a/Classes/Command/BaseCommand.php b/Classes/Command/BaseCommand.php index 24e1108e6..922cea9c4 100644 --- a/Classes/Command/BaseCommand.php +++ b/Classes/Command/BaseCommand.php @@ -191,7 +191,7 @@ protected function getSolrCores(int $pageId): array * * @access protected * - * @param Document $doc The document instance + * @param Document $document The document instance * * @return bool true on success, false otherwise */ diff --git a/Classes/Command/HarvestCommand.php b/Classes/Command/HarvestCommand.php index c9293e62f..e62fa04df 100644 --- a/Classes/Command/HarvestCommand.php +++ b/Classes/Command/HarvestCommand.php @@ -187,6 +187,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ( !is_array($input->getOption('set')) && !empty($input->getOption('set')) + && !empty($oai) ) { $setsAvailable = $oai->listSets(); foreach ($setsAvailable as $setAvailable) { @@ -201,9 +202,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int } } + $identifiers = []; // Get OAI record identifiers to process. try { - $identifiers = $oai->listIdentifiers('mets', $from, $until, $set); + if (!empty($oai)) { + $identifiers = $oai->listIdentifiers('mets', $from, $until, $set); + } else { + $io->error('ERROR: OAI interface does not exist.'); + } } catch (BaseoaipmhException $exception) { $this->handleOaiError($exception, $io); } diff --git a/Classes/Common/AbstractDocument.php b/Classes/Common/AbstractDocument.php index bc13d3ef7..3876d53cb 100644 --- a/Classes/Common/AbstractDocument.php +++ b/Classes/Common/AbstractDocument.php @@ -391,7 +391,7 @@ public abstract function getFileMimeType(string $id): string; * @param array $settings * @param bool $forceReload Force reloading the document instead of returning the cached instance * - * @return MetsDocument|IiifManifest|null Instance of this class, either MetsDocument or IiifManifest + * @return AbstractDocument|null Instance of this class, either MetsDocument or IiifManifest */ public static function &getInstance(string $location, array $settings = [], bool $forceReload = false) { @@ -441,9 +441,11 @@ public static function &getInstance(string $location, array $settings = [], bool // Sanitize input. $pid = max(intval($settings['storagePid']), 0); if ($documentFormat == 'METS') { - $instance = new MetsDocument($location, $pid, $xml); + $instance = new MetsDocument($pid, $location, $xml, $settings); } elseif ($documentFormat == 'IIIF') { - $instance = new IiifManifest($location, $pid, $iiif); + // TODO: Parameter $preloadedDocument of class Kitodo\Dlf\Common\IiifManifest constructor expects SimpleXMLElement|Ubl\Iiif\Presentation\Common\Model\Resources\IiifResourceInterface, Ubl\Iiif\Presentation\Common\Model\AbstractIiifEntity|null given. + // @phpstan-ignore-next-line + $instance = new IiifManifest($pid, $location, $iiif); } if (!is_null($instance)) { @@ -750,10 +752,11 @@ public function getStructureDepth(string $logId) * @abstract * * @param string $location The location URL of the XML file to parse + * @param array $settings The extension settings * * @return void */ - protected abstract function init(string $location): void; + abstract protected function init(string $location, array $settings): void; /** * Reuse any document object that might have been already loaded to determine whether document is METS or IIIF @@ -1086,6 +1089,8 @@ protected function _getRootId(): int { if (!$this->rootIdLoaded) { if ($this->parentId) { + // TODO: Parameter $location of static method AbstractDocument::getInstance() expects string, int|int<1, max> given. + // @phpstan-ignore-next-line $parent = self::getInstance($this->parentId, ['storagePid' => $this->pid]); $this->rootId = $parent->rootId; } @@ -1168,20 +1173,19 @@ protected function _setCPid(int $value): void * * @access protected * - * @param string $location The location URL of the XML file to parse * @param int $pid If > 0, then only document with this PID gets loaded + * @param string $location The location URL of the XML file to parse * @param \SimpleXMLElement|IiifResourceInterface $preloadedDocument Either null or the \SimpleXMLElement * or IiifResourceInterface that has been loaded to determine the basic document format. * * @return void */ - protected function __construct(string $location, int $pid, $preloadedDocument) + protected function __construct(int $pid, string $location, $preloadedDocument, array $settings = []) { $this->pid = $pid; $this->setPreloadedDocument($preloadedDocument); - $this->init($location); + $this->init($location, $settings); $this->establishRecordId($pid); - return; } /** diff --git a/Classes/Common/Helper.php b/Classes/Common/Helper.php index b3c9698d7..1f32b452a 100644 --- a/Classes/Common/Helper.php +++ b/Classes/Common/Helper.php @@ -24,9 +24,6 @@ use TYPO3\CMS\Core\Utility\ArrayUtility; use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\MathUtility; -use TYPO3\CMS\Extbase\Configuration\ConfigurationManager; -use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface; -use TYPO3\CMS\Extbase\Object\ObjectManager; use TYPO3\CMS\Extbase\Utility\LocalizationUtility; use TYPO3\CMS\Core\Domain\Repository\PageRepository; @@ -149,6 +146,8 @@ public static function checkIdentifier(string $id, string $type): bool if (!preg_match('/\d{8}-\d{1}/i', $id)) { return false; } elseif ($checksum == 10) { + //TODO: Binary operation "+" between string and 1 results in an error. + // @phpstan-ignore-next-line return self::checkIdentifier(($digits + 1) . substr($id, -2, 2), 'SWD'); } elseif (substr($id, -1, 1) != $checksum) { return false; @@ -218,11 +217,10 @@ public static function decrypt(string $encrypted) * * @static * - * @param string $content: content of file to read + * @param mixed $content content of file to read * * @return \SimpleXMLElement|false */ - //TODO: make sure that this is called only with string then update public static function getXmlFileAsString($content) { // Don't make simplexml_load_string throw (when $content is an array @@ -605,7 +603,7 @@ public static function getURN(string $base, string $id): string for ($i = 0, $j = strlen($digits); $i < $j; $i++) { $checksum += ($i + 1) * intval(substr($digits, $i, 1)); } - $checksum = substr(intval($checksum / intval(substr($digits, -1, 1))), -1, 1); + $checksum = substr((string) floor($checksum / (int) substr($digits, -1, 1)), -1, 1); return $base . $id . $checksum; } @@ -723,7 +721,7 @@ public static function translate(string $indexName, string $table, string $pid): // Check if "index_name" is an UID. if (MathUtility::canBeInterpretedAsInteger($indexName)) { - $indexName = self::getIndexNameFromUid($indexName, $table, $pid); + $indexName = self::getIndexNameFromUid((int) $indexName, $table, $pid); } /* $labels already contains the translated content element, but with the index_name of the translated content element itself * and not with the $indexName of the original that we receive here. So we have to determine the index_name of the diff --git a/Classes/Common/IiifManifest.php b/Classes/Common/IiifManifest.php index 9a1dd3a6b..4554d75a8 100644 --- a/Classes/Common/IiifManifest.php +++ b/Classes/Common/IiifManifest.php @@ -408,6 +408,8 @@ public function getFileLocation(string $id): string $resource = $this->iiif->getContainedResourceById($id); if (isset($resource)) { if ($resource instanceof CanvasInterface) { + // TODO: Cannot call method getSingleService() on array. + // @phpstan-ignore-next-line return (!empty($resource->getImageAnnotations()) && $resource->getImageAnnotations()->getSingleService() != null) ? $resource->getImageAnnotations()[0]->getSingleService()->getId() : $id; } elseif ($resource instanceof ContentResourceInterface) { return $resource->getSingleService() != null && $resource->getSingleService() instanceof Service ? $resource->getSingleService()->getId() : $id; @@ -458,6 +460,8 @@ public function getLogicalStructure(string $id, bool $recursive = false): array } else { $logUnits[] = $this->iiif; } + // TODO: Variable $logUnits in empty() always exists and is not falsy. + // @phpstan-ignore-next-line if (!empty($logUnits)) { if (!$recursive) { $details = $this->getLogicalStructureInfo($logUnits[0]); @@ -504,7 +508,7 @@ protected function getLogicalStructureInfo(IiifResourceInterface $resource, bool } $details['thumbnailId'] = $resource->getThumbnailUrl(); $details['points'] = ''; - // Load strucural mapping + // Load structural mapping $this->_getSmLinks(); // Load physical structure. $this->_getPhysicalStructure(); @@ -516,7 +520,7 @@ protected function getLogicalStructureInfo(IiifResourceInterface $resource, bool $startCanvas = $resource->getStartCanvasOrFirstCanvas(); $canvases = $resource->getAllCanvases(); } - if ($startCanvas != null) { + if (isset($startCanvas)) { $details['pagination'] = $startCanvas->getLabel(); $startCanvasIndex = array_search($startCanvas, $this->iiif->getDefaultCanvases()); if ($startCanvasIndex !== false) { @@ -681,8 +685,12 @@ public function getMetadata(string $id, int $cPid = 0): array $resArray['format'] > 0 && !empty($resArray['xpath_sorting']) && ($values = $iiifResource->jsonPath($resArray['xpath_sorting']) != null) ) { + // TODO: Call to function is_string() with true will always evaluate to false. + // @phpstan-ignore-next-line if (is_string($values)) { $metadata[$resArray['index_name'] . '_sorting'][0] = [trim((string) $values)]; + // TODO: Instanceof between true and Flow\JSONPath\JSONPath will always evaluate to false. + // @phpstan-ignore-next-line } elseif ($values instanceof JSONPath && is_array($values->data()) && count($values->data()) > 1) { $metadata[$resArray['index_name']] = []; foreach ($values->data() as $value) { @@ -836,7 +844,7 @@ public function getIiif(): IiifResourceInterface /** * @see AbstractDocument::init() */ - protected function init(string $location): void + protected function init(string $location, array $settings = []): void { $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class); } diff --git a/Classes/Common/Indexer.php b/Classes/Common/Indexer.php index 083550ffb..f7102281d 100644 --- a/Classes/Common/Indexer.php +++ b/Classes/Common/Indexer.php @@ -532,8 +532,8 @@ protected static function processPhysical(Document $document, int $page, array $ protected static function solrConnect(int $core, int $pid = 0): bool { // Get Solr instance. - // Connect to Solr server. $solr = Solr::getInstance($core); + // Connect to Solr server. if ($solr->ready) { self::$solr = $solr; // Load indexing configuration if needed. diff --git a/Classes/Common/MetadataInterface.php b/Classes/Common/MetadataInterface.php index 5ff6f624e..1de7ca723 100644 --- a/Classes/Common/MetadataInterface.php +++ b/Classes/Common/MetadataInterface.php @@ -31,8 +31,9 @@ interface MetadataInterface * * @param \SimpleXMLElement $xml The XML to extract the metadata from * @param array &$metadata The metadata array to fill + * @param bool $useExternalApis true if external APIs should be called, false otherwise * * @return void */ - public function extractMetadata(\SimpleXMLElement $xml, array &$metadata): void; + public function extractMetadata(\SimpleXMLElement $xml, array &$metadata, bool $useExternalApis): void; } diff --git a/Classes/Common/MetsDocument.php b/Classes/Common/MetsDocument.php index 613c32440..8886ef4e5 100644 --- a/Classes/Common/MetsDocument.php +++ b/Classes/Common/MetsDocument.php @@ -137,6 +137,12 @@ final class MetsDocument extends AbstractDocument */ protected string $parentHref = ''; + /** + * @access protected + * @var array the extension settings + */ + protected array $settings = []; + /** * This adds metadata from METS structural map to metadata array. * @@ -467,7 +473,7 @@ public function getMetadata(string $id, int $cPid = 0): array class_exists($class) && ($obj = GeneralUtility::makeInstance($class)) instanceof MetadataInterface ) { - $obj->extractMetadata($this->mdSec[$dmdId]['xml'], $metadata); + $obj->extractMetadata($this->mdSec[$dmdId]['xml'], $metadata, $this->settings['useExternalApisForMetadata']); } else { $this->logger->warning('Invalid class/method "' . $class . '->extractMetadata()" for metadata format "' . $this->mdSec[$dmdId]['type'] . '"'); } @@ -711,9 +717,10 @@ public function getStructureDepth(string $logId) /** * @see AbstractDocument::init() */ - protected function init(string $location): void + protected function init(string $location, array $settings): void { $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(get_class($this)); + $this->settings = $settings; // Get METS node from XML file. $this->registerNamespaces($this->xml); $mets = $this->xml->xpath('//mets:mets'); @@ -1221,7 +1228,7 @@ public function __wakeup(): void $this->asXML = ''; $this->xml = $xml; // Rebuild the unserializable properties. - $this->init(''); + $this->init('', $this->settings); } else { $this->logger = GeneralUtility::makeInstance(LogManager::class)->getLogger(static::class); $this->logger->error('Could not load XML after deserialization'); diff --git a/Classes/Common/Solr/SearchResult/Highlight.php b/Classes/Common/Solr/SearchResult/Highlight.php index a484c5ac4..b3d5da963 100644 --- a/Classes/Common/Solr/SearchResult/Highlight.php +++ b/Classes/Common/Solr/SearchResult/Highlight.php @@ -29,12 +29,6 @@ class Highlight */ private string $id; - /** - * @access private - * @var int The parent region's identifier - */ - private int $parentRegionId; - /** * @access private * @var int The horizontal beginning position of found highlight @@ -70,7 +64,8 @@ class Highlight */ public function __construct(array $highlight) { - $this->parentRegionId = $highlight['parentRegionIdx']; + // there is also possibility to access parentRegionIdx + // $this->parentRegionId = $highlight['parentRegionIdx']; $this->xBeginPosition = $highlight['ulx']; $this->xEndPosition = $highlight['lrx']; $this->yBeginPosition = $highlight['uly']; diff --git a/Classes/Common/Solr/Solr.php b/Classes/Common/Solr/Solr.php index 97703a9cd..c3bb6e0d6 100644 --- a/Classes/Common/Solr/Solr.php +++ b/Classes/Common/Solr/Solr.php @@ -528,7 +528,7 @@ public function __get(string $var) || !method_exists($this, $method) ) { $this->logger->warning('There is no getter function for property "' . $var . '"'); - return; + return null; } else { return $this->$method(); } diff --git a/Classes/Common/Solr/SolrSearch.php b/Classes/Common/Solr/SolrSearch.php index ea3b826a9..a76a78f4f 100644 --- a/Classes/Common/Solr/SolrSearch.php +++ b/Classes/Common/Solr/SolrSearch.php @@ -210,6 +210,7 @@ public function offsetExists($offset): bool * * @return mixed */ + #[\ReturnTypeWillChange] public function offsetGet($offset) { $idx = $this->result['document_keys'][$offset]; @@ -361,6 +362,7 @@ public function prepare() $params = []; $matches = []; $fields = Solr::getFields(); + $query = ''; // Set search query. if ( @@ -724,7 +726,7 @@ protected function searchSolr($parameters = [], $enableCache = true) // return the coordinates of highlighted search as absolute coordinates $solrRequest->addParam('hl.ocr.absoluteHighlights', 'on'); // max amount of snippets for a single page - $solrRequest->addParam('hl.snippets', 20); + $solrRequest->addParam('hl.snippets', '20'); // we store the fulltext on page level and can disable this option $solrRequest->addParam('hl.ocr.trackPages', 'off'); } @@ -737,6 +739,8 @@ protected function searchSolr($parameters = [], $enableCache = true) } $result = $solr->service->createResult($selectQuery, $response); + // TODO: Call to an undefined method Solarium\Core\Query\Result\ResultInterface::getGrouping(). + // @phpstan-ignore-next-line $uidGroup = $result->getGrouping()->getGroup('uid'); $resultSet['numberOfToplevels'] = $uidGroup->getNumberOfGroups(); $resultSet['numFound'] = $uidGroup->getMatches(); diff --git a/Classes/Common/Solr/SolrSearchQuery.php b/Classes/Common/Solr/SolrSearchQuery.php index 1ab24a327..33419e226 100644 --- a/Classes/Common/Solr/SolrSearchQuery.php +++ b/Classes/Common/Solr/SolrSearchQuery.php @@ -55,6 +55,8 @@ public function __construct($solrSearch) $this->limit = count($solrSearch); } + // this class contains a lot of methods which are inherited but not implemented + // @phpstan-ignore-next-line public function getSource() {} /** @@ -66,6 +68,8 @@ public function getSource() {} * * @return array */ + // TODO: Return type (array) of method SolrSearchQuery::execute() should be compatible with return type (iterable&TYPO3\CMS\Extbase\Persistence\QueryResultInterface) of method TYPO3\CMS\Extbase\Persistence\QueryInterface::execute() + // @phpstan-ignore-next-line public function execute($returnRawQueryResult = false) { $this->solrSearch->submit($this->offset, $this->limit); @@ -79,6 +83,7 @@ public function execute($returnRawQueryResult = false) return $result; } + // @phpstan-ignore-next-line public function setOrderings(array $orderings) {} /** @@ -111,27 +116,42 @@ public function setOffset($offset): SolrSearchQuery return $this; } + // @phpstan-ignore-next-line public function matching($constraint) {} + // @phpstan-ignore-next-line public function logicalAnd($constraint1) {} + // @phpstan-ignore-next-line public function logicalOr($constraint1) {} + // @phpstan-ignore-next-line public function logicalNot(ConstraintInterface $constraint) {} + // @phpstan-ignore-next-line public function equals($propertyName, $operand, $caseSensitive = true) {} + // @phpstan-ignore-next-line public function like($propertyName, $operand) {} + // @phpstan-ignore-next-line public function contains($propertyName, $operand) {} + // @phpstan-ignore-next-line public function in($propertyName, $operand) {} + // @phpstan-ignore-next-line public function lessThan($propertyName, $operand) {} + // @phpstan-ignore-next-line public function lessThanOrEqual($propertyName, $operand) {} + // @phpstan-ignore-next-line public function greaterThan($propertyName, $operand) {} + // @phpstan-ignore-next-line public function greaterThanOrEqual($propertyName, $operand) {} + // @phpstan-ignore-next-line public function getType() {} public function setQuerySettings(QuerySettingsInterface $querySettings) {} + // @phpstan-ignore-next-line public function getQuerySettings() {} public function count() - { + {// @phpstan-ignore-next-line // TODO? } + // @phpstan-ignore-next-line public function getOrderings() {} /** @@ -158,8 +178,10 @@ public function getOffset(): int return $this->offset; } + // @phpstan-ignore-next-line public function getConstraint() {} public function isEmpty($propertyName) {} public function setSource(SourceInterface $source) {} + // @phpstan-ignore-next-line public function getStatement() {} } diff --git a/Classes/Controller/AbstractController.php b/Classes/Controller/AbstractController.php index d946e2cc2..5baf989c1 100644 --- a/Classes/Controller/AbstractController.php +++ b/Classes/Controller/AbstractController.php @@ -59,9 +59,9 @@ public function injectDocumentRepository(DocumentRepository $documentRepository) /** * @access protected - * @var Document This holds the current document + * @var Document|null This holds the current document */ - protected Document $document; + protected ?Document $document; /** * @access protected diff --git a/Classes/Controller/Backend/NewTenantController.php b/Classes/Controller/Backend/NewTenantController.php index c85b941c5..3d461878f 100644 --- a/Classes/Controller/Backend/NewTenantController.php +++ b/Classes/Controller/Backend/NewTenantController.php @@ -436,17 +436,6 @@ public function indexAction(): void $this->view->assign('recordInfos', $recordInfos); } - /** - * Error function - there is nothing to do at the moment. - * - * @access public - * - * @return void - */ - public function errorAction() - { - } - /** * Get language label for given key and language. * diff --git a/Classes/Controller/BasketController.php b/Classes/Controller/BasketController.php index d5242e439..e1ba0f8dc 100644 --- a/Classes/Controller/BasketController.php +++ b/Classes/Controller/BasketController.php @@ -274,6 +274,8 @@ protected function getBasketData(): Basket */ protected function getEntry(array $data): array { + // TODO: Call to function is_object() with array will always evaluate to false. + // @phpstan-ignore-next-line if (is_object($data)) { $data = get_object_vars($data); } @@ -330,10 +332,14 @@ protected function getDocumentData(int $id, array $data) { // get document instance to load further information $this->loadDocument((int) $id); - if ($this->document) { + if (isset($this->document)) { // replace url param placeholder + // TODO: Parameter #2 $replace of function str_replace expects array|string, int given. + // @phpstan-ignore-next-line $urlParams = str_replace("##page##", (int) $data['page'], $this->settings['pdfparams']); $urlParams = str_replace("##docId##", $this->document->getRecordId(), $urlParams); + // TODO: Parameter #2 $replace of function str_replace expects array|string, int given. + // @phpstan-ignore-next-line $urlParams = str_replace("##startpage##", (int) $data['startpage'], $urlParams); if ($data['startpage'] != $data['endpage']) { $urlParams = str_replace("##endpage##", $data['endpage'] === "" ? "" : (int) $data['endpage'], $urlParams); @@ -450,6 +456,8 @@ protected function addToBasket(array $piVars, Basket $basket): ?Basket if (!in_array($arrayKey, $items)) { $items[$arrayKey] = $documentItem; // replace url param placeholder + // TODO: Parameter #2 $replace of function str_replace expects array|string, int given. + // @phpstan-ignore-next-line $pdfParams = str_replace("##startpage##", $documentItem['startpage'], $this->settings['pdfparams']); $pdfParams = str_replace("##docId##", $this->document->getRecordId(), $pdfParams); $pdfParams = str_replace("##startx##", $documentItem['startX'], $pdfParams); diff --git a/Classes/Controller/CalendarController.php b/Classes/Controller/CalendarController.php index c19596039..fea47290d 100644 --- a/Classes/Controller/CalendarController.php +++ b/Classes/Controller/CalendarController.php @@ -81,10 +81,8 @@ public function mainAction(): void case 'newspaper': case 'ephemera': $this->forward('years', null, null, $this->requestData); - break; case 'year': $this->forward('calendar', null, null, $this->requestData); - break; case 'issue': default: break; @@ -289,10 +287,14 @@ public function yearsAction(): void $max = $yearArray[count($yearArray) - 1]['title']; // if we have an actual documentId it should be used, otherwise leave empty for ($i = 0; $i < $max - $min + 1; $i++) { + // TODO: Binary operation "+" between (array|string) and int<0, max> results in an error. + // @phpstan-ignore-next-line $key = array_search($min + $i, array_column($yearArray, 'title')); if (is_int($key)) { $yearFilled[] = $yearArray[$key]; } else { + // TODO: Binary operation "+" between (array|string) and int<0, max> results in an error. + // @phpstan-ignore-next-line $yearFilled[] = ['title' => $min+$i, 'documentId' => '']; } } diff --git a/Classes/Controller/CollectionController.php b/Classes/Controller/CollectionController.php index 4d332d98f..d329ffac1 100644 --- a/Classes/Controller/CollectionController.php +++ b/Classes/Controller/CollectionController.php @@ -137,6 +137,8 @@ public function listAction(): void // Generate random but unique array key taking priority into account. do { + //TODO: Offset 'priority' does not exist on array{titles: array, volumes: array}. + // @phpstan-ignore-next-line $_key = ($collectionInfo['priority'] * 1000) + mt_rand(0, 1000); } while (!empty($processedCollections[$_key])); diff --git a/Classes/Controller/ListViewController.php b/Classes/Controller/ListViewController.php index fc882db5e..6a7e497e2 100644 --- a/Classes/Controller/ListViewController.php +++ b/Classes/Controller/ListViewController.php @@ -83,7 +83,7 @@ public function mainAction(): void $collection = null; if ($this->searchParams['collection']) { foreach(explode(',', $this->searchParams['collection']) as $collectionEntry) { - $collection[] = $this->collectionRepository->findByUid($collectionEntry); + $collection[] = $this->collectionRepository->findByUid((int) $collectionEntry); } } @@ -92,6 +92,8 @@ public function mainAction(): void if (empty($currentPage)) { $currentPage = 1; } + //TODO: Undefined variable: $widgetPage + // @phpstan-ignore-next-line $GLOBALS['TSFE']->fe_user->setKey('ses', 'widgetPage', $widgetPage); // get all sortable metadata records @@ -103,6 +105,7 @@ public function mainAction(): void $solrResults = null; $numResults = 0; if (is_array($this->searchParams) && !empty($this->searchParams)) { + // @phpstan-ignore-next-line $solrResults = $this->documentRepository->findSolrByCollection($collection ? : null, $this->settings, $this->searchParams, $listedMetadata); $numResults = $solrResults->getNumFound(); diff --git a/Classes/Controller/MetadataController.php b/Classes/Controller/MetadataController.php index ec49faf99..008ebf024 100644 --- a/Classes/Controller/MetadataController.php +++ b/Classes/Controller/MetadataController.php @@ -116,12 +116,14 @@ public function mainAction(): void $metadata = $this->getMetadata(); $topLevelId = $this->currentDocument->toplevelId; // Get titledata? - if (empty($metadata) || ($this->settings['rootline'] == 1 && $metadata[0]['_id'] != $topLevelId)) { + if (!$metadata || ($this->settings['rootline'] == 1 && $metadata[0]['_id'] != $topLevelId)) { + // @phpstan-ignore-next-line $data = $useOriginalIiifManifestMetadata ? $this->currentDocument->getManifestMetadata($topLevelId, $this->settings['storagePid']) : $this->currentDocument->getTitledata($this->settings['storagePid']); $data['_id'] = $topLevelId; array_unshift($metadata, $data); } - if (empty($metadata)) { + // @phpstan-ignore-next-line + if (!$metadata) { $this->logger->warning('No metadata found for document with UID ' . $this->document->getUid()); return; } @@ -229,6 +231,8 @@ private function buildIiifData(array $metadata): array private function buildIiifDataGroup(string $label, string $value): array { // NOTE: Labels are to be escaped in Fluid template + // TODO: Variable $scheme might not be defined. + // @phpstan-ignore-next-line if (IRI::isAbsoluteIri($value) && ($scheme = (new IRI($value))->getScheme()) == 'http' || $scheme == 'https') { //TODO: should really label be converted to empty string if equal to value? $label = $value == $label ? '' : $label; @@ -431,6 +435,7 @@ private function getMetadataForIds(array $id, array $metadata): array $useOriginalIiifManifestMetadata = $this->settings['originalIiifMetadata'] == 1 && $this->currentDocument instanceof IiifManifest; foreach ($id as $sid) { if ($useOriginalIiifManifestMetadata) { + // @phpstan-ignore-next-line $data = $this->currentDocument->getManifestMetadata($sid, $this->settings['storagePid']); } else { $data = $this->currentDocument->getMetadata($sid, $this->settings['storagePid']); diff --git a/Classes/Controller/NavigationController.php b/Classes/Controller/NavigationController.php index 19b873720..219e4eb7a 100644 --- a/Classes/Controller/NavigationController.php +++ b/Classes/Controller/NavigationController.php @@ -87,6 +87,8 @@ public function mainAction(): void $searchSessionParameters = $GLOBALS['TSFE']->fe_user->getKey('ses', 'search'); $widgetPage = $GLOBALS['TSFE']->fe_user->getKey('ses', 'widgetPage'); + //TODO: If condition is always true. + // @phpstan-ignore-next-line if ($searchSessionParameters) { $lastSearchArguments = [ 'tx_dlf_listview' => [ diff --git a/Classes/Controller/SearchController.php b/Classes/Controller/SearchController.php index 74c45f9c7..feffa9cb9 100644 --- a/Classes/Controller/SearchController.php +++ b/Classes/Controller/SearchController.php @@ -370,7 +370,7 @@ private function addCollectionsQuery(string $query): string $collections = null; if ($this->searchParams['collection']) { foreach (explode(',', $this->searchParams['collection']) as $collectionEntry) { - $collections[] = $this->collectionRepository->findByUid($collectionEntry); + $collections[] = $this->collectionRepository->findByUid((int) $collectionEntry); } } if ($collections) { diff --git a/Classes/Domain/Model/Metadata.php b/Classes/Domain/Model/Metadata.php index 74910e0d6..a61b0dcd1 100644 --- a/Classes/Domain/Model/Metadata.php +++ b/Classes/Domain/Model/Metadata.php @@ -148,7 +148,7 @@ public function getL18nParent(): Metadata } /** - * @param int $l18nParent + * @param Metadata $l18nParent */ public function setL18nParent(Metadata $l18nParent): void { diff --git a/Classes/Domain/Model/Structure.php b/Classes/Domain/Model/Structure.php index 3bea2568c..1a3145bdb 100644 --- a/Classes/Domain/Model/Structure.php +++ b/Classes/Domain/Model/Structure.php @@ -75,7 +75,7 @@ public function getL18nParent(): Structure } /** - * @param int $l18nParent + * @param Structure $l18nParent */ public function setL18nParent(Structure $l18nParent): void { diff --git a/Classes/Domain/Repository/CollectionRepository.php b/Classes/Domain/Repository/CollectionRepository.php index 6774f00d0..c57e0eebd 100644 --- a/Classes/Domain/Repository/CollectionRepository.php +++ b/Classes/Domain/Repository/CollectionRepository.php @@ -34,6 +34,8 @@ class CollectionRepository extends Repository * @access protected * @var array Set the default ordering. This is applied to findAll(), too. */ + // TODO: PHPDoc type array of property CollectionRepository::$defaultOrderings is not covariant with 'ASC'|'DESC'> of overridden property TYPO3\CMS\Extbase\Persistence\Repository::$defaultOrderings. + // @phpstan-ignore-next-line protected $defaultOrderings = [ 'label' => QueryInterface::ORDER_ASCENDING, ]; diff --git a/Classes/Domain/Repository/DocumentRepository.php b/Classes/Domain/Repository/DocumentRepository.php index 58cc653bf..6544b57dc 100644 --- a/Classes/Domain/Repository/DocumentRepository.php +++ b/Classes/Domain/Repository/DocumentRepository.php @@ -500,7 +500,7 @@ public function getOaiDocumentList($documentsToProcess): Result * @access public * * @param array $uids - * @param array $checkPartof Whether or not to also match $uids against partof. + * @param bool $checkPartof Whether or not to also match $uids against partof. * * @return array */ @@ -572,7 +572,7 @@ public function findChildrenOfEach(array $uids) * * @access public * - * @param QueryResult|Collection $collection + * @param QueryResult|Collection|null $collection * @param array $settings * @param array $searchParams * @param QueryResult $listedMetadata diff --git a/Classes/ExpressionLanguage/DocumentTypeFunctionProvider.php b/Classes/ExpressionLanguage/DocumentTypeFunctionProvider.php index ee27d4c59..fbeabb655 100644 --- a/Classes/ExpressionLanguage/DocumentTypeFunctionProvider.php +++ b/Classes/ExpressionLanguage/DocumentTypeFunctionProvider.php @@ -43,7 +43,7 @@ class DocumentTypeFunctionProvider implements ExpressionFunctionProviderInterfac * * @return ExpressionFunction[] An array of Function instances */ - public function getFunctions() + public function getFunctions(): array { return [ $this->getDocumentTypeFunction(), @@ -53,10 +53,10 @@ public function getFunctions() /** * This holds the current document * - * @var Document + * @var Document|null * @access protected */ - protected Document $document; + protected ?Document $document; /** * @var ConfigurationManager diff --git a/Classes/Format/AudioVideoMD.php b/Classes/Format/AudioVideoMD.php index 3cd4ec93a..9ac90132f 100644 --- a/Classes/Format/AudioVideoMD.php +++ b/Classes/Format/AudioVideoMD.php @@ -34,22 +34,29 @@ class AudioVideoMD implements MetadataInterface * * @param \SimpleXMLElement $xml The XML to extract the metadata from * @param array &$metadata The metadata array to fill + * @param bool $useExternalApis true if external APIs should be called, false otherwise * * @return void */ - public function extractMetadata(\SimpleXMLElement $xml, array &$metadata): void + public function extractMetadata(\SimpleXMLElement $xml, array &$metadata, bool $useExternalApis = false): void { $xml->registerXPathNamespace('audiomd', 'http://www.loc.gov/audioMD/'); $xml->registerXPathNamespace('videomd', 'http://www.loc.gov/videoMD/'); - if (!empty($audioDuration = (string) $xml->xpath('./audiomd:audioInfo/audiomd:duration')[0])) { + $audioDuration = (string) $xml->xpath('./audiomd:audioInfo/audiomd:duration')[0]; + if (!empty($audioDuration)) { $metadata['audio_duration'] = [$audioDuration]; } - if (!empty($videoDuration = (string) $xml->xpath('./videomd:videoInfo/videomd:duration')[0])) { + $videoDuration = (string) $xml->xpath('./videomd:videoInfo/videomd:duration')[0]; + if (!empty($videoDuration)) { $metadata['video_duration'] = [$videoDuration]; } $metadata['duration'] = $metadata['video_duration'] ?: $metadata['audio_duration'] ?: []; + + if ($useExternalApis) { + // TODO? + } } } diff --git a/Classes/Format/Mods.php b/Classes/Format/Mods.php index eca0b2764..cc63962e9 100644 --- a/Classes/Format/Mods.php +++ b/Classes/Format/Mods.php @@ -38,6 +38,12 @@ class Mods implements MetadataInterface **/ private $metadata; + /** + * @access private + * @var bool The metadata array + **/ + private $useExternalApis; + /** * This extracts the essential MODS metadata from XML * @@ -45,13 +51,15 @@ class Mods implements MetadataInterface * * @param \SimpleXMLElement $xml The XML to extract the metadata from * @param array &$metadata The metadata array to fill + * @param bool $useExternalApis true if external APIs should be called, false otherwise * * @return void */ - public function extractMetadata(\SimpleXMLElement $xml, array &$metadata): void + public function extractMetadata(\SimpleXMLElement $xml, array &$metadata, bool $useExternalApis): void { $this->xml = $xml; $this->metadata = $metadata; + $this->useExternalApis = $useExternalApis; $this->xml->registerXPathNamespace('mods', 'http://www.loc.gov/mods/v3'); @@ -84,7 +92,7 @@ private function getAuthors(): void $authors[$i]->registerXPathNamespace('mods', 'http://www.loc.gov/mods/v3'); $identifier = $authors[$i]->xpath('./mods:name/mods:nameIdentifier[@type="orcid"]'); - if ($this->settings['useExternalApisForMetadata'] && !empty((string) $identifier[0])) { + if ($this->useExternalApis && !empty((string) $identifier[0])) { $this->getAuthorFromOrcidApi((string) $identifier[0], $authors, $i); } else { $this->getAuthorFromXml($authors, $i); @@ -207,7 +215,7 @@ private function getHolders(): void $holders[$i]->registerXPathNamespace('mods', 'http://www.loc.gov/mods/v3'); $identifier = $holders[$i]->xpath('./mods:name/mods:nameIdentifier[@type="viaf"]'); - if ($this->settings['useExternalApisForMetadata'] && !empty((string) $identifier[0])) { + if ($this->useExternalApis && !empty((string) $identifier[0])) { $this->getHolderFromViafApi((string) $identifier[0], $holders, $i); } else { $this->getHolderFromXml($holders, $i); diff --git a/Classes/Format/TeiHeader.php b/Classes/Format/TeiHeader.php index 0dcb05719..37680ac49 100644 --- a/Classes/Format/TeiHeader.php +++ b/Classes/Format/TeiHeader.php @@ -31,10 +31,11 @@ class TeiHeader implements MetadataInterface * * @param \SimpleXMLElement $xml The XML to extract the metadata from * @param array &$metadata The metadata array to fill + * @param bool $useExternalApis true if external APIs should be called, false otherwise * * @return void */ - public function extractMetadata(\SimpleXMLElement $xml, array &$metadata): void + public function extractMetadata(\SimpleXMLElement $xml, array &$metadata, bool $useExternalApis = false): void { $xml->registerXPathNamespace('teihdr', 'http://www.tei-c.org/ns/1.0'); } diff --git a/Classes/Hooks/DataHandler.php b/Classes/Hooks/DataHandler.php index b61dca4aa..605af0b84 100644 --- a/Classes/Hooks/DataHandler.php +++ b/Classes/Hooks/DataHandler.php @@ -39,9 +39,9 @@ class DataHandler implements LoggerAwareInterface /** * @access protected - * @var DocumentRepository + * @var DocumentRepository|null */ - protected DocumentRepository $documentRepository; + protected ?DocumentRepository $documentRepository; /** * Gets document repository diff --git a/Classes/Hooks/ItemsProcFunc.php b/Classes/Hooks/ItemsProcFunc.php index 84482667a..3e0d6dca7 100644 --- a/Classes/Hooks/ItemsProcFunc.php +++ b/Classes/Hooks/ItemsProcFunc.php @@ -86,6 +86,8 @@ public function getTyposcriptConfigFromPluginSiteRoot(array $params): void $this->logger->error($e->getMessage()); } + // TODO: Variable $ts might not be defined. + // @phpstan-ignore-next-line $typoScriptConfig = $ts->setup; $this->storagePid = $typoScriptConfig['plugin.']['tx_dlf.']['persistence.']['storagePid']; @@ -161,10 +163,8 @@ protected function generateList(array &$params, string $fields, string $table, s ->orderBy($sorting) ->execute(); - while ($resArray = $result->fetch(\PDO::FETCH_NUM)) { - if ($resArray) { - $params['items'][] = $resArray; - } + while ($resArray = $result->fetchNumeric()) { + $params['items'][] = $resArray; } } } diff --git a/Classes/Middleware/SearchInDocument.php b/Classes/Middleware/SearchInDocument.php index bc0803792..bf9b3029d 100644 --- a/Classes/Middleware/SearchInDocument.php +++ b/Classes/Middleware/SearchInDocument.php @@ -57,7 +57,7 @@ class SearchInDocument implements MiddlewareInterface * @param ServerRequestInterface $request * @param RequestHandlerInterface $handler * - * @return ResponseInterface JSON response of documents + * @return ResponseInterface JSON response of search suggestions */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { @@ -88,13 +88,14 @@ public function process(ServerRequestInterface $request, RequestHandlerInterface if ($this->solr->ready) { $result = $this->executeSolrQuery($parameters); /** @scrutinizer ignore-call */ - $output['numFound'] = $result->getNumFound(); + $output['numFound'] = $result->getNumFound(); // @phpstan-ignore-line $data = $result->getData(); $highlighting = $data['ocrHighlighting']; $siteFinder = GeneralUtility::makeInstance(SiteFinder::class); $site = $siteFinder->getSiteByPageId($parameters['pid']); + // @phpstan-ignore-next-line foreach ($result as $record) { $resultDocument = new ResultDocument($record, $highlighting, $this->fields); @@ -154,7 +155,7 @@ private function executeSolrQuery($parameters) // return the coordinates of highlighted search as absolute coordinates $solrRequest->addParam('hl.ocr.absoluteHighlights', 'on'); // max amount of snippets for a single page - $solrRequest->addParam('hl.snippets', 40); + $solrRequest->addParam('hl.snippets', '40'); // we store the fulltext on page level and can disable this option $solrRequest->addParam('hl.ocr.trackPages', 'off'); diff --git a/Classes/Pagination/PageGridPagination.php b/Classes/Pagination/PageGridPagination.php index 5cdeb65b5..c42c78e60 100644 --- a/Classes/Pagination/PageGridPagination.php +++ b/Classes/Pagination/PageGridPagination.php @@ -27,6 +27,7 @@ final class PageGridPagination implements PaginationInterface public function __construct(PaginatorInterface $paginator) { + // @phpstan-ignore-next-line $this->paginator = $paginator; } diff --git a/Classes/Updates/FileLocationUpdater.php b/Classes/Updates/FileLocationUpdater.php index 086a2e83c..044dcf348 100644 --- a/Classes/Updates/FileLocationUpdater.php +++ b/Classes/Updates/FileLocationUpdater.php @@ -104,6 +104,7 @@ public function getDescription(): string */ public function updateNecessary(): bool { + /** @var int */ $numRecords = $this->getRecordsFromTable(true); if ($numRecords > 0) { return true; @@ -146,6 +147,7 @@ public function executeUpdate(): bool { $result = true; try { + /** @var int */ $numRecords = $this->getRecordsFromTable(true); if ($numRecords > 0) { $this->performUpdate(); diff --git a/Tests/Functional/Common/MetsDocumentTest.php b/Tests/Functional/Common/MetsDocumentTest.php index 2dd222e16..4cfaeef88 100644 --- a/Tests/Functional/Common/MetsDocumentTest.php +++ b/Tests/Functional/Common/MetsDocumentTest.php @@ -18,7 +18,7 @@ public function setUp(): void protected function doc(string $file) { $url = 'http://web:8001/Tests/Fixtures/MetsDocument/' . $file; - $doc = AbstractDocument::getInstance($url); + $doc = AbstractDocument::getInstance($url, ['useExternalApisForMetadata' => 0]); $this->assertNotNull($doc); return $doc; } diff --git a/Tests/Functional/Common/SolrIndexingTest.php b/Tests/Functional/Common/SolrIndexingTest.php index 559d69df5..faf2e8c70 100644 --- a/Tests/Functional/Common/SolrIndexingTest.php +++ b/Tests/Functional/Common/SolrIndexingTest.php @@ -73,7 +73,7 @@ public function canIndexAndSearchDocument() $document->setSolrcore($core->model->getUid()); $this->persistenceManager->persistAll(); - $doc = AbstractDocument::getInstance($document->getLocation()); + $doc = AbstractDocument::getInstance($document->getLocation(), ['useExternalApisForMetadata' => 0]); $document->setCurrentDocument($doc); $indexingSuccessful = Indexer::add($document, $this->documentRepository); diff --git a/Tests/Functional/FunctionalTestCase.php b/Tests/Functional/FunctionalTestCase.php index 2a7a6523d..869d9a5b1 100644 --- a/Tests/Functional/FunctionalTestCase.php +++ b/Tests/Functional/FunctionalTestCase.php @@ -104,6 +104,7 @@ public function setUp(): void protected function getDlfConfiguration() { return [ + 'useExternalApisForMetadata' => 0, 'fileGrpImages' => 'DEFAULT,MAX', 'fileGrpThumbs' => 'THUMBS', 'fileGrpDownload' => 'DOWNLOAD', diff --git a/composer.json b/composer.json index 9cb43d99e..9cbe60ab1 100644 --- a/composer.json +++ b/composer.json @@ -40,6 +40,7 @@ "solarium/solarium": "^5.2.0" }, "require-dev": { + "phpstan/phpstan": "^1.10.0", "spatie/phpunit-watcher": "^1.23.0", "typo3/cms-backend": "^10.4.36|^11.5.30", "typo3/cms-fluid": "^10.4.36|^11.5.30",