Major overhaul of caching system (work in progress)
[GitHub/WoltLab/WCF.git] / wcfsetup / install / files / lib / system / package / PackageInstallationDispatcher.class.php
index b0c9e18a258802e0fc8b160a4fd297679918da14..aab03e29ba889f1e09bf8526a351dc3183c7c46a 100644 (file)
@@ -1,5 +1,9 @@
 <?php
 namespace wcf\system\package;
+use wcf\system\cache\builder\TemplateListenerCodeCacheBuilder;
+
+use wcf\system\cache\builder\TemplateListenerCacheBuilder;
+
 use wcf\data\application\Application;
 use wcf\data\application\ApplicationEditor;
 use wcf\data\language\category\LanguageCategory;
@@ -10,18 +14,25 @@ use wcf\data\package\installation\queue\PackageInstallationQueue;
 use wcf\data\package\installation\queue\PackageInstallationQueueEditor;
 use wcf\data\package\Package;
 use wcf\data\package\PackageEditor;
+use wcf\system\application\ApplicationHandler;
 use wcf\system\cache\CacheHandler;
 use wcf\system\database\statement\PreparedStatement;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\SystemException;
-use wcf\system\form\container;
-use wcf\system\form\element;
+use wcf\system\form\container\GroupFormElementContainer;
+use wcf\system\form\container\MultipleSelectionFormElementContainer;
+use wcf\system\form\element\MultipleSelectionFormElement;
+use wcf\system\form\element\TextInputFormElement;
 use wcf\system\form\FormDocument;
-use wcf\system\form;
 use wcf\system\language\LanguageFactory;
-use wcf\system\menu\acp\ACPMenu;
+use wcf\system\package\plugin\IPackageInstallationPlugin;
+use wcf\system\package\plugin\ObjectTypePackageInstallationPlugin;
+use wcf\system\package\plugin\SQLPackageInstallationPlugin;
 use wcf\system\request\LinkHandler;
 use wcf\system\request\RouteHandler;
+use wcf\system\setup\Installer;
+use wcf\system\style\StyleHandler;
+use wcf\system\version\VersionHandler;
 use wcf\system\WCF;
 use wcf\util\FileUtil;
 use wcf\util\HeaderUtil;
@@ -31,7 +42,7 @@ use wcf\util\StringUtil;
  * PackageInstallationDispatcher handles the whole installation process.
  * 
  * @author     Alexander Ebert
- * @copyright  2001-2012 WoltLab GmbH
+ * @copyright  2001-2013 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @package    com.woltlab.wcf
  * @subpackage system.package
