Merge branch '2.0'
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / PackageInstallationNodeBuilder.class.php
CommitLineData
11ade432
AE
1<?php
2namespace wcf\system\package;
cfedc216 3use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
8d84809f 4use wcf\data\package\installation\queue\PackageInstallationQueueList;
abfda06b 5use wcf\data\package\Package;
d54c09a0 6use wcf\system\exception\SystemException;
c19ff714 7use wcf\system\Callback;
11ade432 8use wcf\system\WCF;
cfedc216 9use wcf\util\FileUtil;
11ade432
AE
10use wcf\util\StringUtil;
11
12/**
a17de04e 13 * Creates a logical node-based installation tree.
9f959ced 14 *
11ade432 15 * @author Alexander Ebert
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 */
22class PackageInstallationNodeBuilder {
d01a7177
AE
23 /**
24 * true if current node is empty
25 * @var boolean
26 */
27 public $emptyNode = true;
28
11ade432 29 /**
b1a0a33c 30 * active package installation dispatcher
0ad90fc3 31 * @var \wcf\system\package\PackageInstallationDispatcher
11ade432
AE
32 */
33 public $installation = null;
34
35 /**
b1a0a33c 36 * current installation node
11ade432
AE
37 * @var string
38 */
39 public $node = '';
40
41 /**
b1a0a33c 42 * current parent installation node
11ade432
AE
43 * @var string
44 */
45 public $parentNode = '';
46
a1ece4d9
AE
47 /**
48 * list of requirements to be checked before package installation
49 * @var array<array>
50 */
51 public $requirements = array();
52
11ade432
AE
53 /**
54 * current sequence number within one node
11ade432
AE
55 * @var integer
56 */
57 public $sequenceNo = 0;
58
a0b7c762
AE
59 /**
60 * list of packages about to be installed
322d635b 61 * @var array<string>
a0b7c762
AE
62 */
63 protected static $pendingPackages = array();
64
11ade432
AE
65 /**
66 * Creates a new instance of PackageInstallationNodeBuilder
9f959ced 67 *
11ade432
AE
68 * @param PackageInstallationDispatcher $installation
69 */
70 public function __construct(PackageInstallationDispatcher $installation) {
71 $this->installation = $installation;
72 }
73
f2719139
AE
74 /**
75 * Sets parent node.
76 *
77 * @param string $parentNode
78 */
79 public function setParentNode($parentNode) {
80 $this->parentNode = $parentNode;
81 }
82
11ade432
AE
83 /**
84 * Builds nodes for current installation queue.
85 */
86 public function buildNodes() {
87 // required packages
88 $this->buildRequirementNodes();
89
7ddf2af9
AE
90 // register package version
91 self::$pendingPackages[$this->installation->getArchive()->getPackageInfo('name')] = $this->installation->getArchive()->getPackageInfo('version');
92
11ade432 93 // install package itself
7ddf2af9
AE
94 if ($this->installation->queue->action == 'install') {
95 $this->buildPackageNode();
96 }
11ade432
AE
97
98 // package installation plugins
99 $this->buildPluginNodes();
100
77f5aa21
AE
101 // optional packages (ignored on update)
102 if ($this->installation->queue->action == 'install') {
103 $this->buildOptionalNodes();
104 }
8d84809f
AE
105
106 // child queues
107 $this->buildChildQueues();
7ddf2af9
AE
108
109 if ($this->installation->queue->action == 'update') {
110 $this->buildPackageNode();
111 }
11ade432
AE
112 }
113
114 /**
115 * Returns the succeeding node.
9f959ced 116 *
11ade432
AE
117 * @param string $parentNode
118 * @return string
119 */
120 public function getNextNode($parentNode = '') {
121 $sql = "SELECT node
122 FROM wcf".WCF_N."_package_installation_node
cfedc216 123 WHERE processNo = ?
11ade432
AE
124 AND parentNode = ?";
125 $statement = WCF::getDB()->prepareStatement($sql);
126 $statement->execute(array(
11ade432
AE
127 $this->installation->queue->processNo,
128 $parentNode
129 ));
130 $row = $statement->fetchArray();
131
132 if (!$row) {
133 return '';
134 }
135
136 return $row['node'];
137 }
138
e70175d6
AE
139 /**
140 * Returns package name associated with given queue id.
141 *
142 * @param integer $queueID
143 * @return string
144 */
145 public function getPackageNameByQueue($queueID) {
146 $sql = "SELECT packageName
147 FROM wcf".WCF_N."_package_installation_queue
148 WHERE queueID = ?";
149 $statement = WCF::getDB()->prepareStatement($sql);
150 $statement->execute(array($queueID));
151 $row = $statement->fetchArray();
152
153 if (!$row) {
154 return '';
155 }
156
157 return $row['packageName'];
158 }
159
824d9e90
AE
160 /**
161 * Returns installation type by queue id.
162 *
163 * @param integer $queueID
164 * @return string
165 */
166 public function getInstallationTypeByQueue($queueID) {
167 $sql = "SELECT action
168 FROM wcf".WCF_N."_package_installation_queue
169 WHERE queueID = ?";
170 $statement = WCF::getDB()->prepareStatement($sql);
171 $statement->execute(array($queueID));
172 $row = $statement->fetchArray();
173
174 return $row['action'];
175 }
176
11ade432
AE
177 /**
178 * Returns data for current node.
9f959ced 179 *
11ade432
AE
180 * @param string $node
181 * @return array
182 */
183 public function getNodeData($node) {
184 $sql = "SELECT nodeType, nodeData, sequenceNo
185 FROM wcf".WCF_N."_package_installation_node
cfedc216 186 WHERE processNo = ?
11ade432
AE
187 AND node = ?
188 ORDER BY sequenceNo ASC";
189 $statement = WCF::getDB()->prepareStatement($sql);
190 $statement->execute(array(
11ade432
AE
191 $this->installation->queue->processNo,
192 $node
193 ));
194 $data = array();
195 while ($row = $statement->fetchArray()) {
196 $data[] = $row;
197 }
198
199 return $data;
200 }
201
202 /**
203 * Marks a node as completed.
9f959ced 204 *
11ade432
AE
205 * @param string $node
206 */
207 public function completeNode($node) {
208 $sql = "UPDATE wcf".WCF_N."_package_installation_node
209 SET done = 1
cfedc216 210 WHERE processNo = ?
11ade432
AE
211 AND node = ?";
212 $statement = WCF::getDB()->prepareStatement($sql);
213 $statement->execute(array(
11ade432
AE
214 $this->installation->queue->processNo,
215 $node
216 ));
217 }
218
219 /**
220 * Removes all nodes associated with queue's process no.
9f959ced 221 *
11ade432
AE
222 * CAUTION: This method SHOULD NOT be called within the installation process!
223 */
224 public function purgeNodes() {
225 $sql = "DELETE FROM wcf".WCF_N."_package_installation_node
226 WHERE processNo = ?";
227 $statement = WCF::getDB()->prepareStatement($sql);
228 $statement->execute(array(
229 $this->installation->queue->processNo
230 ));
231
232 $sql = "DELETE FROM wcf".WCF_N."_package_installation_form
233 WHERE queueID = ?";
234 $statement = WCF::getDB()->prepareStatement($sql);
235 $statement->execute(array(
236 $this->installation->queue->queueID
237 ));
238 }
239
240 /**
241 * Calculates current setup process.
9f959ced 242 *
11ade432
AE
243 * @param string $node
244 * @return integer
245 */
246 public function calculateProgress($node) {
247 $progress = array(
248 'done' => 0,
249 'outstanding' => 0
250 );
251
252 $sql = "SELECT done
253 FROM wcf".WCF_N."_package_installation_node
f2719139 254 WHERE processNo = ?";
11ade432
AE
255 $statement = WCF::getDB()->prepareStatement($sql);
256 $statement->execute(array(
11ade432
AE
257 $this->installation->queue->processNo
258 ));
259 while ($row = $statement->fetchArray()) {
260 if ($row['done']) {
261 $progress['done']++;
262 }
263 else {
264 $progress['outstanding']++;
265 }
266 }
267
268 if (!$progress['done']) {
269 return 0;
270 }
1a1f7979 271 else if (!$progress['outstanding']) {
11ade432
AE
272 return 100;
273 }
274 else {
275 $total = $progress['done'] + $progress['outstanding'];
276 return round(($progress['done'] / $total) * 100);
277 }
278 }
279
280 /**
281 * Duplicates a node by re-inserting it and moving all descendants into a new tree.
9f959ced 282 *
11ade432
AE
283 * @param string $node
284 * @param integer $sequenceNo
285 */
456008db 286 public function cloneNode($node, $sequenceNo) {
11ade432
AE
287 $newNode = $this->getToken();
288
289 // update descendants
290 $sql = "UPDATE wcf".WCF_N."_package_installation_node
291 SET parentNode = ?
292 WHERE parentNode = ?
11ade432
AE
293 AND processNo = ?";
294 $statement = WCF::getDB()->prepareStatement($sql);
295 $statement->execute(array(
296 $newNode,
297 $node,
11ade432
AE
298 $this->installation->queue->processNo
299 ));
300
301 // create a copy of current node (prevents empty nodes)
302 $sql = "SELECT nodeType, nodeData, done
303 FROM wcf".WCF_N."_package_installation_node
304 WHERE node = ?
11ade432
AE
305 AND processNo = ?
306 AND sequenceNo = ?";
307 $statement = WCF::getDB()->prepareStatement($sql);
308 $statement->execute(array(
309 $node,
11ade432
AE
310 $this->installation->queue->processNo,
311 $sequenceNo
312 ));
313 $row = $statement->fetchArray();
314
315 $sql = "INSERT INTO wcf".WCF_N."_package_installation_node
316 (queueID, processNo, sequenceNo, node, parentNode, nodeType, nodeData, done)
317 VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
318 $statement = WCF::getDB()->prepareStatement($sql);
319 $statement->execute(array(
320 $this->installation->queue->queueID,
321 $this->installation->queue->processNo,
322 0,
323 $newNode,
324 $node,
325 $row['nodeType'],
326 $row['nodeData'],
327 $row['done']
328 ));
329
330 // move other child-nodes greater than $sequenceNo into new node
331 $sql = "UPDATE wcf".WCF_N."_package_installation_node
332 SET parentNode = ?,
333 node = ?,
334 sequenceNo = (sequenceNo - ?)
335 WHERE node = ?
11ade432
AE
336 AND processNo = ?
337 AND sequenceNo > ?";
338 $statement = WCF::getDB()->prepareStatement($sql);
339 $statement->execute(array(
340 $node,
341 $newNode,
342 $sequenceNo,
343 $node,
11ade432
AE
344 $this->installation->queue->processNo,
345 $sequenceNo
346 ));
347 }
348
456008db
AE
349 /**
350 * Inserts a node before given target node. Will shift all target
351 * nodes to provide to be descendants of the new node. If you intend
352 * to insert more than a single node, you should prefer shiftNodes().
353 *
c19ff714 354 * @param string $beforeNode
0ad90fc3 355 * @param \wcf\system\Callback $callback
456008db 356 */
c19ff714 357 public function insertNode($beforeNode, Callback $callback) {
456008db
AE
358 $newNode = $this->getToken();
359
360 // update descendants
361 $sql = "UPDATE wcf".WCF_N."_package_installation_node
362 SET parentNode = ?
363 WHERE parentNode = ?
364 AND processNo = ?";
365 $statement = WCF::getDB()->prepareStatement($sql);
366 $statement->execute(array(
367 $newNode,
368 $beforeNode,
369 $this->installation->queue->processNo
370 ));
371
372 // execute callback
373 $callback($beforeNode, $newNode);
374 }
375
376 /**
377 * Shifts nodes to allow dynamic inserts at runtime.
378 *
379 * @param string $oldParentNode
380 * @param string $newParentNode
381 */
382 public function shiftNodes($oldParentNode, $newParentNode) {
383 $sql = "UPDATE wcf".WCF_N."_package_installation_node
384 SET parentNode = ?
385 WHERE parentNode = ?
386 AND processNo = ?";
387 $statement = WCF::getDB()->prepareStatement($sql);
388 $statement->execute(array(
389 $newParentNode,
390 $oldParentNode,
391 $this->installation->queue->processNo
392 ));
393 }
394
11ade432
AE
395 /**
396 * Builds package node used to install the package itself.
397 */
398 protected function buildPackageNode() {
399 if (!empty($this->node)) {
400 $this->parentNode = $this->node;
401 $this->sequenceNo = 0;
402 }
403
404 $this->node = $this->getToken();
322d635b 405
11ade432 406 // calculate the number of instances of this package
11ade432
AE
407 $sql = "INSERT INTO wcf".WCF_N."_package_installation_node
408 (queueID, processNo, sequenceNo, node, parentNode, nodeType, nodeData)
409 VALUES (?, ?, ?, ?, ?, ?, ?)";
410 $statement = WCF::getDB()->prepareStatement($sql);
411 $statement->execute(array(
412 $this->installation->queue->queueID,
413 $this->installation->queue->processNo,
414 $this->sequenceNo,
415 $this->node,
416 $this->parentNode,
417 'package',
418 serialize(array(
419 'package' => $this->installation->getArchive()->getPackageInfo('name'),
a2ad7897 420 'packageName' => $this->installation->getArchive()->getLocalizedPackageInfo('packageName'),
a2ad7897 421 'packageDescription' => $this->installation->getArchive()->getLocalizedPackageInfo('packageDescription'),
11ade432
AE
422 'packageVersion' => $this->installation->getArchive()->getPackageInfo('version'),
423 'packageDate' => $this->installation->getArchive()->getPackageInfo('date'),
424 'packageURL' => $this->installation->getArchive()->getPackageInfo('packageURL'),
aac1247e 425 'isApplication' => $this->installation->getArchive()->getPackageInfo('isApplication'),
11ade432 426 'author' => $this->installation->getArchive()->getAuthorInfo('author'),
b68f0af4 427 'authorURL' => $this->installation->getArchive()->getAuthorInfo('authorURL') !== null ? $this->installation->getArchive()->getAuthorInfo('authorURL') : '',
11ade432 428 'installDate' => TIME_NOW,
a1ece4d9
AE
429 'updateDate' => TIME_NOW,
430 'requirements' => $this->requirements
11ade432
AE
431 ))
432 ));
433 }
434
435 /**
436 * Builds nodes for required packages, whereas each has it own node.
9f959ced 437 *
11ade432
AE
438 * @return string
439 */
440 protected function buildRequirementNodes() {
f2719139 441 $queue = $this->installation->queue;
11ade432 442
639e325b
AE
443 // handle requirements
444 $requiredPackages = $this->installation->getArchive()->getOpenRequirements();
11ade432
AE
445 foreach ($requiredPackages as $packageName => $package) {
446 if (!isset($package['file'])) {
322d635b
MS
447 if (isset(self::$pendingPackages[$packageName]) && (!isset($package['minversion']) || Package::compareVersion(self::$pendingPackages[$packageName], $package['minversion']) >= 0)) {
448 // the package will already be installed and no
449 // minversion is given or the package which will be
450 // installed satisfies the minversion, thus we can
451 // ignore this requirement
452 continue;
453 }
454
a1ece4d9
AE
455 // requirements will be checked once package is about to be installed
456 $this->requirements[$packageName] = array(
f5b8b8ef 457 'minVersion' => (isset($package['minversion'])) ? $package['minversion'] : '',
a1ece4d9
AE
458 'packageID' => $package['packageID']
459 );
e0431d96 460
11ade432
AE
461 continue;
462 }
463
f2719139
AE
464 if ($this->node == '' && !empty($this->parentNode)) {
465 $this->node = $this->parentNode;
466 }
11ade432 467
f2719139
AE
468 // extract package
469 $index = $this->installation->getArchive()->getTar()->getIndexByFilename($package['file']);
470 if ($index === false) {
d54c09a0
AE
471 // workaround for WCFSetup
472 if (!PACKAGE_ID && $packageName == 'com.woltlab.wcf') {
473 continue;
474 }
475
4202971e 476 throw new SystemException("Unable to find required package '".$package['file']."' within archive of package '".$this->installation->queue->package."'.");
11ade432 477 }
f2719139
AE
478
479 $fileName = FileUtil::getTemporaryFilename('package_', preg_replace('!^.*(?=\.(?:tar\.gz|tgz|tar)$)!i', '', basename($package['file'])));
480 $this->installation->getArchive()->getTar()->extract($index, $fileName);
481
482 // get archive data
483 $archive = new PackageArchive($fileName);
484 $archive->openArchive();
485
6eaad580
MS
486 // check if delivered package has correct identifier
487 if ($archive->getPackageInfo('name') != $packageName) {
488 throw new SystemException("Invalid package file delivered for '".$packageName."' requirement of package '".$this->installation->getArchive()->getPackageInfo('name')."' (delivered package: '".$archive->getPackageInfo('name')."').");
489 }
490
55009267
MS
491 // check if delivered version satisfies minversion
492 if (isset($package['minversion']) && Package::compareVersion($package['minversion'], $archive->getPackageInfo('version')) > 0) {
493 throw new SystemException("Package '".$this->installation->getArchive()->getPackageInfo('name')."' requires package '".$packageName."' at least in version ".$package['minversion'].", but only delivers version ".$archive->getPackageInfo('version').".");
494 }
495
b3782430
AE
496 // get package id
497 $sql = "SELECT packageID
498 FROM wcf".WCF_N."_package
499 WHERE package = ?";
500 $statement = WCF::getDB()->prepareStatement($sql);
501 $statement->execute(array($archive->getPackageInfo('name')));
502 $row = $statement->fetchArray();
503 $packageID = ($row === false) ? null : $row['packageID'];
504
322d635b
MS
505 // check if package will already be installed
506 if (isset(self::$pendingPackages[$packageName])) {
507 if (Package::compareVersion(self::$pendingPackages[$packageName], $archive->getPackageInfo('version')) >= 0) {
508 // the version to be installed satisfies the required version
509 continue;
510 }
511 else {
512 // the new delivered required version of the package has a
513 // higher version number, thus update/replace the existing
514 // package installation queue
515
516 // todo
517 }
518 }
519
f2719139
AE
520 // create new queue
521 $queue = PackageInstallationQueueEditor::create(array(
522 'parentQueueID' => $queue->queueID,
523 'processNo' => $queue->processNo,
524 'userID' => WCF::getUser()->userID,
525 'package' => $archive->getPackageInfo('name'),
b3782430 526 'packageID' => $packageID,
a2ad7897 527 'packageName' => $archive->getLocalizedPackageInfo('packageName'),
f2719139 528 'archive' => $fileName,
b3782430 529 'action' => ($packageID ? 'update' : 'install')
f2719139
AE
530 ));
531
322d635b
MS
532 self::$pendingPackages[$archive->getPackageInfo('name')] = $archive->getPackageInfo('version');
533
f2719139
AE
534 // spawn nodes
535 $installation = new PackageInstallationDispatcher($queue);
536 $installation->nodeBuilder->setParentNode($this->node);
537 $installation->nodeBuilder->buildNodes();
538 $this->node = $installation->nodeBuilder->getCurrentNode();
11ade432
AE
539 }
540 }
541
f2719139
AE
542 /**
543 * Returns current node
544 *
545 * @return string
546 */
547 public function getCurrentNode() {
548 return $this->node;
549 }
550
11ade432
AE
551 /**
552 * Builds package installation plugin nodes, whereas pips could be grouped within
553 * one node, differ from each by nothing but the sequence number.
9f959ced 554 *
11ade432
AE
555 * @return string
556 */
557 protected function buildPluginNodes() {
7ddf2af9
AE
558 if (!empty($this->node)) {
559 $this->parentNode = $this->node;
560 $this->sequenceNo = 0;
561 }
562
563 $this->node = $this->getToken();
564
11ade432
AE
565 $pluginNodes = array();
566
d01a7177 567 $this->emptyNode = true;
11ade432 568 $instructions = ($this->installation->getAction() == 'install') ? $this->installation->getArchive()->getInstallInstructions() : $this->installation->getArchive()->getUpdateInstructions();
36ec5593
AE
569 $count = count($instructions);
570 $i = 0;
11ade432 571 foreach ($instructions as $pip) {
36ec5593
AE
572 $i++;
573
eef3c4aa 574 if (isset($pip['attributes']['run']) && ($pip['attributes']['run'] == 'standalone')) {
d01a7177
AE
575 // move into a new node unless current one is empty
576 if (!$this->emptyNode) {
577 $this->parentNode = $this->node;
578 $this->node = $this->getToken();
579 $this->sequenceNo = 0;
580 }
11ade432
AE
581 $pluginNodes[] = array(
582 'data' => $pip,
583 'node' => $this->node,
584 'parentNode' => $this->parentNode,
585 'sequenceNo' => $this->sequenceNo
586 );
c7aa597c 587
36ec5593
AE
588 // create a new node for following PIPs, unless it is the last one
589 if ($i < $count) {
590 $this->parentNode = $this->node;
591 $this->node = $this->getToken();
592 $this->sequenceNo = 0;
593
594 $this->emptyNode = true;
595 }
11ade432
AE
596 }
597 else {
598 $this->sequenceNo++;
599
600 $pluginNodes[] = array(
601 'data' => $pip,
602 'node' => $this->node,
603 'parentNode' => $this->parentNode,
604 'sequenceNo' => $this->sequenceNo
605 );
d01a7177
AE
606
607 $this->emptyNode = false;
11ade432
AE
608 }
609 }
610
611 // insert nodes
15fa2802 612 if (!empty($pluginNodes)) {
11ade432
AE
613 $sql = "INSERT INTO wcf".WCF_N."_package_installation_node
614 (queueID, processNo, sequenceNo, node, parentNode, nodeType, nodeData)
615 VALUES (?, ?, ?, ?, ?, ?, ?)";
616 $statement = WCF::getDB()->prepareStatement($sql);
15fa2802 617
11ade432
AE
618 foreach ($pluginNodes as $index => $nodeData) {
619 $statement->execute(array(
620 $this->installation->queue->queueID,
621 $this->installation->queue->processNo,
622 $nodeData['sequenceNo'],
623 $nodeData['node'],
624 $nodeData['parentNode'],
625 'pip',
626 serialize($nodeData['data'])
627 ));
628 }
629 }
630 }
631
632 /**
633 * Builds nodes for optional packages, whereas each package exists within
634 * one node with the same parent node, seperated by sequence no (which does
635 * not really matter at this point).
636 */
637 protected function buildOptionalNodes() {
492816d3 638 $packages = array();
11ade432
AE
639
640 $optionalPackages = $this->installation->getArchive()->getOptionals();
641 foreach ($optionalPackages as $package) {
299400c5
AE
642 // check if already installed
643 if (Package::isAlreadyInstalled($package['name'])) {
644 continue;
645 }
646
492816d3
AE
647 // extract package
648 $index = $this->installation->getArchive()->getTar()->getIndexByFilename($package['file']);
649 if ($index === false) {
650 throw new SystemException("Unable to find required package '".$package['file']."' within archive.");
651 }
652
653 $fileName = FileUtil::getTemporaryFilename('package_', preg_replace('!^.*(?=\.(?:tar\.gz|tgz|tar)$)!i', '', basename($package['file'])));
654 $this->installation->getArchive()->getTar()->extract($index, $fileName);
655
656 // get archive data
657 $archive = new PackageArchive($fileName);
658 $archive->openArchive();
11ade432 659
a0b7c762
AE
660 // check if all requirements are met
661 $isInstallable = true;
662 foreach ($archive->getOpenRequirements() as $packageName => $package) {
663 if (!isset($package['file'])) {
664 // requirement is neither installed nor shipped, check if it is about to be installed
322d635b 665 if (!isset(self::$pendingPackages[$packageName])) {
a0b7c762
AE
666 $isInstallable = false;
667 break;
668 }
669 }
670 }
671
bce74466
AE
672 // check for exclusions
673 $excludedPackages = $archive->getConflictedExcludedPackages();
674 if (!empty($excludedPackages)) {
675 $isInstallable = false;
676 }
677
678 $excludingPackages = $archive->getConflictedExcludingPackages();
679 if (!empty($excludingPackages)) {
680 $isInstallable = false;
681 }
682
492816d3
AE
683 $packages[] = array(
684 'archive' => $fileName,
a0b7c762 685 'isInstallable' => $isInstallable,
492816d3 686 'package' => $archive->getPackageInfo('name'),
a2ad7897 687 'packageName' => $archive->getLocalizedPackageInfo('packageName'),
9e51ceb8 688 'packageDescription' => $archive->getLocalizedPackageInfo('packageDescription'),
492816d3
AE
689 'selected' => 0
690 );
a0b7c762 691
322d635b 692 self::$pendingPackages[$archive->getPackageInfo('name')] = $archive->getPackageInfo('version');
11ade432
AE
693 }
694
492816d3 695 if (!empty($packages)) {
11ade432
AE
696 $this->parentNode = $this->node;
697 $this->node = $this->getToken();
492816d3 698 $this->sequenceNo = 0;
11ade432
AE
699
700 $sql = "INSERT INTO wcf".WCF_N."_package_installation_node
701 (queueID, processNo, sequenceNo, node, parentNode, nodeType, nodeData)
702 VALUES (?, ?, ?, ?, ?, ?, ?)";
703 $statement = WCF::getDB()->prepareStatement($sql);
492816d3
AE
704 $statement->execute(array(
705 $this->installation->queue->queueID,
706 $this->installation->queue->processNo,
707 $this->sequenceNo,
708 $this->node,
709 $this->parentNode,
710 'optionalPackages',
711 serialize($packages)
712 ));
11ade432
AE
713 }
714 }
715
8d84809f
AE
716 /**
717 * Recursively build nodes for child queues.
718 */
719 protected function buildChildQueues() {
720 $queueList = new PackageInstallationQueueList();
aa2333e3 721 $queueList->getConditionBuilder()->add("package_installation_queue.parentQueueID = ?", array($this->installation->queue->queueID));
092e2f08 722 $queueList->getConditionBuilder()->add("package_installation_queue.queueID NOT IN (SELECT queueID FROM wcf".WCF_N."_package_installation_node)");
8d84809f
AE
723 $queueList->readObjects();
724
725 foreach ($queueList as $queue) {
726 $installation = new PackageInstallationDispatcher($queue);
39935629
AE
727
728 // work-around for iterative package updates
729 if ($this->installation->queue->action == 'update' && $queue->package == $this->installation->queue->package) {
730 $installation->setPreviousPackage(array(
731 'package' => $this->installation->getArchive()->getPackageInfo('name'),
732 'packageVersion' => $this->installation->getArchive()->getPackageInfo('version')
733 ));
734 }
735
8d84809f
AE
736 $installation->nodeBuilder->setParentNode($this->node);
737 $installation->nodeBuilder->buildNodes();
738 $this->node = $installation->nodeBuilder->getCurrentNode();
739 }
740 }
741
11ade432
AE
742 /**
743 * Returns a short SHA1-hash.
55009267 744 *
11ade432
AE
745 * @return string
746 */
747 protected function getToken() {
838e315b 748 return mb_substr(StringUtil::getRandomID(), 0, 8);
11ade432 749 }
cfedc216
AE
750
751 /**
752 * Returns queue id based upon current node.
753 *
754 * @param integer $processNo
755 * @param string $node
756 * @return integer
757 */
758 public function getQueueByNode($processNo, $node) {
759 $sql = "SELECT queueID
760 FROM wcf".WCF_N."_package_installation_node
761 WHERE processNo = ?
762 AND node = ?";
763 $statement = WCF::getDB()->prepareStatement($sql);
764 $statement->execute(array(
765 $processNo,
766 $node
767 ));
768 $row = $statement->fetchArray();
769
770 return $row['queueID'];
771 }
11ade432 772}