Commit | Line | Data |
---|---|---|
11ade432 AE |
1 | <?php |
2 | namespace wcf\system\package; | |
3 | use wcf\data\package\Package; | |
4 | use wcf\system\database\util\PreparedStatementConditionBuilder; | |
1871b8b3 | 5 | use wcf\system\package\validation\PackageValidationException; |
11ade432 AE |
6 | use wcf\system\io\Tar; |
7 | use wcf\system\WCF; | |
b4cbf821 | 8 | use wcf\util\DateUtil; |
11ade432 | 9 | use wcf\util\FileUtil; |
11ade432 AE |
10 | use wcf\util\XML; |
11 | ||
12 | /** | |
a17de04e | 13 | * Represents the archive of a package. |
11ade432 AE |
14 | * |
15 | * @author Marcel Werk | |
ca4ba303 | 16 | * @copyright 2001-2014 WoltLab GmbH |
11ade432 AE |
17 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
18 | * @package com.woltlab.wcf | |
19 | * @subpackage system.package | |
9f959ced | 20 | * @category Community Framework |
11ade432 AE |
21 | */ |
22 | class PackageArchive { | |
23 | /** | |
9f959ced MS |
24 | * path to package archive |
25 | * @var string | |
11ade432 AE |
26 | */ |
27 | protected $archive = null; | |
28 | ||
29 | /** | |
30 | * package object of an existing package | |
0ad90fc3 | 31 | * @var \wcf\data\package\Package |
11ade432 AE |
32 | */ |
33 | protected $package = null; | |
34 | ||
35 | /** | |
36 | * tar archive object | |
0ad90fc3 | 37 | * @var \wcf\system\io\Tar |
11ade432 AE |
38 | */ |
39 | protected $tar = null; | |
40 | ||
41 | /** | |
42 | * general package information | |
9f959ced | 43 | * @var array |
11ade432 AE |
44 | */ |
45 | protected $packageInfo = array(); | |
46 | ||
47 | /** | |
48 | * author information | |
9f959ced | 49 | * @var array |
11ade432 AE |
50 | */ |
51 | protected $authorInfo = array(); | |
52 | ||
53 | /** | |
54 | * list of requirements | |
9f959ced | 55 | * @var array |
11ade432 AE |
56 | */ |
57 | protected $requirements = array(); | |
58 | ||
59 | /** | |
60 | * list of optional packages | |
9f959ced | 61 | * @var array |
11ade432 AE |
62 | */ |
63 | protected $optionals = array(); | |
64 | ||
65 | /** | |
66 | * list of excluded packages | |
11ade432 AE |
67 | * @var array |
68 | */ | |
69 | protected $excludedPackages = array(); | |
70 | ||
71 | /** | |
72 | * list of instructions | |
11ade432 | 73 | * @var array<array> |
d726f13d | 74 | */ |
11ade432 AE |
75 | protected $instructions = array( |
76 | 'install' => array(), | |
77 | 'update' => array() | |
78 | ); | |
79 | ||
80 | /** | |
81 | * list of php requirements | |
11ade432 AE |
82 | * @var array<array> |
83 | */ | |
84 | protected $phpRequirements = array(); | |
85 | ||
86 | /** | |
87 | * default name of the package.xml file | |
9f959ced | 88 | * @var string |
11ade432 AE |
89 | */ |
90 | const INFO_FILE = 'package.xml'; | |
91 | ||
92 | /** | |
93 | * Creates a new PackageArchive object. | |
94 | * | |
95 | * @param string $archive | |
96 | * @param Package $package | |
97 | */ | |
98 | public function __construct($archive, Package $package = null) { | |
9f959ced | 99 | $this->archive = $archive; // be careful: this is a string within this class, |
11ade432 AE |
100 | // but an object in the packageStartInstallForm.class! |
101 | $this->package = $package; | |
102 | } | |
103 | ||
ec45d5e8 AE |
104 | /** |
105 | * Sets associated package object. | |
106 | * | |
0ad90fc3 | 107 | * @param \wcf\data\package\Package $package |
ec45d5e8 AE |
108 | */ |
109 | public function setPackage(Package $package) { | |
110 | $this->package = $package; | |
111 | } | |
112 | ||
11ade432 AE |
113 | /** |
114 | * Returns the name of the package archive. | |
115 | * | |
116 | * @return string | |
117 | */ | |
118 | public function getArchive() { | |
119 | return $this->archive; | |
120 | } | |
121 | ||
122 | /** | |
123 | * Returns the object of the package archive. | |
124 | * | |
0ad90fc3 | 125 | * @return \wcf\system\io\Tar |
11ade432 AE |
126 | */ |
127 | public function getTar() { | |
128 | return $this->tar; | |
129 | } | |
130 | ||
131 | /** | |
132 | * Opens the package archive and reads package information. | |
133 | */ | |
134 | public function openArchive() { | |
135 | // check whether archive exists and is a TAR archive | |
136 | if (!file_exists($this->archive)) { | |
1871b8b3 | 137 | throw new PackageValidationException(PackageValidationException::FILE_NOT_FOUND, array('archive' => $this->archive)); |
11ade432 | 138 | } |
9f959ced | 139 | |
11ade432 AE |
140 | // open archive and read package information |
141 | $this->tar = new Tar($this->archive); | |
142 | $this->readPackageInfo(); | |
143 | } | |
144 | ||
145 | /** | |
146 | * Extracts information about this package (parses package.xml). | |
147 | */ | |
148 | protected function readPackageInfo() { | |
149 | // search package.xml in package archive | |
150 | // throw error message if not found | |
151 | if ($this->tar->getIndexByFilename(self::INFO_FILE) === false) { | |
1871b8b3 | 152 | throw new PackageValidationException(PackageValidationException::MISSING_PACKAGE_XML, array('archive' => $this->archive)); |
11ade432 AE |
153 | } |
154 | ||
ca6bca55 | 155 | // extract package.xml, parse XML |
11ade432 AE |
156 | // and compile an array with XML::getElementTree() |
157 | $xml = new XML(); | |
158 | try { | |
159 | $xml->loadXML(self::INFO_FILE, $this->tar->extractToString(self::INFO_FILE)); | |
160 | } | |
6286572b | 161 | catch (\Exception $e) { // bugfix to avoid file caching problems |
11ade432 AE |
162 | $xml->loadXML(self::INFO_FILE, $this->tar->extractToString(self::INFO_FILE)); |
163 | } | |
164 | ||
165 | // parse xml | |
166 | $xpath = $xml->xpath(); | |
167 | $package = $xpath->query('/ns:package')->item(0); | |
168 | ||
169 | // package name | |
170 | $packageName = $package->getAttribute('name'); | |
171 | if (!Package::isValidPackageName($packageName)) { | |
172 | // package name is not a valid package identifier | |
1871b8b3 | 173 | throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, array('packageName' => $packageName)); |
11ade432 AE |
174 | } |
175 | ||
176 | $this->packageInfo['name'] = $packageName; | |
177 | ||
178 | // get package information | |
179 | $packageInformation = $xpath->query('./ns:packageinformation', $package)->item(0); | |
180 | $elements = $xpath->query('child::*', $packageInformation); | |
181 | foreach ($elements as $element) { | |
182 | switch ($element->tagName) { | |
183 | case 'packagename': | |
184 | case 'packagedescription': | |
185 | case 'readme': | |
186 | case 'license': | |
187 | if (!isset($this->packageInfo[$element->tagName])) $this->packageInfo[$element->tagName] = array(); | |
188 | ||
189 | $languageCode = 'default'; | |
190 | if ($element->hasAttribute('language')) { | |
191 | $languageCode = $element->getAttribute('language'); | |
192 | } | |
193 | ||
194 | // fix case-sensitive names | |
195 | $name = $element->tagName; | |
196 | if ($name == 'packagename') $name = 'packageName'; | |
197 | else if ($name == 'packagedescription') $name = 'packageDescription'; | |
198 | ||
199 | $this->packageInfo[$name][$languageCode] = $element->nodeValue; | |
200 | break; | |
201 | ||
aac1247e MS |
202 | case 'isapplication': |
203 | $this->packageInfo['isApplication'] = intval($element->nodeValue); | |
11ade432 AE |
204 | break; |
205 | ||
11ade432 AE |
206 | case 'packageurl': |
207 | $this->packageInfo['packageURL'] = $element->nodeValue; | |
208 | break; | |
209 | ||
210 | case 'version': | |
b4cbf821 | 211 | if (!Package::isValidVersion($element->nodeValue)) { |
1871b8b3 | 212 | throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_VERSION, array('packageVersion' => $element->nodeValue)); |
11ade432 AE |
213 | } |
214 | ||
215 | $this->packageInfo['version'] = $element->nodeValue; | |
216 | break; | |
217 | ||
218 | case 'date': | |
b4cbf821 | 219 | DateUtil::validateDate($element->nodeValue); |
11ade432 | 220 | |
7611351c | 221 | $this->packageInfo['date'] = @strtotime($element->nodeValue); |
11ade432 AE |
222 | break; |
223 | } | |
224 | } | |
225 | ||
226 | // get author information | |
227 | $authorInformation = $xpath->query('./ns:authorinformation', $package)->item(0); | |
228 | $elements = $xpath->query('child::*', $authorInformation); | |
229 | foreach ($elements as $element) { | |
230 | $tagName = ($element->tagName == 'authorurl') ? 'authorURL' : $element->tagName; | |
231 | $this->authorInfo[$tagName] = $element->nodeValue; | |
232 | } | |
233 | ||
234 | // get required packages | |
235 | $elements = $xpath->query('child::ns:requiredpackages/ns:requiredpackage', $package); | |
236 | foreach ($elements as $element) { | |
237 | if (!Package::isValidPackageName($element->nodeValue)) { | |
1871b8b3 | 238 | throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, array('packageName' => $element->nodeValue)); |
11ade432 AE |
239 | } |
240 | ||
241 | // read attributes | |
242 | $data = array('name' => $element->nodeValue); | |
243 | $attributes = $xpath->query('attribute::*', $element); | |
244 | foreach ($attributes as $attribute) { | |
245 | $data[$attribute->name] = $attribute->value; | |
246 | } | |
549376a4 | 247 | |
11ade432 AE |
248 | $this->requirements[$element->nodeValue] = $data; |
249 | } | |
9f959ced MS |
250 | |
251 | // get optional packages | |
11ade432 AE |
252 | $elements = $xpath->query('child::ns:optionalpackages/ns:optionalpackage', $package); |
253 | foreach ($elements as $element) { | |
254 | if (!Package::isValidPackageName($element->nodeValue)) { | |
1871b8b3 | 255 | throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, array('packageName' => $element->nodeValue)); |
11ade432 AE |
256 | } |
257 | ||
258 | // read attributes | |
259 | $data = array('name' => $element->nodeValue); | |
260 | $attributes = $xpath->query('attribute::*', $element); | |
261 | foreach ($attributes as $attribute) { | |
262 | $data[$attribute->name] = $attribute->value; | |
263 | } | |
549376a4 | 264 | |
11ade432 AE |
265 | $this->optionals[] = $data; |
266 | } | |
267 | ||
268 | // get excluded packages | |
269 | $elements = $xpath->query('child::ns:excludedpackages/ns:excludedpackage', $package); | |
270 | foreach ($elements as $element) { | |
271 | if (!Package::isValidPackageName($element->nodeValue)) { | |
1871b8b3 | 272 | throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, array('packageName' => $element->nodeValue)); |
11ade432 AE |
273 | } |
274 | ||
275 | // read attributes | |
276 | $data = array('name' => $element->nodeValue); | |
277 | $attributes = $xpath->query('attribute::*', $element); | |
278 | foreach ($attributes as $attribute) { | |
279 | $data[$attribute->name] = $attribute->value; | |
280 | } | |
281 | ||
282 | $this->excludedPackages[] = $data; | |
283 | } | |
284 | ||
285 | // get instructions | |
286 | $elements = $xpath->query('./ns:instructions', $package); | |
287 | foreach ($elements as $element) { | |
288 | $instructionData = array(); | |
289 | $instructions = $xpath->query('./ns:instruction', $element); | |
290 | foreach ($instructions as $instruction) { | |
291 | $data = array(); | |
292 | $attributes = $xpath->query('attribute::*', $instruction); | |
293 | foreach ($attributes as $attribute) { | |
294 | $data[$attribute->name] = $attribute->value; | |
295 | } | |
296 | ||
297 | $instructionData[] = array( | |
298 | 'attributes' => $data, | |
299 | 'pip' => $instruction->getAttribute('type'), | |
300 | 'value' => $instruction->nodeValue | |
301 | ); | |
302 | } | |
303 | ||
304 | $fromVersion = $element->getAttribute('fromversion'); | |
305 | $type = $element->getAttribute('type'); | |
306 | ||
307 | if ($type == 'install') { | |
308 | $this->instructions['install'] = $instructionData; | |
309 | } | |
310 | else { | |
ca38121b | 311 | $this->instructions['update'][$fromVersion] = $instructionData; |
11ade432 AE |
312 | } |
313 | } | |
314 | ||
315 | // get php requirements | |
430f87ec | 316 | /*$requirements = $xpath->query('./ns:phprequirements', $package); |
11ade432 AE |
317 | foreach ($requirements as $requirement) { |
318 | $elements = $xpath->query('child::*', $requirement); | |
319 | foreach ($elements as $element) { | |
320 | switch ($element->tagName) { | |
321 | case 'version': | |
322 | $this->phpRequirements['version'] = $element->nodeValue; | |
9f959ced | 323 | break; |
11ade432 AE |
324 | |
325 | case 'setting': | |
326 | $this->phpRequirements['settings'][$element->getAttribute('name')] = $element->nodeValue; | |
9f959ced | 327 | break; |
11ade432 AE |
328 | |
329 | case 'extension': | |
330 | $this->phpRequirements['extensions'][] = $element->nodeValue; | |
9f959ced | 331 | break; |
11ade432 AE |
332 | |
333 | case 'function': | |
334 | $this->phpRequirements['functions'][] = $element->nodeValue; | |
9f959ced | 335 | break; |
11ade432 AE |
336 | |
337 | case 'class': | |
338 | $this->phpRequirements['classes'][] = $element->nodeValue; | |
9f959ced | 339 | break; |
11ade432 AE |
340 | } |
341 | } | |
430f87ec | 342 | }*/ |
11ade432 AE |
343 | |
344 | // add com.woltlab.wcf to package requirements | |
345 | if (!isset($this->requirements['com.woltlab.wcf']) && $this->packageInfo['name'] != 'com.woltlab.wcf') { | |
346 | $this->requirements['com.woltlab.wcf'] = array('name' => 'com.woltlab.wcf'); | |
347 | } | |
348 | ||
349 | if ($this->package != null) { | |
49ec1439 | 350 | $this->filterUpdateInstructions(); |
11ade432 AE |
351 | } |
352 | ||
353 | // set default values | |
aac1247e | 354 | if (!isset($this->packageInfo['isApplication'])) $this->packageInfo['isApplication'] = 0; |
11ade432 | 355 | if (!isset($this->packageInfo['packageURL'])) $this->packageInfo['packageURL'] = ''; |
11ade432 AE |
356 | } |
357 | ||
49ec1439 AE |
358 | /** |
359 | * Filters update instructions. | |
360 | */ | |
361 | protected function filterUpdateInstructions() { | |
362 | $validFromVersion = null; | |
363 | foreach ($this->instructions['update'] as $fromVersion => $update) { | |
364 | if (Package::checkFromversion($this->package->packageVersion, $fromVersion)) { | |
365 | $validFromVersion = $fromVersion; | |
366 | break; | |
367 | } | |
368 | } | |
369 | ||
370 | if ($validFromVersion === null) { | |
371 | $this->instructions['update'] = array(); | |
372 | } | |
373 | else { | |
374 | $this->instructions['update'] = $this->instructions['update'][$validFromVersion]; | |
375 | } | |
376 | } | |
377 | ||
11ade432 AE |
378 | /** |
379 | * Downloads the package archive. | |
380 | * | |
381 | * @return string path to the dowloaded file | |
382 | */ | |
383 | public function downloadArchive() { | |
11ade432 AE |
384 | $prefix = 'package'; |
385 | ||
386 | // file transfer via hypertext transfer protocol. | |
387 | $this->archive = FileUtil::downloadFileFromHttp($this->archive, $prefix); | |
388 | ||
389 | // unzip tar | |
390 | $this->archive = self::unzipPackageArchive($this->archive); | |
391 | ||
392 | return $this->archive; | |
393 | } | |
394 | ||
395 | /** | |
396 | * Closes and deletes the tar archive of this package. | |
397 | */ | |
398 | public function deleteArchive() { | |
399 | if ($this->tar instanceof Tar) { | |
400 | $this->tar->close(); | |
401 | } | |
402 | ||
403 | @unlink($this->archive); | |
404 | } | |
405 | ||
406 | /** | |
28410a97 | 407 | * Returns true if the package archive supports a new installation. |
11ade432 AE |
408 | * |
409 | * @return boolean | |
410 | */ | |
411 | public function isValidInstall() { | |
412 | return !empty($this->instructions['install']); | |
413 | } | |
414 | ||
415 | /** | |
416 | * Checks if the new package is compatible with | |
417 | * the package that is about to be updated. | |
9f959ced | 418 | * |
49ec1439 AE |
419 | * @param \wcf\data\package\Package $package |
420 | * @return boolean isValidUpdate | |
11ade432 | 421 | */ |
49ec1439 AE |
422 | public function isValidUpdate(Package $package = null) { |
423 | if ($this->package === null && $package !== null) { | |
424 | $this->setPackage($package); | |
425 | ||
426 | // re-evaluate update data | |
427 | $this->filterUpdateInstructions(); | |
428 | } | |
429 | ||
11ade432 AE |
430 | // Check name of the installed package against the name of the update. Both must be identical. |
431 | if ($this->packageInfo['name'] != $this->package->package) { | |
432 | return false; | |
433 | } | |
434 | ||
435 | // Check if the version number of the installed package is lower than the version number to which | |
436 | // it's about to be updated. | |
437 | if (Package::compareVersion($this->packageInfo['version'], $this->package->packageVersion) != 1) { | |
438 | return false; | |
439 | } | |
49ec1439 | 440 | |
11ade432 AE |
441 | // Check if the package provides an instructions block for the update from the installed package version |
442 | if (empty($this->instructions['update'])) { | |
443 | return false; | |
444 | } | |
49ec1439 | 445 | |
11ade432 AE |
446 | return true; |
447 | } | |
448 | ||
449 | /** | |
8c442546 AE |
450 | * Checks if the current package is already installed, as it is not |
451 | * possible to install non-applications multiple times within the | |
452 | * same environment. | |
11ade432 | 453 | * |
8c442546 | 454 | * @return boolean |
11ade432 AE |
455 | */ |
456 | public function isAlreadyInstalled() { | |
f1c1fc65 AE |
457 | $sql = "SELECT COUNT(*) AS count |
458 | FROM wcf".WCF_N."_package | |
39bea7dd | 459 | WHERE package = ?"; |
11ade432 AE |
460 | $statement = WCF::getDB()->prepareStatement($sql); |
461 | $statement->execute(array($this->packageInfo['name'])); | |
f1c1fc65 | 462 | $row = $statement->fetchArray(); |
11ade432 | 463 | |
f1c1fc65 | 464 | return ($row['count'] > 0) ? true : false; |
11ade432 AE |
465 | } |
466 | ||
549376a4 MS |
467 | /** |
468 | * Returns true if the package is an application and has an unique abbrevation. | |
469 | * | |
470 | * @return boolean | |
471 | */ | |
472 | public function hasUniqueAbbreviation() { | |
473 | if (!$this->packageInfo['isApplication']) { | |
474 | return true; | |
475 | } | |
476 | ||
477 | $sql = "SELECT COUNT(*) | |
478 | FROM wcf".WCF_N."_package | |
479 | WHERE isApplication = ? | |
480 | AND package LIKE ?"; | |
481 | $statement = WCF::getDB()->prepareStatement($sql); | |
482 | $statement->execute(array( | |
483 | 1, | |
4e82ea84 | 484 | '%.'.Package::getAbbreviation($this->packageInfo['name']) |
549376a4 MS |
485 | )); |
486 | ||
487 | return $statement->fetchColumn(); | |
488 | } | |
489 | ||
11ade432 AE |
490 | /** |
491 | * Returns information about the author of this package archive. | |
492 | * | |
39bea7dd | 493 | * @param string $name name of the requested information |
11ade432 AE |
494 | * @return string |
495 | */ | |
496 | public function getAuthorInfo($name) { | |
497 | if (isset($this->authorInfo[$name])) return $this->authorInfo[$name]; | |
498 | return null; | |
499 | } | |
500 | ||
501 | /** | |
502 | * Returns information about this package. | |
503 | * | |
39bea7dd | 504 | * @param string $name name of the requested information |
11ade432 AE |
505 | * @return mixed |
506 | */ | |
507 | public function getPackageInfo($name) { | |
508 | if (isset($this->packageInfo[$name])) return $this->packageInfo[$name]; | |
509 | return null; | |
510 | } | |
511 | ||
a2ad7897 MS |
512 | /** |
513 | * Returns a localized information about this package. | |
514 | * | |
515 | * @param string $name | |
4000bdbb | 516 | * @return string |
a2ad7897 MS |
517 | */ |
518 | public function getLocalizedPackageInfo($name) { | |
519 | if (isset($this->packageInfo[$name][WCF::getLanguage()->getFixedLanguageCode()])) { | |
520 | return $this->packageInfo[$name][WCF::getLanguage()->getFixedLanguageCode()]; | |
521 | } | |
522 | else if (isset($this->packageInfo[$name]['default'])) { | |
523 | return $this->packageInfo[$name]['default']; | |
524 | } | |
525 | ||
4000bdbb MW |
526 | if (!empty($this->packageInfo[$name])) { |
527 | return reset($this->packageInfo[$name]); | |
528 | } | |
529 | ||
530 | return ''; | |
a2ad7897 MS |
531 | } |
532 | ||
11ade432 AE |
533 | /** |
534 | * Returns a list of all requirements of this package. | |
535 | * | |
536 | * @return array | |
537 | */ | |
538 | public function getRequirements() { | |
539 | return $this->requirements; | |
540 | } | |
541 | ||
542 | /** | |
543 | * Returns a list of all delivered optional packages of this package. | |
544 | * | |
545 | * @return array | |
546 | */ | |
547 | public function getOptionals() { | |
548 | return $this->optionals; | |
549 | } | |
550 | ||
551 | /** | |
552 | * Returns a list of excluded packages. | |
553 | * | |
554 | * @return array | |
555 | */ | |
556 | public function getExcludedPackages() { | |
557 | return $this->excludedPackages; | |
558 | } | |
559 | ||
560 | /** | |
561 | * Returns the package installation instructions. | |
562 | * | |
563 | * @return array | |
564 | */ | |
565 | public function getInstallInstructions() { | |
566 | return $this->instructions['install']; | |
567 | } | |
568 | ||
569 | /** | |
570 | * Returns the package update instructions. | |
571 | * | |
572 | * @return array | |
573 | */ | |
574 | public function getUpdateInstructions() { | |
575 | return $this->instructions['update']; | |
576 | } | |
577 | ||
578 | /** | |
579 | * Checks which package requirements do already exist in right version. | |
580 | * Returns a list with all existing requirements. | |
581 | * | |
582 | * @return array | |
583 | */ | |
584 | public function getAllExistingRequirements() { | |
585 | $existingRequirements = array(); | |
586 | $existingPackages = array(); | |
587 | if ($this->package !== null) { | |
07c78f25 | 588 | $sql = "SELECT package.* |
11ade432 AE |
589 | FROM wcf".WCF_N."_package_requirement requirement |
590 | LEFT JOIN wcf".WCF_N."_package package | |
591 | ON (package.packageID = requirement.requirement) | |
592 | WHERE requirement.packageID = ?"; | |
593 | $statement = WCF::getDB()->prepareStatement($sql); | |
594 | $statement->execute(array($this->package->packageID)); | |
595 | while ($row = $statement->fetchArray()) { | |
596 | $existingRequirements[$row['package']] = $row; | |
597 | } | |
598 | } | |
9f959ced | 599 | |
11ade432 AE |
600 | // build sql |
601 | $packageNames = array(); | |
602 | $requirements = $this->getRequirements(); | |
603 | foreach ($requirements as $requirement) { | |
604 | if (isset($existingRequirements[$requirement['name']])) { | |
605 | $existingPackages[$requirement['name']] = array(); | |
606 | $existingPackages[$requirement['name']][$existingRequirements[$requirement['name']]['packageID']] = $existingRequirements[$requirement['name']]; | |
607 | } | |
608 | else { | |
609 | $packageNames[] = $requirement['name']; | |
610 | } | |
611 | } | |
9f959ced | 612 | |
11ade432 AE |
613 | // check whether the required packages do already exist |
614 | if (!empty($packageNames)) { | |
615 | $conditions = new PreparedStatementConditionBuilder(); | |
616 | $conditions->add("package.package IN (?)", array($packageNames)); | |
617 | ||
07c78f25 | 618 | $sql = "SELECT package.* |
11ade432 AE |
619 | FROM wcf".WCF_N."_package package |
620 | ".$conditions; | |
621 | $statement = WCF::getDB()->prepareStatement($sql); | |
622 | $statement->execute($conditions->getParameters()); | |
623 | while ($row = $statement->fetchArray()) { | |
624 | // check required package version | |
625 | if (isset($requirements[$row['package']]['minversion']) && Package::compareVersion($row['packageVersion'], $requirements[$row['package']]['minversion']) == -1) { | |
626 | continue; | |
627 | } | |
628 | ||
629 | if (!isset($existingPackages[$row['package']])) { | |
630 | $existingPackages[$row['package']] = array(); | |
631 | } | |
632 | ||
633 | $existingPackages[$row['package']][$row['packageID']] = $row; | |
634 | } | |
635 | } | |
636 | ||
637 | return $existingPackages; | |
638 | } | |
639 | ||
640 | /** | |
641 | * Checks which package requirements do already exist in database. | |
642 | * Returns a list with the existing requirements. | |
643 | * | |
644 | * @return array | |
645 | */ | |
646 | public function getExistingRequirements() { | |
647 | // build sql | |
648 | $packageNames = array(); | |
649 | foreach ($this->requirements as $requirement) { | |
650 | $packageNames[] = $requirement['name']; | |
651 | } | |
9f959ced | 652 | |
11ade432 AE |
653 | // check whether the required packages do already exist |
654 | $existingPackages = array(); | |
655 | if (!empty($packageNames)) { | |
656 | $conditions = new PreparedStatementConditionBuilder(); | |
657 | $conditions->add("package IN (?)", array($packageNames)); | |
658 | ||
39bea7dd | 659 | $sql = "SELECT * |
11ade432 AE |
660 | FROM wcf".WCF_N."_package |
661 | ".$conditions; | |
662 | $statement = WCF::getDB()->prepareStatement($sql); | |
663 | $statement->execute($conditions->getParameters()); | |
664 | while ($row = $statement->fetchArray()) { | |
665 | if (!isset($existingPackages[$row['package']])) { | |
666 | $existingPackages[$row['package']] = array(); | |
667 | } | |
668 | ||
669 | $existingPackages[$row['package']][$row['packageVersion']] = $row; | |
670 | } | |
671 | ||
672 | // sort multiple packages by version number | |
673 | foreach ($existingPackages as $packageName => $instances) { | |
674 | uksort($instances, array('wcf\data\package\Package', 'compareVersion')); | |
675 | ||
676 | // get package with highest version number (get last package) | |
677 | $existingPackages[$packageName] = array_pop($instances); | |
678 | } | |
679 | } | |
680 | ||
681 | return $existingPackages; | |
682 | } | |
683 | ||
684 | /** | |
685 | * Returns a list of all open requirements of this package. | |
686 | * | |
687 | * @return array | |
688 | */ | |
689 | public function getOpenRequirements() { | |
690 | // get all existing requirements | |
691 | $existingPackages = $this->getExistingRequirements(); | |
692 | ||
693 | // check for open requirements | |
694 | $openRequirements = array(); | |
695 | foreach ($this->requirements as $requirement) { | |
696 | if (isset($existingPackages[$requirement['name']])) { | |
697 | // package does already exist | |
698 | // maybe an update is necessary | |
0eaa9bba MS |
699 | if (isset($requirement['minversion'])) { |
700 | if (Package::compareVersion($existingPackages[$requirement['name']]['packageVersion'], $requirement['minversion']) >= 0) { | |
701 | // package does already exist in needed version | |
e3369fd2 | 702 | // skip installation of requirement |
0eaa9bba MS |
703 | continue; |
704 | } | |
705 | else { | |
706 | $requirement['existingVersion'] = $existingPackages[$requirement['name']]['packageVersion']; | |
707 | } | |
708 | } | |
709 | else { | |
11ade432 AE |
710 | continue; |
711 | } | |
712 | ||
713 | $requirement['packageID'] = $existingPackages[$requirement['name']]['packageID']; | |
714 | $requirement['action'] = 'update'; | |
715 | } | |
716 | else { | |
717 | // package does not exist | |
718 | // new installation is necessary | |
719 | $requirement['packageID'] = 0; | |
720 | $requirement['action'] = 'install'; | |
721 | } | |
722 | ||
723 | $openRequirements[$requirement['name']] = $requirement; | |
724 | } | |
725 | ||
726 | return $openRequirements; | |
727 | } | |
728 | ||
729 | /** | |
a17de04e MS |
730 | * Extracts the requested file in the package archive to the temp folder |
731 | * and returns the path to the extracted file. | |
11ade432 AE |
732 | * |
733 | * @param string $filename | |
734 | * @param string $tempPrefix | |
a17de04e | 735 | * @return string |
11ade432 AE |
736 | */ |
737 | public function extractTar($filename, $tempPrefix = 'package_') { | |
738 | // search the requested tar archive in our package archive. | |
739 | // throw error message if not found. | |
740 | if (($fileIndex = $this->tar->getIndexByFilename($filename)) === false) { | |
1871b8b3 AE |
741 | throw new PackageValidationException(PackageValidationException::FILE_NOT_FOUND, array( |
742 | 'archive' => $this->archive, | |
743 | 'targetArchive' => $filename | |
744 | )); | |
11ade432 AE |
745 | } |
746 | ||
747 | // requested tar archive was found | |
748 | $fileInfo = $this->tar->getFileInfo($fileIndex); | |
749 | $filename = FileUtil::getTemporaryFilename($tempPrefix, preg_replace('!^.*?(\.(?:tar\.gz|tgz|tar))$!i', '\\1', $fileInfo['filename'])); | |
750 | $this->tar->extract($fileIndex, $filename); | |
751 | ||
752 | return $filename; | |
753 | } | |
754 | ||
755 | /** | |
a17de04e | 756 | * Unzips compressed package archives and returns the temporary file name. |
11ade432 | 757 | * |
39bea7dd | 758 | * @param string $archive filename |
a17de04e | 759 | * @return string |
11ade432 AE |
760 | */ |
761 | public static function unzipPackageArchive($archive) { | |
762 | if (!FileUtil::isURL($archive)) { | |
763 | $tar = new Tar($archive); | |
764 | $tar->close(); | |
765 | if ($tar->isZipped()) { | |
766 | $tmpName = FileUtil::getTemporaryFilename('package_'); | |
767 | if (FileUtil::uncompressFile($archive, $tmpName)) { | |
768 | return $tmpName; | |
769 | } | |
770 | } | |
771 | } | |
772 | ||
773 | return $archive; | |
774 | } | |
775 | ||
776 | /** | |
1f2ce8fd | 777 | * Returns a list of packages which exclude this package. |
11ade432 | 778 | * |
0ad90fc3 | 779 | * @return array<\wcf\data\package\Package> |
11ade432 AE |
780 | */ |
781 | public function getConflictedExcludingPackages() { | |
782 | $conflictedPackages = array(); | |
783 | $sql = "SELECT package.*, package_exclusion.* | |
784 | FROM wcf".WCF_N."_package_exclusion package_exclusion | |
785 | LEFT JOIN wcf".WCF_N."_package package | |
1f2ce8fd | 786 | ON (package.packageID = package_exclusion.packageID) |
37001c4e | 787 | WHERE excludedPackage = ?"; |
11ade432 AE |
788 | $statement = WCF::getDB()->prepareStatement($sql); |
789 | $statement->execute(array($this->packageInfo['name'])); | |
790 | while ($row = $statement->fetchArray()) { | |
791 | if (!empty($row['excludedPackageVersion'])) { | |
792 | if (Package::compareVersion($this->packageInfo['version'], $row['excludedPackageVersion'], '<')) { | |
793 | continue; | |
794 | } | |
795 | } | |
796 | ||
1f2ce8fd | 797 | $conflictedPackages[$row['packageID']] = new Package(null, $row); |
11ade432 AE |
798 | } |
799 | ||
800 | return $conflictedPackages; | |
801 | } | |
802 | ||
803 | /** | |
1f2ce8fd | 804 | * Returns a list of packages which are excluded by this package. |
11ade432 | 805 | * |
0ad90fc3 | 806 | * @return array<\wcf\data\package\Package> |
11ade432 AE |
807 | */ |
808 | public function getConflictedExcludedPackages() { | |
809 | $conflictedPackages = array(); | |
15fa2802 | 810 | if (!empty($this->excludedPackages)) { |
89b98373 MS |
811 | $excludedPackages = array(); |
812 | foreach ($this->excludedPackages as $excludedPackageData) { | |
1f2ce8fd | 813 | $excludedPackages[$excludedPackageData['name']] = $excludedPackageData['version']; |
89b98373 MS |
814 | } |
815 | ||
11ade432 | 816 | $conditions = new PreparedStatementConditionBuilder(); |
1f2ce8fd | 817 | $conditions->add("package IN (?)", array(array_keys($excludedPackages))); |
11ade432 AE |
818 | |
819 | $sql = "SELECT * | |
820 | FROM wcf".WCF_N."_package | |
821 | ".$conditions; | |
822 | $statement = WCF::getDB()->prepareStatement($sql); | |
823 | $statement->execute($conditions->getParameters()); | |
824 | while ($row = $statement->fetchArray()) { | |
1f2ce8fd MS |
825 | if (!empty($excludedPackages[$row['package']])) { |
826 | if (Package::compareVersion($row['packageVersion'], $excludedPackages[$row['package']], '<')) { | |
11ade432 AE |
827 | continue; |
828 | } | |
1f2ce8fd | 829 | $row['excludedPackageVersion'] = $excludedPackages[$row['package']]; |
11ade432 AE |
830 | } |
831 | ||
1f2ce8fd | 832 | $conflictedPackages[$row['packageID']] = new Package(null, $row); |
11ade432 AE |
833 | } |
834 | } | |
835 | ||
836 | return $conflictedPackages; | |
837 | } | |
838 | ||
839 | /** | |
840 | * Returns a list of instructions for installation or update. | |
841 | * | |
842 | * @param string $type | |
843 | * @return array | |
844 | */ | |
845 | public function getInstructions($type) { | |
846 | if (isset($this->instructions[$type])) { | |
847 | return $this->instructions[$type]; | |
848 | } | |
849 | ||
850 | return null; | |
851 | } | |
852 | ||
853 | /** | |
854 | * Returns a list of php requirements for current package. | |
855 | * | |
856 | * @return array<array> | |
857 | */ | |
858 | public function getPhpRequirements() { | |
859 | return $this->phpRequirements; | |
860 | } | |
861 | } |