@@ -74,6 +85,12 @@ class PackageInstallationDispatcher {
         */
        const CONFIG_FILE = 'config.inc.php';
        
+       /**
+        * holds state of structuring version tables
+        * @var boolean
+        */
+       protected $requireRestructureVersionTables = false;     
+       
        /**
         * Creates a new instance of PackageInstallationDispatcher.
         *
@@ -90,7 +107,7 @@ class PackageInstallationDispatcher {
         * Installs node components and returns next node.
         * 
         * @param       string          $node
-        * @return      PackageInstallationStep
+        * @return      wcf\system\package\PackageInstallationStep
         */
        public function install($node) {
                $nodes = $this->nodeBuilder->getNodeData($node);
@@ -130,19 +147,40 @@ class PackageInstallationDispatcher {
                $node = $this->nodeBuilder->getNextNode($node);
                $step->setNode($node);
                
-               // update options.inc.php and save localized package infos
+               // perform post-install/update actions
                if ($node == '') {
+                       // update options.inc.php
                        OptionEditor::resetCache();
                        
                        if ($this->action == 'install') {
+                               // save localized package infos
                                $this->saveLocalizedPackageInfos();
                                
                                // remove all cache files after WCFSetup
                                if (!PACKAGE_ID) {
-                                       CacheHandler::getInstance()->clear(WCF_DIR.'cache/', 'cache.*.php');
+                                       CacheHandler::getInstance()->flushAll();
                                }
+                               
+                               // rebuild application paths
+                               ApplicationHandler::rebuild();
+                               ApplicationEditor::setup();
                        }
-               }
+                       
+                       // remove template listener cache
+                       TemplateListenerCacheBuilder::getInstance()->reset();
+                       TemplateListenerCodeCacheBuilder::getInstance()->reset();
+                               
+                       // reset language cache
+                       LanguageFactory::getInstance()->clearCache();
+                       LanguageFactory::getInstance()->deleteLanguageCache();
+                       
+                       // reset stylesheets
+                       StyleHandler::resetStylesheets();
+               }       
+               
+               if ($this->requireRestructureVersionTables) {
+                       $this->restructureVersionTables();
+               }                       
                
                return $step;
        }
@@ -150,7 +188,7 @@ class PackageInstallationDispatcher {
        /**
         * Returns current package archive.
         * 
-        * @return      PackageArchive
+        * @return      wcf\system\package\PackageArchive
         */
        public function getArchive() {
                if ($this->archive === null) {
@@ -230,7 +268,7 @@ class PackageInstallationDispatcher {
                        if (count($this->getArchive()->getExcludedPackages()) > 0) {
                                $sql = "INSERT INTO     wcf".WCF_N."_package_exclusion 
                                                        (packageID, excludedPackage, excludedPackageVersion)
-                                       VALUES          (?, ?, ?)";
+                                       VALUES          (?, ?, ?)";
                                $statement = WCF::getDB()->prepareStatement($sql);
                                
                                foreach ($this->getArchive()->getExcludedPackages() as $excludedPackage) {
@@ -240,12 +278,6 @@ class PackageInstallationDispatcher {
                        
                        // if package is plugin to com.woltlab.wcf it must not have any other requirement
                        $requirements = $this->getArchive()->getRequirements();
-                       if ($package->parentPackageID == 1 && !empty($requirements)) {
-                               foreach ($requirements as $package => $data) {
-                                       if ($package == 'com.woltlab.wcf') continue;
-                                       throw new SystemException('Package '.$package->package.' is plugin of com.woltlab.wcf (WCF) but has more than one requirement.');
-                               }
-                       }
                        
                        // insert requirements and dependencies
                        $requirements = $this->getArchive()->getAllExistingRequirements();
@@ -267,15 +299,6 @@ class PackageInstallationDispatcher {
                                }
                        }
                        
-                       // build requirement map
-                       Package::rebuildPackageRequirementMap($package->packageID);
-                       
-                       // rebuild dependencies
-                       Package::rebuildPackageDependencies($package->packageID);
-                       if ($this->action == 'update') {
-                               Package::rebuildParentPackageDependencies($package->packageID);
-                       }
-                       
                        // reload queue
                        $this->queue = new PackageInstallationQueue($this->queue->queueID);
                        $this->package = null;
@@ -293,27 +316,18 @@ class PackageInstallationDispatcher {
                                        'packageID' => $package->packageID
                                ));
                        }
-                       
-                       // insert dependencies on parent package if applicable
-                       $this->installPackageParent();
                }
                
                if ($this->getPackage()->isApplication && $this->getPackage()->package != 'com.woltlab.wcf' && $this->getAction() == 'install') {
                        if (empty($this->getPackage()->packageDir)) {
                                $document = $this->promptPackageDir();
-                               if ($document !== null && $document instanceof form\FormDocument) {
+                               if ($document !== null && $document instanceof FormDocument) {
                                        $installationStep->setDocument($document);
                                }
                                
                                $installationStep->setSplitNode();
                        }
                }
-               else if ($this->getPackage()->parentPackageID) {
-                       $packageEditor = new PackageEditor($this->getPackage());
-                       $packageEditor->update(array(
-                               'packageDir' => $this->getPackage()->getParentPackage()->packageDir
-                       ));
-               }
                
                return $installationStep;
        }
@@ -334,7 +348,6 @@ class PackageInstallationDispatcher {
                
                // get language list
                $languageList = new LanguageList();
-               $languageList->sqlLimit = 0;
                $languageList->readObjects();
                
                // workaround for WCFSetup
@@ -399,7 +412,7 @@ class PackageInstallationDispatcher {
                        if (isset($infoValues[$language->languageCode])) {
                                $value = $infoValues[$language->languageCode];
                        }
-               
+                       
                        $statement->execute(array(
                                $language->languageID,
                                'wcf.acp.package.'.$infoName.'.package'.$package->packageID,
@@ -410,50 +423,6 @@ class PackageInstallationDispatcher {
                }
        }
        
-       /**
-        * Sets parent package and rebuilds dependencies for both.
-        */
-       protected function installPackageParent() {
-               // do not handle parent package if current package is an application or does not have a plugin tag while within installation process
-               if ($this->getArchive()->getPackageInfo('isApplication') || $this->getAction() != 'install' || !$this->getArchive()->getPackageInfo('plugin')) {
-                       return;
-               }
-               
-               // get parent package from requirements
-               $sql = "SELECT  requirement
-                       FROM    wcf".WCF_N."_package_requirement
-                       WHERE   packageID = ?
-                               AND requirement IN (
-                                       SELECT  packageID
-                                       FROM    wcf".WCF_N."_package
-                                       WHERE   package = ?
-                               )";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute(array(
-                       $this->getPackage()->packageID,
-                       $this->getArchive()->getPackageInfo('plugin')
-               ));
-               $row = $statement->fetchArray();
-               if (!$row || empty($row['requirement'])) {
-                       throw new SystemException("can not find any available installations of required parent package '".$this->getArchive()->getPackageInfo('plugin')."'");
-               }
-               
-               // save parent package
-               $packageEditor = new PackageEditor($this->getPackage());
-               $packageEditor->update(array(
-                       'parentPackageID' => $row['requirement']
-               ));
-               
-               // rebuild parent package dependencies                                                          
-               Package::rebuildParentPackageDependencies($this->getPackage()->packageID);
-               
-               // rebuild parent's parent package dependencies
-               Package::rebuildParentPackageDependencies($row['requirement']);
-               
-               // reload package object on next request
-               $this->package = null;
-       }
-       
        /**
         * Executes a package installation plugin.
         * 
@@ -487,10 +456,14 @@ class PackageInstallationDispatcher {
                
                $plugin = new $className($this, $nodeData);
                
-               if (!($plugin instanceof \wcf\system\package\plugin\IPackageInstallationPlugin)) {
+               if (!($plugin instanceof IPackageInstallationPlugin)) {
                        throw new SystemException("'".$className."' does not implement 'wcf\system\package\plugin\IPackageInstallationPlugin'");
                }
                
+               if ($plugin instanceof SQLPackageInstallationPlugin || $plugin instanceof ObjectTypePackageInstallationPlugin) {
+                       $this->requireRestructureVersionTables = true;
+               }
+               
                // execute PIP
                try {
                        $document = $plugin->{$this->action}();
@@ -507,11 +480,18 @@ class PackageInstallationDispatcher {
                return $step;
        }
        
+       /**
+        * Displays a list to select optional packages or installs selection.
+        * 
+        * @param       string          $currentNode
+        * @param       array           $nodeData
+        * @return      wcf\system\package\PackageInstallationStep
+        */
        protected function selectOptionalPackages($currentNode, array $nodeData) {
                $installationStep = new PackageInstallationStep();
                
                $document = $this->promptOptionalPackages($nodeData);
-               if ($document !== null && $document instanceof form\FormDocument) {
+               if ($document !== null && $document instanceof FormDocument) {
                        $installationStep->setDocument($document);
                        $installationStep->setSplitNode();
                }
@@ -556,15 +536,15 @@ class PackageInstallationDispatcher {
        }
        
        /**
-        * Extracts files from .tar (or .tar.gz) archive and installs them
-        *
+        * Extracts files from .tar(.gz) archive and installs them
+        * 
         * @param       string                  $targetDir
         * @param       string                  $sourceArchive
         * @param       FileHandler             $fileHandler
         * @return      wcf\system\setup\Installer
         */
        public function extractFiles($targetDir, $sourceArchive, $fileHandler = null) {
-               return new \wcf\system\setup\Installer($targetDir, $sourceArchive, $fileHandler);
+               return new Installer($targetDir, $sourceArchive, $fileHandler);
        }
        
        /**
@@ -588,8 +568,8 @@ class PackageInstallationDispatcher {
        protected function promptPackageDir() {
                if (!PackageInstallationFormManager::findForm($this->queue, 'packageDir')) {
                        
-                       $container = new container\GroupFormElementContainer();
-                       $packageDir = new element\TextInputFormElement($container);
+                       $container = new GroupFormElementContainer();
+                       $packageDir = new TextInputFormElement($container);
                        $packageDir->setName('packageDir');
                        $packageDir->setLabel(WCF::getLanguage()->get('wcf.acp.package.packageDir.input'));
                        
@@ -598,7 +578,7 @@ class PackageInstallationDispatcher {
                        $packageDir->setValue($defaultPath);
                        $container->appendChild($packageDir);
                        
-                       $document = new form\FormDocument('packageDir');
+                       $document = new FormDocument('packageDir');
                        $document->appendContainer($container);
                        
                        PackageInstallationFormManager::registerForm($this->queue, $document);
@@ -649,13 +629,18 @@ class PackageInstallationDispatcher {
                }
        }
        
+       /**
+        * Prompts a selection of optional packages.
+        * 
+        * @return      mixed
+        */
        protected function promptOptionalPackages(array $packages) {
                if (!PackageInstallationFormManager::findForm($this->queue, 'optionalPackages')) {
-                       $container = new container\MultipleSelectionFormElementContainer();
+                       $container = new MultipleSelectionFormElementContainer();
                        $container->setName('optionalPackages');
                        
                        foreach ($packages as $package) {
-                               $optionalPackage = new element\MultipleSelectionFormElement($container);
+                               $optionalPackage = new MultipleSelectionFormElement($container);
                                $optionalPackage->setName('optionalPackages');
                                $optionalPackage->setLabel($package['packageName']);
                                $optionalPackage->setValue($package['package']);
@@ -663,7 +648,7 @@ class PackageInstallationDispatcher {
                                $container->appendChild($optionalPackage);
                        }
                        
-                       $document = new form\FormDocument('optionalPackages');
+                       $document = new FormDocument('optionalPackages');
                        $document->appendContainer($container);
                        
                        PackageInstallationFormManager::registerForm($this->queue, $document);
@@ -700,7 +685,7 @@ class PackageInstallationDispatcher {
         * starts the installation, update or uninstallation of the first entry.
         * 
         * @param       integer         $parentQueueID
-        * @param       integer         $processNo
+        * @param       integer         $processNo
         */
        public static function openQueue($parentQueueID = 0, $processNo = 0) {
                $conditions = new PreparedStatementConditionBuilder();
@@ -723,62 +708,12 @@ class PackageInstallationDispatcher {
                        exit;
                }
                else {
-                       $url = LinkHandler::getInstance()->getLink('Package', array(), 'action='.$packageInstallation['action'].'&queueID='.$packageInstallation['queueID']);
+                       $url = LinkHandler::getInstance()->getLink('PackageInstallationConfirm', array(), 'action='.$packageInstallation['action'].'&queueID='.$packageInstallation['queueID']);
                        HeaderUtil::redirect($url);
                        exit;
                }
        }
        
-       /**
-        * Displays last confirmation before plugin installation.
-        */
-       public function beginInstallation() {
-               // get requirements
-               $requirements = $this->getArchive()->getRequirements();
-               $openRequirements = $this->getArchive()->getOpenRequirements();
-               
-               $updatableInstances = array();
-               $missingPackages = 0;
-               foreach ($requirements as $key => $requirement) {
-                       if (isset($openRequirements[$requirement['name']])) {
-                               $requirements[$key]['status'] = 'missing';
-                               $requirements[$key]['action'] = $openRequirements[$requirement['name']]['action'];
-                               
-                               if (!isset($requirements[$key]['file'])) {
-                                       if ($openRequirements[$requirement['name']]['action'] === 'update') {
-                                               $requirements[$key]['status'] = 'missingVersion';
-                                               $requirements[$key]['existingVersion'] = $openRequirements[$requirement['name']]['existingVersion'];
-                                       }
-                                       $missingPackages++;
-                               }
-                               else {
-                                       $requirements[$key]['status'] = 'delivered';
-                               }
-                       }
-                       else {
-                               $requirements[$key]['status'] = 'installed';
-                       }
-               }
-               
-               // get other instances
-               if ($this->action == 'install') {
-                       $updatableInstances = $this->getArchive()->getUpdatableInstances();
-               }
-               
-               ACPMenu::getInstance()->setActiveMenuItem('wcf.acp.menu.link.package.install');
-               WCF::getTPL()->assign(array(
-                       'archive' => $this->getArchive(),
-                       'requiredPackages' => $requirements,
-                       'missingPackages' => $missingPackages,
-                       'updatableInstances' => $updatableInstances,
-                       'excludingPackages' => $this->getArchive()->getConflictedExcludingPackages(),
-                       'excludedPackages' => $this->getArchive()->getConflictedExcludedPackages(),
-                       'queueID' => $this->queue->queueID
-               ));
-               WCF::getTPL()->display('packageInstallationConfirm');
-               exit;
-       }
-       
        /**
         * Checks the package installation queue for outstanding entries.
         * 
@@ -787,7 +722,7 @@ class PackageInstallationDispatcher {
        public static function checkPackageInstallationQueue() {
                $sql = "SELECT          queueID
                        FROM            wcf".WCF_N."_package_installation_queue
-                       WHERE           userID = ?
+                       WHERE           userID = ?
                                        AND parentQueueID = 0
                                        AND done = 0
                        ORDER BY        queueID ASC";
@@ -806,9 +741,6 @@ class PackageInstallationDispatcher {
         * Executes post-setup actions.
         */
        public function completeSetup() {
-               // rebuild dependencies
-               Package::rebuildPackageDependencies($this->queue->packageID);
-               
                // mark queue as done
                $queueEditor = new PackageInstallationQueueEditor($this->queue);
                $queueEditor->update(array(
@@ -831,7 +763,7 @@ class PackageInstallationDispatcher {
                LanguageEditor::deleteLanguageFiles();
                
                // reset all caches
-               CacheHandler::getInstance()->clear(WCF_DIR.'cache/', '*');
+               CacheHandler::getInstance()->flushAll();
        }
        
        /**
@@ -948,7 +880,7 @@ class PackageInstallationDispatcher {
         * @param       string          $function
         * @return      boolean
         * @see         http://de.php.net/manual/en/function.function-exists.php#77980
-        */     
+        */
        protected static function functionExists($function) {
                if (extension_loaded('suhosin')) {
                        $blacklist = @ini_get('suhosin.executor.func.blacklist');
@@ -1029,4 +961,52 @@ class PackageInstallationDispatcher {
                        break;
                }
        }
+       
+       /*
+        * Restructure version tables.
+        */
+       protected function restructureVersionTables() {
+               $objectTypes = VersionHandler::getInstance()->getObjectTypes();
+               
+               if (empty($objectTypes)) {
+                       return;
+               }
+               
+               // base structure of version tables
+               $versionTableBaseColumns = array();
+               $versionTableBaseColumns[] = array('name' => 'versionID', 'data' => array('type' => 'INT', 'key' => 'PRIMARY', 'autoIncrement' => 'AUTO_INCREMENT'));
+               $versionTableBaseColumns[] = array('name' => 'versionUserID', 'data' => array('type' => 'INT'));
+               $versionTableBaseColumns[] = array('name' => 'versionUsername', 'data' => array('type' => 'VARCHAR', 'length' => 255));
+               $versionTableBaseColumns[] = array('name' => 'versionTime', 'data' => array('type' => 'INT'));
+               
+               foreach ($objectTypes as $objectTypeID => $objectType) {
+                       // get structure of base table
+                       $baseTableColumns = WCF::getDB()->getEditor()->getColumns($objectType::getDatabaseTableName());
+                       // get structure of version table
+                       $versionTableColumns = WCF::getDB()->getEditor()->getColumns($objectType::getDatabaseVersionTableName());
+                       
+                       if (empty($versionTableColumns)) {
+                               $columns = array_merge($versionTableBaseColumns, $baseTableColumns);
+                               
+                               WCF::getDB()->getEditor()->createTable($objectType::getDatabaseVersionTableName(), $columns);
+                       }
+                       else {
+                               // check garbage columns in versioned table
+                               foreach ($versionTableColumns as $columnData) {
+                                       if (!array_search($columnData['name'], $baseTableColumns, true)) {
+                                               // delete column
+                                               WCF::getDB()->getEditor()->dropColumn($objectType::getDatabaseVersionTableName(), $columnData['name']);
+                                       }
+                               }
+                               
+                               // check new columns for versioned table
+                               foreach ($baseTableColumns as $columnData) {
+                                       if (!array_search($columnData['name'], $versionTableColumns, true)) {
+                                               // add colum
+                                               WCF::getDB()->getEditor()->addColumn($objectType::getDatabaseVersionTableName(), $columnData['name'], $columnData['data']);
+                                       }
+                               }
+                       }
+               }
+       }
 }