1<?php 2 3/* 4 * This file is part of the TYPO3 CMS project. 5 * 6 * It is free software; you can redistribute it and/or modify it under 7 * the terms of the GNU General Public License, either version 2 8 * of the License, or any later version. 9 * 10 * For the full copyright and license information, please read the 11 * LICENSE.txt file that was distributed with this source code. 12 * 13 * The TYPO3 project - inspiring people to share! 14 */ 15 16namespace TYPO3\CMS\Install\FolderStructure; 17 18use TYPO3\CMS\Core\Messaging\FlashMessage; 19use TYPO3\CMS\Install\FolderStructure\Exception\InvalidArgumentException; 20 21/** 22 * A link 23 * @internal This class is only meant to be used within EXT:install and is not part of the TYPO3 Core API. 24 */ 25class LinkNode extends AbstractNode implements NodeInterface 26{ 27 /** 28 * @var string Optional link target 29 */ 30 protected $target = ''; 31 32 /** 33 * Implement constructor 34 * 35 * @param array $structure Structure array 36 * @param NodeInterface $parent Parent object 37 * @throws Exception\InvalidArgumentException 38 */ 39 public function __construct(array $structure, NodeInterface $parent = null) 40 { 41 if ($parent === null) { 42 throw new InvalidArgumentException( 43 'Link node must have parent', 44 1380485700 45 ); 46 } 47 $this->parent = $parent; 48 49 // Ensure name is a single segment, but not a path like foo/bar or an absolute path /foo 50 if (str_contains($structure['name'], '/')) { 51 throw new InvalidArgumentException( 52 'File name must not contain forward slash', 53 1380546061 54 ); 55 } 56 $this->name = $structure['name']; 57 58 if (isset($structure['target']) && $structure['target'] !== '') { 59 $this->target = $structure['target']; 60 } 61 } 62 63 /** 64 * Get own status 65 * Returns information status if running on Windows 66 * Returns OK status if is link and possible target is correct 67 * Else returns error (not fixable) 68 * 69 * @return FlashMessage[] 70 */ 71 public function getStatus(): array 72 { 73 if ($this->isWindowsOs()) { 74 return [ 75 new FlashMessage( 76 'This node is not handled for Windows OS and should be checked manually.', 77 $this->getRelativePathBelowSiteRoot() . ' should be a link, but this support is incomplete for Windows.', 78 FlashMessage::INFO 79 ), 80 ]; 81 } 82 83 if (!$this->exists()) { 84 return [ 85 new FlashMessage( 86 'Links cannot be fixed by this system', 87 $this->getRelativePathBelowSiteRoot() . ' should be a link, but it does not exist', 88 FlashMessage::ERROR 89 ), 90 ]; 91 } 92 93 if (!$this->isLink()) { 94 $type = @filetype($this->getAbsolutePath()); 95 if ($type) { 96 $messageBody = 97 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a link,' . 98 ' but is of type ' . $type . '. This cannot be fixed automatically. Please investigate.' 99 ; 100 } else { 101 $messageBody = 102 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' . 103 ' but is of unknown type, probably because an upper level directory does not exist. Please investigate.' 104 ; 105 } 106 return [ 107 new FlashMessage( 108 $messageBody, 109 'Path ' . $this->getRelativePathBelowSiteRoot() . ' is not a link', 110 FlashMessage::WARNING 111 ), 112 ]; 113 } 114 115 if (!$this->isTargetCorrect()) { 116 return [ 117 new FlashMessage( 118 'Link target should be ' . $this->getTarget() . ' but is ' . $this->getCurrentTarget(), 119 $this->getRelativePathBelowSiteRoot() . ' is a link, but link target is not as specified', 120 FlashMessage::ERROR 121 ), 122 ]; 123 } 124 $message = 'Is a link'; 125 if ($this->getTarget() !== '') { 126 $message .= ' and correctly points to target ' . $this->getTarget(); 127 } 128 return [ 129 new FlashMessage( 130 $message, 131 $this->getRelativePathBelowSiteRoot() 132 ), 133 ]; 134 } 135 136 /** 137 * Fix structure 138 * 139 * If there is nothing to fix, returns an empty array 140 * 141 * @return FlashMessage[] 142 */ 143 public function fix(): array 144 { 145 return []; 146 } 147 148 /** 149 * Get link target 150 * 151 * @return string Link target 152 */ 153 protected function getTarget() 154 { 155 return $this->target; 156 } 157 158 /** 159 * Find out if node is a link 160 * 161 * @throws Exception\InvalidArgumentException 162 * @return bool TRUE if node is a link 163 */ 164 protected function isLink() 165 { 166 if (!$this->exists()) { 167 throw new InvalidArgumentException( 168 'Link does not exist', 169 1380556246 170 ); 171 } 172 return @is_link($this->getAbsolutePath()); 173 } 174 175 /** 176 * Checks if the real link target is identical to given target 177 * 178 * @throws Exception\InvalidArgumentException 179 * @return bool TRUE if target is correct 180 */ 181 protected function isTargetCorrect() 182 { 183 if (!$this->exists()) { 184 throw new InvalidArgumentException( 185 'Link does not exist', 186 1380556245 187 ); 188 } 189 if (!$this->isLink()) { 190 throw new InvalidArgumentException( 191 'Node is not a link', 192 1380556247 193 ); 194 } 195 196 $result = false; 197 $expectedTarget = $this->getTarget(); 198 if (empty($expectedTarget)) { 199 $result = true; 200 } else { 201 $actualTarget = $this->getCurrentTarget(); 202 if ($expectedTarget === rtrim((string)$actualTarget, '/')) { 203 $result = true; 204 } 205 } 206 return $result; 207 } 208 209 /** 210 * Return current target of link 211 * 212 * @return false|string target 213 */ 214 protected function getCurrentTarget() 215 { 216 return readlink($this->getAbsolutePath()); 217 } 218} 219