TYPO3  7.6
T3xDownloader.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Composer\Installer\Downloader;
3 
4 /***************************************************************
5  * Copyright notice
6  *
7  * (c) 2013 Sascha Egerer <sascha.egerer@dkd.de>
8  * All rights reserved
9  *
10  * This script is part of the TYPO3 project. The TYPO3 project is
11  * free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; either version 2 of the License, or
14  * (at your option) any later version.
15  *
16  * The GNU General Public License can be found at
17  * http://www.gnu.org/copyleft/gpl.html.
18  *
19  * This script is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22  * GNU General Public License for more details.
23  *
24  * This copyright notice MUST APPEAR in all copies of the script!
25  ***************************************************************/
26 
27 use Composer\Package\PackageInterface;
28 use Composer\Downloader\ArchiveDownloader;
29 use Composer\Downloader\ChangeReportInterface;
30 
37 class T3xDownloader extends ArchiveDownloader implements ChangeReportInterface {
38 
42  protected $package;
43 
47  public function download(PackageInterface $package, $path) {
48  // set package so we can use it in the extract method
49  $this->package = $package;
50  parent::download($package, $path);
51  }
52 
57  protected function extract($file, $path) {
58  // get file contents
59  $fileContentStream = file_get_contents($file);
60  $extensionData = $this->decodeTerExchangeData($fileContentStream);
61 
62  if (substr($path, -1) !== DIRECTORY_SEPARATOR) {
63  $path .= DIRECTORY_SEPARATOR;
64  }
65 
66  $files = $this->extractFilesArrayFromExtensionData($extensionData);
67  $directories = $this->extractDirectoriesFromExtensionData($files);
68  $this->createDirectoriesForExtensionFiles($directories, $path);
69  $this->writeExtensionFiles($files, $path);
70  $this->writeEmConf($extensionData, $path);
71  }
72 
76  public function getLocalChanges(PackageInterface $package, $path) {
77  $messages = array();
78  $path = rtrim($path, '/') . '/';
79 
80  // check if there is a ext_emconf.php
81  try {
82  $emMetaData = $this->getEmConfMetaData($path);
83  $extensionFiles = unserialize($emMetaData['_md5_values_when_last_written']);
84  foreach ($extensionFiles as $extensionFileName => $extensionFileHash) {
85  if (substr($extensionFileName, -1) === '/') {
86  continue;
87  }
88  if (is_file($path . $extensionFileName)) {
89  $localFileContentHash = md5(file_get_contents($path . $extensionFileName));
90 
91  if (substr($localFileContentHash, 0, 4) !== $extensionFileHash) {
92  $messages[] = $extensionFileName . ' - File is modified';
93  }
94  } else {
95  $messages[] = $extensionFileName . ' - File is missing';
96  }
97  }
98 
99 
100  if ($package->getPrettyVersion() !== $emMetaData['version']) {
101  $messages[] = 'Local Version is ' . $emMetaData['version'] . ' but should be ' . $package->getPrettyVersion();
102  }
103 
104  unset($EM_CONF);
105  } catch (\RuntimeException $e) {
106  $messages[] = $e->getMessage();
107  }
108 
109  return implode("\n", $messages);
110  }
111 
116  protected function getEmConfMetaData($path) {
117  if (!is_file($path . 'ext_emconf.php')) {
118  throw new \RuntimeException('Package is unstable. "ext_emconf.php" is missing', 1439568877);
119  }
120  $_EXTKEY = basename($path);
121  include($path . 'ext_emconf.php');
122 
123  if (!is_array($EM_CONF[$_EXTKEY])) {
124  throw new \RuntimeException('Package is unstable. "ext_emconf.php" is corrupt', 1439569163);
125  }
126 
127  return $EM_CONF[$_EXTKEY];
128  }
129 
135  public function decodeTerExchangeData($stream) {
136  $parts = explode(':', $stream, 3);
137  if ($parts[1] === 'gzcompress') {
138  if (function_exists('gzuncompress')) {
139  $parts[2] = gzuncompress($parts[2]);
140  } else {
141  throw new \RuntimeException('Decoding Error: No decompressor available for compressed content. gzcompress()/gzuncompress() functions are not available!', 1359124403);
142  }
143  }
144  if (md5($parts[2]) === $parts[0]) {
145  $output = unserialize($parts[2]);
146  if (!is_array($output)) {
147  throw new \RuntimeException('Error: Content could not be unserialized to an array. Strange (since MD5 hashes match!)', 1359124554);
148  }
149  } else {
150  throw new \RuntimeException('Error: MD5 mismatch. Maybe the extension file was downloaded and saved as a text file and thereby corrupted!?', 1359124556);
151  }
152  return $output;
153  }
154 
155 
162  protected function extractFilesArrayFromExtensionData(array $extensionData) {
163  return $extensionData['FILES'];
164  }
165 
172  protected function extractDirectoriesFromExtensionData(array $files) {
173  $directories = array();
174  foreach ($files as $filePath => $file) {
175  preg_match('/(.*)\\//', $filePath, $matches);
176  if (count($matches) > 0) {
177  $directories[] = $matches[0];
178  }
179  }
180  return $directories;
181  }
182 
191  protected function createDirectoriesForExtensionFiles(array $directories, $rootPath) {
192  foreach ($directories as $directory) {
193  $this->createNestedDirectory($rootPath . $directory);
194  }
195  }
196 
203  protected function createNestedDirectory($directory) {
204  $currentPath = $directory;
205  if (!@is_dir($currentPath)) {
206  do {
207  $separatorPosition = strrpos($currentPath, DIRECTORY_SEPARATOR);
208  $currentPath = substr($currentPath, 0, $separatorPosition);
209  } while (!is_dir($currentPath) && $separatorPosition !== FALSE);
210 
211  $result = @mkdir($directory, 0777, TRUE);
212  if (!$result) {
213  throw new \RuntimeException('Could not create directory "' . $directory . '"!', 1170251400);
214  }
215  }
216  }
217 
225  protected function writeExtensionFiles(array $files, $rootPath) {
226  foreach ($files as $file) {
227  if (empty($file['name']) || substr($file['name'], -1) === '/') {
228  continue;
229  }
230  $filename = $rootPath . $file['name'];
231  $content = $file['content'];
232  if ($fd = fopen($filename, 'wb')) {
233  fwrite($fd, $content);
234  fclose($fd);
235  }
236  }
237  }
238 
243  protected function writeEmConf(array $extensionData, $path) {
244  $emConfFileData = array();
245  if (file_exists($path . 'ext_emconf.php')) {
246  $emConfFileData = $this->getEmConfMetaData($path);
247  }
248  $extensionData['EM_CONF'] = array_replace_recursive($emConfFileData, $extensionData['EM_CONF']);
249  $emConfContent = $this->constructEmConf($extensionData);
250  if ($fd = fopen($path . 'ext_emconf.php', 'wb')) {
251  fwrite($fd, $emConfContent);
252  fclose($fd);
253  }
254  }
255 
263  public function constructEmConf(array $extensionData) {
264  $emConf = $this->fixEmConf($extensionData['EM_CONF']);
265  $emConf['_md5_values_when_last_written'] = serialize($this->extensionMD5array($extensionData['FILES']));
266  $uniqueIdentifier = md5($emConf['_md5_values_when_last_written']);
267  $emConf = var_export($emConf, TRUE);
268  $code = '<?php
269 
270 /***************************************************************
271  * Extension Manager/Repository config file for ext "' . $extensionData['extKey'] . '".
272  *
273  * Auto generated | Identifier: ' . $uniqueIdentifier . '
274  *
275  * Manual updates:
276  * Only the data in the array - everything else is removed by next
277  * writing. "version" and "dependencies" must not be touched!
278  ***************************************************************/
279 
280 $EM_CONF[$_EXTKEY] = ' . $emConf . ';
281 
282 ?>';
283  return str_replace(' ', chr(9), $code);
284  }
285 
292  function extensionMD5array(array $filesArray) {
293  $md5Array = array();
294 
295  // Traverse files.
296  foreach ($filesArray as $fileName => $fileInfo) {
297  $fileName = ltrim($fileName, '/');
298  if ($fileName !== 'ext_emconf.php') {
299  $md5Array[$fileName] = substr($fileInfo['content_md5'], 0, 4);
300  }
301  }
302 
303  return $md5Array;
304  }
305 
312  public function fixEmConf(array $emConf) {
313  if (
314  !isset($emConf['constraints']) || !isset($emConf['constraints']['depends'])
315  || !isset($emConf['constraints']['conflicts']) || !isset($emConf['constraints']['suggests'])
316  ) {
317  if (!isset($emConf['constraints']) || !isset($emConf['constraints']['depends'])) {
318  $emConf['constraints']['depends'] = $this->stringToDependency($emConf['dependencies']);
319  if ((string)$emConf['PHP_version'] !== '') {
320  $emConf['constraints']['depends']['php'] = $emConf['PHP_version'];
321  }
322  if ((string)$emConf['TYPO3_version'] !== '') {
323  $emConf['constraints']['depends']['typo3'] = $emConf['TYPO3_version'];
324  }
325  }
326  if (!isset($emConf['constraints']) || !isset($emConf['constraints']['conflicts'])) {
327  $emConf['constraints']['conflicts'] = $this->stringToDependency($emConf['conflicts']);
328  }
329  if (!isset($emConf['constraints']) || !isset($emConf['constraints']['suggests'])) {
330  $emConf['constraints']['suggests'] = array();
331  }
332  } elseif (isset($emConf['constraints']) && isset($emConf['dependencies'])) {
333  $emConf['suggests'] = isset($emConf['suggests']) ? $emConf['suggests'] : array();
334  $emConf['dependencies'] = $this->dependencyToString($emConf['constraints']);
335  $emConf['conflicts'] = $this->dependencyToString($emConf['constraints'], 'conflicts');
336  }
337 
338  // Remove TER v1-style entries
339  unset($emConf['dependencies']);
340  unset($emConf['conflicts']);
341  unset($emConf['suggests']);
342  unset($emConf['private']);
343  unset($emConf['download_password']);
344  unset($emConf['TYPO3_version']);
345  unset($emConf['PHP_version']);
346  unset($emConf['internal']);
347  unset($emConf['module']);
348  unset($emConf['loadOrder']);
349  unset($emConf['lockType']);
350  unset($emConf['shy']);
351  unset($emConf['priority']);
352  unset($emConf['modify_tables']);
353  unset($emConf['CGLcompliance']);
354  unset($emConf['CGLcompliance_note']);
355 
356  return $emConf;
357  }
358 
370  static public function dependencyToString($dependency, $type = 'depends') {
371  if (is_array($dependency)) {
372  if (isset($dependency[$type]['php'])) {
373  unset($dependency[$type]['php']);
374  }
375  if (isset($dependency[$type]['typo3'])) {
376  unset($dependency[$type]['typo3']);
377  }
378  $dependencyString = count($dependency[$type]) ? implode(',', array_keys($dependency[$type])) : '';
379  return $dependencyString;
380  }
381  return '';
382  }
383 
395  public function stringToDependency($dependency) {
396  $constraint = array();
397  if (is_string($dependency) && strlen($dependency)) {
398  $dependency = explode(',', $dependency);
399  foreach ($dependency as $v) {
400  $constraint[$v] = '';
401  }
402  }
403  return $constraint;
404  }
405 
412  protected function convertDependencies($dependencies) {
413  $newDependencies = array();
414  $dependenciesArray = unserialize($dependencies);
415  if (is_array($dependenciesArray)) {
416  foreach ($dependenciesArray as $version) {
417  if (!empty($version['extensionKey'])) {
418  $newDependencies[$version['kind']][$version['extensionKey']] = $version['versionRange'];
419  }
420  }
421  }
422  return $newDependencies;
423  }
424 }