0f3fb3d3eda39da2fab1ae3b1057132ea3df3316
[Packages/TYPO3.CMS.git] / typo3 / sysext / extensionmanager / Tests / Unit / Utility / FileHandlingUtilityTest.php
1 <?php
2 namespace TYPO3\CMS\Extensionmanager\Tests\Unit\Utility;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 2012
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 /**
28 * Testcase
29 *
30 */
31 class FileHandlingUtilityTest extends \TYPO3\CMS\Core\Tests\UnitTestCase {
32
33 /**
34 * @var array List of created fake extensions to be deleted in tearDown() again
35 */
36 protected $fakedExtensions = array();
37
38 /**
39 * @var array List of resources (files or empty directories) that need to be removed in tearDown() again
40 */
41 protected $resourcesToRemove = array();
42
43 /**
44 * @return void
45 */
46 public function tearDown() {
47 foreach ($this->fakedExtensions as $extension => $dummy) {
48 \TYPO3\CMS\Core\Utility\GeneralUtility::rmdir(PATH_site . 'typo3conf/ext/' . $extension, TRUE);
49 }
50 foreach ($this->resourcesToRemove as $resource) {
51 if (file_exists($resource) && is_file($resource)) {
52 unlink($resource);
53 } elseif(file_exists($resource) && is_dir($resource)) {
54 rmdir($resource);
55 }
56 }
57 parent::tearDown();
58 }
59
60 /**
61 * Creates a fake extension inside typo3temp/. No configuration is created,
62 * just the folder
63 *
64 * @param bool $extkeyOnly
65 * @return string The extension key
66 */
67 protected function createFakeExtension($extkeyOnly = FALSE) {
68 $extKey = strtolower(uniqid('testing'));
69 $absExtPath = PATH_site . 'typo3conf/ext/' . $extKey . '/';
70 $relPath = 'typo3conf/ext/' . $extKey . '/';
71 $this->fakedExtensions[$extKey] = array(
72 'siteRelPath' => $relPath,
73 'siteAbsPath' => $absExtPath
74 );
75 if ($extkeyOnly === TRUE) {
76 return $extKey;
77 }
78 \TYPO3\CMS\Core\Utility\GeneralUtility::mkdir($absExtPath);
79 return $extKey;
80 }
81
82 /**
83 * @test
84 * @return void
85 */
86 public function makeAndClearExtensionDirRemovesExtensionDirIfAlreadyExists() {
87 $extKey = $this->createFakeExtension();
88 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('removeDirectory', 'addDirectory'));
89 $fileHandlerMock->expects($this->once())->method('removeDirectory')->with(PATH_site . 'typo3conf/ext/' . $extKey . '/');
90 $fileHandlerMock->_call('makeAndClearExtensionDir', $extKey);
91 }
92
93 /**
94 * @return array
95 */
96 public function invalidRelativePathDataProvider() {
97 return array(
98 array('../../'),
99 array('/foo/bar'),
100 array('foo//bar'),
101 array('foo/bar' . chr(0)),
102 );
103 }
104
105 /**
106 * @param string $invalidRelativePath
107 * @test
108 * @dataProvider invalidRelativePathDataProvider
109 * @expectedException \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
110 */
111 public function getAbsolutePathThrowsExceptionForInvalidRelativePaths($invalidRelativePath) {
112 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('dummy'));
113 $fileHandlerMock->_call('getAbsolutePath', $invalidRelativePath);
114 }
115
116 /**
117 * @return array
118 */
119 public function validRelativePathDataProvider() {
120 return array(
121 array('foo/../bar', PATH_site . 'bar'),
122 array('bas', PATH_site . 'bas'),
123 );
124 }
125
126 /**
127 * @param string $validRelativePath
128 * @param string $expectedAbsolutePath
129 * @test
130 * @dataProvider validRelativePathDataProvider
131 */
132 public function getAbsolutePathReturnsAbsolutePathForValidRelativePaths($validRelativePath, $expectedAbsolutePath) {
133 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('dummy'));
134 $this->assertSame($expectedAbsolutePath, $fileHandlerMock->_call('getAbsolutePath', $validRelativePath));
135 }
136
137 /**
138 * @test
139 * @return void
140 */
141 public function makeAndClearExtensionDirAddsDir() {
142 $extKey = $this->createFakeExtension();
143 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('removeDirectory', 'addDirectory'));
144 $fileHandlerMock->expects($this->once())->method('addDirectory')->with(PATH_site . 'typo3conf/ext/' . $extKey . '/');
145 $fileHandlerMock->_call('makeAndClearExtensionDir', $extKey);
146 }
147
148 /**
149 * @test
150 * @expectedException \TYPO3\CMS\Extensionmanager\Exception\ExtensionManagerException
151 * @return void
152 */
153 public function makeAndClearExtensionDirThrowsExceptionOnInvalidPath() {
154 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('removeDirectory', 'addDirectory'));
155 $fileHandlerMock->_call('makeAndClearExtensionDir', 'testing123', 'fakepath');
156 }
157
158 /**
159 * @test
160 * @return void
161 */
162 public function addDirectoryAddsDirectory() {
163 $extDirPath = $this->fakedExtensions[$this->createFakeExtension(TRUE)]['siteAbsPath'];
164 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('dummy'));
165 $this->assertFalse(is_dir($extDirPath));
166 $fileHandlerMock->_call('addDirectory', $extDirPath);
167 $this->assertTrue(is_dir($extDirPath));
168 }
169
170 /**
171 * @test
172 * @return void
173 */
174 public function removeDirectoryRemovesDirectory() {
175 $extDirPath = $this->fakedExtensions[$this->createFakeExtension()]['siteAbsPath'];
176 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('dummy'));
177 $this->assertTrue(is_dir($extDirPath));
178 $fileHandlerMock->_call('removeDirectory', $extDirPath);
179 $this->assertFalse(is_dir($extDirPath));
180 }
181
182 /**
183 * @test
184 * @return void
185 */
186 public function removeDirectoryRemovesSymlink() {
187 $absoluteSymlinkPath = PATH_site . 'typo3temp/' . uniqid('test_symlink_');
188 $absoluteFilePath = PATH_site . 'typo3temp/' . uniqid('test_file_');
189 touch($absoluteFilePath);
190 $this->resourcesToRemove[] = $absoluteFilePath;
191 symlink($absoluteFilePath, $absoluteSymlinkPath);
192 $fileHandler = new \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility();
193 $fileHandler->removeDirectory($absoluteSymlinkPath);
194 $this->assertFalse(is_link($absoluteSymlinkPath));
195 }
196
197 /**
198 * @test
199 * @return void
200 */
201 public function removeDirectoryDoesNotRemoveContentOfSymlinkedTargetDirectory() {
202 $absoluteSymlinkPath = PATH_site . 'typo3temp/' . uniqid('test_symlink_');
203 $absoluteDirectoryPath = PATH_site . 'typo3temp/' . uniqid('test_dir_') . '/';
204 $relativeFilePath = uniqid('test_file_');
205
206 mkdir($absoluteDirectoryPath);
207 touch($absoluteDirectoryPath . $relativeFilePath);
208
209 $this->resourcesToRemove[] = $absoluteDirectoryPath . $relativeFilePath;
210 $this->resourcesToRemove[] = $absoluteDirectoryPath;
211
212 symlink($absoluteDirectoryPath, $absoluteSymlinkPath);
213
214 $fileHandler = new \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility();
215 $fileHandler->removeDirectory($absoluteSymlinkPath);
216 $this->assertTrue(is_file($absoluteDirectoryPath . $relativeFilePath));
217 }
218
219 /**
220 * @test
221 * @return void
222 */
223 public function unpackExtensionFromExtensionDataArrayCreatesTheExtensionDirectory() {
224 $extensionData = array(
225 'extKey' => 'test'
226 );
227 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array(
228 'makeAndClearExtensionDir',
229 'writeEmConfToFile',
230 'extractFilesArrayFromExtensionData',
231 'extractDirectoriesFromExtensionData',
232 'createDirectoriesForExtensionFiles',
233 'writeExtensionFiles'
234 ));
235 $fileHandlerMock->expects($this->once())->method('extractFilesArrayFromExtensionData')->will($this->returnValue(array()));
236 $fileHandlerMock->expects($this->once())->method('extractDirectoriesFromExtensionData')->will($this->returnValue(array()));
237 $fileHandlerMock->expects($this->once())->method('makeAndClearExtensionDir')->with($extensionData['extKey']);
238 $fileHandlerMock->_call('unpackExtensionFromExtensionDataArray', $extensionData);
239 }
240
241 /**
242 * @test
243 * @return void
244 */
245 public function extractFilesArrayFromExtensionDataReturnsFileArray() {
246 $extensionData = array(
247 'key' => 'test',
248 'FILES' => array(
249 'filename1' => 'dummycontent',
250 'filename2' => 'dummycontent2'
251 )
252 );
253 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('makeAndClearExtensionDir'));
254 $extractedFiles = $fileHandlerMock->_call('extractFilesArrayFromExtensionData', $extensionData);
255 $this->assertArrayHasKey('filename1', $extractedFiles);
256 $this->assertArrayHasKey('filename2', $extractedFiles);
257 }
258
259 /**
260 * @test
261 * @return void
262 */
263 public function writeExtensionFilesWritesFiles() {
264 $files = array(
265 'ChangeLog' => array(
266 'name' => 'ChangeLog',
267 'size' => 4559,
268 'mtime' => 1219448527,
269 'is_executable' => FALSE,
270 'content' => 'some content to write'
271 ),
272 'README' => array(
273 'name' => 'README',
274 'size' => 4566,
275 'mtime' => 1219448533,
276 'is_executable' => FALSE,
277 'content' => 'FEEL FREE TO ADD SOME DOCUMENTATION HERE'
278 )
279 );
280 $rootPath = ($extDirPath = $this->fakedExtensions[$this->createFakeExtension()]['siteAbsPath']);
281 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('makeAndClearExtensionDir'));
282 $fileHandlerMock->_call('writeExtensionFiles', $files, $rootPath);
283 $this->assertTrue(file_exists($rootPath . 'ChangeLog'));
284 }
285
286 /**
287 * @test
288 * @return void
289 */
290 public function extractDirectoriesFromExtensionDataExtractsDirectories() {
291 $files = array(
292 'doc/ChangeLog' => array(
293 'name' => 'ChangeLog',
294 'size' => 4559,
295 'mtime' => 1219448527,
296 'is_executable' => FALSE,
297 'content' => 'some content to write'
298 ),
299 'mod/doc/README' => array(
300 'name' => 'README',
301 'size' => 4566,
302 'mtime' => 1219448533,
303 'is_executable' => FALSE,
304 'content' => 'FEEL FREE TO ADD SOME DOCUMENTATION HERE'
305 )
306 );
307 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('makeAndClearExtensionDir'));
308 $extractedDirectories = $fileHandlerMock->_call('extractDirectoriesFromExtensionData', $files);
309 $this->assertContains('doc/', $extractedDirectories);
310 $this->assertContains('mod/doc/', $extractedDirectories);
311 }
312
313 /**
314 * @test
315 * @return void
316 */
317 public function createDirectoriesForExtensionFilesCreatesDirectories() {
318 $rootPath = $this->fakedExtensions[$this->createFakeExtension()]['siteAbsPath'];
319 $directories = array(
320 'doc/',
321 'mod/doc/'
322 );
323 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('makeAndClearExtensionDir'));
324 $this->assertFalse(is_dir($rootPath . 'doc/'));
325 $this->assertFalse(is_dir($rootPath . 'mod/doc/'));
326 $fileHandlerMock->_call('createDirectoriesForExtensionFiles', $directories, $rootPath);
327 $this->assertTrue(is_dir($rootPath . 'doc/'));
328 $this->assertTrue(is_dir($rootPath . 'mod/doc/'));
329 }
330
331 /**
332 * @test
333 * @return void
334 */
335 public function writeEmConfWritesEmConfFile() {
336 $extKey = $this->createFakeExtension();
337 $extensionData = array(
338 'extKey' => $extKey,
339 'EM_CONF' => array(
340 'title' => 'Plugin cache engine',
341 'description' => 'Provides an interface to cache plugin content elements based on 4.3 caching framework',
342 'category' => 'Frontend',
343 'shy' => 0
344 )
345 );
346 $rootPath = $this->fakedExtensions[$extKey]['siteAbsPath'];
347 $emConfUtilityMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\EmConfUtility', array('constructEmConf'));
348 $emConfUtilityMock->expects($this->once())->method('constructEmConf')->with($extensionData)->will($this->returnValue(var_export($extensionData['EM_CONF'], TRUE)));
349 $fileHandlerMock = $this->getAccessibleMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('makeAndClearExtensionDir'));
350 $fileHandlerMock->_set('emConfUtility', $emConfUtilityMock);
351 $fileHandlerMock->_call('writeEmConfToFile', $extensionData, $rootPath);
352 $this->assertTrue(file_exists($rootPath . 'ext_emconf.php'));
353 }
354
355 /**
356 * @return \PHPUnit_Framework_MockObject_MockObject|\TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility
357 */
358 protected function getPreparedFileHandlingMockForDirectoryCreationTests() {
359 /** @var $fileHandlerMock \TYPO3\CMS\Extensionmanager\Utility\FileHandlingUtility|\PHPUnit_Framework_MockObject_MockObject */
360 $fileHandlerMock = $this->getMock('TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility', array('createNestedDirectory', 'getAbsolutePath', 'directoryExists'));
361 $fileHandlerMock->expects($this->any())
362 ->method('getAbsolutePath')
363 ->will($this->returnArgument(0));
364 return $fileHandlerMock;
365 }
366
367 /**
368 * @test
369 */
370 public function uploadFolderIsNotCreatedIfNotRequested() {
371 $fileHandlerMock = $this->getPreparedFileHandlingMockForDirectoryCreationTests();
372 $fileHandlerMock->expects($this->never())
373 ->method('createNestedDirectory');
374 $fileHandlerMock->ensureConfiguredDirectoriesExist(array(
375 'key' => 'foo_bar',
376 'uploadfolder' => 0,
377 )
378 );
379 }
380
381 /**
382 * @test
383 */
384 public function additionalFoldersAreNotCreatedIfNotRequested() {
385 $fileHandlerMock = $this->getPreparedFileHandlingMockForDirectoryCreationTests();
386 $fileHandlerMock->expects($this->never())
387 ->method('createNestedDirectory');
388 $fileHandlerMock->ensureConfiguredDirectoriesExist(array(
389 'key' => 'foo_bar',
390 'createDirs' => '',
391 )
392 );
393 }
394
395 /**
396 * @test
397 */
398 public function configuredUploadFolderIsCreatedIfRequested() {
399 $fileHandlerMock = $this->getPreparedFileHandlingMockForDirectoryCreationTests();
400 $fileHandlerMock->expects($this->once())
401 ->method('createNestedDirectory')
402 ->with('uploads/tx_foobar/');
403 $fileHandlerMock->ensureConfiguredDirectoriesExist(array(
404 'key' => 'foo_bar',
405 'uploadfolder' => 1,
406 )
407 );
408 }
409
410 /**
411 * @test
412 */
413 public function configuredAdditionalDirectoriesAreCreatedIfRequested() {
414 $fileHandlerMock = $this->getPreparedFileHandlingMockForDirectoryCreationTests();
415 $fileHandlerMock->expects($this->exactly(2))
416 ->method('createNestedDirectory')
417 ->will($this->returnCallback(function($path) {
418 if (!in_array($path, array('foo/bar', 'baz/foo'))) {
419 throw new \Exception('Path "' . $path . '" is not expected to be created');
420 }
421
422 })
423 );
424 $fileHandlerMock->ensureConfiguredDirectoriesExist(array(
425 'key' => 'foo_bar',
426 'createDirs' => 'foo/bar, baz/foo',
427 )
428 );
429 }
430
431 /**
432 * @test
433 */
434 public function configuredDirectoriesAreNotCreatedIfTheyAlreadyExist() {
435 $fileHandlerMock = $this->getPreparedFileHandlingMockForDirectoryCreationTests();
436 $fileHandlerMock->expects($this->exactly(3))
437 ->method('directoryExists')
438 ->will($this->returnValue(TRUE));
439 $fileHandlerMock->expects($this->never())
440 ->method('createNestedDirectory');
441 $fileHandlerMock->ensureConfiguredDirectoriesExist(array(
442 'key' => 'foo_bar',
443 'uploadfolder' => 1,
444 'createDirs' => 'foo/bar, baz/foo',
445 )
446 );
447 }
448
449 /**
450 * Warning: This test asserts multiple things at once to keep the setup short.
451 *
452 * @test
453 */
454 public function createZipFileFromExtensionGeneratesCorrectArchive() {
455 // Create extension for testing:
456 $extKey = $this->createFakeExtension();
457 $extensionRoot = $this->fakedExtensions[$extKey]['siteAbsPath'];
458
459 // Build mocked fileHandlingUtility:
460 $fileHandlerMock = $this->getAccessibleMock(
461 'TYPO3\\CMS\\Extensionmanager\\Utility\\FileHandlingUtility',
462 array('getAbsoluteExtensionPath', 'getExtensionVersion')
463 );
464 $fileHandlerMock->expects($this->any())
465 ->method('getAbsoluteExtensionPath')
466 ->will($this->returnValue($extensionRoot));
467 $fileHandlerMock->expects($this->any())
468 ->method('getExtensionVersion')
469 ->will($this->returnValue('0.0.0'));
470
471 // Add files and directories to extension:
472 touch($extensionRoot . 'emptyFile.txt');
473 file_put_contents($extensionRoot . 'notEmptyFile.txt', 'content');
474 touch($extensionRoot . '.hiddenFile');
475 mkdir($extensionRoot . 'emptyDir');
476 mkdir($extensionRoot . 'notEmptyDir');
477 touch($extensionRoot . 'notEmptyDir/file.txt');
478
479 // Create zip-file from extension
480 $filename = $fileHandlerMock->_call('createZipFileFromExtension', $extKey);
481
482 $expectedFilename = PATH_site . 'typo3temp/' . $extKey . '_0.0.0_' . date('YmdHi') . '.zip';
483 $this->assertEquals($expectedFilename, $filename, 'Archive file name differs from expectation');
484
485 // File was created
486 $this->assertTrue(file_exists($filename), 'Zip file not created');
487 $this->resourcesToRemove[] = $filename;
488
489 // Read archive and check its contents
490 $archive = new \ZipArchive();
491 $this->assertTrue($archive->open($filename), 'Unable to open archive');
492 $this->assertEquals($archive->statName('emptyFile.txt')->size, 0, 'Empty file not in archive');
493 $this->assertEquals($archive->getFromName('notEmptyFile.txt'), 'content', 'Expected content not found');
494 $this->assertFalse($archive->statName('.hiddenFile'), 'Hidden file not in archive');
495 $this->assertTrue(is_array($archive->statName('emptyDir/')), 'Empty directory not in archive');
496 $this->assertTrue(is_array($archive->statName('notEmptyDir/')), 'Not empty directory not in archive');
497 $this->assertTrue(is_array($archive->statName('notEmptyDir/file.txt')), 'File within directory not in archive');
498
499 // Check that the archive has no additional content
500 $this->assertEquals($archive->numFiles, 5, 'Too many or too less files in archive');
501 }
502 }