TYPO3 CMS  TYPO3_8-7
DocumentationFile.php
Go to the documentation of this file.
1 <?php
2 declare(strict_types = 1);
3 
5 
6 /*
7  * This file is part of the TYPO3 CMS project.
8  *
9  * It is free software; you can redistribute it and/or modify it under
10  * the terms of the GNU General Public License, either version 2
11  * of the License, or any later version.
12  *
13  * For the full copyright and license information, please read the
14  * LICENSE.txt file that was distributed with this source code.
15  *
16  * The TYPO3 project - inspiring people to share!
17  */
18 
22 
27 {
31  protected $registry;
32 
36  protected $tagsTotal = [];
37 
44  protected $changelogPath = '';
45 
50  public function __construct(Registry $registry = null, $changelogDir = '')
51  {
52  $this->registry = $registry;
53  if ($this->registry === null) {
54  $this->registry = new Registry();
55  }
56  $this->changelogPath = $changelogDir !== '' ? $changelogDir : realpath(PATH_site . ExtensionManagementUtility::siteRelPath('core') . 'Documentation/Changelog');
57  $this->changelogPath = strtr($this->changelogPath, '\\', '/');
58  }
59 
67  public function findDocumentationFiles(string $path): array
68  {
69  if (strcasecmp($path, $this->changelogPath) < 0 || strpos($path, $this->changelogPath) === false) {
70  throw new \InvalidArgumentException('the given path does not belong to the changelog dir. Aborting', 1485425530);
71  }
72 
73  $documentationFiles = [];
74  $versionDirectories = scandir($path);
75 
76  $fileInfo = pathinfo($path);
77  $absolutePath = strtr($fileInfo['dirname'], '\\', '/') . '/' . $fileInfo['basename'];
78  foreach ($versionDirectories as $version) {
79  $directory = $absolutePath . '/' . $version;
80  $documentationFiles += $this->getDocumentationFilesForVersion($directory, $version);
81  }
82  $this->tagsTotal = $this->collectTagTotal($documentationFiles);
83 
84  return $documentationFiles;
85  }
86 
93  public function getListEntry(string $file): array
94  {
95  if (strcasecmp($file, $this->changelogPath) < 0 || strpos($file, $this->changelogPath) === false) {
96  throw new \InvalidArgumentException('the given file does not belong to the changelog dir. Aborting', 1485425531);
97  }
98  $lines = file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
99  $headline = $this->extractHeadline($lines);
100  $entry['headline'] = $headline;
101  $entry['filepath'] = $file;
102  $entry['tags'] = $this->extractTags($lines);
103  $entry['class'] = 'default';
104  foreach ($entry['tags'] as $key => $tag) {
105  if (strpos($tag, 'cat:') === 0) {
106  $substr = substr($tag, 4);
107  $entry['class'] = strtolower($substr);
108  $entry['tags'][$key] = $substr;
109  }
110  }
111  $entry['tagList'] = implode(',', $entry['tags']);
112  $entry['content'] = file_get_contents($file);
113  $entry['parsedContent'] = $this->parseContent($entry['content']);
114  $entry['file_hash'] = md5($entry['content']);
115 
116  return [md5($file) => $entry];
117  }
118 
125  protected function isRelevantFile(array $fileInfo): bool
126  {
127  $isRelevantFile = $fileInfo['extension'] === 'rst' && $fileInfo['filename'] !== 'Index';
128  // file might be ignored by users choice
129  if ($isRelevantFile && $this->isFileIgnoredByUsersChoice($fileInfo['basename'])) {
130  $isRelevantFile = false;
131  }
132 
133  return $isRelevantFile;
134  }
135 
142  protected function extractTags(array $file): array
143  {
144  $tags = $this->extractTagsFromFile($file);
145  // Headline starting with the category like Breaking, Important or Feature
146  $tags[] = $this->extractCategoryFromHeadline($file);
147 
148  return $tags;
149  }
150 
158  protected function extractTagsFromFile(array $file): array
159  {
160  foreach ($file as $line) {
161  if (strpos($line, '.. index::') === 0) {
162  $tagString = substr($line, strlen('.. index:: '));
163  return GeneralUtility::trimExplode(',', $tagString, true);
164  }
165  }
166 
167  return [];
168  }
169 
178  protected function extractCategoryFromHeadline(array $lines): string
179  {
180  $headline = $this->extractHeadline($lines);
181  if (strpos($headline, ':') !== false) {
182  return 'cat:' . substr($headline, 0, strpos($headline, ':'));
183  }
184 
185  return '';
186  }
187 
194  protected function extractHeadline(array $lines): string
195  {
196  $index = 0;
197  while (strpos($lines[$index], '..') === 0 || strpos($lines[$index], '==') === 0) {
198  $index++;
199  }
200  return trim($lines[$index]);
201  }
202 
210  protected function isRelevantDirectory(string $versionDirectory, string $version): bool
211  {
212  return is_dir($versionDirectory) && $version !== '.' && $version !== '..';
213  }
214 
223  string $docDirectory,
224  string $version
225  ): array {
226  $documentationFiles = [];
227  if ($this->isRelevantDirectory($docDirectory, $version)) {
228  $documentationFiles[$version] = [];
229  $absolutePath = strtr(dirname($docDirectory), '\\', '/') . '/' . $version;
230  $rstFiles = scandir($docDirectory);
231  foreach ($rstFiles as $file) {
232  $fileInfo = pathinfo($file);
233  if ($this->isRelevantFile($fileInfo)) {
234  $filePath = $absolutePath . '/' . $fileInfo['basename'];
235  $documentationFiles[$version] += $this->getListEntry($filePath);
236  }
237  }
238  }
239 
240  return $documentationFiles;
241  }
242 
249  protected function collectTagTotal($documentationFiles): array
250  {
251  $tags = [];
252  foreach ($documentationFiles as $versionArray) {
253  foreach ($versionArray as $fileArray) {
254  $tags = array_merge(array_unique($tags), $fileArray['tags']);
255  }
256  }
257 
258  return array_unique($tags);
259  }
260 
266  public function getTagsTotal(): array
267  {
268  return $this->tagsTotal;
269  }
270 
277  protected function isFileIgnoredByUsersChoice(string $filename): bool
278  {
279  $isFileIgnoredByUsersChoice = false;
280 
281  $ignoredFiles = $this->registry->get('upgradeAnalysisIgnoreFilter', 'ignoredDocumentationFiles');
282  if (is_array($ignoredFiles)) {
283  foreach ($ignoredFiles as $filePath) {
284  if ($filePath !== null && strlen($filePath) > 0) {
285  if (strpos($filePath, $filename) !== false) {
286  $isFileIgnoredByUsersChoice = true;
287  break;
288  }
289  }
290  }
291  }
292  return $isFileIgnoredByUsersChoice;
293  }
294 
301  protected function parseContent(string $rstContent): string
302  {
303  $content = htmlspecialchars($rstContent);
304  $content = preg_replace('/:issue:`([\d]*)`/', '<a href="https://forge.typo3.org/issues/\\1" target="_blank" rel="noopener noreferrer">\\1</a>', $content);
305  $content = preg_replace('/#([\d]*)/', '#<a href="https://forge.typo3.org/issues/\\1" target="_blank" rel="noopener noreferrer">\\1</a>', $content);
306  $content = preg_replace('/(\n([=]*)\n(.*)\n([=]*)\n)/', '', $content, 1);
307  $content = preg_replace('/.. index::(.*)/', '', $content);
308  $content = preg_replace('/.. include::(.*)/', '', $content);
309  return trim($content);
310  }
311 }
isRelevantDirectory(string $versionDirectory, string $version)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
__construct(Registry $registry=null, $changelogDir='')
getDocumentationFilesForVersion(string $docDirectory, string $version)