‪TYPO3CMS  ‪main
DocumentationFile.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
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 
19 
20 use Symfony\Component\Finder\Finder;
21 use Symfony\Component\Finder\SplFileInfo;
26 
33 {
38  private string ‪$changelogPath;
39 
40  public function ‪__construct(string $changelogDir = '')
41  {
42  $this->changelogPath = $changelogDir !== '' ? $changelogDir : (string)realpath(‪ExtensionManagementUtility::extPath('core') . 'Documentation/Changelog');
43  $this->changelogPath = str_replace('\\', '/', $this->changelogPath);
44  }
45 
52  public function ‪findDocumentationDirectories(string $path): array
53  {
54  if (strcasecmp($path, $this->changelogPath) < 0 || !str_contains($path, $this->changelogPath)) {
55  throw new \InvalidArgumentException('the given path does not belong to the changelog dir. Aborting', 1537158043);
56  }
57 
58  $currentVersion = (int)explode('.', ‪VersionNumberUtility::getNumericTypo3Version())[0];
59  $versions = range($currentVersion, $currentVersion - 2);
60  $pattern = '(' . implode('\.*|', $versions) . '\.*)';
61  ‪$finder = new Finder();
63  ->depth(0)
64  ->sortByName(true)
65  ->name($pattern)
66  ->in($path);
67 
68  $directories = [];
69  foreach (‪$finder->directories() as $directory) {
71  $directories[] = $directory->getBasename();
72  }
73 
74  return $directories;
75  }
76 
83  public function ‪findDocumentationFiles(string $path): array
84  {
85  if (strcasecmp($path, $this->changelogPath) < 0 || !str_contains($path, $this->changelogPath)) {
86  throw new \InvalidArgumentException('the given path does not belong to the changelog dir. Aborting', 1485425530);
87  }
88  return $this->‪getDocumentationFilesForVersion($path);
89  }
90 
97  public function ‪getListEntry(string $file): array
98  {
99  $entry = [];
100  if (strcasecmp($file, $this->changelogPath) < 0 || !str_contains($file, $this->changelogPath)) {
101  throw new \InvalidArgumentException('the given file does not belong to the changelog dir. Aborting', 1485425531);
102  }
103  $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
104  $lines = is_array($lines) ? $lines : [];
105  $headline = $this->‪extractHeadline($lines);
106  $entry['version'] = ‪PathUtility::basename(‪PathUtility::dirname($file));
107  $entry['headline'] = $headline;
108  $entry['filepath'] = $file;
109  $entry['filename'] = pathinfo($file)['filename'];
110  $entry['tags'] = $this->‪extractTags($lines);
111  $entry['class'] = 'default';
112  foreach ($entry['tags'] as $key => $tag) {
113  if (str_starts_with($tag, 'cat:')) {
114  $substr = substr($tag, 4);
115  $entry['class'] = strtolower($substr);
116  $entry['tags'][$key] = $substr;
117  }
118  }
119  $entry['tagList'] = implode(',', $entry['tags']);
120  $entry['tagArray'] = $entry['tags'];
121  $entry['content'] = (string)file_get_contents($file);
122  $entry['parsedContent'] = $this->‪parseContent($entry['content']);
123  $entry['file_hash'] = md5($entry['content']);
124  if ($entry['version'] !== '') {
125  $entry['url']['documentation'] = sprintf(
126  'https://docs.typo3.org/c/typo3/cms-core/main/en-us/Changelog/%s/%s.html',
127  $entry['version'],
128  $entry['filename']
129  );
130  }
131  $issueId = $this->‪parseIssueId($entry['filename']);
132  if ($issueId) {
133  $entry['url']['issue'] = sprintf('https://forge.typo3.org/issues/%s', $issueId);
134  }
135 
136  return [md5($file) => $entry];
137  }
138 
144  private function ‪extractTags(array $file): array
145  {
146  $tags = $this->‪extractTagsFromFile($file);
147  // Headline starting with the category like Breaking, Important or Feature
148  $tags[] = $this->‪extractCategoryFromHeadline($file);
149  natcasesort($tags);
150 
151  return $tags;
152  }
153 
161  private function ‪extractTagsFromFile(array $file): array
162  {
163  foreach ($file as $line) {
164  if (str_starts_with($line, '.. index::')) {
165  $tagString = substr($line, strlen('.. index:: '));
166  return ‪GeneralUtility::trimExplode(',', $tagString, true);
167  }
168  }
169 
170  return [];
171  }
172 
178  private function ‪extractCategoryFromHeadline(array $lines): string
179  {
180  $headline = $this->‪extractHeadline($lines);
181  if (str_contains($headline, ':')) {
182  return 'cat:' . substr($headline, 0, (int)strpos($headline, ':'));
183  }
184  return '';
185  }
186 
190  private function ‪extractHeadline(array $lines): string
191  {
192  $index = 0;
193  while (str_starts_with($lines[$index], '..') || str_starts_with($lines[$index], '==')) {
194  $index++;
195  }
196  return trim($lines[$index]);
197  }
198 
202  private function ‪getDocumentationFilesForVersion(string $docDirectory): array
203  {
204  $documentationFiles = [[]];
205  $absolutePath = str_replace('\\', '/', $docDirectory);
206  ‪$finder = $this->‪getDocumentFinder()->in($absolutePath);
207  foreach (‪$finder->files() as $file) {
209  $documentationFiles[] = $this->‪getListEntry($file->getPathname());
210  }
211  return array_merge(...$documentationFiles);
212  }
213 
214  private function ‪parseContent(string $rstContent): string
215  {
216  $content = htmlspecialchars($rstContent, ENT_COMPAT | ENT_SUBSTITUTE);
217  $content = (string)preg_replace('/:issue:`([\d]*)`/', '<a href="https://forge.typo3.org/issues/\\1" target="_blank" rel="noreferrer">\\1</a>', $content);
218  $content = (string)preg_replace('/#([\d]*)/', '#<a href="https://forge.typo3.org/issues/\\1" target="_blank" rel="noreferrer">\\1</a>', $content);
219  $content = (string)preg_replace('/(\n([=]*)\n(.*)\n([=]*)\n)/', '', $content, 1);
220  $content = (string)preg_replace('/.. index::(.*)/', '', $content);
221  $content = (string)preg_replace('/.. include::(.*)/', '', $content);
222  return trim($content);
223  }
224 
225  private function ‪parseIssueId(string $filename): ?string
226  {
227  return ‪GeneralUtility::trimExplode('-', $filename)[1] ?? null;
228  }
229 
230  private function ‪getDocumentFinder(): Finder
231  {
232  ‪$finder = new Finder();
233  ‪$finder
234  ->depth(0)
235  ->sortByName()
236  ->name('/^(Feature|Breaking|Deprecation|Important)\-\d+.+\.rst$/i');
237  return ‪$finder;
238  }
239 }
‪TYPO3\CMS\Core\Utility\VersionNumberUtility
Definition: VersionNumberUtility.php:26
‪TYPO3\CMS\Core\Utility\PathUtility
Definition: PathUtility.php:27
‪TYPO3\CMS\Install\UpgradeAnalysis
Definition: DocumentationFile.php:18
‪$finder
‪if(PHP_SAPI !=='cli') $finder
Definition: header-comment.php:22
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile
Definition: DocumentationFile.php:33
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\extractTagsFromFile
‪array extractTagsFromFile(array $file)
Definition: DocumentationFile.php:161
‪TYPO3\CMS\Core\Utility\VersionNumberUtility\getNumericTypo3Version
‪static getNumericTypo3Version()
Definition: VersionNumberUtility.php:51
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\$changelogPath
‪string $changelogPath
Definition: DocumentationFile.php:38
‪TYPO3\CMS\Core\Utility\PathUtility\basename
‪static basename(string $path)
Definition: PathUtility.php:219
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\parseIssueId
‪parseIssueId(string $filename)
Definition: DocumentationFile.php:225
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\parseContent
‪parseContent(string $rstContent)
Definition: DocumentationFile.php:214
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\findDocumentationFiles
‪array findDocumentationFiles(string $path)
Definition: DocumentationFile.php:83
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility
Definition: ExtensionManagementUtility.php:32
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\__construct
‪__construct(string $changelogDir='')
Definition: DocumentationFile.php:40
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\getListEntry
‪getListEntry(string $file)
Definition: DocumentationFile.php:97
‪TYPO3\CMS\Core\Utility\PathUtility\dirname
‪static dirname(string $path)
Definition: PathUtility.php:243
‪TYPO3\CMS\Core\Utility\ExtensionManagementUtility\extPath
‪static extPath(string $key, string $script='')
Definition: ExtensionManagementUtility.php:82
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\extractTags
‪extractTags(array $file)
Definition: DocumentationFile.php:144
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\getDocumentFinder
‪getDocumentFinder()
Definition: DocumentationFile.php:230
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\extractCategoryFromHeadline
‪extractCategoryFromHeadline(array $lines)
Definition: DocumentationFile.php:178
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\extractHeadline
‪extractHeadline(array $lines)
Definition: DocumentationFile.php:190
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\getDocumentationFilesForVersion
‪getDocumentationFilesForVersion(string $docDirectory)
Definition: DocumentationFile.php:202
‪TYPO3\CMS\Core\Utility\GeneralUtility
Definition: GeneralUtility.php:52
‪TYPO3\CMS\Install\UpgradeAnalysis\DocumentationFile\findDocumentationDirectories
‪string[] findDocumentationDirectories(string $path)
Definition: DocumentationFile.php:52
‪TYPO3\CMS\Core\Utility\GeneralUtility\trimExplode
‪static list< string > trimExplode(string $delim, string $string, bool $removeEmptyValues=false, int $limit=0)
Definition: GeneralUtility.php:822