Revert "[TASK] Avoid slow array functions in loops"
[Packages/TYPO3.CMS.git] / typo3 / sysext / form / Classes / Mvc / Configuration / YamlSource.php
1 <?php
2 declare(strict_types = 1);
3 namespace TYPO3\CMS\Form\Mvc\Configuration;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It originated from the Neos.Form package (www.neos.io)
9 *
10 * It is free software; you can redistribute it and/or modify it under
11 * the terms of the GNU General Public License, either version 2
12 * of the License, or any later version.
13 *
14 * For the full copyright and license information, please read the
15 * LICENSE.txt file that was distributed with this source code.
16 *
17 * The TYPO3 project - inspiring people to share!
18 */
19
20 use Symfony\Component\Yaml\Exception\ParseException;
21 use Symfony\Component\Yaml\Yaml;
22 use TYPO3\CMS\Core\Resource\Exception\InsufficientFileAccessPermissionsException;
23 use TYPO3\CMS\Core\Resource\File;
24 use TYPO3\CMS\Core\Resource\FolderInterface;
25 use TYPO3\CMS\Core\Utility\ArrayUtility;
26 use TYPO3\CMS\Core\Utility\GeneralUtility;
27 use TYPO3\CMS\Form\Mvc\Configuration\Exception\FileWriteException;
28 use TYPO3\CMS\Form\Mvc\Configuration\Exception\NoSuchFileException;
29 use TYPO3\CMS\Form\Mvc\Configuration\Exception\ParseErrorException;
30 use TYPO3\CMS\Form\Slot\FilePersistenceSlot;
31
32 /**
33 * Configuration source based on YAML files
34 *
35 * Scope: frontend / backend
36 * @internal
37 */
38 class YamlSource
39 {
40 /**
41 * @var FilePersistenceSlot
42 */
43 protected $filePersistenceSlot;
44
45 /**
46 * @param FilePersistenceSlot $filePersistenceSlot
47 */
48 public function injectFilePersistenceSlot(FilePersistenceSlot $filePersistenceSlot)
49 {
50 $this->filePersistenceSlot = $filePersistenceSlot;
51 }
52
53 /**
54 * Loads the specified configuration files and returns its merged content
55 * as an array.
56 *
57 * @param array $filesToLoad
58 * @return array
59 * @throws ParseErrorException
60 * @throws NoSuchFileException
61 * @internal
62 */
63 public function load(array $filesToLoad): array
64 {
65 $configuration = [];
66 foreach ($filesToLoad as $fileToLoad) {
67 if ($fileToLoad instanceof File) {
68 $fileIdentifier = $fileToLoad->getIdentifier();
69 $rawYamlContent = $fileToLoad->getContents();
70 if ($rawYamlContent === false) {
71 throw new NoSuchFileException(
72 'The file "' . $fileIdentifier . '" does not exist.',
73 1498802253
74 );
75 }
76 } else {
77 $fileIdentifier = $fileToLoad;
78 $fileToLoad = GeneralUtility::getFileAbsFileName($fileToLoad);
79 if (is_file($fileToLoad)) {
80 $rawYamlContent = file_get_contents($fileToLoad);
81 } else {
82 throw new NoSuchFileException(
83 'The file "' . $fileToLoad . '" does not exist.',
84 1471473378
85 );
86 }
87 }
88
89 try {
90 $loadedConfiguration = Yaml::parse($rawYamlContent);
91
92 if (is_array($loadedConfiguration)) {
93 $configuration = array_replace_recursive($configuration, $loadedConfiguration);
94 }
95 } catch (ParseException $exception) {
96 throw new ParseErrorException(
97 'An error occurred while parsing file "' . $fileIdentifier . '": ' . $exception->getMessage(),
98 1480195405
99 );
100 }
101 }
102
103 $configuration = ArrayUtility::convertBooleanStringsToBooleanRecursive($configuration);
104 return $configuration;
105 }
106
107 /**
108 * Save the specified configuration array to the given file in YAML format.
109 *
110 * @param File|string $fileToSave The file to write to.
111 * @param array $configuration The configuration to save
112 * @return mixed
113 * @throws FileWriteException if the file could not be written
114 * @internal
115 */
116 public function save($fileToSave, array $configuration)
117 {
118 try {
119 $header = $this->getHeaderFromFile($fileToSave);
120 } catch (InsufficientFileAccessPermissionsException $e) {
121 throw new FileWriteException($e->getMessage(), 1512584488, $e);
122 }
123
124 $yaml = Yaml::dump($configuration, 99, 2);
125
126 if ($fileToSave instanceof File) {
127 try {
128 $this->filePersistenceSlot->allowInvocation(
129 FilePersistenceSlot::COMMAND_FILE_SET_CONTENTS,
130 $this->buildCombinedIdentifier(
131 $fileToSave->getParentFolder(),
132 $fileToSave->getName()
133 ),
134 $this->filePersistenceSlot->getContentSignature(
135 $header . LF . $yaml
136 )
137 );
138 $fileToSave->setContents($header . LF . $yaml);
139 } catch (InsufficientFileAccessPermissionsException $e) {
140 throw new FileWriteException($e->getMessage(), 1512582753, $e);
141 }
142 } else {
143 $byteCount = @file_put_contents($fileToSave, $header . LF . $yaml);
144
145 if ($byteCount === false) {
146 $error = error_get_last();
147 throw new FileWriteException($error['message'], 1512582929);
148 }
149 }
150
151 return $return;
152 }
153
154 /**
155 * Read the header part from the given file. That means, every line
156 * until the first non comment line is found.
157 *
158 * @param File|string $file
159 * @return string The header of the given YAML file
160 */
161 protected function getHeaderFromFile($file): string
162 {
163 $header = '';
164 if ($file instanceof File) {
165 $fileLines = explode(LF, $file->getContents());
166 } elseif (is_file($file)) {
167 $fileLines = file($file);
168 } else {
169 return '';
170 }
171
172 foreach ($fileLines as $line) {
173 if (preg_match('/^#/', $line)) {
174 $header .= $line;
175 } else {
176 break;
177 }
178 }
179 return $header;
180 }
181
182 /**
183 * @param FolderInterface $folder
184 * @param string $fileName
185 * @return string
186 */
187 protected function buildCombinedIdentifier(FolderInterface $folder, string $fileName): string
188 {
189 return sprintf(
190 '%d:%s%s',
191 $folder->getStorage()->getUid(),
192 $folder->getIdentifier(),
193 $fileName
194 );
195 }
196 }