Added data import function (WIP)
authorMarcel Werk <burntime@woltlab.com>
Sat, 29 Jun 2013 17:07:50 +0000 (19:07 +0200)
committerMarcel Werk <burntime@woltlab.com>
Sat, 29 Jun 2013 17:07:50 +0000 (19:07 +0200)
15 files changed:
com.woltlab.wcf/acpMenu.xml
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/objectTypeDefinition.xml
com.woltlab.wcf/userGroupOption.xml
wcfsetup/install/files/acp/templates/dataImport.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/form/DataImportForm.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/user/group/UserGroupAction.class.php
wcfsetup/install/files/lib/system/exporter/AbstractExporter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/exporter/IExporter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/importer/IImporter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/importer/ImportHandler.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/importer/UserGroupImporter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/importer/UserImporter.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/worker/ImportWorker.class.php [new file with mode: 0644]
wcfsetup/setup/db/install.sql

index 99f3c3fca36a44d53dfc6ee64acfd060e7a0d22e..cdb9d329c14409565daec1e991029c0c7ebb6b24 100644 (file)
                        <parent>wcf.acp.menu.link.maintenance</parent>
                        <permissions>admin.system.canManageApplication</permissions>
                </acpmenuitem>
+               
+               <acpmenuitem name="wcf.acp.menu.link.maintenance.import">
+                       <controller><![CDATA[wcf\acp\form\DataImport]]></controller>
+                       <parent>wcf.acp.menu.link.maintenance</parent>
+                       <permissions>admin.system.canImportData</permissions>
+               </acpmenuitem>
                <!-- /maintenance -->
                
                <!-- log -->
index c15955563d4299fbea78cd021e996b7e0e560087..4d1172930ad7d41acf06730ca015ee1b1e48ce01 100644 (file)
                        <name>com.woltlab.wcf.label</name>
                        <definitionname>com.woltlab.wcf.acl</definitionname>
                </type>
+               
+               <!-- importers -->
+               <type>
+                       <name>com.woltlab.wcf.user</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname><![CDATA[wcf\system\importer\UserImporter]]></classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.user.group</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname><![CDATA[wcf\system\importer\UserGroupImporter]]></classname>
+               </type>
+               <!-- <type>
+                       <name>com.woltlab.wcf.user.option</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname><![CDATA[wcf\system\importer\UserOptionImporter]]></classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.user.avatar</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname><![CDATA[wcf\system\importer\UserAvatarImporter]]></classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.user.comment</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname><![CDATA[wcf\system\importer\UserCommentImporter]]></classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.user.comment.response</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname><![CDATA[wcf\system\importer\UserCommentResponseImporter]]></classname>
+               </type>
+               <type>
+                       <name>com.woltlab.wcf.user.follower</name>
+                       <definitionname>com.woltlab.wcf.importer</definitionname>
+                       <classname><![CDATA[wcf\system\importer\UserFollowerImporter]]></classname>
+               </type>-->
+               <!-- /importers -->
        </import>
 </data>
\ No newline at end of file
index b53929cef2661e86e234a77a1876056e5950b53c..2fba98658400166e03129bacfd8dd089c60e11c5 100644 (file)
                        <name>com.woltlab.wcf.poll</name>
                        <interfacename><![CDATA[wcf\system\poll\IPollHandler]]></interfacename>
                </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.importer</name>
+                       <interfacename><![CDATA[wcf\system\importer\IImporter]]></interfacename>
+               </definition>
+               
+               <definition>
+                       <name>com.woltlab.wcf.exporter</name>
+                       <interfacename><![CDATA[wcf\system\exporter\IExporter]]></interfacename>
+               </definition>
        </import>
 </data>
index 46add8db970ac88bfabd4af2cdf32af722c9e8d5..fe420e20b24b534f7f45f9fba42776f2c8b796a5 100644 (file)
                                <defaultvalue>0</defaultvalue>
                                <admindefaultvalue>1</admindefaultvalue>
                        </option>
+                       <option name="admin.system.canImportData">
+                               <categoryname>admin.system</categoryname>
+                               <optiontype>boolean</optiontype>
+                               <defaultvalue>0</defaultvalue>
+                               <admindefaultvalue>1</admindefaultvalue>
+                       </option>
                        <option name="admin.system.canManageCronjob">
                                <categoryname>admin.system</categoryname>
                                <optiontype>boolean</optiontype>
