TYPO3 CMS  TYPO3_7-6
DocumentationService.php
Go to the documentation of this file.
1 <?php
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 
18 
23 {
29  public function getOfficialDocuments()
30  {
31  $documents = [];
32 
33  $json = GeneralUtility::getUrl('https://docs.typo3.org/typo3cms/documents.json');
34  if ($json) {
35  $documents = json_decode($json, true);
36  foreach ($documents as &$document) {
37  $document['icon'] = \TYPO3\CMS\Documentation\Utility\MiscUtility::getIcon($document['key']);
38  }
39 
40  // Cache file locally to be able to create a composer.json file when fetching a document
41  $absoluteCacheFilename = GeneralUtility::getFileAbsFileName('typo3temp/Documentation/documents.json');
42  GeneralUtility::writeFileToTypo3tempDir($absoluteCacheFilename, $json);
43  }
44  return $documents;
45  }
46 
52  public function getLocalExtensions()
53  {
54  $documents = [];
55 
56  foreach ($GLOBALS['TYPO3_LOADED_EXT'] as $extensionKey => $extensionData) {
57  $absoluteExtensionPath = GeneralUtility::getFileAbsFileName($extensionData['siteRelPath']);
58  if (is_file($absoluteExtensionPath . 'README.rst') || is_file($absoluteExtensionPath . 'Documentation' . DIRECTORY_SEPARATOR . 'Index.rst')) {
60  if ($extensionData['type'] === 'S') {
61  $version = TYPO3_branch;
62  } else {
63  $version = substr($metadata['release'], -4) === '-dev' ? 'latest' : $metadata['release'];
64  }
65 
66  $documentKey = 'typo3cms.extensions.' . $extensionKey;
67  $documents[] = [
68  'title' => $metadata['title'],
70  'type' => 'Extension',
71  'key' => $documentKey,
72  'shortcut' => $extensionKey,
73  'url' => 'https://docs.typo3.org/typo3cms/extensions/' . $extensionKey . '/',
74  'version' => $version,
75  ];
76  }
77  }
78 
79  return $documents;
80  }
81 
98  public function fetchNearestDocument($url, $key, $version = 'latest', $language = 'default')
99  {
100  // In case we could not find a working combination
101  $success = false;
102 
103  $packages = $this->getAvailablePackages($url);
104  if (empty($packages)) {
105  return $success;
106  }
107 
108  $languages = [$language];
109  if ($language !== 'default') {
110  $languages[] = 'default';
111  }
112  foreach ($languages as $language) {
113  // Step 1)
114  if (isset($packages[$version][$language])) {
115  $success |= $this->fetchDocument($url, $key, $version, $language);
116  // Fetch next language
117  continue;
118  } else {
119  if (isset($packages[$version])) {
120  foreach ($packages[$version] as $locale => $_) {
121  if (GeneralUtility::isFirstPartOfStr($locale, $language)) {
122  $success |= $this->fetchDocument($url, $key, $version, $locale);
123  // Fetch next language (jump current foreach up to the loop of $languages)
124  continue 2;
125  }
126  }
127  }
128  }
129  // Step 2)
130  if (preg_match('/^(\d+\.\d+)\.\d+$/', $version, $matches)) {
131  // Instead of a 3-digit version, try to get it on 2 digits
132  $shortVersion = $matches[1];
133  if (isset($packages[$shortVersion][$language])) {
134  $success |= $this->fetchDocument($url, $key, $shortVersion, $language);
135  // Fetch next language
136  continue;
137  }
138  }
139  // Step 3)
140  if ($version !== 'latest' && isset($packages['latest'][$language])) {
141  $success |= $this->fetchDocument($url, $key, 'latest', $language);
142  // Fetch next language
143  continue;
144  }
145  }
146 
147  return $success;
148  }
149 
159  public function fetchDocument($url, $key, $version = 'latest', $language = 'default')
160  {
161  $result = false;
162  $url = rtrim($url, '/') . '/';
163 
164  $packagePrefix = substr($key, strrpos($key, '.') + 1);
165  $languageSegment = str_replace('_', '-', strtolower($language));
166  $packageName = sprintf('%s-%s-%s.zip', $packagePrefix, $version, $languageSegment);
167  $packageUrl = $url . 'packages/' . $packageName;
168  $absolutePathToZipFile = GeneralUtility::getFileAbsFileName('typo3temp/Documentation/' . $packageName);
169 
170  $packages = $this->getAvailablePackages($url);
171  if (empty($packages) || !isset($packages[$version][$language])) {
172  return false;
173  }
174 
175  // Check if a local version of the package is already present
176  $hasArchive = false;
177  if (is_file($absolutePathToZipFile)) {
178  $localMd5 = md5_file($absolutePathToZipFile);
179  $remoteMd5 = $packages[$version][$language];
180  $hasArchive = $localMd5 === $remoteMd5;
181  }
182 
183  if (!$hasArchive) {
184  $content = GeneralUtility::getUrl($packageUrl);
185  if ($content) {
186  GeneralUtility::writeFileToTypo3tempDir($absolutePathToZipFile, $content);
187  }
188  }
189 
190  if (is_file($absolutePathToZipFile)) {
191  $absoluteDocumentPath = GeneralUtility::getFileAbsFileName('typo3conf/Documentation/');
192 
193  $result = $this->unzipDocumentPackage($absolutePathToZipFile, $absoluteDocumentPath);
194 
195  // Create a composer.json file
196  $absoluteCacheFilename = GeneralUtility::getFileAbsFileName('typo3temp/Documentation/documents.json');
197  $documents = json_decode(file_get_contents($absoluteCacheFilename), true);
198  foreach ($documents as $document) {
199  if ($document['key'] === $key) {
200  $composerData = [
201  'name' => $document['title'],
202  'type' => 'documentation',
203  'description' => 'TYPO3 ' . $document['type'],
204  ];
205  $relativeComposerFilename = $key . '/' . $language . '/composer.json';
206  $absoluteComposerFilename = GeneralUtility::getFileAbsFileName('typo3conf/Documentation/' . $relativeComposerFilename);
207  GeneralUtility::writeFile($absoluteComposerFilename, json_encode($composerData));
208  break;
209  }
210  }
211  }
212 
213  return $result;
214  }
215 
223  protected function getAvailablePackages($url)
224  {
225  $packages = [];
226  $url = rtrim($url, '/') . '/';
227  $indexUrl = $url . 'packages/packages.xml';
228 
229  $remote = GeneralUtility::getUrl($indexUrl);
230  if ($remote) {
231  $packages = $this->parsePackagesXML($remote);
232  }
233 
234  return $packages;
235  }
236 
244  protected function parsePackagesXML($string)
245  {
246  // Disables the functionality to allow external entities to be loaded when parsing the XML, must be kept
247  $previousValueOfEntityLoader = libxml_disable_entity_loader(true);
248  $data = json_decode(json_encode((array)simplexml_load_string($string)), true);
249  libxml_disable_entity_loader($previousValueOfEntityLoader);
250  if (count($data) !== 2) {
251  throw new \TYPO3\CMS\Documentation\Exception\XmlParser('Error in XML parser while decoding packages XML file.', 1374222437);
252  }
253 
254  // SimpleXML does not properly handle arrays with only 1 item
255  if ($data['languagePackIndex']['languagepack'][0] === null) {
256  $data['languagePackIndex']['languagepack'] = [$data['languagePackIndex']['languagepack']];
257  }
258 
259  $packages = [];
260  foreach ($data['languagePackIndex']['languagepack'] as $languagePack) {
261  $language = $languagePack['@attributes']['language'];
262  $version = $languagePack['@attributes']['version'];
263  $packages[$version][$language] = $languagePack['md5'];
264  }
265 
266  return $packages;
267  }
268 
277  protected function unzipDocumentPackage($file, $path)
278  {
279  $zip = zip_open($file);
280  if (is_resource($zip)) {
281  $result = true;
282 
283  if (!is_dir($path)) {
285  }
286 
287  while (($zipEntry = zip_read($zip)) !== false) {
288  $zipEntryName = zip_entry_name($zipEntry);
289  if (strpos($zipEntryName, '/') !== false) {
290  $zipEntryPathSegments = explode('/', $zipEntryName);
291  $fileName = array_pop($zipEntryPathSegments);
292  // It is a folder, because the last segment is empty, let's create it
293  if (empty($fileName)) {
294  GeneralUtility::mkdir_deep($path, implode('/', $zipEntryPathSegments));
295  } else {
296  $absoluteTargetPath = GeneralUtility::getFileAbsFileName($path . implode('/', $zipEntryPathSegments) . '/' . $fileName);
297  if (trim($absoluteTargetPath) !== '') {
298  $return = GeneralUtility::writeFile(
299  $absoluteTargetPath, zip_entry_read($zipEntry, zip_entry_filesize($zipEntry))
300  );
301  if ($return === false) {
302  throw new \TYPO3\CMS\Documentation\Exception\Document('Could not write file ' . $zipEntryName, 1374161546);
303  }
304  } else {
305  throw new \TYPO3\CMS\Documentation\Exception\Document('Could not write file ' . $zipEntryName, 1374161532);
306  }
307  }
308  } else {
309  throw new \TYPO3\CMS\Documentation\Exception\Document('Extension directory missing in zip file!', 1374161519);
310  }
311  }
312  } else {
313  throw new \TYPO3\CMS\Documentation\Exception\Document('Unable to open zip file ' . $file, 1374161508);
314  }
315 
316  return $result;
317  }
318 }
fetchDocument($url, $key, $version='latest', $language='default')
static mkdir_deep($directory, $deepDirectory='')
static isFirstPartOfStr($str, $partStr)
static writeFileToTypo3tempDir($filepath, $content)
$remote
Definition: server.php:70
static getUrl($url, $includeHeader=0, $requestHeaders=false, &$report=null)
fetchNearestDocument($url, $key, $version='latest', $language='default')
static getFileAbsFileName($filename, $onlyRelative=true, $relToTYPO3_mainDir=false)
if(TYPO3_MODE==='BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static writeFile($file, $content, $changePermissions=false)