[BUGFIX] ext:install Better error handling in first folder step
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / FolderStructure / FileNode.php
1 <?php
2 namespace TYPO3\CMS\Install\FolderStructure;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2013 Christian Kuhn <lolli@schwarzbu.ch>
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26
27 use TYPO3\CMS\Install\Status;
28
29 /**
30 * A file
31 */
32 class FileNode extends AbstractNode implements NodeInterface {
33
34 /**
35 * @var NULL|string Default for files is 0660
36 */
37 protected $targetPermission = '0660';
38
39 /**
40 * @var string|NULL Target content of file. If NULL, target content is ignored
41 */
42 protected $targetContent = NULL;
43
44 /**
45 * Implement constructor
46 *
47 * @param array $structure Structure array
48 * @param NodeInterface $parent Parent object
49 * @throws Exception\InvalidArgumentException
50 */
51 public function __construct(array $structure, NodeInterface $parent = NULL) {
52 if (is_null($parent)) {
53 throw new \TYPO3\CMS\Install\FolderStructure\Exception\InvalidArgumentException(
54 'File node must have parent',
55 1366927513
56 );
57 }
58 $this->parent = $parent;
59
60 // Ensure name is a single segment, but not a path like foo/bar or an absolute path /foo
61 if (strstr($structure['name'], '/') !== FALSE) {
62 throw new \TYPO3\CMS\Install\FolderStructure\Exception\InvalidArgumentException(
63 'File name must not contain forward slash',
64 1366222204
65 );
66 }
67 $this->name = $structure['name'];
68
69 if (isset($structure['targetPermission'])) {
70 $this->targetPermission = $structure['targetPermission'];
71 }
72
73 if (isset($structure['targetContent']) && isset($structure['targetContentFile'])) {
74 throw new \TYPO3\CMS\Install\FolderStructure\Exception\InvalidArgumentException(
75 'Either targetContent or targetContentFile can be set, but not both',
76 1380364361
77 );
78 }
79
80 if (isset($structure['targetContent'])) {
81 $this->targetContent = $structure['targetContent'];
82 }
83 if (isset($structure['targetContentFile'])) {
84 if (!is_readable($structure['targetContentFile'])) {
85 throw new \TYPO3\CMS\Install\FolderStructure\Exception\InvalidArgumentException(
86 'targetContentFile ' . $structure['targetContentFile'] . ' does not exist or is not readable',
87 1380364362
88 );
89 }
90 $this->targetContent = file_get_contents($structure['targetContentFile']);
91 }
92 }
93
94 /**
95 * Get own status
96 * Returns warning if file not exists
97 * Returns error if file exists but content is not as expected (can / shouldn't be fixed)
98 *
99 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
100 */
101 public function getStatus() {
102 $result = array();
103 if (!$this->exists()) {
104 $status = new Status\WarningStatus();
105 $status->setTitle($this->getRelativePathBelowSiteRoot() . ' does not exist');
106 $result[] = $status;
107 } else {
108 $result[] = $this->getSelfStatus();
109 }
110 return $result;
111 }
112
113 /**
114 * Fix structure
115 *
116 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
117 */
118 public function fix() {
119 $result = $this->fixSelf();
120 return $result;
121 }
122
123 /**
124 * Fix this node: create if not there, fix permissions
125 *
126 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
127 */
128 protected function fixSelf() {
129 $result = array();
130 if (!$this->exists()) {
131 $resultCreateFile = $this->createFile();
132 $result[] = $resultCreateFile;
133 if ($resultCreateFile instanceof \TYPO3\CMS\Install\Status\OkStatus
134 && !is_null($this->targetContent)
135 ) {
136 $result[] = $this->setContent();
137 }
138 }
139 if (!$this->isFile()) {
140 $status = new Status\ErrorStatus();
141 $status->setTitle('Path ' . $this->getRelativePathBelowSiteRoot() . ' is not a file');
142 $fileType = @filetype($this->getAbsolutePath());
143 if ($fileType) {
144 $status->setMessage(
145 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' .
146 ' but is of type ' . $fileType . '. I can not fix this. Please investigate.'
147 );
148 } else {
149 $status->setMessage(
150 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' .
151 ' but is of unknown type, probably because some upper level directory does not exist. Please investigate.'
152 );
153 }
154 $result[] = $status;
155 } elseif (!$this->isPermissionCorrect()) {
156 $result[] = $this->fixPermission();
157 }
158 return $result;
159 }
160
161 /**
162 * Create file if not exists
163 *
164 * @throws Exception
165 * @return \TYPO3\CMS\Install\Status\StatusInterface
166 */
167 protected function createFile() {
168 if ($this->exists()) {
169 throw new Exception(
170 'File ' . $this->getAbsolutePath() . ' already exists',
171 1367048077
172 );
173 }
174 $result = @touch($this->getAbsolutePath());
175 if ($result === TRUE) {
176 $status = new Status\OkStatus();
177 $status->setTitle('File ' . $this->getRelativePathBelowSiteRoot() . ' successfully created.');
178 } else {
179 $status = new Status\ErrorStatus();
180 $status->setTitle('File ' . $this->getRelativePathBelowSiteRoot() . ' not created!');
181 $status->setMessage(
182 'The target file could not be created. There is probably some' .
183 ' group or owner permission problem on the parent directory.'
184 );
185 }
186 return $status;
187 }
188
189 /**
190 * Get status of file
191 *
192 * @return \TYPO3\CMS\Install\Status\StatusInterface
193 */
194 protected function getSelfStatus() {
195 $result = NULL;
196 if (!$this->isFile()) {
197 $status = new Status\ErrorStatus();
198 $status->setTitle($this->getRelativePathBelowSiteRoot() . ' is not a file');
199 $status->setMessage(
200 'Path ' . $this->getAbsolutePath() . ' should be a file,' .
201 ' but is of type ' . filetype($this->getAbsolutePath())
202 );
203 $result = $status;
204 } elseif (!$this->isWritable()) {
205 $status = new Status\WarningStatus();
206 $status->setTitle($this->getRelativePathBelowSiteRoot() . ' is not writable');
207 $status->setMessage(
208 'Path ' . $this->getAbsolutePath() . ' exists, but no file below' .
209 ' can be created.'
210 );
211 $result = $status;
212 } elseif (!$this->isPermissionCorrect()) {
213 $status = new Status\WarningStatus();
214 $status->setTitle($this->getRelativePathBelowSiteRoot() . ' has wrong permission');
215 $status->setMessage(
216 'Target permission are ' . $this->targetPermission .
217 ' but current permission are ' . $this->getCurrentPermission()
218 );
219 $result = $status;
220 } elseif (!$this->isContentCorrect()) {
221 $status = new Status\ErrorStatus();
222 $status->setTitle($this->getRelativePathBelowSiteRoot() . ' content differs');
223 $status->setMessage(
224 'File content is not identical to target content. Probably, this file was' .
225 ' changed manually. The content will not be fixed to not override your changes.'
226 );
227 $result = $status;
228 } else {
229 $status = new Status\OkStatus();
230 $status->setTitle($this->getRelativePathBelowSiteRoot());
231 $result = $status;
232 }
233 return $result;
234 }
235
236 /**
237 * Compare current file content with target file content
238 *
239 * @throws Exception If file does not exist
240 * @return boolean TRUE if current and target file content are identical
241 */
242 protected function isContentCorrect() {
243 $absolutePath = $this->getAbsolutePath();
244 if (is_link($absolutePath) || !is_file($absolutePath)) {
245 throw new Exception(
246 'File ' . $absolutePath . ' must exist',
247 1367056363
248 );
249 }
250 $result = FALSE;
251 if (is_null($this->targetContent)) {
252 $result = TRUE;
253 } else {
254 $targetContentHash = md5($this->targetContent);
255 $currentContentHash = md5(file_get_contents($absolutePath));
256 if ($targetContentHash === $currentContentHash) {
257 $result = TRUE;
258 }
259 }
260 return $result;
261 }
262
263 /**
264 * Sets content of file to target content
265 *
266 * @throws Exception If file does not exist
267 * @return \TYPO3\CMS\Install\Status\StatusInterface
268 */
269 protected function setContent() {
270 $absolutePath = $this->getAbsolutePath();
271 if (is_link($absolutePath) || !is_file($absolutePath)) {
272 throw new Exception(
273 'File ' . $absolutePath . ' must exist',
274 1367060201
275 );
276 }
277 if (is_null($this->targetContent)) {
278 throw new Exception(
279 'Target content not defined for ' . $absolutePath,
280 1367060202
281 );
282 }
283 $result = @file_put_contents($absolutePath, $this->targetContent);
284 if ($result !== FALSE) {
285 $status = new Status\OkStatus();
286 $status->setTitle('Set content to ' . $this->getRelativePathBelowSiteRoot());
287 } else {
288 $status = new Status\ErrorStatus();
289 $status->setTitle('Setting content to ' . $this->getRelativePathBelowSiteRoot() . ' failed');
290 $status->setMessage('Setting content of the file failed for unknown reasons.');
291 }
292 return $status;
293 }
294
295 /**
296 * Checks if not is a file
297 *
298 * @return boolean
299 */
300 protected function isFile() {
301 $path = $this->getAbsolutePath();
302 return (!is_link($path) && is_file($path));
303 }
304 }
305 ?>