diff --git a/wcfsetup/install/files/acp/templates/dataImport.tpl b/wcfsetup/install/files/acp/templates/dataImport.tpl
new file mode 100644 (file)
index 0000000..e8fb954
--- /dev/null
@@ -0,0 +1,202 @@
+{include file='header' pageTitle='wcf.acp.dataImport'}
+
+<script type="text/javascript">
+       //<![CDATA[
+       $(function() {
+               {if $queue|isset}
+                       WCF.Language.addObject({
+                               {implode from=$importers item=importer}'wcf.acp.dataImport.import.{@$importer}': '{lang}wcf.acp.dataImport.import.{@$importer}{/lang}'{/implode}
+                       });
+                       
+                       var $queue = [ {implode from=$queue item=item}'{@$item}'{/implode} ];
+                       var $queueID = 0;
+                       
+                       function runProcess() {
+                               new WCF.ACP.Worker('mail', 'wcf\\system\\worker\\ImportWorker', WCF.Language.get('wcf.acp.dataImport.import.' + $queue[$queueID]), {
+                                       objectType: $queue[$queueID]
+                               }, function(worker) {
+                                       $queueID++;
+                                       if ($queueID < $queue.length) {
+                                               worker._dialog.wcfDialog('close');
+                                               runProcess();
+                                       }
+                               });
+                       }
+                       
+                       runProcess();
+               {/if}
+               
+               $('.jsImportSection').change(function(event) {
+                       var $section = $(event.currentTarget);
+                       window.dtdesign = $section;
+                       if ($section.is(':checked')) {
+                               $section.parent().next().find('input[type=checkbox]').prop('checked', 'checked');
+                       }
+                       else {
+                               $section.parent().next().find('input[type=checkbox]').prop('checked', false);
+                       }
+               });
+
+               $('.jsImportItem').change(function(event) {
+                       var $item = $(event.currentTarget);
+                       if ($item.is(':checked')) {
+                               $item.parents('.jsImportCollection').find('.jsImportSection').prop('checked', 'checked');
+                       }
+                       else {
+                               var $collection = $item.parents('.jsImportCollection');
+                               var $checkedItems = $collection.find('.jsImportItem:checked');
+                               if (!$checkedItems.length) {
+                                       $collection.find('.jsImportSection').prop('checked', false);
+                               }
+                       }
+               });
+       });
+       //]]>
+</script>
+
+<header class="boxHeadline">
+       <h1>{lang}wcf.acp.dataImport{/lang}</h1>
+</header>
+
+{if $errorField}
+       <p class="error">{lang}wcf.global.form.error{/lang} ({$errorField})</p>
+{/if}
+
+<div class="contentNavigation">
+       {hascontent}
+               <nav>
+                       <ul>
+                               {content}
+                                       {event name='contentNavigationButtons'}
+                               {/content}
+                       </ul>
+               </nav>
+       {/hascontent}
+</div>
+
+{if !$exporterName}
+       {if !$availableExporters|count}
+               <p class="info">{lang}wcf.acp.dataImport.selectExporter.noExporters{/lang}</p>
+       {else}
+               <form method="get" action="{link controller='DataImport'}{/link}">
+                       <div class="container containerPadding marginTop">
+                               <fieldset>
+                                       <legend>{lang}wcf.acp.dataImport.selectExporter{/lang}</legend>
+                                       
+                                       <dl{if $errorField == 'exporterName'} class="formError"{/if}>
+                                               <dt><label for="exporterName">{lang}wcf.acp.dataImport.exporter{/lang}</label></dt>
+                                               <dd>
+                                                       <select name="exporterName" id="exporterName">
+                                                               {foreach from=$availableExporters key=availableExporterName item=availableExporter}
+                                                                       <option value="{@$availableExporterName}">{lang}wcf.acp.dataImport.exporter.{@$availableExporterName}{/lang}</option>
+                                                               {/foreach}
+                                                       </select>
+                                                       {if $errorField == 'exporterName'}
+                                                               <small class="innerError">
+                                                                       {if $errorType == 'empty'}
+                                                                               {lang}wcf.global.form.error.empty{/lang}
+                                                                       {else}
+                                                                               {lang}wcf.acp.dataImport.exporterName.error.{@$errorType}{/lang}
+                                                                       {/if}
+                                                               </small>
+                                                       {/if}
+                                               </dd>
+                                       </dl>
+                                       
+                                       {event name='selectExporterFields'}
+                               </fieldset>
+                       </div>
+               
+                       <div class="formSubmit">
+                               {@SID_INPUT_TAG}
+                               <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+                       </div>
+               </form>
+       {/if}
+{else}
+       <form method="post" action="{link controller='DataImport'}{/link}">
+               <div class="container containerPadding marginTop">
+                       <fieldset>
+                               <legend>{lang}wcf.acp.dataImport.configure.data{/lang}</legend>
+                               
+                               <small>{lang}wcf.acp.dataImport.configure.data.description{/lang}</small>
+                               
+                               {foreach from=$supportedData key=objectTypeName item=objectTypes}
+                                       <dl class="wide">
+                                               <dd class="jsImportCollection">
+                                                       <label><input type="checkbox" name="selectedData[]" value="{@$objectTypeName}" class="jsImportSection"{if $objectTypeName|in_array:$selectedData}checked="checked" {/if}/> {lang}wcf.acp.dataImport.data.{@$objectTypeName}{/lang}</label>
+                                                       <p>
+                                                               {foreach from=$objectTypes item=objectTypeName}
+                                                                       <label><input type="checkbox" name="selectedData[]" value="{@$objectTypeName}" class="jsImportItem"{if $objectTypeName|in_array:$selectedData}checked="checked" {/if}/> {lang}wcf.acp.dataImport.data.{@$objectTypeName}{/lang}</label>
+                                                               {/foreach}
+                                                       </p>
+                                               </dd>
+                                       </dl>
+                               {/foreach}
+                       </fieldset>
+                       
+                       <fieldset>
+                               <legend>{lang}wcf.acp.dataImport.configure.settings{/lang}</legend>
+                               
+                               
+                       </fieldset>
+                       
+                       <fieldset>
+                               <legend>{lang}wcf.acp.dataImport.configure.database{/lang}</legend>
+                               
+                               <dl>
+                                       <dt><label for="dbHost">{lang}wcf.acp.dataImport.configure.database.host{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="dbHost" name="dbHost" value="{$dbHost}" class="long" />
+                                       </dd>
+                               </dl>
+                               
+                               <dl>
+                                       <dt><label for="dbUser">{lang}wcf.acp.dataImport.configure.database.user{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="dbUser" name="dbUser" value="{$dbUser}" class="medium" />
+                                       </dd>
+                               </dl>
+                               
+                               <dl>
+                                       <dt><label for="dbPassword">{lang}wcf.acp.dataImport.configure.database.password{/lang}</label></dt>
+                                       <dd>
+                                               <input type="password" id="dbPassword" name="dbPassword" value="{$dbPassword}" class="medium" />
+                                       </dd>
+                               </dl>
+                               
+                               <dl>
+                                       <dt><label for="dbName">{lang}wcf.acp.dataImport.configure.database.name{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="dbName" name="dbName" value="{$dbName}" class="medium" />
+                                       </dd>
+                               </dl>
+                               
+                               <dl>
+                                       <dt><label for="dbPrefix">{lang}wcf.acp.dataImport.configure.database.prefix{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="dbPrefix" name="dbPrefix" value="{$dbPrefix}" class="short" />
+                                       </dd>
+                               </dl>
+                       </fieldset>
+                       
+                       <fieldset>
+                               <legend>{lang}wcf.acp.dataImport.configure.fileSystem{/lang}</legend>
+                               
+                               <dl>
+                                       <dt><label for="fileSystemPath">{lang}wcf.acp.dataImport.configure.fileSystem.path{/lang}</label></dt>
+                                       <dd>
+                                               <input type="text" id="fileSystemPath" name="fileSystemPath" value="{$fileSystemPath}" class="long" />
+                                       </dd>
+                               </dl>
+                       </fieldset>
+               </div>
+       
+               <div class="formSubmit">
+                       <input type="hidden" name="exporterName" value="{$exporterName}" />
+                       <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s" />
+               </div>
+       </form>
+{/if}
+
+{include file='footer'}
diff --git a/wcfsetup/install/files/lib/acp/form/DataImportForm.class.php b/wcfsetup/install/files/lib/acp/form/DataImportForm.class.php
new file mode 100644 (file)
index 0000000..55707c6
--- /dev/null
@@ -0,0 +1,227 @@
+<?php
+namespace wcf\acp\form;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\form\AbstractForm;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Provides the data import form.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf.user
+ * @subpackage acp.form
+ * @category   Community Framework
+ */
+class DataImportForm extends AbstractForm {
+       /**
+        * @see wcf\page\AbstractPage::$activeMenuItem
+        */
+       public $activeMenuItem = 'wcf.acp.menu.link.maintenance.import';
+       
+       /**
+        * @see wcf\page\AbstractPage::$neededPermissions
+        */
+       public $neededPermissions = array('admin.system.canImportData');
+       
+       /**
+        * list of available exporters
+        * @var array
+        */
+       public $exporters = array();
+       
+       /**
+        * exporter name
+        * @var string
+        */
+       public $exporterName = '';
+       
+       /**
+        * exporter object
+        * @var wcf\system\exporter\IExporter
+        */
+       public $exporter = null;
+       
+       /**
+        * list of available importers
+        * @var array<string>
+        */
+       public $importers = array();
+       
+       /**
+        * list of supported data types
+        * @var array
+        */
+       public $supportedData = array();
+       
+       /**
+        * selected data types
+        * @var array
+        */
+       public $selectedData = array();
+       
+       /**
+        * database host name
+        * @var string
+        */
+       public $dbHost = '';
+       
+       /**
+        * database user name
+        * @var string
+        */
+       public $dbUser = '';
+       
+       /**
+        * database password
+        * @var string
+        */
+       public $dbPassword = '';
+       
+       /**
+        * database name
+        * @var string
+        */
+       public $dbName = '';
+       
+       /**
+        * database table prefix
+        * @var string
+        */
+       public $dbPrefix = '';
+       
+       /**
+        * file system path
+        * @var string
+        */
+       public $fileSystemPath = '';
+       
+       /**
+        * @see wcf\page\IPage::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               // get available exporters/importers
+               $this->exporters = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.exporter');
+               $this->importers = array_keys(ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.importer'));
+               
+               if (isset($_REQUEST['exporterName'])) {
+                       $this->exporterName = $_REQUEST['exporterName'];
+                       if (!isset($this->exporters[$this->exporterName])) {
+                               throw new IllegalLinkException();
+                       }
+                       
+                       $this->exporter = $this->exporters[$this->exporterName]->getProcessor();
+                       $this->supportedData = $this->exporter->getSupportedData();
+                       
+                       // remove unsupported data
+                       foreach ($this->supportedData as $key => $subData) {
+                               if (!in_array($key, $this->importers)) {
+                                       unset($this->supportedData[$key]);
+                                       continue;
+                               }
+                               
+                               foreach ($subData as $key2 => $value) {
+                                       if (!in_array($value, $this->importers)) {
+                                               unset($this->supportedData[$key][$key2]);
+                                       }
+                               }
+                       }
+                       
+                       // get default database prefix
+                       if (!count($_POST)) {
+                               $this->dbPrefix = $this->exporter->getDefaultDatabasePrefix();
+                       }
+               }
+       }
+       
+       /**
+        * @see wcf\form\IForm::readFormParameters()
+        */
+       public function readFormParameters() {
+               parent::readFormParameters();
+       
+               if (isset($_POST['selectedData']) && is_array($_POST['selectedData'])) $this->selectedData = $_POST['selectedData'];
+               
+               if (isset($_POST['dbHost'])) $this->dbHost = StringUtil::trim($_POST['dbHost']);
+               if (isset($_POST['dbUser'])) $this->dbUser = StringUtil::trim($_POST['dbUser']);
+               if (isset($_POST['dbPassword'])) $this->dbPassword = $_POST['dbPassword'];
+               if (isset($_POST['dbName'])) $this->dbName = StringUtil::trim($_POST['dbName']);
+               if (isset($_POST['dbPrefix'])) $this->dbPrefix = StringUtil::trim($_POST['dbPrefix']);
+               if (isset($_POST['fileSystemPath'])) $this->fileSystemPath = StringUtil::trim($_POST['fileSystemPath']);
+       }
+       
+       /**
+        * @see wcf\form\IForm::validate()
+        */
+       public function validate() {
+               parent::validate();
+       
+               $this->exporter->setData($this->dbHost, $this->dbUser, $this->dbPassword, $this->dbName, $this->dbPrefix, $this->fileSystemPath);
+               
+               // validate database Access
+               if (!$this->exporter->validateDatabaseAccess()) {
+                       throw new UserInputException('database');
+               }
+               
+               // validate selected data
+               if (!$this->exporter->validateSelectedData($this->selectedData)) {
+                       throw new UserInputException('selectedData');
+               }
+               
+               // validate file access
+               if (!$this->exporter->validateFileAccess()) {
+                       throw new UserInputException('fileSystemPath');
+               }
+       }
+       
+       /**
+        * @see wcf\form\IForm::save()
+        */
+       public function save() {
+               parent::save();
+       
+               // get queue
+               $queue = $this->exporter->getQueue();
+               
+               // save import data
+               WCF::getSession()->register('importData', array(
+                       'exporterName' => $this->exporterName,
+                       'dbHost' => $this->dbHost,
+                       'dbUser' => $this->dbUser,
+                       'dbPassword' => $this->dbPassword,
+                       'dbName' => $this->dbName,
+                       'dbPrefix' => $this->dbPrefix,
+                       'fileSystemPath' => $this->fileSystemPath,
+               ));
+               
+               WCF::getTPL()->assign('queue', $queue);
+       }
+       
+       /**
+        * @see wcf\page\IPage::assignVariables()
+        */
+       public function assignVariables() {
+               parent::assignVariables();
+       
+               WCF::getTPL()->assign(array(
+                       'exporter' => $this->exporter,
+                       'importers' => $this->importers,
+                       'exporterName' => $this->exporterName,
+                       'availableExporters' => $this->exporters,
+                       'supportedData' => $this->supportedData,
+                       'selectedData' => $this->selectedData,
+                       'dbHost' => $this->dbHost,
+                       'dbUser' => $this->dbUser,
+                       'dbPassword' => $this->dbPassword,
+                       'dbName' => $this->dbName,
+                       'dbPrefix' => $this->dbPrefix,
+                       'fileSystemPath' => $this->fileSystemPath
+               ));
+       }
+}
index e0c969c23ea30d64e6f81f2a45fde60afe54676e..9a8995becc942e689a30222b40eb6dccf2ff727c 100644 (file)
@@ -39,8 +39,10 @@ class UserGroupAction extends AbstractDatabaseObjectAction {
        public function create() {
                $group = parent::create();
                
-               $groupEditor = new UserGroupEditor($group);
-               $groupEditor->updateGroupOptions($this->parameters['options']);
+               if (isset($this->parameters['options'])) {
+                       $groupEditor = new UserGroupEditor($group);
+                       $groupEditor->updateGroupOptions($this->parameters['options']);
+               }
                
                return $group;
        }
diff --git a/wcfsetup/install/files/lib/system/exporter/AbstractExporter.class.php b/wcfsetup/install/files/lib/system/exporter/AbstractExporter.class.php
new file mode 100644 (file)
index 0000000..5b901c8
--- /dev/null
@@ -0,0 +1,143 @@
+<?php
+namespace wcf\system\exporter;
+use wcf\system\database\DatabaseException;
+use wcf\system\database\MySQLDatabase;
+use wcf\system\exception\SystemException;
+use wcf\util\FileUtil;
+
+/**
+ * Basic implementation of IExporter.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.exporter
+ * @category   Community Framework
+ */
+abstract class AbstractExporter implements IExporter {
+       /**
+        * database host name
+        * @var string
+        */
+       protected $databaseHost = '';
+       
+       /**
+        * database username
+        * @var string
+        */
+       protected $databaseUser = '';
+       
+       /**
+        * database password
+        * @var string
+        */
+       protected $databasePassword = '';
+       
+       /**
+        * database name
+        * @var string
+        */
+       protected $databaseName = '';
+       
+       /**
+        * table prefix
+        * @var string
+        */
+       protected $databasePrefix = '';
+       
+       /**
+        * file system path
+        * @var string
+        */
+       protected $fileSystemPath = '';
+       
+       /**
+        * database connection
+        * @var wcf\system\database\Database
+        */
+       protected $database = null;
+       
+       /**
+        * object type => method names
+        * @var array
+        */
+       protected $methods = array();
+       
+       /**
+        * limits for items per run
+        * @var array<integer>
+        */
+       protected $limits = array();
+       
+       /**
+        * default limit for items per run
+        * @var integer
+        */
+       protected $defaultLimit = 1000;
+       
+       /**
+        * @see wcf\system\exporter\IExporter::setData()
+        */
+       public function setData($databaseHost, $databaseUser, $databasePassword, $databaseName, $databasePrefix, $fileSystemPath) {
+               $this->databaseHost = $databaseHost;
+               $this->databaseUser = $databaseUser;
+               $this->databasePassword = $databasePassword;
+               $this->databaseName = $databaseName;
+               $this->databasePrefix = $databasePrefix;
+               $this->fileSystemPath = ($fileSystemPath ? FileUtil::addTrailingSlash($fileSystemPath) : '');
+       }
+       
+       /**
+        * @see wcf\system\exporter\IExporter::init()
+        */
+       public function init() {
+               $this->database = new MySQLDatabase($this->databaseHost, $this->databaseUser, $this->databasePassword, $this->databaseName, 0);
+       }
+       
+       /**
+        * @see wcf\system\exporter\IExporter::validateDatabaseAccess()
+        */
+       public function validateDatabaseAccess() {
+               try {
+                       $this->init();
+               }
+               catch (DatabaseException $e) {
+                       return false;
+               }
+               
+               return true;
+       }
+       
+       /**
+        * @see wcf\system\exporter\IExporter::getDefaultDatabasePrefix()
+        */
+       public function getDefaultDatabasePrefix() {
+               return '';
+       }
+       
+       /**
+        * @see wcf\system\exporter\IExporter::countLoops()
+        */
+       public function countLoops($objectType) {
+               if (!isset($this->methods[$objectType]) || !method_exists($this, 'count'.$this->methods[$objectType])) {
+                       throw new SystemException("unknown object type '".$objectType."' given");
+               }
+               
+               $count = call_user_func(array($this, 'count'.$this->methods[$objectType]));
+               $limit = (isset($this->limits[$objectType]) ? $this->limits[$objectType] : $this->defaultLimit);
+               return ceil($count / $limit);
+       }
+       
+       /**
+        * @see wcf\system\exporter\IExporter::exportData()
+        */
+       public function exportData($objectType, $loopCount = 0) {
+               if (!isset($this->methods[$objectType]) || !method_exists($this, 'export'.$this->methods[$objectType])) {
+                       throw new SystemException("unknown object type '".$objectType."' given");
+               }
+               
+               $limit = (isset($this->limits[$objectType]) ? $this->limits[$objectType] : $this->defaultLimit);
+               call_user_func(array($this, 'export'.$this->methods[$objectType]), $loopCount * $limit, $limit);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/exporter/IExporter.class.php b/wcfsetup/install/files/lib/system/exporter/IExporter.class.php
new file mode 100644 (file)
index 0000000..fed6217
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+namespace wcf\system\exporter;
+
+/**
+ * Basic interface for all exporters.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.exporter
+ * @category   Community Framework
+ */
+interface IExporter {
+       /**
+        * Sets database access data.
+        * 
+        * @param       string          $databaseHost
+        * @param       string          $databaseUser
+        * @param       string          $databasePassword
+        * @param       string          $databaseName
+        * @param       string          $databasePrefix
+        * @param       string          $fileSystemPath
+        */
+       public function setData($databaseHost, $databaseUser, $databasePassword, $databaseName, $databasePrefix, $fileSystemPath);
+       
+       /**
+        * Initializes this exporter.
+        */
+       public function init();
+       
+       /**
+        * Counts the number of required loops for given type.
+        * 
+        * @param       string          $objectType
+        * @return      integer
+        */
+       public function countLoops($objectType);
+       
+       /**
+        * Runs the data export.
+        *
+        * @param       string          $objectType
+        * @param       integer         $loopCount
+        */
+       public function exportData($objectType, $loopCount = 0);
+       
+       /**
+        * Validates database access. Returns false on failure.
+        *
+        * @return      boolean
+        */
+       public function validateDatabaseAccess();
+       
+       /**
+        * Validates given file system path. Returns false on failure.
+        *
+        * @return      boolean
+        */
+       public function validateFileAccess();
+       
+       /**
+        * Validates the selected data types. Returns false on failure.
+        * 
+        * @param       array           $selectedData
+        * @return      boolean
+        */
+       public function validateSelectedData(array $selectedData);
+       
+       /**
+        * Returns the import worker queue.
+        * 
+        * @return      array
+        */
+       public function getQueue();
+       
+       /**
+        * Returns the supported data types.
+        * 
+        * @return      array<string>
+        */
+       public function getSupportedData();
+       
+       /**
+        * Returns a default database table prefix.
+        * 
+        * @return      string
+        */
+       public function getDefaultDatabasePrefix();
+}
diff --git a/wcfsetup/install/files/lib/system/importer/IImporter.class.php b/wcfsetup/install/files/lib/system/importer/IImporter.class.php
new file mode 100644 (file)
index 0000000..a7a1d2a
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\system\importer;
+
+/**
+ * Basic interface for all importer.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.importer
+ * @category   Community Framework
+ */
+interface IImporter {
+       /**
+        * Imports a data set.
+        * 
+        * @param       mixed           $oldID
+        * @param       array           $data
+        * @return      mixed           new id
+        */
+       public function import($oldID, array $data);
+}
diff --git a/wcfsetup/install/files/lib/system/importer/ImportHandler.class.php b/wcfsetup/install/files/lib/system/importer/ImportHandler.class.php
new file mode 100644 (file)
index 0000000..e461484
--- /dev/null
@@ -0,0 +1,106 @@
+<?php
+namespace wcf\system\importer;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\exception\SystemException;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Handles data import.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.importer
+ * @category   Community Framework
+ */
+class ImportHandler extends SingletonFactory {
+       /**
+        * id map cache
+        * @var array 
+        */
+       protected $idMappingCache = array();
+       
+       /**
+        * list of available importers
+        * @var array
+        */
+       protected $objectTypes = array();
+       
+       /**
+        * list of available importer processors
+        * @var array
+        */
+       protected $importers = array();
+       
+       /**
+        * @see wcf\system\SingletonFactory::init()
+        */
+       protected function init() {
+               $this->objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.importer');
+       }
+       
+       /**
+        * Gets a data importer.
+        * 
+        * @param       string          $type
+        * @return      wcf\system\importer\IImporter
+        */
+       public function getImporter($type) {
+               if (!isset($this->importers[$type])) {
+                       if (!isset($this->objectTypes[$type])) {
+                               throw new SystemException("unknown importer '".$type."'");
+                       }
+                       
+                       $this->importers[$type] = $this->objectTypes[$type]->getProcessor();
+               }
+               
+               return $this->importers[$type];
+       }
+       
+       /**
+        * Gets a new id from id mapping.
+        *
+        * @param       string          $type
+        * @param       mixed           $oldID
+        * @return      integer         $newID
+        */
+       public function getNewID($type, $oldID) {
+               $objectTypeID = $this->objectTypes[$type]->objectTypeID;
+               
+               if (!isset($this->idMappingCache[$objectTypeID][$oldID])) {
+                       $this->idMappingCache[$objectTypeID][$oldID] = 0;
+                       
+                       $sql = "SELECT  newID
+                               FROM    wcf".WCF_N."_import_mapping
+                               WHERE   objectTypeID = ?
+                                       AND oldID = ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       $statement->execute(array($objectTypeID, $oldID));
+                       $row = $statement->fetchArray();
+                       if ($row !== false) $this->idMappingCache[$objectTypeID][$oldID] = $row['newID'];
+               }
+               
+               return $this->idMappingCache[$objectTypeID][$oldID];
+       }
+       
+       /**
+        * Saves an id mapping.
+        *
+        * @param       string          $type
+        * @param       integer         $oldID
+        * @param       integer         $newID
+        */
+       public function saveNewID($type, $oldID, $newID) {
+               $objectTypeID = $this->objectTypes[$type]->objectTypeID;
+               
+               $sql = "INSERT IGNORE INTO      wcf".WCF_N."_import_mapping
+                                               (objectTypeID, oldID, newID)
+                       VALUES                  (?, ?, ?)";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($objectTypeID, $oldID, $newID));
+               
+               unset($this->idMappingCache[$objectTypeID][$oldID]);
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/importer/UserGroupImporter.class.php b/wcfsetup/install/files/lib/system/importer/UserGroupImporter.class.php
new file mode 100644 (file)
index 0000000..254307a
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+namespace wcf\system\importer;
+use wcf\data\user\group\UserGroupAction;
+
+/**
+ * Imports user groups.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.importer
+ * @category   Community Framework
+ */
+class UserGroupImporter implements IImporter {
+       /**
+        * @see wcf\system\importer::import()
+        */
+       public function import($oldID, array $data) {
+               $action = new UserGroupAction(array(), 'create', array(
+                       'data' => $data         
+               ));
+               $returnValues = $action->executeAction();
+               $newGroupID = $returnValues['returnValues']->groupID;
+               
+               ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.user.group', $oldID, $newGroupID);
+               
+               return $newGroupID;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/importer/UserImporter.class.php b/wcfsetup/install/files/lib/system/importer/UserImporter.class.php
new file mode 100644 (file)
index 0000000..ac5764d
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+namespace wcf\system\importer;
+use wcf\data\user\UserAction;
+use wcf\system\database\DatabaseException;
+use wcf\system\WCF;
+
+/**
+ * Imports users.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.importer
+ * @category   Community Framework
+ */
+class UserImporter implements IImporter {
+       /**
+        * @see wcf\system\importer::import()
+        */
+       public function import($oldID, array $data) {
+               $sql = "SELECT  COUNT(*) AS count
+                       FROM    wcf".WCF_N."_user
+                       WHERE   userID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($oldID));
+               $row = $statement->fetchArray();
+               if (!$row['count']) $data['userID'] = $oldID;
+               
+               $action = new UserAction(array(), 'create', array(
+                       'data' => $data
+               ));
+               $returnValues = $action->executeAction();
+               
+               $newUserID = $returnValues['returnValues']->groupID;
+               ImportHandler::getInstance()->saveNewID('com.woltlab.wcf.user', $oldID, $newUserID);
+               
+               return $newUserID;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/worker/ImportWorker.class.php b/wcfsetup/install/files/lib/system/worker/ImportWorker.class.php
new file mode 100644 (file)
index 0000000..6176b07
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+namespace wcf\system\worker;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\exception\SystemException;
+use wcf\system\WCF;
+
+/**
+ * Worker implementation for data import.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2013 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.worker
+ * @category   Community Framework
+ */
+class ImportWorker extends AbstractWorker {
+       /**
+        * import data
+        * @var array
+        */
+       protected $importData = null;
+       
+       /**
+        * exporter object
+        * @var wcf\system\exporter\IExporter
+        */
+       protected $exporter = null;
+       
+       /**
+        * @see wcf\system\worker\IWorker::validate()
+        */
+       public function validate() {
+               WCF::getSession()->checkPermissions(array('admin.system.canImportData'));
+               
+               if (!isset($this->parameters['objectType'])) {
+                       throw new SystemException("parameter 'objectType' missing");
+               }
+               
+               // get import data
+               $this->importData = WCF::getSession()->getVar('importData');
+               if ($this->importData === null) {
+                       throw new SystemException("import data missing");
+               }
+               
+               // get exporter
+               $this->exporter = ObjectTypeCache::getInstance()->getObjectTypeByName('com.woltlab.wcf.exporter', $this->importData['exporterName'])->getProcessor();
+               
+               // set data
+               $this->exporter->setData($this->importData['dbHost'], $this->importData['dbUser'], $this->importData['dbPassword'], $this->importData['dbName'], $this->importData['dbPrefix'], $this->importData['fileSystemPath']);
+               $this->exporter->init();
+       }
+       
+       /**
+        * @see wcf\system\worker\AbstractWorker::countObjects()
+        */
+       protected function countObjects() {
+               $this->count = $this->exporter->countLoops($this->parameters['objectType']);
+       }
+       
+       /**
+        * @see wcf\system\worker\IWorker::getProgress()
+        */
+       public function getProgress() {
+               $this->countObjects();
+               
+               if (!$this->count) {
+                       return 100;
+               }
+               
+               $progress = (($this->loopCount + 1) / $this->count) * 100;
+               if ($progress > 100) $progress = 100;
+               return round($progress, 0);
+       }
+       
+       /**
+        * @see wcf\system\worker\IWorker::execute()
+        */
+       public function execute() {
+               if (!$this->count) {
+                       return;
+               }
+
+               $this->exporter->exportData($this->parameters['objectType'], $this->loopCount);
+       }
+       
+       /**
+        * @see wcf\system\worker\IWorker::getProceedURL()
+        */
+       public function getProceedURL() {
+               return '';
+       }
+}
index 12905005a4db07f6d7c3c34ffd9bf5f111f0d65c..f8a67d2e20e19476a9f5d40685ee5bae36ea8009 100644 (file)
@@ -326,6 +326,14 @@ CREATE TABLE wcf1_event_listener (
        UNIQUE KEY packageID (packageID, environment, eventClassName, eventName, listenerClassName)
 );
 
+DROP TABLE IF EXISTS wcf1_import_mapping;
+CREATE TABLE wcf1_import_mapping (
+       objectTypeID INT(10) NOT NULL,
+       oldID VARCHAR(255) NOT NULL,
+       newID INT(10) NOT NULL,
+       UNIQUE KEY (objectTypeID, oldID)
+);
+
 DROP TABLE IF EXISTS wcf1_label;
 CREATE TABLE wcf1_label (
        labelID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
@@ -1433,6 +1441,8 @@ ALTER TABLE wcf1_dashboard_box ADD FOREIGN KEY (packageID) REFERENCES wcf1_packa
 ALTER TABLE wcf1_dashboard_option ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_dashboard_option ADD FOREIGN KEY (boxID) REFERENCES wcf1_dashboard_box (boxID) ON DELETE CASCADE;
 
+ALTER TABLE wcf1_import_mapping ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
+
 ALTER TABLE wcf1_tracked_visit ADD FOREIGN KEY (objectTypeID) REFERENCES wcf1_object_type (objectTypeID) ON DELETE CASCADE;
 ALTER TABLE wcf1_tracked_visit ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE;