Uninstallation of packages with dependencies now working
authorAlexander Ebert <ebert@woltlab.com>
Wed, 5 Oct 2011 18:41:12 +0000 (20:41 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Wed, 5 Oct 2011 18:41:12 +0000 (20:41 +0200)
wcfsetup/install/files/lib/acp/action/UninstallPackageAction.class.php
wcfsetup/install/files/lib/system/package/PackageUninstallationDispatcher.class.php
wcfsetup/install/files/lib/system/package/PackageUninstallationNodeBuilder.class.php

index ae054a77a901c17f8ed65b7ed19e8ca1c176e838..6f69cdd4ac7ac6a99151985266643a7ba3253fa1 100644 (file)
@@ -64,42 +64,38 @@ class UninstallPackageAction extends InstallPackageAction {
                        throw new IllegalLinkException();
                }
                
-               if (PackageUninstallationDispatcher::hasDependencies($package->packageID)) {
-                       throw new SystemException('hasDependencies');
-               }
-               else {
-                       // get new process no
-                       $processNo = PackageInstallationQueue::getNewProcessNo();
-                       
-                       // create queue
-                       $queue = PackageInstallationQueueEditor::create(array(
-                               'processNo' => $processNo,
-                               'userID' => WCF::getUser()->userID,
-                               'packageName' => $package->getName(),
-                               'packageID' => $package->packageID,
-                               'action' => 'uninstall',
-                               'cancelable' => 0
-                       ));
+               // get new process no
+               $processNo = PackageInstallationQueue::getNewProcessNo();
                        
-                       // initialize uninstallation
-                       $this->installation = new PackageUninstallationDispatcher($queue);
+               // create queue
+               $queue = PackageInstallationQueueEditor::create(array(
+                       'processNo' => $processNo,
+                       'userID' => WCF::getUser()->userID,
+                       'packageName' => $package->getName(),
+                       'packageID' => $package->packageID,
+                       'action' => 'uninstall',
+                       'cancelable' => 0
+               ));
                        
-                       $this->installation->nodeBuilder->purgeNodes();
-                       $this->installation->nodeBuilder->buildNodes();
+               // initialize uninstallation
+               $this->installation = new PackageUninstallationDispatcher($queue);
                        
-                       WCF::getTPL()->assign(array(
-                               'queue' => $queue
-                       ));
+               $this->installation->nodeBuilder->purgeNodes();
+               $this->installation->nodeBuilder->buildNodes();
                        
-                       $this->data = array(
-                               'template' => WCF::getTPL()->fetch($this->templateName),
-                               'step' => 'uninstall',
-                               'node' => $this->installation->nodeBuilder->getNextNode(),
-                               'currentAction' => WCF::getLanguage()->get('wcf.package.installation.step.uninstalling'),
-                               'progress' => 0,
-                               'queueID' => $queue->queueID
-                       );
-               }
+               WCF::getTPL()->assign(array(
+                       'queue' => $queue
+               ));
+               
+               $queueID = $this->installation->nodeBuilder->getQueueByNode($queue->processNo, $this->installation->nodeBuilder->getNextNode());
+               $this->data = array(
+                       'template' => WCF::getTPL()->fetch($this->templateName),
+                       'step' => 'uninstall',
+                       'node' => $this->installation->nodeBuilder->getNextNode(),
+                       'currentAction' => WCF::getLanguage()->get('wcf.package.installation.step.uninstalling'),
+                       'progress' => 0,
+                       'queueID' => $queueID
+               );
        }
        
        /**
@@ -115,7 +111,7 @@ class UninstallPackageAction extends InstallPackageAction {
                        // remove node data
                        $this->installation->nodeBuilder->purgeNodes();
                        
-                       // TODO: Show 'success' template at this point
+                       // show success
                        $this->data = array(
                                'progress' => 100,
                                'step' => 'success'
@@ -124,10 +120,12 @@ class UninstallPackageAction extends InstallPackageAction {
                }
                
                // continue with next node
+               $queueID = $this->installation->nodeBuilder->getQueueByNode($this->installation->queue->processNo, $this->node);
                $this->data = array(
                        'step' => 'uninstall',
                        'node' => $node,
-                       'progress' => $this->installation->nodeBuilder->calculateProgress($this->node)
+                       'progress' => $this->installation->nodeBuilder->calculateProgress($this->node),
+                       'queueID' => $queueID
                );
        }
        
index adfcb5e06155ca4f38f968bd694abd0b890b9195..7361f45d342bd36d9038bb5da56dfcfc76bdffbd 100644 (file)
@@ -4,6 +4,7 @@ use wcf\system\menu\acp\ACPMenu;
 use wcf\data\option\OptionEditor;
 use wcf\data\package\Package;
 use wcf\data\package\PackageEditor;
+use wcf\data\package\PackageList;
 use wcf\data\package\installation\queue\PackageInstallationQueue;
 use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
 use wcf\system\cache\CacheHandler;
@@ -174,6 +175,54 @@ class PackageUninstallationDispatcher extends PackageInstallationDispatcher {
                return $packages;
        }
        
+       /**
+        * Returns an ordered list of depenencies for given package id. The order is
+        * curcial, whereas the first package has to be uninstalled first.
+        * 
+        * @package     integer
+        * @return      wcf\data\package\PackageList
+        */
+       public static function getOrderedPackageDependencies($packageID) {
+               $sql = "SELECT          packageID, MAX(level) AS level
+                       FROM            wcf".WCF_N."_package_requirement_map
+                       WHERE           requirement = ?
+                       GROUP BY        packageID";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($packageID));
+               
+               $dependencies = array();
+               while ($row = $statement->fetchArray()) {
+                       $dependencies[$row['packageID']] = $row['level'];
+               }
+               
+               $packageIDs = array();
+               $maxLevel = max(array_values($dependencies));
+               if ($maxLevel == 0) {
+                       // order does not matter
+                       $packageIDs = array_keys($dependencies);
+               }
+               else {
+                       // order by level while ignoring individual connections as they don't
+                       // matter if uninstall begins with the lowest dependency in tree
+                       for ($i = $maxLevel; $i >= 0; $i--) {
+                               foreach ($dependencies as $packageID => $level) {
+                                       if ($level == $i) {
+                                               $packageIDs[] = $packageID;
+                                               unset($dependencies[$packageID]);
+                                       }
+                               }
+                       }
+               }
+               
+               // get packages
+               $packageList = new PackageList();
+               $packageList->sqlLimit = 0;
+               $packageList->getConditionBuilder()->add("packageID IN (?)", array($packageIDs));
+               $packageList->readObjects();
+               
+               return $packageList;
+       }
+       
        /**
         * Returns true if package has dependencies
         *
index 2068697562757515d82560f6ad3f111e90fb697b..ef912ee07bdd934dc29626be16f1cc1bcacf2592 100644 (file)
@@ -1,5 +1,7 @@
 <?php
 namespace wcf\system\package;
+use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
+use wcf\system\package\PackageUninstallationDispatcher;
 use wcf\system\WCF;
 
 /**
@@ -19,6 +21,13 @@ class PackageUninstallationNodeBuilder extends PackageInstallationNodeBuilder {
         * Builds node for current uninstallation queue.
         */
        public function buildNodes() {
+               if (!empty($this->parentNode)) {
+                       $this->node = $this->getToken();
+               }
+               
+               // build nodes for dependent packages
+               $this->buildDependentPackageNodes();
+               
                // build pip nodes
                $this->buildPluginNodes();
                
@@ -26,12 +35,45 @@ class PackageUninstallationNodeBuilder extends PackageInstallationNodeBuilder {
                $this->buildPackageNode();
        }
        
+       /**
+        * Builds nodes for all dependent packages.
+        */
+       protected function buildDependentPackageNodes() {
+               if (!PackageUninstallationDispatcher::hasDependencies($this->installation->queue->packageID)) {
+                       return;
+               }
+               
+               $packageList = PackageUninstallationDispatcher::getOrderedPackageDependencies($this->installation->queue->packageID);
+               $queue = $this->installation->queue;
+               
+               foreach ($packageList as $package) {
+                       $queue = PackageInstallationQueueEditor::create(array(
+                               'processNo' => $queue->processNo,
+                               'parentQueueID' => $queue->queueID,
+                               'userID' => WCF::getUser()->userID,
+                               'package' => $package->package,
+                               'packageName' => $package->getName(),
+                               'packageID' => $package->packageID,
+                               'action' => 'uninstall'
+                       ));
+                       
+                       // spawn nodes
+                       $uninstallation = new PackageUninstallationDispatcher($queue);
+                       $uninstallation->nodeBuilder->setParentNode($this->node);
+                       $uninstallation->nodeBuilder->buildNodes();
+                       $this->parentNode = $uninstallation->nodeBuilder->getCurrentNode();
+                       $this->node = $this->getToken();
+               }
+       }
+       
        /**
         * Creates a node-tree for package installation plugins, whereas the PIP- and files-plugin
         * will be executed last.
         */
        protected function buildPluginNodes() {
-               $this->node = $this->getToken();
+               if (empty($this->node)) {
+                       $this->node = $this->getToken();
+               }
                
                // fetch ordered pips
                $pips = array();
@@ -47,8 +89,8 @@ class PackageUninstallationNodeBuilder extends PackageInstallationNodeBuilder {
                
                // insert pips
                $sql = "INSERT INTO     wcf".WCF_N."_package_installation_node
-                                       (queueID, processNo, sequenceNo, node, nodeType, nodeData)
-                       VALUES          (?, ?, ?, ?, ?, ?)";
+                                       (queueID, processNo, sequenceNo, node, parentNode, nodeType, nodeData)
+                       VALUES          (?, ?, ?, ?, ?, ?, ?)";
                $statement = WCF::getDB()->prepareStatement($sql);
                $sequenceNo = 0;
                
@@ -58,6 +100,7 @@ class PackageUninstallationNodeBuilder extends PackageInstallationNodeBuilder {
                                $this->installation->queue->processNo,
                                $sequenceNo,
                                $this->node,
+                               $this->parentNode,
                                'pip',
                                serialize(array(
                                        'pluginName' => $pip['pluginName'],