parent = $parent; // Ensure name is a single segment, but not a path like foo/bar or an absolute path /foo if (str_contains($structure['name'], '/')) { throw new InvalidArgumentException( 'File name must not contain forward slash', 1366222207 ); } $this->name = $structure['name']; if (isset($structure['targetPermission'])) { $this->setTargetPermission($structure['targetPermission']); } if (isset($structure['targetContent']) && isset($structure['targetContentFile'])) { throw new InvalidArgumentException( 'Either targetContent or targetContentFile can be set, but not both', 1380364361 ); } if (isset($structure['targetContent'])) { $this->targetContent = $structure['targetContent']; } if (isset($structure['targetContentFile'])) { if (!is_readable($structure['targetContentFile'])) { throw new InvalidArgumentException( 'targetContentFile ' . $structure['targetContentFile'] . ' does not exist or is not readable', 1380364362 ); } $fileContent = file_get_contents($structure['targetContentFile']); if ($fileContent === false) { throw new InvalidArgumentException( 'Error while reading targetContentFile ' . $structure['targetContentFile'], 1380364363 ); } $this->targetContent = $fileContent; } } /** * Get own status * Returns warning if file not exists * Returns error if file exists but content is not as expected (can / shouldn't be fixed) * * @return FlashMessage[] */ public function getStatus(): array { $result = []; if (!$this->exists()) { $result[] = new FlashMessage( 'By using "Try to fix errors" we can try to create it', 'File ' . $this->getRelativePathBelowSiteRoot() . ' does not exist', ContextualFeedbackSeverity::WARNING ); } else { $result = $this->getSelfStatus(); } return $result; } /** * Fix structure * * If there is nothing to fix, returns an empty array * * @return FlashMessage[] */ public function fix(): array { $result = $this->fixSelf(); return $result; } /** * Fix this node: create if not there, fix permissions * * @return FlashMessage[] */ protected function fixSelf(): array { $result = []; if (!$this->exists()) { $resultCreateFile = $this->createFile(); $result[] = $resultCreateFile; if ($resultCreateFile->getSeverity() === ContextualFeedbackSeverity::OK && $this->targetContent !== null ) { $result[] = $this->setContent(); if (!$this->isPermissionCorrect()) { $result[] = $this->fixPermission(); } } } elseif (!$this->isFile()) { $fileType = @filetype($this->getAbsolutePath()); if ($fileType) { $messageBody = 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' . ' but is of type ' . $fileType . '. This cannot be fixed automatically. Please investigate.' ; } else { $messageBody = 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' . ' but is of unknown type, probably because an upper level directory does not exist. Please investigate.' ; } $result[] = new FlashMessage( $messageBody, 'Path ' . $this->getRelativePathBelowSiteRoot() . ' is not a file', ContextualFeedbackSeverity::ERROR ); } elseif (!$this->isPermissionCorrect()) { $result[] = $this->fixPermission(); } return $result; } /** * Create file if not exists * * @throws Exception */ protected function createFile(): FlashMessage { if ($this->exists()) { throw new Exception( 'File ' . $this->getRelativePathBelowSiteRoot() . ' already exists', 1367048077 ); } $result = @touch($this->getAbsolutePath()); if ($result === true) { return new FlashMessage( '', 'File ' . $this->getRelativePathBelowSiteRoot() . ' successfully created.' ); } return new FlashMessage( 'The target file could not be created. There is probably a' . ' group or owner permission problem on the parent directory.', 'File ' . $this->getRelativePathBelowSiteRoot() . ' not created!', ContextualFeedbackSeverity::ERROR ); } /** * Get status of file * * @return FlashMessage[] */ protected function getSelfStatus(): array { $result = []; if (!$this->isFile()) { $result[] = new FlashMessage( 'Path ' . $this->getAbsolutePath() . ' should be a file,' . ' but is of type ' . filetype($this->getAbsolutePath()), $this->getRelativePathBelowSiteRoot() . ' is not a file', ContextualFeedbackSeverity::ERROR ); } elseif (!$this->isWritable()) { $result[] = new FlashMessage( 'File ' . $this->getRelativePathBelowSiteRoot() . ' exists, but is not writable.', 'File ' . $this->getRelativePathBelowSiteRoot() . ' is not writable', ContextualFeedbackSeverity::NOTICE ); } elseif (!$this->isPermissionCorrect()) { $result[] = new FlashMessage( 'Default configured permissions are ' . $this->getTargetPermission() . ' but file permissions are ' . $this->getCurrentPermission(), 'File ' . $this->getRelativePathBelowSiteRoot() . ' permissions mismatch', ContextualFeedbackSeverity::NOTICE ); } if ($this->isFile() && !$this->isContentCorrect()) { $result[] = new FlashMessage( 'File content is not identical to default content. This file may have been changed manually.' . ' The Install Tool will not overwrite the current version!', 'File ' . $this->getRelativePathBelowSiteRoot() . ' content differs', ContextualFeedbackSeverity::NOTICE ); } else { $result[] = new FlashMessage( 'Is a file with the default content and configured permissions of ' . $this->getTargetPermission(), 'File ' . $this->getRelativePathBelowSiteRoot() ); } return $result; } /** * Compare current file content with target file content * * @throws Exception If file does not exist * @return bool TRUE if current and target file content are identical */ protected function isContentCorrect() { $absolutePath = $this->getAbsolutePath(); if (is_link($absolutePath) || !is_file($absolutePath)) { throw new Exception( 'File ' . $absolutePath . ' must exist', 1367056363 ); } $result = false; if ($this->targetContent === null) { $result = true; } else { $targetContentHash = md5($this->targetContent); $currentContentHash = md5((string)file_get_contents($absolutePath)); if ($targetContentHash === $currentContentHash) { $result = true; } } return $result; } /** * Sets content of file to target content * * @throws Exception If file does not exist */ protected function setContent(): FlashMessage { $absolutePath = $this->getAbsolutePath(); if (is_link($absolutePath) || !is_file($absolutePath)) { throw new Exception( 'File ' . $absolutePath . ' must exist', 1367060201 ); } if ($this->targetContent === null) { throw new Exception( 'Target content not defined for ' . $absolutePath, 1367060202 ); } $result = @file_put_contents($absolutePath, $this->targetContent); if ($result !== false) { return new FlashMessage( '', 'Set content to ' . $this->getRelativePathBelowSiteRoot() ); } return new FlashMessage( 'Setting content of the file failed for unknown reasons.', 'Setting content to ' . $this->getRelativePathBelowSiteRoot() . ' failed', ContextualFeedbackSeverity::ERROR ); } /** * Checks if not is a file * * @return bool */ protected function isFile() { $path = $this->getAbsolutePath(); return !is_link($path) && is_file($path); } }