Commit | Line | Data |
---|---|---|
11ade432 AE |
1 | <?php |
2 | namespace wcf\system\package; | |
c244fcc6 | 3 | use wcf\data\application\Application; |
11ade432 | 4 | use wcf\data\application\ApplicationEditor; |
a2ad7897 | 5 | use wcf\data\language\category\LanguageCategory; |
11ade432 | 6 | use wcf\data\language\LanguageEditor; |
a2ad7897 | 7 | use wcf\data\language\LanguageList; |
8aa93ba8 | 8 | use wcf\data\option\OptionEditor; |
ec1b1daf TB |
9 | use wcf\data\package\installation\queue\PackageInstallationQueue; |
10 | use wcf\data\package\installation\queue\PackageInstallationQueueEditor; | |
2bc9f31d TB |
11 | use wcf\data\package\Package; |
12 | use wcf\data\package\PackageEditor; | |
f1c1fc65 | 13 | use wcf\system\application\ApplicationHandler; |
14d48464 | 14 | use wcf\data\object\type\ObjectTypeCache; |
f1dc282d | 15 | use wcf\system\cache\builder\TemplateListenerCodeCacheBuilder; |
11ade432 | 16 | use wcf\system\cache\CacheHandler; |
a2ad7897 | 17 | use wcf\system\database\statement\PreparedStatement; |
11ade432 | 18 | use wcf\system\database\util\PreparedStatementConditionBuilder; |
fb14b37f | 19 | use wcf\system\event\EventHandler; |
11ade432 | 20 | use wcf\system\exception\SystemException; |
a99fcf96 MS |
21 | use wcf\system\form\container\GroupFormElementContainer; |
22 | use wcf\system\form\container\MultipleSelectionFormElementContainer; | |
23 | use wcf\system\form\element\MultipleSelectionFormElement; | |
24 | use wcf\system\form\element\TextInputFormElement; | |
b4282c86 | 25 | use wcf\system\form\FormDocument; |
a2ad7897 | 26 | use wcf\system\language\LanguageFactory; |
4d4a5125 K |
27 | use wcf\system\package\plugin\IPackageInstallationPlugin; |
28 | use wcf\system\package\plugin\ObjectTypePackageInstallationPlugin; | |
29 | use wcf\system\package\plugin\SQLPackageInstallationPlugin; | |
3e0e6b2c | 30 | use wcf\system\request\LinkHandler; |
3dcfb497 | 31 | use wcf\system\request\RouteHandler; |
8f3fc897 | 32 | use wcf\system\setup\Installer; |
cd0fbe9a | 33 | use wcf\system\style\StyleHandler; |
fb14b37f | 34 | use wcf\system\user\storage\UserStorageHandler; |
2bc9f31d | 35 | use wcf\system\WCF; |
11ade432 AE |
36 | use wcf\util\FileUtil; |
37 | use wcf\util\HeaderUtil; | |
38 | use wcf\util\StringUtil; | |
39 | ||
40 | /** | |
41 | * PackageInstallationDispatcher handles the whole installation process. | |
9f959ced | 42 | * |
11ade432 | 43 | * @author Alexander Ebert |
ca4ba303 | 44 | * @copyright 2001-2014 WoltLab GmbH |
11ade432 AE |
45 | * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php> |
46 | * @package com.woltlab.wcf | |
47 | * @subpackage system.package | |
9f959ced | 48 | * @category Community Framework |
11ade432 AE |
49 | */ |
50 | class PackageInstallationDispatcher { | |
51 | /** | |
52 | * current installation type | |
11ade432 AE |
53 | * @var string |
54 | */ | |
55 | protected $action = ''; | |
56 | ||
57 | /** | |
58 | * instance of PackageArchive | |
0ad90fc3 | 59 | * @var \wcf\system\package\PackageArchive |
11ade432 AE |
60 | */ |
61 | public $archive = null; | |
62 | ||
63 | /** | |
64 | * instance of PackageInstallationNodeBuilder | |
0ad90fc3 | 65 | * @var \wcf\system\package\PackageInstallationNodeBuilder |
11ade432 AE |
66 | */ |
67 | public $nodeBuilder = null; | |
68 | ||
69 | /** | |
70 | * instance of Package | |
0ad90fc3 | 71 | * @var \wcf\data\package\Package |
11ade432 AE |
72 | */ |
73 | public $package = null; | |
74 | ||
75 | /** | |
76 | * instance of PackageInstallationQueue | |
0ad90fc3 | 77 | * @var \wcf\system\package\PackageInstallationQueue |
11ade432 AE |
78 | */ |
79 | public $queue = null; | |
80 | ||
81 | /** | |
82 | * default name of the config file | |
9f959ced | 83 | * @var string |
11ade432 AE |
84 | */ |
85 | const CONFIG_FILE = 'config.inc.php'; | |
86 | ||
82a7de7f K |
87 | /** |
88 | * holds state of structuring version tables | |
06355ec3 | 89 | * @var boolean |
82a7de7f | 90 | */ |
39935629 AE |
91 | protected $requireRestructureVersionTables = false; |
92 | ||
93 | /** | |
94 | * data of previous package in queue | |
95 | * @var array<string> | |
96 | */ | |
97 | protected $previousPackageData = null; | |
82a7de7f | 98 | |
11ade432 AE |
99 | /** |
100 | * Creates a new instance of PackageInstallationDispatcher. | |
59dc0db6 | 101 | * |
0ad90fc3 | 102 | * @param \wcf\data\package\installation\queue\PackageInstallationQueue $queue |
11ade432 AE |
103 | */ |
104 | public function __construct(PackageInstallationQueue $queue) { | |
105 | $this->queue = $queue; | |
106 | $this->nodeBuilder = new PackageInstallationNodeBuilder($this); | |
107 | ||
108 | $this->action = $this->queue->action; | |
109 | } | |
110 | ||
39935629 AE |
111 | /** |
112 | * Sets data of previous package in queue. | |
113 | * | |
114 | * @param array<string> $packageData | |
115 | */ | |
116 | public function setPreviousPackage(array $packageData) { | |
117 | $this->previousPackageData = $packageData; | |
118 | } | |
119 | ||
11ade432 AE |
120 | /** |
121 | * Installs node components and returns next node. | |
9f959ced | 122 | * |
11ade432 | 123 | * @param string $node |
0ad90fc3 | 124 | * @return \wcf\system\package\PackageInstallationStep |
11ade432 AE |
125 | */ |
126 | public function install($node) { | |
127 | $nodes = $this->nodeBuilder->getNodeData($node); | |
128 | ||
129 | // invoke node-specific actions | |
130 | foreach ($nodes as $data) { | |
131 | $nodeData = unserialize($data['nodeData']); | |
132 | ||
133 | switch ($data['nodeType']) { | |
134 | case 'package': | |
135 | $step = $this->installPackage($nodeData); | |
136 | break; | |
137 | ||
138 | case 'pip': | |
139 | $step = $this->executePIP($nodeData); | |
140 | break; | |
cfedc216 | 141 | |
492816d3 | 142 | case 'optionalPackages': |
456008db | 143 | $step = $this->selectOptionalPackages($node, $nodeData); |
492816d3 AE |
144 | break; |
145 | ||
cfedc216 AE |
146 | default: |
147 | die("Unknown node type: '".$data['nodeType']."'"); | |
148 | break; | |
11ade432 AE |
149 | } |
150 | ||
151 | if ($step->splitNode()) { | |
456008db | 152 | $this->nodeBuilder->cloneNode($node, $data['sequenceNo']); |
11ade432 AE |
153 | break; |
154 | } | |
155 | } | |
156 | ||
157 | // mark node as completed | |
158 | $this->nodeBuilder->completeNode($node); | |
159 | ||
160 | // assign next node | |
d7802a05 | 161 | $tmp = $node; |
8aa93ba8 AE |
162 | $node = $this->nodeBuilder->getNextNode($node); |
163 | $step->setNode($node); | |
164 | ||
07e26cb9 | 165 | // perform post-install/update actions |
8aa93ba8 | 166 | if ($node == '') { |
07e26cb9 | 167 | // update options.inc.php |
8aa93ba8 | 168 | OptionEditor::resetCache(); |
5475a95d | 169 | |
1e996eac | 170 | if ($this->action == 'install') { |
07e26cb9 | 171 | // save localized package infos |
1e996eac | 172 | $this->saveLocalizedPackageInfos(); |
14077e5c AE |
173 | |
174 | // remove all cache files after WCFSetup | |
175 | if (!PACKAGE_ID) { | |
b401cd0d | 176 | CacheHandler::getInstance()->flushAll(); |
d43bb3f0 AE |
177 | |
178 | if (WCF::getSession()->getVar('__wcfSetup_developerMode')) { | |
179 | $sql = "UPDATE wcf".WCF_N."_option | |
180 | SET optionValue = ? | |
181 | WHERE optionName = ?"; | |
182 | $statement = WCF::getDB()->prepareStatement($sql); | |
183 | $statement->execute(array( | |
184 | 1, | |
185 | 'enable_debug_mode' | |
186 | )); | |
05fe4769 AE |
187 | |
188 | // update options.inc.php | |
189 | OptionEditor::resetCache(); | |
d43bb3f0 | 190 | } |
14077e5c | 191 | } |
f1c1fc65 AE |
192 | |
193 | // rebuild application paths | |
194 | ApplicationHandler::rebuild(); | |
131de123 | 195 | ApplicationEditor::setup(); |
1e996eac | 196 | } |
07e26cb9 AE |
197 | |
198 | // remove template listener cache | |
b401cd0d | 199 | TemplateListenerCodeCacheBuilder::getInstance()->reset(); |
6dfe934f | 200 | |
07e26cb9 AE |
201 | // reset language cache |
202 | LanguageFactory::getInstance()->clearCache(); | |
203 | LanguageFactory::getInstance()->deleteLanguageCache(); | |
83c78288 AE |
204 | |
205 | // reset stylesheets | |
206 | StyleHandler::resetStylesheets(); | |
ca9a7a6a | 207 | |
e3fc42e8 MS |
208 | // clear user storage |
209 | UserStorageHandler::getInstance()->clear(); | |
fb14b37f | 210 | |
824d9e90 AE |
211 | // rebuild config files for affected applications |
212 | $sql = "SELECT package.packageID | |
26cf6096 TD |
213 | FROM wcf".WCF_N."_package_installation_queue queue, |
214 | wcf".WCF_N."_package package | |
824d9e90 AE |
215 | WHERE queue.processNo = ? |
216 | AND package.packageID = queue.packageID | |
217 | AND package.packageID <> ? | |
218 | AND package.isApplication = ?"; | |
219 | $statement = WCF::getDB()->prepareStatement($sql); | |
220 | $statement->execute(array( | |
221 | $this->queue->processNo, | |
222 | 1, | |
223 | 1 | |
224 | )); | |
225 | while ($row = $statement->fetchArray()) { | |
226 | Package::writeConfigFile($row['packageID']); | |
227 | } | |
228 | ||
fb14b37f | 229 | EventHandler::getInstance()->fireAction($this, 'postInstall'); |
824d9e90 | 230 | |
36fe10e2 AE |
231 | // remove archives |
232 | $sql = "SELECT archive | |
233 | FROM wcf".WCF_N."_package_installation_queue | |
234 | WHERE processNo = ?"; | |
235 | $statement = WCF::getDB()->prepareStatement($sql); | |
236 | $statement->execute(array($this->queue->processNo)); | |
237 | while ($row = $statement->fetchArray()) { | |
238 | @unlink($row['archive']); | |
36fe10e2 AE |
239 | } |
240 | ||
241 | // delete queues | |
824d9e90 AE |
242 | $sql = "DELETE FROM wcf".WCF_N."_package_installation_queue |
243 | WHERE processNo = ?"; | |
244 | $statement = WCF::getDB()->prepareStatement($sql); | |
b953f892 | 245 | $statement->execute(array($this->queue->processNo)); |
e3fc42e8 | 246 | } |
82a7de7f K |
247 | |
248 | if ($this->requireRestructureVersionTables) { | |
249 | $this->restructureVersionTables(); | |
e3fc42e8 | 250 | } |
11ade432 AE |
251 | |
252 | return $step; | |
253 | } | |
254 | ||
255 | /** | |
256 | * Returns current package archive. | |
9f959ced | 257 | * |
0ad90fc3 | 258 | * @return \wcf\system\package\PackageArchive |
11ade432 AE |
259 | */ |
260 | public function getArchive() { | |
261 | if ($this->archive === null) { | |
39935629 AE |
262 | $package = $this->getPackage(); |
263 | // check if we're doing an iterative update of the same package | |
264 | if ($this->previousPackageData !== null && $this->getPackage()->package == $this->previousPackageData['package']) { | |
d8782831 | 265 | if (Package::compareVersion($this->getPackage()->packageVersion, $this->previousPackageData['packageVersion'], '<')) { |
39935629 AE |
266 | // fake package to simulate the package version required by current archive |
267 | $this->getPackage()->setPackageVersion($this->previousPackageData['packageVersion']); | |
268 | } | |
269 | } | |
270 | ||
ca38121b | 271 | $this->archive = new PackageArchive($this->queue->archive, $this->getPackage()); |
11ade432 AE |
272 | |
273 | if (FileUtil::isURL($this->archive->getArchive())) { | |
274 | // get return value and update entry in | |
275 | // package_installation_queue with this value | |
276 | $archive = $this->archive->downloadArchive(); | |
277 | $queueEditor = new PackageInstallationQueueEditor($this->queue); | |
278 | $queueEditor->update(array( | |
279 | 'archive' => $archive | |
280 | )); | |
281 | } | |
282 | ||
283 | $this->archive->openArchive(); | |
284 | } | |
285 | ||
286 | return $this->archive; | |
287 | } | |
288 | ||
289 | /** | |
290 | * Installs current package. | |
9f959ced | 291 | * |
11ade432 AE |
292 | * @param array $nodeData |
293 | */ | |
294 | protected function installPackage(array $nodeData) { | |
295 | $installationStep = new PackageInstallationStep(); | |
296 | ||
a1ece4d9 AE |
297 | // check requirements |
298 | if (!empty($nodeData['requirements'])) { | |
299 | foreach ($nodeData['requirements'] as $package => $requirementData) { | |
300 | // get existing package | |
301 | if ($requirementData['packageID']) { | |
302 | $sql = "SELECT packageName, packageVersion | |
303 | FROM wcf".WCF_N."_package | |
304 | WHERE packageID = ?"; | |
305 | $statement = WCF::getDB()->prepareStatement($sql); | |
306 | $statement->execute(array($requirementData['packageID'])); | |
307 | } | |
308 | else { | |
309 | // try to find matching package | |
310 | $sql = "SELECT packageName, packageVersion | |
311 | FROM wcf".WCF_N."_package | |
312 | WHERE package = ?"; | |
313 | $statement = WCF::getDB()->prepareStatement($sql); | |
314 | $statement->execute(array($package)); | |
315 | } | |
316 | $row = $statement->fetchArray(); | |
317 | ||
318 | // package is required but not available | |
319 | if ($row === false) { | |
320 | throw new SystemException("Package '".$package."' is required by '".$nodeData['packageName']."', but is neither installed nor shipped."); | |
321 | } | |
322 | ||
323 | // check version requirements | |
324 | if ($requirementData['minVersion']) { | |
325 | if (Package::compareVersion($row['packageVersion'], $requirementData['minVersion']) < 0) { | |
95cda2bf | 326 | throw new SystemException("Package '".$nodeData['packageName']."' requires package '".$row['packageName']."' in version '".$requirementData['minVersion']."', but only version '".$row['packageVersion']."' is installed"); |
a1ece4d9 AE |
327 | } |
328 | } | |
329 | } | |
330 | } | |
331 | unset($nodeData['requirements']); | |
332 | ||
b3782430 AE |
333 | // update package |
334 | if ($this->queue->packageID) { | |
335 | $packageEditor = new PackageEditor(new Package($this->queue->packageID)); | |
336 | $packageEditor->update($nodeData); | |
337 | ||
a48477ee MS |
338 | // delete old excluded packages |
339 | $sql = "DELETE FROM wcf".WCF_N."_package_exclusion | |
340 | WHERE packageID = ?"; | |
341 | $statement = WCF::getDB()->prepareStatement($sql); | |
342 | $statement->execute(array($this->queue->packageID)); | |
b3782430 | 343 | |
a93b160e MS |
344 | // delete old requirements and dependencies |
345 | $sql = "DELETE FROM wcf".WCF_N."_package_requirement | |
346 | WHERE packageID = ?"; | |
347 | $statement = WCF::getDB()->prepareStatement($sql); | |
348 | $statement->execute(array($this->queue->packageID)); | |
b3782430 AE |
349 | } |
350 | else { | |
11ade432 AE |
351 | // create package entry |
352 | $package = PackageEditor::create($nodeData); | |
353 | ||
354 | // update package id for current queue | |
355 | $queueEditor = new PackageInstallationQueueEditor($this->queue); | |
356 | $queueEditor->update(array( | |
357 | 'packageID' => $package->packageID | |
358 | )); | |
359 | ||
11ade432 AE |
360 | // reload queue |
361 | $this->queue = new PackageInstallationQueue($this->queue->queueID); | |
362 | $this->package = null; | |
363 | ||
aac1247e | 364 | if ($package->isApplication) { |
838e315b | 365 | $host = str_replace(RouteHandler::getProtocol(), '', RouteHandler::getHost()); |
3dcfb497 | 366 | $path = RouteHandler::getPath(array('acp')); |
08d76680 | 367 | |
11ade432 AE |
368 | // insert as application |
369 | ApplicationEditor::create(array( | |
3dcfb497 AE |
370 | 'domainName' => $host, |
371 | 'domainPath' => $path, | |
82542641 AE |
372 | 'cookieDomain' => $host, |
373 | 'cookiePath' => $path, | |
11ade432 AE |
374 | 'packageID' => $package->packageID |
375 | )); | |
376 | } | |
11ade432 AE |
377 | } |
378 | ||
a48477ee MS |
379 | // save excluded packages |
380 | if (count($this->getArchive()->getExcludedPackages())) { | |
381 | $sql = "INSERT INTO wcf".WCF_N."_package_exclusion | |
a93b160e MS |
382 | (packageID, excludedPackage, excludedPackageVersion) |
383 | VALUES (?, ?, ?)"; | |
a48477ee MS |
384 | $statement = WCF::getDB()->prepareStatement($sql); |
385 | ||
386 | foreach ($this->getArchive()->getExcludedPackages() as $excludedPackage) { | |
387 | $statement->execute(array($this->queue->packageID, $excludedPackage['name'], (!empty($excludedPackage['version']) ? $excludedPackage['version'] : ''))); | |
388 | } | |
389 | } | |
390 | ||
a93b160e MS |
391 | // insert requirements and dependencies |
392 | $requirements = $this->getArchive()->getAllExistingRequirements(); | |
393 | if (!empty($requirements)) { | |
394 | $sql = "INSERT INTO wcf".WCF_N."_package_requirement | |
395 | (packageID, requirement) | |
396 | VALUES (?, ?)"; | |
397 | $statement = WCF::getDB()->prepareStatement($sql); | |
398 | ||
399 | foreach ($requirements as $identifier => $possibleRequirements) { | |
ba4e725b | 400 | $requirement = array_shift($possibleRequirements); |
a93b160e MS |
401 | |
402 | $statement->execute(array($this->queue->packageID, $requirement['packageID'])); | |
403 | } | |
404 | } | |
405 | ||
aac1247e | 406 | if ($this->getPackage()->isApplication && $this->getPackage()->package != 'com.woltlab.wcf' && $this->getAction() == 'install') { |
11ade432 AE |
407 | if (empty($this->getPackage()->packageDir)) { |
408 | $document = $this->promptPackageDir(); | |
6b63ee9a | 409 | if ($document !== null && $document instanceof FormDocument) { |
11ade432 AE |
410 | $installationStep->setDocument($document); |
411 | } | |
412 | ||
413 | $installationStep->setSplitNode(); | |
414 | } | |
415 | } | |
11ade432 AE |
416 | |
417 | return $installationStep; | |
418 | } | |
419 | ||
5475a95d MS |
420 | /** |
421 | * Saves the localized package infos. | |
422 | * | |
423 | * @todo license and readme | |
424 | */ | |
425 | protected function saveLocalizedPackageInfos() { | |
426 | $package = new Package($this->queue->packageID); | |
427 | ||
428 | // localize package information | |
429 | $sql = "INSERT INTO wcf".WCF_N."_language_item | |
430 | (languageID, languageItem, languageItemValue, languageCategoryID, packageID) | |
431 | VALUES (?, ?, ?, ?, ?)"; | |
432 | $statement = WCF::getDB()->prepareStatement($sql); | |
433 | ||
434 | // get language list | |
435 | $languageList = new LanguageList(); | |
5475a95d MS |
436 | $languageList->readObjects(); |
437 | ||
438 | // workaround for WCFSetup | |
439 | if (!PACKAGE_ID) { | |
440 | $sql = "SELECT * | |
441 | FROM wcf".WCF_N."_language_category | |
442 | WHERE languageCategory = ?"; | |
443 | $statement2 = WCF::getDB()->prepareStatement($sql); | |
444 | $statement2->execute(array('wcf.acp.package')); | |
445 | $languageCategory = $statement2->fetchObject('wcf\data\language\category\LanguageCategory'); | |
446 | } | |
447 | else { | |
448 | $languageCategory = LanguageFactory::getInstance()->getCategory('wcf.acp.package'); | |
449 | } | |
450 | ||
451 | // save package name | |
452 | $this->saveLocalizedPackageInfo($statement, $languageList, $languageCategory, $package, 'packageName'); | |
453 | ||
454 | // save package description | |
455 | $this->saveLocalizedPackageInfo($statement, $languageList, $languageCategory, $package, 'packageDescription'); | |
456 | ||
457 | // update description and name | |
458 | $packageEditor = new PackageEditor($package); | |
459 | $packageEditor->update(array( | |
460 | 'packageDescription' => 'wcf.acp.package.packageDescription.package'.$this->queue->packageID, | |
461 | 'packageName' => 'wcf.acp.package.packageName.package'.$this->queue->packageID | |
462 | )); | |
463 | } | |
464 | ||
a2ad7897 MS |
465 | /** |
466 | * Saves a localized package info. | |
467 | * | |
0ad90fc3 MW |
468 | * @param \wcf\system\database\statement\PreparedStatement $statement |
469 | * @param \wcf\data\language\LanguageList $languageList | |
470 | * @param \wcf\data\language\category\LanguageCategory $languageCategory | |
471 | * @param \wcf\data\package\Package $package | |
a2ad7897 MS |
472 | * @param string $infoName |
473 | */ | |
5475a95d | 474 | protected function saveLocalizedPackageInfo(PreparedStatement $statement, $languageList, LanguageCategory $languageCategory, Package $package, $infoName) { |
a2ad7897 MS |
475 | $infoValues = $this->getArchive()->getPackageInfo($infoName); |
476 | ||
477 | // get default value for languages without specified information | |
478 | $defaultValue = ''; | |
479 | if (isset($infoValues['default'])) { | |
480 | $defaultValue = $infoValues['default']; | |
481 | } | |
482 | else if (isset($infoValues['en'])) { | |
483 | // fallback to English | |
484 | $defaultValue = $infoValues['en']; | |
485 | } | |
486 | else if (isset($infoValues[WCF::getLanguage()->getFixedLanguageCode()])) { | |
487 | // fallback to the language of the current user | |
488 | $defaultValue = $infoValues[WCF::getLanguage()->getFixedLanguageCode()]; | |
489 | } | |
490 | else if ($infoName == 'packageName') { | |
491 | // fallback to the package identifier for the package name | |
164c2641 | 492 | $defaultValue = $this->getArchive()->getPackageInfo('name'); |
a2ad7897 MS |
493 | } |
494 | ||
5475a95d | 495 | foreach ($languageList as $language) { |
a2ad7897 MS |
496 | $value = $defaultValue; |
497 | if (isset($infoValues[$language->languageCode])) { | |
498 | $value = $infoValues[$language->languageCode]; | |
499 | } | |
a17de04e | 500 | |
a2ad7897 MS |
501 | $statement->execute(array( |
502 | $language->languageID, | |
503 | 'wcf.acp.package.'.$infoName.'.package'.$package->packageID, | |
504 | $value, | |
505 | $languageCategory->languageCategoryID, | |
506 | 1 | |
507 | )); | |
508 | } | |
509 | } | |
510 | ||
11ade432 AE |
511 | /** |
512 | * Executes a package installation plugin. | |
9f959ced | 513 | * |
ffdf6f14 | 514 | * @param array step |
11ade432 AE |
515 | * @return boolean |
516 | */ | |
517 | protected function executePIP(array $nodeData) { | |
ffdf6f14 | 518 | $step = new PackageInstallationStep(); |
11ade432 AE |
519 | |
520 | // fetch all pips associated with current PACKAGE_ID and include pips | |
521 | // previously installed by current installation queue | |
fd2f91ae | 522 | $sql = "SELECT pluginName, className |
b6ae7d04 MW |
523 | FROM wcf".WCF_N."_package_installation_plugin |
524 | WHERE pluginName = ?"; | |
11ade432 AE |
525 | $statement = WCF::getDB()->prepareStatement($sql); |
526 | $statement->execute(array( | |
b6ae7d04 | 527 | $nodeData['pip'] |
11ade432 AE |
528 | )); |
529 | $row = $statement->fetchArray(); | |
530 | ||
b6ae7d04 | 531 | // PIP is unknown |
fd2f91ae | 532 | if (!$row || (strcmp($nodeData['pip'], $row['pluginName']) !== 0)) { |
b6ae7d04 | 533 | throw new SystemException("unable to find package installation plugin '".$nodeData['pip']."'"); |
11ade432 AE |
534 | } |
535 | ||
536 | // valdidate class definition | |
537 | $className = $row['className']; | |
538 | if (!class_exists($className)) { | |
4fe0b42b | 539 | throw new SystemException("unable to find class '".$className."'"); |
11ade432 AE |
540 | } |
541 | ||
542 | $plugin = new $className($this, $nodeData); | |
543 | ||
4d4a5125 | 544 | if (!($plugin instanceof IPackageInstallationPlugin)) { |
2dfb09ee | 545 | throw new SystemException("'".$className."' does not implement 'wcf\system\package\plugin\IPackageInstallationPlugin'"); |
11ade432 AE |
546 | } |
547 | ||
4d4a5125 | 548 | if ($plugin instanceof SQLPackageInstallationPlugin || $plugin instanceof ObjectTypePackageInstallationPlugin) { |
82a7de7f K |
549 | $this->requireRestructureVersionTables = true; |
550 | } | |
551 | ||
11ade432 | 552 | // execute PIP |
381495c4 | 553 | $document = null; |
11ade432 | 554 | try { |
ffdf6f14 | 555 | $document = $plugin->{$this->action}(); |
11ade432 AE |
556 | } |
557 | catch (SplitNodeException $e) { | |
ffdf6f14 | 558 | $step->setSplitNode(); |
11ade432 AE |
559 | } |
560 | ||
ffdf6f14 AE |
561 | if ($document !== null && ($document instanceof FormDocument)) { |
562 | $step->setDocument($document); | |
563 | $step->setSplitNode(); | |
564 | } | |
565 | ||
566 | return $step; | |
11ade432 AE |
567 | } |
568 | ||
cd0fbe9a AE |
569 | /** |
570 | * Displays a list to select optional packages or installs selection. | |
571 | * | |
572 | * @param string $currentNode | |
573 | * @param array $nodeData | |
0ad90fc3 | 574 | * @return \wcf\system\package\PackageInstallationStep |
cd0fbe9a | 575 | */ |
456008db | 576 | protected function selectOptionalPackages($currentNode, array $nodeData) { |
492816d3 AE |
577 | $installationStep = new PackageInstallationStep(); |
578 | ||
579 | $document = $this->promptOptionalPackages($nodeData); | |
6b63ee9a | 580 | if ($document !== null && $document instanceof FormDocument) { |
492816d3 | 581 | $installationStep->setDocument($document); |
456008db AE |
582 | $installationStep->setSplitNode(); |
583 | } | |
584 | // insert new nodes for each package | |
585 | else if (is_array($document)) { | |
586 | // get target child node | |
587 | $node = $currentNode; | |
588 | $queue = $this->queue; | |
589 | $shiftNodes = false; | |
590 | ||
591 | foreach ($nodeData as $package) { | |
592 | if (in_array($package['package'], $document)) { | |
a0b7c762 AE |
593 | // ignore uninstallable packages |
594 | if (!$package['isInstallable']) { | |
595 | continue; | |
596 | } | |
597 | ||
456008db AE |
598 | if (!$shiftNodes) { |
599 | $this->nodeBuilder->shiftNodes($currentNode, 'tempNode'); | |
600 | $shiftNodes = true; | |
601 | } | |
602 | ||
603 | $queue = PackageInstallationQueueEditor::create(array( | |
604 | 'parentQueueID' => $queue->queueID, | |
605 | 'processNo' => $this->queue->processNo, | |
606 | 'userID' => WCF::getUser()->userID, | |
607 | 'package' => $package['package'], | |
608 | 'packageName' => $package['packageName'], | |
609 | 'archive' => $package['archive'], | |
610 | 'action' => $queue->action | |
611 | )); | |
612 | ||
613 | $installation = new PackageInstallationDispatcher($queue); | |
614 | $installation->nodeBuilder->setParentNode($node); | |
615 | $installation->nodeBuilder->buildNodes(); | |
616 | $node = $installation->nodeBuilder->getCurrentNode(); | |
617 | } | |
fe1e50bb AE |
618 | else { |
619 | // remove archive | |
620 | @unlink($package['archive']); | |
621 | } | |
456008db AE |
622 | } |
623 | ||
624 | // shift nodes | |
625 | if ($shiftNodes) { | |
626 | $this->nodeBuilder->shiftNodes('tempNode', $node); | |
627 | } | |
492816d3 | 628 | } |
492816d3 AE |
629 | |
630 | return $installationStep; | |
631 | } | |
632 | ||
11ade432 | 633 | /** |
cd0fbe9a | 634 | * Extracts files from .tar(.gz) archive and installs them |
a17de04e | 635 | * |
9f959ced MS |
636 | * @param string $targetDir |
637 | * @param string $sourceArchive | |
11ade432 | 638 | * @param FileHandler $fileHandler |
0ad90fc3 | 639 | * @return \wcf\system\setup\Installer |
11ade432 AE |
640 | */ |
641 | public function extractFiles($targetDir, $sourceArchive, $fileHandler = null) { | |
8f3fc897 | 642 | return new Installer($targetDir, $sourceArchive, $fileHandler); |
11ade432 AE |
643 | } |
644 | ||
645 | /** | |
646 | * Returns current package. | |
9f959ced | 647 | * |
0ad90fc3 | 648 | * @return \wcf\data\package\Package |
11ade432 AE |
649 | */ |
650 | public function getPackage() { | |
651 | if ($this->package === null) { | |
652 | $this->package = new Package($this->queue->packageID); | |
653 | } | |
654 | ||
655 | return $this->package; | |
656 | } | |
657 | ||
658 | /** | |
aac1247e | 659 | * Prompts for a text input for package directory (applies for applications only) |
9f959ced | 660 | * |
0ad90fc3 | 661 | * @return \wcf\system\form\FormDocument |
11ade432 AE |
662 | */ |
663 | protected function promptPackageDir() { | |
664 | if (!PackageInstallationFormManager::findForm($this->queue, 'packageDir')) { | |
eb89d921 | 665 | |
a99fcf96 MS |
666 | $container = new GroupFormElementContainer(); |
667 | $packageDir = new TextInputFormElement($container); | |
11ade432 AE |
668 | $packageDir->setName('packageDir'); |
669 | $packageDir->setLabel(WCF::getLanguage()->get('wcf.acp.package.packageDir.input')); | |
670 | ||
f1c5deb4 | 671 | $defaultPath = FileUtil::addTrailingSlash(FileUtil::unifyDirSeparator(dirname(WCF_DIR))); |
c00510a9 AE |
672 | // check if there is already an application |
673 | $sql = "SELECT COUNT(*) AS count | |
674 | FROM wcf".WCF_N."_package | |
675 | WHERE packageDir = ?"; | |
676 | $statement = WCF::getDB()->prepareStatement($sql); | |
677 | $statement->execute(array('../')); | |
678 | $row = $statement->fetchArray(); | |
679 | if ($row['count']) { | |
680 | // use abbreviation | |
681 | $defaultPath .= strtolower(Package::getAbbreviation($this->getPackage()->package)) . '/'; | |
682 | } | |
683 | ||
11ade432 AE |
684 | $packageDir->setValue($defaultPath); |
685 | $container->appendChild($packageDir); | |
686 | ||
6b63ee9a | 687 | $document = new FormDocument('packageDir'); |
11ade432 AE |
688 | $document->appendContainer($container); |
689 | ||
690 | PackageInstallationFormManager::registerForm($this->queue, $document); | |
691 | return $document; | |
692 | } | |
693 | else { | |
694 | $document = PackageInstallationFormManager::getForm($this->queue, 'packageDir'); | |
695 | $document->handleRequest(); | |
72eca8cd TD |
696 | $packageDir = FileUtil::addTrailingSlash(FileUtil::getRealPath(FileUtil::unifyDirSeparator($document->getValue('packageDir')))); |
697 | if ($packageDir === '/') $packageDir = ''; | |
11ade432 AE |
698 | |
699 | if ($packageDir !== null) { | |
9b623913 | 700 | // validate package dir |
97021967 | 701 | if (file_exists($packageDir . 'global.php')) { |
9b623913 | 702 | $document->setError('packageDir', WCF::getLanguage()->get('wcf.acp.package.packageDir.notAvailable')); |
9bdcaaaf | 703 | return $document; |
9b623913 AE |
704 | } |
705 | ||
706 | // set package dir | |
11ade432 AE |
707 | $packageEditor = new PackageEditor($this->getPackage()); |
708 | $packageEditor->update(array( | |
709 | 'packageDir' => FileUtil::getRelativePath(WCF_DIR, $packageDir) | |
710 | )); | |
711 | ||
97021967 AE |
712 | // determine domain path, in some environments (e.g. ISPConfig) the $_SERVER paths are |
713 | // faked and differ from the real filesystem path | |
1428f1ca AE |
714 | if (PACKAGE_ID) { |
715 | $wcfDomainPath = ApplicationHandler::getInstance()->getWCF()->domainPath; | |
716 | } | |
717 | else { | |
718 | $sql = "SELECT domainPath | |
719 | FROM wcf".WCF_N."_application | |
720 | WHERE packageID = ?"; | |
721 | $statement = WCF::getDB()->prepareStatement($sql); | |
722 | $statement->execute(array(1)); | |
723 | $row = $statement->fetchArray(); | |
724 | ||
725 | $wcfDomainPath = $row['domainPath']; | |
726 | } | |
727 | ||
f2024036 | 728 | $documentRoot = str_replace($wcfDomainPath, '', FileUtil::unifyDirSeparator(WCF_DIR)); |
76fb30f2 | 729 | $domainPath = str_replace($documentRoot, '', $packageDir); |
c244fcc6 AE |
730 | |
731 | // update application path | |
732 | $application = new Application($this->getPackage()->packageID); | |
733 | $applicationEditor = new ApplicationEditor($application); | |
734 | $applicationEditor->update(array( | |
a66a4f9a AE |
735 | 'domainPath' => $domainPath, |
736 | 'cookiePath' => $domainPath | |
c244fcc6 AE |
737 | )); |
738 | ||
11ade432 AE |
739 | // create directory and set permissions |
740 | @mkdir($packageDir, 0777, true); | |
1232bce2 | 741 | FileUtil::makeWritable($packageDir); |
11ade432 AE |
742 | } |
743 | ||
744 | return null; | |
745 | } | |
746 | } | |
747 | ||
cd0fbe9a AE |
748 | /** |
749 | * Prompts a selection of optional packages. | |
750 | * | |
751 | * @return mixed | |
752 | */ | |
492816d3 AE |
753 | protected function promptOptionalPackages(array $packages) { |
754 | if (!PackageInstallationFormManager::findForm($this->queue, 'optionalPackages')) { | |
a99fcf96 | 755 | $container = new MultipleSelectionFormElementContainer(); |
eb89d921 | 756 | $container->setName('optionalPackages'); |
6fb53650 MW |
757 | $container->setLabel(WCF::getLanguage()->get('wcf.acp.package.optionalPackages')); |
758 | $container->setDescription(WCF::getLanguage()->get('wcf.acp.package.optionalPackages.description')); | |
492816d3 AE |
759 | |
760 | foreach ($packages as $package) { | |
a99fcf96 | 761 | $optionalPackage = new MultipleSelectionFormElement($container); |
e1e35281 | 762 | $optionalPackage->setName('optionalPackages'); |
492816d3 AE |
763 | $optionalPackage->setLabel($package['packageName']); |
764 | $optionalPackage->setValue($package['package']); | |
970ae117 | 765 | $optionalPackage->setDescription($package['packageDescription']); |
c13895fa | 766 | if (!$package['isInstallable']) { |
a0b7c762 AE |
767 | $optionalPackage->setDisabledMessage(WCF::getLanguage()->get('wcf.acp.package.install.optionalPackage.missingRequirements')); |
768 | } | |
492816d3 AE |
769 | |
770 | $container->appendChild($optionalPackage); | |
771 | } | |
772 | ||
6b63ee9a | 773 | $document = new FormDocument('optionalPackages'); |
492816d3 AE |
774 | $document->appendContainer($container); |
775 | ||
776 | PackageInstallationFormManager::registerForm($this->queue, $document); | |
777 | return $document; | |
778 | } | |
779 | else { | |
780 | $document = PackageInstallationFormManager::getForm($this->queue, 'optionalPackages'); | |
781 | $document->handleRequest(); | |
782 | ||
456008db | 783 | return $document->getValue('optionalPackages'); |
492816d3 AE |
784 | } |
785 | } | |
786 | ||
11ade432 AE |
787 | /** |
788 | * Returns current package id. | |
9f959ced | 789 | * |
11ade432 AE |
790 | * @return integer |
791 | */ | |
792 | public function getPackageID() { | |
793 | return $this->queue->packageID; | |
794 | } | |
795 | ||
796 | /** | |
797 | * Returns current package installation type. | |
9f959ced | 798 | * |
11ade432 AE |
799 | * @return string |
800 | */ | |
801 | public function getAction() { | |
802 | return $this->action; | |
803 | } | |
804 | ||
805 | /** | |
806 | * Opens the package installation queue and | |
807 | * starts the installation, update or uninstallation of the first entry. | |
9f959ced | 808 | * |
11ade432 | 809 | * @param integer $parentQueueID |
39bea7dd | 810 | * @param integer $processNo |
11ade432 AE |
811 | */ |
812 | public static function openQueue($parentQueueID = 0, $processNo = 0) { | |
813 | $conditions = new PreparedStatementConditionBuilder(); | |
814 | $conditions->add("userID = ?", array(WCF::getUser()->userID)); | |
815 | $conditions->add("parentQueueID = ?", array($parentQueueID)); | |
816 | if ($processNo != 0) $conditions->add("processNo = ?", array($processNo)); | |
817 | $conditions->add("done = ?", array(0)); | |
818 | ||
819 | $sql = "SELECT * | |
820 | FROM wcf".WCF_N."_package_installation_queue | |
821 | ".$conditions." | |
822 | ORDER BY queueID ASC"; | |
823 | $statement = WCF::getDB()->prepareStatement($sql); | |
824 | $statement->execute($conditions->getParameters()); | |
825 | $packageInstallation = $statement->fetchArray(); | |
826 | ||
827 | if (!isset($packageInstallation['queueID'])) { | |
6dcaf901 | 828 | $url = LinkHandler::getInstance()->getLink('PackageList'); |
3e0e6b2c | 829 | HeaderUtil::redirect($url); |
11ade432 AE |
830 | exit; |
831 | } | |
832 | else { | |
ec45d5e8 | 833 | $url = LinkHandler::getInstance()->getLink('PackageInstallationConfirm', array(), 'queueID='.$packageInstallation['queueID']); |
3e0e6b2c | 834 | HeaderUtil::redirect($url); |
11ade432 AE |
835 | exit; |
836 | } | |
837 | } | |
838 | ||
11ade432 AE |
839 | /** |
840 | * Checks the package installation queue for outstanding entries. | |
9f959ced | 841 | * |
11ade432 AE |
842 | * @return integer |
843 | */ | |
844 | public static function checkPackageInstallationQueue() { | |
845 | $sql = "SELECT queueID | |
846 | FROM wcf".WCF_N."_package_installation_queue | |
39bea7dd | 847 | WHERE userID = ? |
11ade432 AE |
848 | AND parentQueueID = 0 |
849 | AND done = 0 | |
850 | ORDER BY queueID ASC"; | |
851 | $statement = WCF::getDB()->prepareStatement($sql); | |
852 | $statement->execute(array(WCF::getUser()->userID)); | |
853 | $row = $statement->fetchArray(); | |
854 | ||
855 | if (!$row) { | |
856 | return 0; | |
857 | } | |
858 | ||
859 | return $row['queueID']; | |
860 | } | |
861 | ||
11ade432 AE |
862 | /** |
863 | * Executes post-setup actions. | |
864 | */ | |
865 | public function completeSetup() { | |
14bb2ca0 AE |
866 | // remove archives |
867 | $sql = "SELECT archive | |
868 | FROM wcf".WCF_N."_package_installation_queue | |
869 | WHERE processNo = ?"; | |
870 | $statement = WCF::getDB()->prepareStatement($sql); | |
871 | $statement->execute(array($this->queue->processNo)); | |
872 | while ($row = $statement->fetchArray()) { | |
873 | @unlink($row['archive']); | |
874 | } | |
11ade432 | 875 | |
14bb2ca0 AE |
876 | // delete queues |
877 | $sql = "DELETE FROM wcf".WCF_N."_package_installation_queue | |
878 | WHERE processNo = ?"; | |
879 | $statement = WCF::getDB()->prepareStatement($sql); | |
b953f892 | 880 | $statement->execute(array($this->queue->processNo)); |
ca38121b | 881 | |
cfedc216 AE |
882 | // clear language files once whole installation is completed |
883 | LanguageEditor::deleteLanguageFiles(); | |
11ade432 | 884 | |
cfedc216 | 885 | // reset all caches |
b401cd0d | 886 | CacheHandler::getInstance()->flushAll(); |
11ade432 AE |
887 | } |
888 | ||
889 | /** | |
890 | * Updates queue information. | |
891 | */ | |
892 | public function updatePackage() { | |
893 | if (empty($this->queue->packageName)) { | |
894 | $queueEditor = new PackageInstallationQueueEditor($this->queue); | |
895 | $queueEditor->update(array( | |
a2ad7897 | 896 | 'packageName' => $this->getArchive()->getLocalizedPackageInfo('packageName') |
11ade432 AE |
897 | )); |
898 | ||
899 | // reload queue | |
900 | $this->queue = new PackageInstallationQueue($this->queue->queueID); | |
901 | } | |
902 | } | |
903 | ||
904 | /** | |
905 | * Validates specific php requirements. | |
906 | * | |
907 | * @param array $requirements | |
908 | * @return array<array> | |
909 | */ | |
910 | public static function validatePHPRequirements(array $requirements) { | |
911 | $errors = array(); | |
912 | ||
913 | // validate php version | |
914 | if (isset($requirements['version'])) { | |
915 | $passed = false; | |
916 | if (version_compare(PHP_VERSION, $requirements['version'], '>=')) { | |
917 | $passed = true; | |
918 | } | |
919 | ||
920 | if (!$passed) { | |
921 | $errors['version'] = array( | |
922 | 'required' => $requirements['version'], | |
923 | 'installed' => PHP_VERSION | |
924 | ); | |
925 | } | |
926 | } | |
927 | ||
928 | // validate extensions | |
929 | if (isset($requirements['extensions'])) { | |
930 | foreach ($requirements['extensions'] as $extension) { | |
931 | $passed = (extension_loaded($extension)) ? true : false; | |
932 | ||
933 | if (!$passed) { | |
934 | $errors['extension'][] = array( | |
935 | 'extension' => $extension | |
936 | ); | |
937 | } | |
938 | } | |
939 | } | |
940 | ||
941 | // validate settings | |
942 | if (isset($requirements['settings'])) { | |
943 | foreach ($requirements['settings'] as $setting => $value) { | |
944 | $iniValue = ini_get($setting); | |
945 | ||
946 | $passed = self::compareSetting($setting, $value, $iniValue); | |
947 | if (!$passed) { | |
948 | $errors['setting'][] = array( | |
949 | 'setting' => $setting, | |
950 | 'required' => $value, | |
951 | 'installed' => ($iniValue === false) ? '(unknown)' : $iniValue | |
952 | ); | |
953 | } | |
954 | } | |
955 | } | |
956 | ||
957 | // validate functions | |
958 | if (isset($requirements['functions'])) { | |
959 | foreach ($requirements['functions'] as $function) { | |
838e315b | 960 | $function = mb_strtolower($function); |
11ade432 AE |
961 | |
962 | $passed = self::functionExists($function); | |
963 | if (!$passed) { | |
964 | $errors['function'][] = array( | |
965 | 'function' => $function | |
966 | ); | |
967 | } | |
968 | } | |
969 | } | |
970 | ||
971 | // validate classes | |
972 | if (isset($requirements['classes'])) { | |
973 | foreach ($requirements['classes'] as $class) { | |
974 | $passed = false; | |
975 | ||
976 | // see: http://de.php.net/manual/en/language.oop5.basic.php | |
977 | if (preg_match('~[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*.~', $class)) { | |
978 | $globalClass = '\\'.$class; | |
979 | ||
980 | if (class_exists($globalClass, false)) { | |
981 | $passed = true; | |
982 | } | |
983 | } | |
984 | ||
985 | if (!$passed) { | |
986 | $errors['class'][] = array( | |
987 | 'class' => $class | |
988 | ); | |
989 | } | |
990 | } | |
991 | ||
992 | } | |
993 | ||
994 | return $errors; | |
995 | } | |
996 | ||
997 | /** | |
998 | * Validates if an function exists and is not blacklisted by suhosin extension. | |
999 | * | |
1000 | * @param string $function | |
1001 | * @return boolean | |
1002 | * @see http://de.php.net/manual/en/function.function-exists.php#77980 | |
a17de04e | 1003 | */ |
11ade432 AE |
1004 | protected static function functionExists($function) { |
1005 | if (extension_loaded('suhosin')) { | |
1006 | $blacklist = @ini_get('suhosin.executor.func.blacklist'); | |
1007 | if (!empty($blacklist)) { | |
1008 | $blacklist = explode(',', $blacklist); | |
1009 | foreach ($blacklist as $disabledFunction) { | |
838e315b | 1010 | $disabledFunction = mb_strtolower(StringUtil::trim($disabledFunction)); |
11ade432 AE |
1011 | |
1012 | if ($function == $disabledFunction) { | |
1013 | return false; | |
1014 | } | |
1015 | } | |
1016 | } | |
1017 | } | |
1018 | ||
1019 | return function_exists($function); | |
1020 | } | |
1021 | ||
1022 | /** | |
1023 | * Compares settings, converting values into compareable ones. | |
1024 | * | |
1025 | * @param string $setting | |
1026 | * @param string $value | |
1027 | * @param mixed $compareValue | |
1028 | * @return boolean | |
1029 | */ | |
1030 | protected static function compareSetting($setting, $value, $compareValue) { | |
1031 | if ($compareValue === false) return false; | |
1032 | ||
838e315b | 1033 | $value = mb_strtolower($value); |
11ade432 AE |
1034 | $trueValues = array('1', 'on', 'true'); |
1035 | $falseValues = array('0', 'off', 'false'); | |
1036 | ||
1037 | // handle values considered as 'true' | |
1038 | if (in_array($value, $trueValues)) { | |
1039 | return ($compareValue) ? true : false; | |
1040 | } | |
1041 | // handle values considered as 'false' | |
1042 | else if (in_array($value, $falseValues)) { | |
1043 | return (!$compareValue) ? true : false; | |
1044 | } | |
1045 | else if (!is_numeric($value)) { | |
1046 | $compareValue = self::convertShorthandByteValue($compareValue); | |
1047 | $value = self::convertShorthandByteValue($value); | |
1048 | } | |
1049 | ||
1050 | return ($compareValue >= $value) ? true : false; | |
1051 | } | |
1052 | ||
1053 | /** | |
1054 | * Converts shorthand byte values into an integer representing bytes. | |
1055 | * | |
1056 | * @param string $value | |
1057 | * @return integer | |
1058 | * @see http://www.php.net/manual/en/faq.using.php#faq.using.shorthandbytes | |
1059 | */ | |
1060 | protected static function convertShorthandByteValue($value) { | |
1061 | // convert into bytes | |
838e315b | 1062 | $lastCharacter = mb_substr($value, -1); |
11ade432 AE |
1063 | switch ($lastCharacter) { |
1064 | // gigabytes | |
1065 | case 'g': | |
1066 | return (int)$value * 1073741824; | |
1067 | break; | |
1068 | ||
1069 | // megabytes | |
1070 | case 'm': | |
1071 | return (int)$value * 1048576; | |
1072 | break; | |
1073 | ||
1074 | // kilobytes | |
1075 | case 'k': | |
1076 | return (int)$value * 1024; | |
1077 | break; | |
1078 | ||
1079 | default: | |
1080 | return $value; | |
1081 | break; | |
1082 | } | |
1083 | } | |
a99fcf96 | 1084 | |
82a7de7f K |
1085 | /* |
1086 | * Restructure version tables. | |
1087 | */ | |
1088 | protected function restructureVersionTables() { | |
14d48464 | 1089 | $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.versionableObject'); |
82a7de7f K |
1090 | |
1091 | if (empty($objectTypes)) { | |
1092 | return; | |
1093 | } | |
1094 | ||
4d4a5125 | 1095 | // base structure of version tables |
82a7de7f | 1096 | $versionTableBaseColumns = array(); |
14d48464 | 1097 | $versionTableBaseColumns[] = array('name' => 'versionID', 'data' => array('type' => 'INT', 'length' => 10, 'key' => 'PRIMARY', 'autoIncrement' => 'AUTO_INCREMENT')); |
1098 | $versionTableBaseColumns[] = array('name' => 'versionUserID', 'data' => array('type' => 'INT', 'length' => 10)); | |
82a7de7f | 1099 | $versionTableBaseColumns[] = array('name' => 'versionUsername', 'data' => array('type' => 'VARCHAR', 'length' => 255)); |
14d48464 | 1100 | $versionTableBaseColumns[] = array('name' => 'versionTime', 'data' => array('type' => 'INT', 'length' => 10)); |
a99fcf96 | 1101 | |
e3fc42e8 | 1102 | foreach ($objectTypes as $objectType) { |
4457f91f | 1103 | if (!class_exists($objectType->className)) { |
1104 | // versionable database object isn't available anymore | |
1105 | // the object type gets deleted later on during the uninstallation | |
1106 | continue; | |
1107 | } | |
14d48464 | 1108 | $baseTableColumns = WCF::getDB()->getEditor()->getColumns(call_user_func(array($objectType->className, 'getDatabaseTableName'))); |
2d63c13c | 1109 | |
14d48464 | 1110 | // remove primary key from base table columns |
df48b4e6 | 1111 | foreach ($baseTableColumns as $key => $column) { |
1112 | if ($column['data']['key'] == 'PRIMARY') { | |
14d48464 | 1113 | $baseTableColumns[$key]['data']['key'] = ''; |
1114 | } | |
1115 | $baseTableColumns[$key]['data']['autoIncrement'] = false; | |
1116 | } | |
1117 | ||
4d4a5125 | 1118 | // get structure of version table |
14d48464 | 1119 | $versionTableColumns = array(); |
1120 | try { | |
1121 | $versionTableColumns = WCF::getDB()->getEditor()->getColumns(call_user_func(array($objectType->className, 'getDatabaseVersionTableName'))); | |
1122 | } | |
1123 | catch (\Exception $e) { } | |
82a7de7f | 1124 | |
5fe135db | 1125 | if (empty($versionTableColumns)) { |
82a7de7f | 1126 | $columns = array_merge($versionTableBaseColumns, $baseTableColumns); |
14d48464 | 1127 | WCF::getDB()->getEditor()->createTable(call_user_func(array($objectType->className, 'getDatabaseVersionTableName')), $columns); |
2d63c13c | 1128 | |
4457f91f | 1129 | // add version table to plugin |
1130 | $sql = "INSERT INTO wcf".WCF_N."_package_installation_sql_log | |
1131 | (packageID, sqlTable) | |
1132 | VALUES (?, ?)"; | |
1133 | $statement = WCF::getDB()->prepareStatement($sql); | |
1134 | $statement->execute(array( | |
1135 | $this->queue->packageID, | |
1136 | call_user_func(array($objectType->className, 'getDatabaseVersionTableName')) | |
1137 | )); | |
82a7de7f K |
1138 | } |
1139 | else { | |
ed787005 | 1140 | $baseTableColumnNames = $versionTableColumnNames = $versionTableBaseColumnNames = array(); |
1141 | foreach ($baseTableColumns as $column) { | |
1142 | $baseTableColumnNames[] = $column['name']; | |
1143 | } | |
1144 | foreach ($versionTableColumns as $column) { | |
1145 | $versionTableColumnNames[] = $column['name']; | |
1146 | } | |
1147 | foreach ($versionTableBaseColumns as $column) { | |
1148 | $versionTableBaseColumnNames[] = $column['name']; | |
1149 | } | |
2d63c13c | 1150 | |
4d4a5125 | 1151 | // check garbage columns in versioned table |
82a7de7f | 1152 | foreach ($versionTableColumns as $columnData) { |
ed787005 | 1153 | if (!in_array($columnData['name'], $baseTableColumnNames) && !in_array($columnData['name'], $versionTableBaseColumnNames)) { |
4d4a5125 | 1154 | // delete column |
14d48464 | 1155 | WCF::getDB()->getEditor()->dropColumn(call_user_func(array($objectType->className, 'getDatabaseVersionTableName')), $columnData['name']); |
82a7de7f K |
1156 | } |
1157 | } | |
1158 | ||
4d4a5125 | 1159 | // check new columns for versioned table |
82a7de7f | 1160 | foreach ($baseTableColumns as $columnData) { |
ed787005 | 1161 | if (!in_array($columnData['name'], $versionTableColumnNames)) { |
4d4a5125 | 1162 | // add colum |
14d48464 | 1163 | WCF::getDB()->getEditor()->addColumn(call_user_func(array($objectType->className, 'getDatabaseVersionTableName')), $columnData['name'], $columnData['data']); |
82a7de7f K |
1164 | } |
1165 | } | |
1166 | } | |
1167 | } | |
a99fcf96 | 1168 | } |
11ade432 | 1169 | } |