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( 'Directory name must not contain forward slash', 1366226639 ); } $this->name = $structure['name']; if (isset($structure['targetPermission'])) { $this->setTargetPermission($structure['targetPermission']); } if (array_key_exists('children', $structure)) { $this->createChildren($structure['children']); } } /** * Get own status and status of child objects * * @return FlashMessage[] */ public function getStatus(): array { $result = []; if (!$this->exists()) { $status = new FlashMessage( 'The Install Tool can try to create it', 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' does not exist', ContextualFeedbackSeverity::WARNING ); $result[] = $status; } else { $result = $this->getSelfStatus(); } return array_merge($result, $this->getChildrenStatus()); } /** * Create a test file and delete again if directory exists * * @return bool TRUE if test file creation was successful */ public function isWritable() { $result = true; if (!$this->exists()) { $result = false; } elseif (!$this->canFileBeCreated()) { $result = false; } return $result; } /** * Fix structure * * If there is nothing to fix, returns an empty array * * @return FlashMessage[] */ public function fix(): array { $result = $this->fixSelf(); foreach ($this->children as $child) { /** @var NodeInterface $child */ $result = array_merge($result, $child->fix()); } return $result; } /** * Fix this directory: * * - create with correct permissions if it was not existing * - if there is no "write" permissions, try to fix it * - leave it alone otherwise * * @return FlashMessage[] */ protected function fixSelf(): array { $result = []; if (!$this->exists()) { $resultCreateDirectory = $this->createDirectory(); $result[] = $resultCreateDirectory; if ($resultCreateDirectory->getSeverity() === ContextualFeedbackSeverity::OK && !$this->isPermissionCorrect() ) { $result[] = $this->fixPermission(); } } elseif (!$this->isWritable()) { // If directory is not writable, we might have permissions to fix that // Try it: $result[] = $this->fixPermission(); } elseif (!$this->isDirectory()) { $fileType = @filetype($this->getAbsolutePath()); if ($fileType) { $messageBody = 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a directory,' . ' but is of type ' . $fileType . '. This cannot be fixed automatically. Please investigate.' ; } else { $messageBody = 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a directory,' . ' 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 directory', ContextualFeedbackSeverity::ERROR ); } return $result; } /** * Create directory if not exists * * @throws Exception */ protected function createDirectory(): FlashMessage { if ($this->exists()) { throw new Exception( 'Directory ' . $this->getAbsolutePath() . ' already exists', 1366740091 ); } $result = @mkdir($this->getAbsolutePath()); if ($result === true) { return new FlashMessage( '', 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' successfully created.' ); } return new FlashMessage( 'The target directory could not be created. There is probably a' . ' group or owner permission problem on the parent directory.', 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' not created!', ContextualFeedbackSeverity::ERROR ); } /** * Get status of directory - used in root and directory node * * @return FlashMessage[] */ protected function getSelfStatus(): array { $result = []; if (!$this->isDirectory()) { $result[] = new FlashMessage( 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' should be a directory,' . ' but is of type ' . filetype($this->getAbsolutePath()), $this->getRelativePathBelowSiteRoot() . ' is not a directory', ContextualFeedbackSeverity::ERROR ); } elseif (!$this->isWritable()) { $result[] = new FlashMessage( 'Path ' . $this->getAbsolutePath() . ' exists, but no file underneath it' . ' can be created.', 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' is not writable', ContextualFeedbackSeverity::ERROR ); } elseif (!$this->isPermissionCorrect()) { $result[] = new FlashMessage( 'Default configured permissions are ' . $this->getTargetPermission() . ' but current permissions are ' . $this->getCurrentPermission(), 'Directory ' . $this->getRelativePathBelowSiteRoot() . ' permissions mismatch', ContextualFeedbackSeverity::NOTICE ); } else { $result[] = new FlashMessage( 'Is a directory with the configured permissions of ' . $this->getTargetPermission(), 'Directory ' . $this->getRelativePathBelowSiteRoot() ); } return $result; } /** * Get status of children * * @return FlashMessage[] */ protected function getChildrenStatus(): array { $result = []; foreach ($this->children as $child) { /** @var NodeInterface $child */ $result = array_merge($result, $child->getStatus()); } return $result; } /** * Create a test file and delete again - helper for isWritable * * @return bool TRUE if test file creation was successful */ protected function canFileBeCreated() { $testFileName = StringUtility::getUniqueId('installToolTest_'); $result = @touch($this->getAbsolutePath() . '/' . $testFileName); if ($result === true) { unlink($this->getAbsolutePath() . '/' . $testFileName); } return $result; } /** * Checks if not is a directory * * @return bool True if node is a directory */ protected function isDirectory() { $path = $this->getAbsolutePath(); return !@is_link($path) && @is_dir($path); } /** * Create children nodes - done in directory and root node * * @param array $structure Array of children * @throws Exception\InvalidArgumentException */ protected function createChildren(array $structure) { foreach ($structure as $child) { if (!array_key_exists('type', $child)) { throw new InvalidArgumentException( 'Child must have type', 1366222204 ); } if (!array_key_exists('name', $child)) { throw new InvalidArgumentException( 'Child must have name', 1366222205 ); } $name = $child['name']; foreach ($this->children as $existingChild) { /** @var NodeInterface $existingChild */ if ($existingChild->getName() === $name) { throw new InvalidArgumentException( 'Child name must be unique', 1366222206 ); } } $this->children[] = new $child['type']($child, $this); } } }