Commit | Line | Data |
---|---|---|
11ade432 AE |
1 | <?php |
2 | namespace wcf\system\package; | |
cfedc216 | 3 | use wcf\data\package\installation\queue\PackageInstallationQueueEditor; |
8d84809f | 4 | use wcf\data\package\installation\queue\PackageInstallationQueueList; |
abfda06b | 5 | use wcf\data\package\Package; |
d54c09a0 | 6 | use wcf\system\exception\SystemException; |
c19ff714 | 7 | use wcf\system\Callback; |
11ade432 | 8 | use wcf\system\WCF; |
cfedc216 | 9 | use wcf\util\FileUtil; |
11ade432 AE |
10 | use 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 | */ |
22 | class 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 | } |