75cb22ac39a9290abfb7116eea3eb30b272a0585
[Packages/TYPO3.CMS.git] / typo3 / sysext / install / Classes / UpgradeAnalysis / DocumentationFile.php
1 <?php
2 declare(strict_types=1);
3 namespace TYPO3\CMS\Install\UpgradeAnalysis;
4
5 /*
6 * This file is part of the TYPO3 CMS project.
7 *
8 * It is free software; you can redistribute it and/or modify it under
9 * the terms of the GNU General Public License, either version 2
10 * of the License, or any later version.
11 *
12 * For the full copyright and license information, please read the
13 * LICENSE.txt file that was distributed with this source code.
14 *
15 * The TYPO3 project - inspiring people to share!
16 */
17
18 use TYPO3\CMS\Core\Utility\GeneralUtility;
19
20 /**
21 * Provide information about documentation files
22 */
23 class DocumentationFile
24 {
25
26 /**
27 * @var array Unified array of used tags
28 */
29 protected $tagsTotal = [];
30
31 /**
32 * Traverse given directory, select files
33 *
34 * @param string $path
35 * @return array file details of affected documentation files
36 */
37 public function findDocumentationFiles(string $path): array
38 {
39 $documentationFiles = [];
40 $versionDirectories = scandir($path);
41
42 $fileInfo = pathinfo($path);
43 $absolutePath = $fileInfo['dirname'] . DIRECTORY_SEPARATOR . $fileInfo['basename'];
44 foreach ($versionDirectories as $version) {
45 $directory = $absolutePath . DIRECTORY_SEPARATOR . $version;
46 $documentationFiles += $this->getDocumentationFilesForVersion($directory, $version);
47 }
48 $this->tagsTotal = $this->collectTagTotal($documentationFiles);
49
50 return $documentationFiles;
51 }
52
53 /**
54 * True if file should be considered
55 *
56 * @param array $fileInfo
57 * @return bool
58 */
59 protected function isRelevantFile(array $fileInfo): bool
60 {
61 return $fileInfo['extension'] === 'rst' && $fileInfo['filename'] !== 'Index';
62 }
63
64 /**
65 * Add tags from file
66 *
67 * @param array $file file content, each line is an array item
68 * @return array
69 */
70 protected function extractTags(array $file): array
71 {
72 $tags = $this->extractTagsFromFile($file);
73 // Headline starting with the category like Breaking, Important or Feature
74 $tags[] = $this->extractCategoryFromHeadline($file);
75
76 return $tags;
77 }
78
79 /**
80 * Files must contain an index entry, detailing any number of manual tags
81 * each of these tags is extracted and added to the general tag structure for the file
82 *
83 * @param array $file file content, each line is an array item
84 * @return array extracted tags
85 */
86 protected function extractTagsFromFile(array $file): array
87 {
88 foreach ($file as $line) {
89 if (strpos($line, '.. index::') === 0) {
90 $tagString = substr($line, strlen('.. index:: '));
91 return GeneralUtility::trimExplode(',', $tagString, true);
92 }
93 }
94
95 return [];
96 }
97
98 /**
99 * Files contain a headline (provided as input parameter,
100 * it starts with the category string.
101 * This will used as a tag
102 *
103 * @param array $lines
104 * @return string
105 */
106 protected function extractCategoryFromHeadline(array $lines): string
107 {
108 $headline = $this->extractHeadline($lines);
109 if (strpos($headline, ':') !== false) {
110 return 'cat:' . substr($headline, 0, strpos($headline, ':'));
111 }
112
113 return '';
114 }
115
116 /**
117 * First line is headline mark, skip it
118 * second line is headline
119 *
120 * @param array $lines
121 * @return string
122 */
123 protected function extractHeadline(array $lines): string
124 {
125 $index = 0;
126 while (strpos($lines[$index], '..') === 0 || strpos($lines[$index], '==') === 0) {
127 $index++;
128 }
129 return trim($lines[$index]);
130 }
131
132 /**
133 * Get issue number from headline
134 *
135 * @param string $headline
136 * @return int
137 */
138 protected function extractIssueNumber(string $headline): int
139 {
140 return (int)substr($headline, strpos($headline, '#') + 1, 5);
141 }
142
143 /**
144 * Get main information from a .rst file
145 *
146 * @param string $file
147 * @return array
148 */
149 protected function getListEntry(string $file): array
150 {
151 $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
152 $headline = $this->extractHeadline($lines);
153 $entry['headline'] = $headline;
154 $entry['filepath'] = $file;
155 $entry['tags'] = $this->extractTags($lines);
156 $entry['tagList'] = implode(',', $entry['tags']);
157 $entry['content'] = file_get_contents($file);
158 $issueNumber = $this->extractIssueNumber($headline);
159
160 return [$issueNumber => $entry];
161 }
162
163 /**
164 * True if files within directory should be considered
165 *
166 * @param string $versionDirectory
167 * @param string $version
168 * @return bool
169 */
170 protected function isRelevantDirectory(string $versionDirectory, string $version): bool
171 {
172 return is_dir($versionDirectory) && $version !== '.' && $version !== '..';
173 }
174
175 /**
176 * Handle a single directory
177 *
178 * @param string $docDirectory
179 * @param string $version
180 * @return array
181 */
182 protected function getDocumentationFilesForVersion(
183 string $docDirectory,
184 string $version
185 ): array {
186 $documentationFiles = [];
187 if ($this->isRelevantDirectory($docDirectory, $version)) {
188 $documentationFiles[$version] = [];
189 $absolutePath = dirname($docDirectory) . DIRECTORY_SEPARATOR . $version;
190 $rstFiles = scandir($docDirectory);
191 foreach ($rstFiles as $file) {
192 $fileInfo = pathinfo($file);
193 if ($this->isRelevantFile($fileInfo)) {
194 $filePath = $absolutePath . DIRECTORY_SEPARATOR . $fileInfo['basename'];
195 $documentationFiles[$version] += $this->getListEntry($filePath);
196 }
197 }
198 }
199
200 return $documentationFiles;
201 }
202
203 /**
204 * Merge tag list
205 *
206 * @param $documentationFiles
207 * @return array
208 */
209 protected function collectTagTotal($documentationFiles): array
210 {
211 $tags = [];
212 foreach ($documentationFiles as $versionArray) {
213 foreach ($versionArray as $fileArray) {
214 $tags = array_merge(array_unique($tags), $fileArray['tags']);
215 }
216 }
217
218 return array_unique($tags);
219 }
220
221 /**
222 * Return full tag list
223 *
224 * @return array
225 */
226 public function getTagsTotal(): array
227 {
228 return $this->tagsTotal;
229 }
230 }