originalFile = $originalFile; $this->originalFileSha1 = $this->originalFile->getSha1(); $this->storage = $originalFile->getStorage()->getProcessingFolder()->getStorage(); $this->taskType = $taskType; $this->processingConfiguration = $processingConfiguration; if (is_array($databaseRow)) { $this->reconstituteFromDatabaseRecord($databaseRow); } } /** * Creates a ProcessedFile object from a database record. */ protected function reconstituteFromDatabaseRecord(array $databaseRow): void { $this->taskType = $this->taskType ?: $databaseRow['task_type']; $this->processingConfiguration = $this->processingConfiguration ?: (array)unserialize($databaseRow['configuration'] ?? '', ['allowed_classes' => [Area::class]]); $this->originalFileSha1 = $databaseRow['originalfilesha1']; $this->identifier = (string)$databaseRow['identifier']; $this->name = (string)$databaseRow['name']; $this->properties = $databaseRow; $this->processingUrl = $databaseRow['processing_url'] ?? ''; if (!empty($databaseRow['storage']) && (int)$this->storage->getUid() !== (int)$databaseRow['storage']) { $this->storage = GeneralUtility::makeInstance(StorageRepository::class)->findByUid($databaseRow['storage']); } } /******************* * CONTENTS RELATED *******************/ /** * Replace the current file contents with the given string * * @throws \BadMethodCallException */ public function setContents(string $contents): self { throw new \BadMethodCallException('Setting contents not possible for processed file.', 1305438528); } /** * Injects a local file, which is a processing result into the object. * * @param string $filePath * @throws \RuntimeException */ public function updateWithLocalFile(string $filePath): void { if (empty($this->identifier)) { throw new \RuntimeException('Cannot update original file!', 1350582054); } $processingFolder = $this->originalFile->getStorage()->getProcessingFolder($this->originalFile); $addedFile = $this->storage->updateProcessedFile($filePath, $this, $processingFolder); // Update some related properties $this->identifier = $addedFile->getIdentifier(); $this->originalFileSha1 = $this->originalFile->getSha1(); $this->updateProperties($addedFile->getProperties()); $this->deleted = false; $this->updated = true; } /***************************************** * STORAGE AND MANAGEMENT RELATED METHODS *****************************************/ /** * Returns TRUE if this file is indexed */ public function isIndexed(): false { // Processed files are never indexed; instead you might be looking for isPersisted() return false; } /** * Checks whether the ProcessedFile already has an entry in sys_file_processedfile table */ public function isPersisted(): bool { return array_key_exists('uid', $this->properties) && $this->properties['uid'] > 0; } /** * Checks whether the ProcessedFile Object is newly created */ public function isNew(): bool { return !$this->isPersisted(); } /** * Checks whether the object since last reconstitution, and therefore * needs persistence again */ public function isUpdated(): bool { return $this->updated; } /** * Sets a new file name */ public function setName(string $name): void { // Remove the existing file, but only we actually have a name or the name has changed if (!empty($this->name) && $this->name !== $name && $this->exists()) { $this->delete(); } $this->name = $name; // @todo this is a *weird* hack that will fail if the storage is non-hierarchical! $this->identifier = $this->storage->getProcessingFolder($this->originalFile)->getIdentifier() . $this->name; $this->updated = true; } /** * Checks if this file exists. * Since the original file may reside in a different storage * we ask the original file if it exists in case the processed is representing it * * @return bool TRUE if this file physically exists */ public function exists(): bool { if ($this->usesOriginalFile()) { return $this->originalFile->exists(); } return parent::exists(); } /****************** * SPECIAL METHODS ******************/ /** * Returns TRUE if this file is already processed. */ public function isProcessed(): bool { return $this->updated || ($this->isPersisted() && !$this->needsReprocessing()); } /** * Getter for the Original, unprocessed File */ public function getOriginalFile(): File { return $this->originalFile; } /** * Get the identifier of the file * * If there is no processed file in the file system (as the original file did not have to be modified e.g. * when the original image is in the boundaries of the maxW/maxH stuff), then just return the identifier of * the original file * * @return non-empty-string */ public function getIdentifier(): string { return (!$this->usesOriginalFile()) ? $this->identifier : $this->getOriginalFile()->getIdentifier(); } public function setIdentifier(string $identifier): void { $this->identifier = $identifier; } /** * Get the name of the file * * If there is no processed file in the file system (as the original file did not have to be modified e.g. * when the original image is in the boundaries of the maxW/maxH stuff) * then just return the name of the original file * * @return non-empty-string */ public function getName(): string { if ($this->usesOriginalFile()) { return $this->originalFile->getName(); } return $this->name; } /** * Updates properties of this object. Do not use this to reconstitute an object from the database; use * reconstituteFromDatabaseRecord() instead! */ public function updateProperties(array $properties): void { if (array_key_exists('uid', $properties) && MathUtility::canBeInterpretedAsInteger($properties['uid'])) { $this->properties['uid'] = $properties['uid']; } if (isset($properties['processing_url'])) { $this->processingUrl = $properties['processing_url']; } // @todo we should have a blacklist of properties that might not be updated $this->properties = array_merge($this->properties, $properties); // @todo when should this update be done? if (!$this->isUnchanged() && $this->exists()) { $storage = $this->storage; if ($this->usesOriginalFile()) { $storage = $this->originalFile->getStorage(); } $this->properties = array_merge($this->properties, $storage->getFileInfo($this)); } } /** * Basic array function for the DB update * * @return array */ public function toArray(): array { if ($this->usesOriginalFile()) { $properties = $this->originalFile->getProperties(); unset($properties['uid']); $properties['identifier'] = ''; $properties['name'] = null; $properties['processing_url'] = ''; // Use width + height set in processed file $properties['width'] = $this->properties['width'] ?? 0; $properties['height'] = $this->properties['height'] ?? 0; } else { $properties = $this->properties; $properties['identifier'] = $this->getIdentifier(); $properties['name'] = $this->getName(); } $properties['configuration'] = (new ConfigurationService())->serialize($this->processingConfiguration); return array_merge($properties, [ 'storage' => $this->getStorage()->getUid(), 'task_type' => $this->taskType, 'configurationsha1' => sha1($properties['configuration']), 'original' => $this->originalFile->getUid(), 'originalfilesha1' => $this->originalFileSha1, ]); } /** * Returns TRUE if this file has not been changed during processing (i.e., we just deliver the original file) */ protected function isUnchanged(): bool { return !($this->properties['width'] ?? false) && $this->usesOriginalFile(); } /** * Defines that the original file should be used. */ public function setUsesOriginalFile(): void { // @todo check if some of these properties can/should be set in a generic update method $this->identifier = $this->originalFile->getIdentifier(); $this->updated = true; $this->processingUrl = ''; $this->originalFileSha1 = $this->originalFile->getSha1(); } public function updateProcessingUrl(string $url): void { $this->updated = true; $this->processingUrl = $url; } public function usesOriginalFile(): bool { return empty($this->identifier) || $this->identifier === $this->originalFile->getIdentifier(); } /** * Returns TRUE if the original file of this file changed and the file should be processed again. */ public function isOutdated(): bool { return $this->needsReprocessing(); } /** * Delete processed file */ public function delete(bool $force = false): bool { if (!$force && $this->isUnchanged()) { return false; } // Only delete file when original isn't used if (!$this->usesOriginalFile()) { return parent::delete(); } return true; } /** * Getter for file-properties * * @param non-empty-string $key */ public function getProperty(string $key): mixed { // The uid always (!) has to come from this file and never the original file (see getOriginalFile() to get this) if ($this->isUnchanged() && $key !== 'uid') { return $this->originalFile->getProperty($key); } return $this->properties[$key] ?? null; } /** * Get the MIME type of this file * * @throws \RuntimeException * @return non-empty-string mime type */ public function getMimeType(): string { if ($this->usesOriginalFile()) { return $this->getOriginalFile()->getMimeType(); } return parent::getMimeType(); } /** * @throws \RuntimeException * @return int<0, max> */ public function getSize(): int { if ($this->usesOriginalFile()) { return $this->getOriginalFile()->getSize(); } return parent::getSize(); } /** * Returns the uid of this file */ public function getUid(): int { return (int)($this->properties['uid'] ?? 0); } /** * Checks if the ProcessedFile needs reprocessing */ public function needsReprocessing(): bool { $fileMustBeRecreated = false; // if original is missing we can not reprocess the file if ($this->originalFile->isMissing()) { return false; } // processedFile does not exist if (!$this->usesOriginalFile() && !$this->exists()) { $fileMustBeRecreated = true; } // original file changed if ($this->originalFile->getSha1() !== $this->originalFileSha1) { $fileMustBeRecreated = true; } if (!array_key_exists('uid', $this->properties)) { $fileMustBeRecreated = true; } // remove outdated file if ($fileMustBeRecreated && $this->exists()) { $this->delete(); } return $fileMustBeRecreated; } /** * Returns the processing information */ public function getProcessingConfiguration(): array { return $this->processingConfiguration; } /** * Getter for the task identifier. */ public function getTaskIdentifier(): string { return $this->taskType; } /** * Returns a publicly accessible URL for this file * * @return non-empty-string|null NULL if file is deleted, the generated URL otherwise */ public function getPublicUrl(): ?string { if (isset($this->processingUrl) && $this->processingUrl !== '') { return $this->processingUrl; } if ($this->deleted) { return null; } if ($this->usesOriginalFile()) { return $this->getOriginalFile()->getPublicUrl(); } return $this->getStorage()->getPublicUrl($this); } }