5ad7e26cfa7e7e49756eb9ab97c6e190c702366b
[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|integer Default for files is octal 0664 == decimal 436
36 */
37 protected $targetPermission = '0664';
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 1366222207
65 );
66 }
67 $this->name = $structure['name'];
68
69 if (isset($structure['targetPermission'])) {
70 $this->setTargetPermission($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('File ' . $this->getRelativePathBelowSiteRoot() . ' does not exist');
106 $status->setMessage('By using "Try to fix errors" we can try to create it');
107 $result[] = $status;
108 } else {
109 $result = $this->getSelfStatus();
110 }
111 return $result;
112 }
113
114 /**
115 * Fix structure
116 *
117 * If there is nothing to fix, returns an empty array
118 *
119 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
120 */
121 public function fix() {
122 $result = $this->fixSelf();
123 return $result;
124 }
125
126 /**
127 * Fix this node: create if not there, fix permissions
128 *
129 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
130 */
131 protected function fixSelf() {
132 $result = array();
133 if (!$this->exists()) {
134 $resultCreateFile = $this->createFile();
135 $result[] = $resultCreateFile;
136 if ($resultCreateFile instanceof \TYPO3\CMS\Install\Status\OkStatus
137 && !is_null($this->targetContent)
138 ) {
139 $result[] = $this->setContent();
140 if (!$this->isPermissionCorrect()) {
141 $result[] = $this->fixPermission();
142 }
143 }
144 } elseif (!$this->isFile()) {
145 $status = new Status\ErrorStatus();
146 $status->setTitle('Path ' . $this->getRelativePathBelowSiteRoot() . ' is not a file');
147 $fileType = @filetype($this->getAbsolutePath());
148 if ($fileType) {
149 $status->setMessage(
150 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' .
151 ' but is of type ' . $fileType . '. This cannot be fixed automatically. Please investigate.'
152 );
153 } else {
154 $status->setMessage(
155 'The target ' . $this->getRelativePathBelowSiteRoot() . ' should be a file,' .
156 ' but is of unknown type, probably because an upper level directory does not exist. Please investigate.'
157 );
158 }
159 $result[] = $status;
160 } elseif (!$this->isPermissionCorrect()) {
161 $result[] = $this->fixPermission();
162 }
163 return $result;
164 }
165
166 /**
167 * Create file if not exists
168 *
169 * @throws Exception
170 * @return \TYPO3\CMS\Install\Status\StatusInterface
171 */
172 protected function createFile() {
173 if ($this->exists()) {
174 throw new Exception(
175 'File ' . $this->getRelativePathBelowSiteRoot() . ' already exists',
176 1367048077
177 );
178 }
179 $result = @touch($this->getAbsolutePath());
180 if ($result === TRUE) {
181 $status = new Status\OkStatus();
182 $status->setTitle('File ' . $this->getRelativePathBelowSiteRoot() . ' successfully created.');
183 } else {
184 $status = new Status\ErrorStatus();
185 $status->setTitle('File ' . $this->getRelativePathBelowSiteRoot() . ' not created!');
186 $status->setMessage(
187 'The target file could not be created. There is probably a' .
188 ' group or owner permission problem on the parent directory.'
189 );
190 }
191 return $status;
192 }
193
194 /**
195 * Get status of file
196 *
197 * @return array<\TYPO3\CMS\Install\Status\StatusInterface>
198 */
199 protected function getSelfStatus() {
200 $result = array();
201 if (!$this->isFile()) {
202 $status = new Status\ErrorStatus();
203 $status->setTitle($this->getRelativePathBelowSiteRoot() . ' is not a file');
204 $status->setMessage(
205 'Path ' . $this->getAbsolutePath() . ' should be a file,' .
206 ' but is of type ' . filetype($this->getAbsolutePath())
207 );
208 $result[] = $status;
209 } elseif (!$this->isWritable()) {
210 $status = new Status\NoticeStatus();
211 $status->setTitle('File ' . $this->getRelativePathBelowSiteRoot() . ' is not writable');
212 $status->setMessage(
213 'File ' . $this->getRelativePathBelowSiteRoot() . ' exists, but is not writeable.'
214 );
215 $result[] = $status;
216 } elseif (!$this->isPermissionCorrect()) {
217 $status = new Status\NoticeStatus();
218 $status->setTitle('File ' . $this->getRelativePathBelowSiteRoot() . ' permissions mismatch');
219 $status->setMessage(
220 'Default configured permissions are ' . $this->getTargetPermission() .
221 ' but file permissions are ' . $this->getCurrentPermission()
222 );
223 $result[] = $status;
224 }
225 if ($this->isFile() && !$this->isContentCorrect()) {
226 $status = new Status\NoticeStatus();
227 $status->setTitle('File ' . $this->getRelativePathBelowSiteRoot() . ' content differs');
228 $status->setMessage(
229 'File content is not identical to default content. This file may have been changed manually.' .
230 ' The Install Tool will not overwrite the current version!'
231 );
232 $result[] = $status;
233 } else {
234 $status = new Status\OkStatus();
235 $status->setTitle('File ' . $this->getRelativePathBelowSiteRoot());
236 $status->setMessage(
237 'Is a file with the default content and configured permissions of ' . $this->getTargetPermission()
238 );
239 $result[] = $status;
240 }
241 return $result;
242 }
243
244 /**
245 * Compare current file content with target file content
246 *
247 * @throws Exception If file does not exist
248 * @return boolean TRUE if current and target file content are identical
249 */
250 protected function isContentCorrect() {
251 $absolutePath = $this->getAbsolutePath();
252 if (is_link($absolutePath) || !is_file($absolutePath)) {
253 throw new Exception(
254 'File ' . $absolutePath . ' must exist',
255 1367056363
256 );
257 }
258 $result = FALSE;
259 if (is_null($this->targetContent)) {
260 $result = TRUE;
261 } else {
262 $targetContentHash = md5($this->targetContent);
263 $currentContentHash = md5(file_get_contents($absolutePath));
264 if ($targetContentHash === $currentContentHash) {
265 $result = TRUE;
266 }
267 }
268 return $result;
269 }
270
271 /**
272 * Sets content of file to target content
273 *
274 * @throws Exception If file does not exist
275 * @return \TYPO3\CMS\Install\Status\StatusInterface
276 */
277 protected function setContent() {
278 $absolutePath = $this->getAbsolutePath();
279 if (is_link($absolutePath) || !is_file($absolutePath)) {
280 throw new Exception(
281 'File ' . $absolutePath . ' must exist',
282 1367060201
283 );
284 }
285 if (is_null($this->targetContent)) {
286 throw new Exception(
287 'Target content not defined for ' . $absolutePath,
288 1367060202
289 );
290 }
291 $result = @file_put_contents($absolutePath, $this->targetContent);
292 if ($result !== FALSE) {
293 $status = new Status\OkStatus();
294 $status->setTitle('Set content to ' . $this->getRelativePathBelowSiteRoot());
295 } else {
296 $status = new Status\ErrorStatus();
297 $status->setTitle('Setting content to ' . $this->getRelativePathBelowSiteRoot() . ' failed');
298 $status->setMessage('Setting content of the file failed for unknown reasons.');
299 }
300 return $status;
301 }
302
303 /**
304 * Checks if not is a file
305 *
306 * @return boolean
307 */
308 protected function isFile() {
309 $path = $this->getAbsolutePath();
310 return (!is_link($path) && is_file($path));
311 }
312 }