[BUGFIX] Use proper URL for file list of returnUrl in file_upload
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Tests / FunctionalTestCaseBootstrapUtility.php
1 <?php
2 namespace TYPO3\CMS\Core\Tests;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16
17 /**
18 * Utility class to set up and bootstrap TYPO3 CMS for functional tests
19 */
20 class FunctionalTestCaseBootstrapUtility
21 {
22 /**
23 * @var string Identifier calculated from test case class
24 */
25 protected $identifier;
26
27 /**
28 * @var string Absolute path to test instance document root
29 */
30 protected $instancePath;
31
32 /**
33 * @var string Name of test database
34 */
35 protected $databaseName;
36
37 /**
38 * @var string Name of original database
39 */
40 protected $originalDatabaseName;
41
42 /**
43 * @var array These extensions are always loaded
44 */
45 protected $defaultActivatedCoreExtensions = array(
46 'core',
47 'backend',
48 'frontend',
49 'lang',
50 'extbase',
51 'install',
52 );
53
54 /**
55 * @var array These folder are always created
56 */
57 protected $defaultFoldersToCreate = array(
58 '',
59 '/fileadmin',
60 '/typo3temp',
61 '/typo3temp/var',
62 '/typo3temp/var/transient',
63 '/typo3temp/assets',
64 '/typo3conf',
65 '/typo3conf/ext',
66 '/uploads'
67 );
68
69 /**
70 * Calculate a "unique" identifier for the test database and the
71 * instance patch based on the given test case class name.
72 *
73 * @param string $testCaseClassName Name of test case class
74 * @return string
75 */
76 public static function getInstanceIdentifier($testCaseClassName)
77 {
78 // 7 characters of sha1 should be enough for a unique identification
79 return substr(sha1($testCaseClassName), 0, 7);
80 }
81
82 /**
83 * Calculates path to TYPO3 CMS test installation for this test case.
84 *
85 * @param string $testCaseClassName Name of test case class
86 * @return string
87 */
88 public static function getInstancePath($testCaseClassName)
89 {
90 return ORIGINAL_ROOT . 'typo3temp/var/tests/functional-' . static::getInstanceIdentifier($testCaseClassName);
91 }
92
93 /**
94 * Set up creates a test instance and database.
95 *
96 * @param string $testCaseClassName Name of test case class
97 * @param array $coreExtensionsToLoad Array of core extensions to load
98 * @param array $testExtensionsToLoad Array of test extensions to load
99 * @param array $pathsToLinkInTestInstance Array of source => destination path pairs to be linked
100 * @param array $configurationToUse Array of TYPO3_CONF_VARS that need to be overridden
101 * @param array $additionalFoldersToCreate Array of folder paths to be created
102 * @return string Path to TYPO3 CMS test installation for this test case
103 */
104 public function setUp(
105 $testCaseClassName,
106 array $coreExtensionsToLoad,
107 array $testExtensionsToLoad,
108 array $pathsToLinkInTestInstance,
109 array $configurationToUse,
110 array $additionalFoldersToCreate
111 ) {
112 $this->setUpIdentifier($testCaseClassName);
113 $this->setUpInstancePath($testCaseClassName);
114 if ($this->recentTestInstanceExists()) {
115 $this->setUpBasicTypo3Bootstrap();
116 $this->initializeTestDatabase();
117 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables();
118 } else {
119 $this->removeOldInstanceIfExists();
120 $this->setUpInstanceDirectories($additionalFoldersToCreate);
121 $this->setUpInstanceCoreLinks();
122 $this->linkTestExtensionsToInstance($testExtensionsToLoad);
123 $this->linkPathsInTestInstance($pathsToLinkInTestInstance);
124 $this->setUpLocalConfiguration($configurationToUse);
125 $this->setUpPackageStates($coreExtensionsToLoad, $testExtensionsToLoad);
126 $this->setUpBasicTypo3Bootstrap();
127 $this->setUpTestDatabase();
128 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->loadExtensionTables();
129 $this->createDatabaseStructure();
130 }
131
132 return $this->instancePath;
133 }
134
135 /**
136 * Checks whether the current test instance exists and is younger than
137 * some minutes.
138 *
139 * @return bool
140 */
141 protected function recentTestInstanceExists()
142 {
143 if (@file_get_contents($this->instancePath . '/last_run.txt') <= (time() - 300)) {
144 return false;
145 } else {
146 // Test instance exists and is pretty young -> re-use
147 return true;
148 }
149 }
150
151 /**
152 * Calculate a "unique" identifier for the test database and the
153 * instance patch based on the given test case class name.
154 *
155 * As a result, the database name will be identical between different
156 * test runs, but different between each test case.
157 *
158 * @param string $testCaseClassName Name of test case class
159 * @return void
160 */
161 protected function setUpIdentifier($testCaseClassName)
162 {
163 $this->identifier = static::getInstanceIdentifier($testCaseClassName);
164 }
165
166 /**
167 * Calculates path to TYPO3 CMS test installation for this test case.
168 *
169 * @param string $testCaseClassName Name of test case class
170 * @return void
171 */
172 protected function setUpInstancePath($testCaseClassName)
173 {
174 $this->instancePath = static::getInstancePath($testCaseClassName);
175 }
176
177 /**
178 * Remove test instance folder structure in setUp() if it exists.
179 * This may happen if a functional test before threw a fatal.
180 *
181 * @return void
182 */
183 protected function removeOldInstanceIfExists()
184 {
185 if (is_dir($this->instancePath)) {
186 $this->removeInstance();
187 }
188 }
189
190 /**
191 * Create folder structure of test instance.
192 *
193 * @param array $additionalFoldersToCreate Array of additional folders to be created
194 * @throws Exception
195 * @return void
196 */
197 protected function setUpInstanceDirectories(array $additionalFoldersToCreate = array())
198 {
199 $foldersToCreate = array_merge($this->defaultFoldersToCreate, $additionalFoldersToCreate);
200 foreach ($foldersToCreate as $folder) {
201 $success = mkdir($this->instancePath . $folder);
202 if (!$success) {
203 throw new Exception(
204 'Creating directory failed: ' . $this->instancePath . $folder,
205 1376657189
206 );
207 }
208 }
209
210 // Store the time we created this directory
211 file_put_contents($this->instancePath . '/last_run.txt', time());
212 }
213
214 /**
215 * Link TYPO3 CMS core from "parent" instance.
216 *
217 * @throws Exception
218 * @return void
219 */
220 protected function setUpInstanceCoreLinks()
221 {
222 $linksToSet = array(
223 ORIGINAL_ROOT . 'typo3' => $this->instancePath . '/typo3',
224 ORIGINAL_ROOT . 'index.php' => $this->instancePath . '/index.php'
225 );
226 foreach ($linksToSet as $from => $to) {
227 $success = symlink($from, $to);
228 if (!$success) {
229 throw new Exception(
230 'Creating link failed: from ' . $from . ' to: ' . $to,
231 1376657199
232 );
233 }
234 }
235 }
236
237 /**
238 * Link test extensions to the typo3conf/ext folder of the instance.
239 *
240 * @param array $extensionPaths Contains paths to extensions relative to document root
241 * @throws Exception
242 * @return void
243 */
244 protected function linkTestExtensionsToInstance(array $extensionPaths)
245 {
246 foreach ($extensionPaths as $extensionPath) {
247 $absoluteExtensionPath = ORIGINAL_ROOT . $extensionPath;
248 if (!is_dir($absoluteExtensionPath)) {
249 throw new Exception(
250 'Test extension path ' . $absoluteExtensionPath . ' not found',
251 1376745645
252 );
253 }
254 $destinationPath = $this->instancePath . '/typo3conf/ext/' . basename($absoluteExtensionPath);
255 $success = symlink($absoluteExtensionPath, $destinationPath);
256 if (!$success) {
257 throw new Exception(
258 'Can not link extension folder: ' . $absoluteExtensionPath . ' to ' . $destinationPath,
259 1376657142
260 );
261 }
262 }
263 }
264
265 /**
266 * Link paths inside the test instance, e.g. from a fixture fileadmin subfolder to the
267 * test instance fileadmin folder
268 *
269 * @param array $pathsToLinkInTestInstance Contains paths as array of source => destination in key => value pairs of folders relative to test instance root
270 * @throws \TYPO3\CMS\Core\Tests\Exception if a source path could not be found
271 * @throws \TYPO3\CMS\Core\Tests\Exception on failing creating the symlink
272 * @return void
273 * @see \TYPO3\CMS\Core\Tests\FunctionalTestCase::$pathsToLinkInTestInstance
274 */
275 protected function linkPathsInTestInstance(array $pathsToLinkInTestInstance)
276 {
277 foreach ($pathsToLinkInTestInstance as $sourcePathToLinkInTestInstance => $destinationPathToLinkInTestInstance) {
278 $sourcePath = $this->instancePath . '/' . ltrim($sourcePathToLinkInTestInstance, '/');
279 if (!file_exists($sourcePath)) {
280 throw new Exception(
281 'Path ' . $sourcePath . ' not found',
282 1376745645
283 );
284 }
285 $destinationPath = $this->instancePath . '/' . ltrim($destinationPathToLinkInTestInstance, '/');
286 $success = symlink($sourcePath, $destinationPath);
287 if (!$success) {
288 throw new Exception(
289 'Can not link the path ' . $sourcePath . ' to ' . $destinationPath,
290 1389969623
291 );
292 }
293 }
294 }
295
296 /**
297 * Create LocalConfiguration.php file in the test instance
298 *
299 * @param array $configurationToMerge
300 * @throws Exception
301 * @return void
302 */
303 protected function setUpLocalConfiguration(array $configurationToMerge)
304 {
305 $databaseName = trim(getenv('typo3DatabaseName'));
306 $databaseHost = trim(getenv('typo3DatabaseHost'));
307 $databaseUsername = trim(getenv('typo3DatabaseUsername'));
308 $databasePassword = trim(getenv('typo3DatabasePassword'));
309 $databasePort = trim(getenv('typo3DatabasePort'));
310 $databaseSocket = trim(getenv('typo3DatabaseSocket'));
311 if ($databaseName || $databaseHost || $databaseUsername || $databasePassword || $databasePort || $databaseSocket) {
312 // Try to get database credentials from environment variables first
313 $originalConfigurationArray = array(
314 'DB' => array(),
315 );
316 if ($databaseName) {
317 $originalConfigurationArray['DB']['database'] = $databaseName;
318 }
319 if ($databaseHost) {
320 $originalConfigurationArray['DB']['host'] = $databaseHost;
321 }
322 if ($databaseUsername) {
323 $originalConfigurationArray['DB']['username'] = $databaseUsername;
324 }
325 if ($databasePassword) {
326 $originalConfigurationArray['DB']['password'] = $databasePassword;
327 }
328 if ($databasePort) {
329 $originalConfigurationArray['DB']['port'] = $databasePort;
330 }
331 if ($databaseSocket) {
332 $originalConfigurationArray['DB']['socket'] = $databaseSocket;
333 }
334 } elseif (file_exists(ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php')) {
335 // See if a LocalConfiguration file exists in "parent" instance to get db credentials from
336 $originalConfigurationArray = require ORIGINAL_ROOT . 'typo3conf/LocalConfiguration.php';
337 } else {
338 throw new Exception(
339 'Database credentials for functional tests are neither set through environment'
340 . ' variables, and can not be found in an existing LocalConfiguration file',
341 1397406356
342 );
343 }
344
345 // Base of final LocalConfiguration is core factory configuration
346 $finalConfigurationArray = require ORIGINAL_ROOT . 'typo3/sysext/core/Configuration/FactoryConfiguration.php';
347
348 $this->mergeRecursiveWithOverrule($finalConfigurationArray, require ORIGINAL_ROOT . 'typo3/sysext/core/Build/Configuration/FunctionalTestsConfiguration.php');
349 $this->mergeRecursiveWithOverrule($finalConfigurationArray, $configurationToMerge);
350 $finalConfigurationArray['DB'] = $originalConfigurationArray['DB'];
351 // Calculate and set new database name
352 $this->originalDatabaseName = $originalConfigurationArray['DB']['database'];
353 $this->databaseName = $this->originalDatabaseName . '_ft' . $this->identifier;
354
355 // Maximum database name length for mysql is 64 characters
356 if (strlen($this->databaseName) > 64) {
357 $maximumOriginalDatabaseName = 64 - strlen('_ft' . $this->identifier);
358 throw new Exception(
359 'The name of the database that is used for the functional test (' . $this->databaseName . ')' .
360 ' exceeds the maximum length of 64 character allowed by MySQL. You have to shorten your' .
361 ' original database name to ' . $maximumOriginalDatabaseName . ' characters',
362 1377600104
363 );
364 }
365
366 $finalConfigurationArray['DB']['database'] = $this->databaseName;
367
368 $result = $this->writeFile(
369 $this->instancePath . '/typo3conf/LocalConfiguration.php',
370 '<?php' . chr(10) .
371 'return ' .
372 $this->arrayExport(
373 $finalConfigurationArray
374 ) .
375 ';' . chr(10) .
376 '?>'
377 );
378 if (!$result) {
379 throw new Exception('Can not write local configuration', 1376657277);
380 }
381 }
382
383 /**
384 * Compile typo3conf/PackageStates.php containing default packages like core,
385 * a functional test specific list of additional core extensions, and a list of
386 * test extensions.
387 *
388 * @param array $coreExtensionsToLoad Additional core extensions to load
389 * @param array $testExtensionPaths Paths to extensions relative to document root
390 * @throws Exception
391 * @TODO Figure out what the intention of the upper arguments is
392 */
393 protected function setUpPackageStates(array $coreExtensionsToLoad, array $testExtensionPaths)
394 {
395 $packageStates = array(
396 'packages' => array(),
397 'version' => 4,
398 );
399
400 // Register default list of extensions and set active
401 foreach ($this->defaultActivatedCoreExtensions as $extensionName) {
402 $packageStates['packages'][$extensionName] = array(
403 'state' => 'active',
404 'packagePath' => 'typo3/sysext/' . $extensionName . '/',
405 'classesPath' => 'Classes/',
406 );
407 }
408
409 // Register additional core extensions and set active
410 foreach ($coreExtensionsToLoad as $extensionName) {
411 if (isset($packageSates['packages'][$extensionName])) {
412 throw new Exception(
413 $extensionName . ' is already registered as default core extension to load, no need to load it explicitly',
414 1390913893
415 );
416 }
417 $packageStates['packages'][$extensionName] = array(
418 'state' => 'active',
419 'packagePath' => 'typo3/sysext/' . $extensionName . '/',
420 'classesPath' => 'Classes/',
421 );
422 }
423
424 // Activate test extensions that have been symlinked before
425 foreach ($testExtensionPaths as $extensionPath) {
426 $extensionName = basename($extensionPath);
427 if (isset($packageSates['packages'][$extensionName])) {
428 throw new Exception(
429 $extensionName . ' is already registered as extension to load, no need to load it explicitly',
430 1390913894
431 );
432 }
433 $packageStates['packages'][$extensionName] = array(
434 'state' => 'active',
435 'packagePath' => 'typo3conf/ext/' . $extensionName . '/',
436 'classesPath' => 'Classes/',
437 );
438 }
439
440 $result = $this->writeFile(
441 $this->instancePath . '/typo3conf/PackageStates.php',
442 '<?php' . chr(10) .
443 'return ' .
444 $this->arrayExport(
445 $packageStates
446 ) .
447 ';' . chr(10) .
448 '?>'
449 );
450 if (!$result) {
451 throw new Exception('Can not write PackageStates', 1381612729);
452 }
453 }
454
455 /**
456 * Bootstrap basic TYPO3
457 *
458 * @return void
459 */
460 protected function setUpBasicTypo3Bootstrap()
461 {
462 $_SERVER['PWD'] = $this->instancePath;
463 $_SERVER['argv'][0] = 'index.php';
464
465 define('TYPO3_MODE', 'BE');
466
467 $classLoader = require rtrim(realpath($this->instancePath . '/typo3'), '\\/') . '/../vendor/autoload.php';
468 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()
469 ->initializeClassLoader($classLoader)
470 ->setRequestType(TYPO3_REQUESTTYPE_BE | TYPO3_REQUESTTYPE_CLI)
471 ->baseSetup('')
472 ->loadConfigurationAndInitialize(true)
473 ->loadTypo3LoadedExtAndExtLocalconf(true)
474 ->setFinalCachingFrameworkCacheConfiguration()
475 ->defineLoggingAndExceptionConstants()
476 ->unsetReservedGlobalVariables();
477 }
478
479 /**
480 * Populate $GLOBALS['TYPO3_DB'] and create test database
481 *
482 * @throws \TYPO3\CMS\Core\Tests\Exception
483 * @return void
484 */
485 protected function setUpTestDatabase()
486 {
487 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->initializeTypo3DbGlobal();
488 /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
489 $database = $GLOBALS['TYPO3_DB'];
490 if (!$database->sql_pconnect()) {
491 throw new Exception(
492 'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
493 . ' connection to the database was attempted to be established!',
494 1377620117
495 );
496 }
497
498 // Drop database in case a previous test had a fatal and did not clean up properly
499 $database->admin_query('DROP DATABASE IF EXISTS `' . $this->databaseName . '`');
500 $createDatabaseResult = $database->admin_query('CREATE DATABASE `' . $this->databaseName . '` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci');
501 if (!$createDatabaseResult) {
502 $user = $GLOBALS['TYPO3_CONF_VARS']['DB']['username'];
503 $host = $GLOBALS['TYPO3_CONF_VARS']['DB']['host'];
504 throw new Exception(
505 'Unable to create database with name ' . $this->databaseName . '. This is probably a permission problem.'
506 . ' For this instance this could be fixed executing'
507 . ' "GRANT ALL ON `' . $this->originalDatabaseName . '_ft%`.* TO `' . $user . '`@`' . $host . '`;"',
508 1376579070
509 );
510 }
511 $database->setDatabaseName($this->databaseName);
512 // On windows, this still works, but throws a warning, which we need to discard.
513 @$database->sql_select_db();
514 }
515
516 /**
517 * Populate $GLOBALS['TYPO3_DB'] reusing an existing database with
518 * all tables truncated.
519 *
520 * @throws \TYPO3\CMS\Core\Tests\Exception
521 * @return void
522 */
523 protected function initializeTestDatabase()
524 {
525 \TYPO3\CMS\Core\Core\Bootstrap::getInstance()->initializeTypo3DbGlobal();
526 /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
527 $database = $GLOBALS['TYPO3_DB'];
528 if (!$database->sql_pconnect()) {
529 throw new Exception(
530 'TYPO3 Fatal Error: The current username, password or host was not accepted when the'
531 . ' connection to the database was attempted to be established!',
532 1377620117
533 );
534 }
535 $this->databaseName = $GLOBALS['TYPO3_CONF_VARS']['DB']['database'];
536 $database->setDatabaseName($this->databaseName);
537 $database->sql_select_db();
538 foreach ($database->admin_get_tables() as $table) {
539 $database->admin_query('TRUNCATE ' . $table['Name'] . ';');
540 }
541 }
542
543 /**
544 * Create tables and import static rows
545 *
546 * @return void
547 */
548 protected function createDatabaseStructure()
549 {
550 /** @var \TYPO3\CMS\Install\Service\SqlSchemaMigrationService $schemaMigrationService */
551 $schemaMigrationService = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Install\Service\SqlSchemaMigrationService::class);
552 /** @var \TYPO3\CMS\Extbase\Object\ObjectManager $objectManager */
553 $objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
554 /** @var \TYPO3\CMS\Install\Service\SqlExpectedSchemaService $expectedSchemaService */
555 $expectedSchemaService = $objectManager->get(\TYPO3\CMS\Install\Service\SqlExpectedSchemaService::class);
556
557 // Raw concatenated ext_tables.sql and friends string
558 $expectedSchemaString = $expectedSchemaService->getTablesDefinitionString(true);
559 $statements = $schemaMigrationService->getStatementArray($expectedSchemaString, true);
560 list($_, $insertCount) = $schemaMigrationService->getCreateTables($statements, true);
561
562 $fieldDefinitionsFile = $schemaMigrationService->getFieldDefinitions_fileContent($expectedSchemaString);
563 $fieldDefinitionsDatabase = $schemaMigrationService->getFieldDefinitions_database();
564 $difference = $schemaMigrationService->getDatabaseExtra($fieldDefinitionsFile, $fieldDefinitionsDatabase);
565 $updateStatements = $schemaMigrationService->getUpdateSuggestions($difference);
566
567 $schemaMigrationService->performUpdateQueries($updateStatements['add'], $updateStatements['add']);
568 $schemaMigrationService->performUpdateQueries($updateStatements['change'], $updateStatements['change']);
569 $schemaMigrationService->performUpdateQueries($updateStatements['create_table'], $updateStatements['create_table']);
570
571 foreach ($insertCount as $table => $count) {
572 $insertStatements = $schemaMigrationService->getTableInsertStatements($statements, $table);
573 foreach ($insertStatements as $insertQuery) {
574 $insertQuery = rtrim($insertQuery, ';');
575 /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
576 $database = $GLOBALS['TYPO3_DB'];
577 $database->admin_query($insertQuery);
578 }
579 }
580 }
581
582 /**
583 * Drop test database.
584 *
585 * @throws \TYPO3\CMS\Core\Tests\Exception
586 * @return void
587 */
588 protected function tearDownTestDatabase()
589 {
590 /** @var \TYPO3\CMS\Core\Database\DatabaseConnection $database */
591 $database = $GLOBALS['TYPO3_DB'];
592 $result = $database->admin_query('DROP DATABASE `' . $this->databaseName . '`');
593 if (!$result) {
594 throw new Exception(
595 'Dropping test database ' . $this->databaseName . ' failed',
596 1376583188
597 );
598 }
599 }
600
601 /**
602 * Removes instance directories and files
603 *
604 * @throws \TYPO3\CMS\Core\Tests\Exception
605 * @return void
606 */
607 protected function removeInstance()
608 {
609 $success = $this->rmdir($this->instancePath, true);
610 if (!$success) {
611 throw new Exception(
612 'Can not remove folder: ' . $this->instancePath,
613 1376657210
614 );
615 }
616 }
617
618 /**
619 * COPIED FROM GeneralUtility
620 *
621 * Wrapper function for rmdir, allowing recursive deletion of folders and files
622 *
623 * @param string $path Absolute path to folder, see PHP rmdir() function. Removes trailing slash internally.
624 * @param bool $removeNonEmpty Allow deletion of non-empty directories
625 * @return bool TRUE if @rmdir went well!
626 */
627 protected function rmdir($path, $removeNonEmpty = false)
628 {
629 $OK = false;
630 // Remove trailing slash
631 $path = preg_replace('|/$|', '', $path);
632 if (file_exists($path)) {
633 $OK = true;
634 if (!is_link($path) && is_dir($path)) {
635 if ($removeNonEmpty == true && ($handle = opendir($path))) {
636 while ($OK && false !== ($file = readdir($handle))) {
637 if ($file == '.' || $file == '..') {
638 continue;
639 }
640 $OK = $this->rmdir($path . '/' . $file, $removeNonEmpty);
641 }
642 closedir($handle);
643 }
644 if ($OK) {
645 $OK = @rmdir($path);
646 }
647 } else {
648 // If $path is a symlink to a folder we need rmdir() on Windows systems
649 if (!stristr(PHP_OS, 'darwin') && stristr(PHP_OS, 'win') && is_link($path) && is_dir($path . '/')) {
650 $OK = rmdir($path);
651 } else {
652 $OK = unlink($path);
653 }
654 }
655 clearstatcache();
656 } elseif (is_link($path)) {
657 $OK = unlink($path);
658 clearstatcache();
659 }
660 return $OK;
661 }
662
663 /**
664 * COPIED FROM GeneralUtility
665 *
666 * Writes $content to the file $file
667 *
668 * @param string $file Filepath to write to
669 * @param string $content Content to write
670 * @return bool TRUE if the file was successfully opened and written to.
671 */
672 protected function writeFile($file, $content)
673 {
674 if ($fd = fopen($file, 'wb')) {
675 $res = fwrite($fd, $content);
676 fclose($fd);
677 if ($res === false) {
678 return false;
679 }
680 return true;
681 }
682 return false;
683 }
684
685 /**
686 * COPIED FROM ArrayUtility
687 *
688 * Exports an array as string.
689 * Similar to var_export(), but representation follows the TYPO3 core CGL.
690 *
691 * See unit tests for detailed examples
692 *
693 * @param array $array Array to export
694 * @param int $level Internal level used for recursion, do *not* set from outside!
695 * @return string String representation of array
696 * @throws \RuntimeException
697 */
698 protected function arrayExport(array $array = array(), $level = 0)
699 {
700 $lines = 'array(' . chr(10);
701 $level++;
702 $writeKeyIndex = false;
703 $expectedKeyIndex = 0;
704 foreach ($array as $key => $value) {
705 if ($key === $expectedKeyIndex) {
706 $expectedKeyIndex++;
707 } else {
708 // Found a non integer or non consecutive key, so we can break here
709 $writeKeyIndex = true;
710 break;
711 }
712 }
713 foreach ($array as $key => $value) {
714 // Indention
715 $lines .= str_repeat(chr(9), $level);
716 if ($writeKeyIndex) {
717 // Numeric / string keys
718 $lines .= is_int($key) ? $key . ' => ' : '\'' . $key . '\' => ';
719 }
720 if (is_array($value)) {
721 if (!empty($value)) {
722 $lines .= $this->arrayExport($value, $level);
723 } else {
724 $lines .= 'array(),' . chr(10);
725 }
726 } elseif (is_int($value) || is_float($value)) {
727 $lines .= $value . ',' . chr(10);
728 } elseif (is_null($value)) {
729 $lines .= 'NULL' . ',' . chr(10);
730 } elseif (is_bool($value)) {
731 $lines .= $value ? 'TRUE' : 'FALSE';
732 $lines .= ',' . chr(10);
733 } elseif (is_string($value)) {
734 // Quote \ to \\
735 $stringContent = str_replace('\\', '\\\\', $value);
736 // Quote ' to \'
737 $stringContent = str_replace('\'', '\\\'', $stringContent);
738 $lines .= '\'' . $stringContent . '\'' . ',' . chr(10);
739 } else {
740 throw new \RuntimeException('Objects are not supported', 1342294986);
741 }
742 }
743 $lines .= str_repeat(chr(9), ($level - 1)) . ')' . ($level - 1 == 0 ? '' : ',' . chr(10));
744 return $lines;
745 }
746
747 /**
748 * COPIED FROM ArrayUtility
749 *
750 * Merges two arrays recursively and "binary safe" (integer keys are
751 * overridden as well), overruling similar values in the original array
752 * with the values of the overrule array.
753 * In case of identical keys, ie. keeping the values of the overrule array.
754 *
755 * This method takes the original array by reference for speed optimization with large arrays
756 *
757 * The differences to the existing PHP function array_merge_recursive() are:
758 * * Keys of the original array can be unset via the overrule array. ($enableUnsetFeature)
759 * * Much more control over what is actually merged. ($addKeys, $includeEmptyValues)
760 * * Elements or the original array get overwritten if the same key is present in the overrule array.
761 *
762 * @param array $original Original array. It will be *modified* by this method and contains the result afterwards!
763 * @param array $overrule Overrule array, overruling the original array
764 * @param bool $addKeys If set to FALSE, keys that are NOT found in $original will not be set. Thus only existing value can/will be overruled from overrule array.
765 * @param bool $includeEmptyValues If set, values from $overrule will overrule if they are empty or zero.
766 * @param bool $enableUnsetFeature If set, special values "__UNSET" can be used in the overrule array in order to unset array keys in the original array.
767 * @return void
768 */
769 protected function mergeRecursiveWithOverrule(array &$original, array $overrule, $addKeys = true, $includeEmptyValues = true, $enableUnsetFeature = true)
770 {
771 foreach ($overrule as $key => $_) {
772 if ($enableUnsetFeature && $overrule[$key] === '__UNSET') {
773 unset($original[$key]);
774 continue;
775 }
776 if (isset($original[$key]) && is_array($original[$key])) {
777 if (is_array($overrule[$key])) {
778 self::mergeRecursiveWithOverrule($original[$key], $overrule[$key], $addKeys, $includeEmptyValues, $enableUnsetFeature);
779 }
780 } elseif (
781 ($addKeys || isset($original[$key])) &&
782 ($includeEmptyValues || $overrule[$key])
783 ) {
784 $original[$key] = $overrule[$key];
785 }
786 }
787 // This line is kept for backward compatibility reasons.
788 reset($original);
789 }
790 }