Implemented worker system based upon AJAX dialogs.
authorAlexander Ebert <ebert@woltlab.com>
Tue, 13 Sep 2011 19:06:34 +0000 (21:06 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 13 Sep 2011 19:26:47 +0000 (21:26 +0200)
Reworked the previous worker system relying on the old-style WCF 1.1 worker system as used by the installation itself. Right now the system is pretty unstable and I'm not sure wether the loop calculation is right.

wcfsetup/install/files/acp/js/WCF.ACP.js
wcfsetup/install/files/acp/templates/worker.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/acp/action/WorkerProxyAction.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/worker/AbstractWorker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/worker/IWorker.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/worker/MailWorker.class.php [new file with mode: 0644]

index ecb5779700c84ccf1f6c48c115fb058423f990e3..f09fda84c753da39093f48a2514787239cba47e2 100644 (file)
@@ -530,4 +530,89 @@ WCF.ACP.User.List.prototype = {
                        WCF.Clipboard.sendRequest(item);
                }
        }
+};
+
+/**
+ * Worker support for ACP.
+ * 
+ * @param      string          dialogID
+ * @param      string          className
+ * @param      object          options
+ */
+WCF.ACP.Worker = function(dialogID, className, options) { this.init(dialogID, className, options); };
+WCF.ACP.Worker.prototype = {
+       /**
+        * dialog id
+        * @var string
+        */
+       _dialogID: null,
+       
+       /**
+        * dialog object
+        * @var jQuery
+        */
+       _dialog: null,
+       
+       /**
+        * Initializes a new worker instance.
+        * 
+        * @param       string          dialogID
+        * @param       string          className
+        * @param       object          options
+        */
+       init: function(dialogID, className, options) {
+               this._dialogID = dialogID + 'Worker';
+               options = options || { };
+               
+               // initialize AJAX-based dialog
+               WCF.showAJAXDialog(this._dialogID, true, {
+                       ajax: {
+                               url: 'index.php?action=WorkerProxy&t=' + SECURITY_TOKEN + SID_ARG_2ND,
+                               type: 'POST',
+                               data: {
+                                       className: className,
+                                       parameters: options
+                               },
+                               success: $.proxy(this._handleResponse, this)
+                       },
+                       preventClose: true,
+                       hideTitle: true
+               });
+       },
+       
+       /**
+        * Handles response from server.
+        */
+       _handleResponse: function() {
+               // init binding
+               if (this._dialog === null) {
+                       this._dialog = $('#' + $.wcfEscapeID(this._dialogID));
+               }
+               
+               // fetch data returned by server response
+               var $data = this._dialog.data('responseData');
+               
+               // update progress
+               this._dialog.find('#workerProgress').attr('value', $data.progress).text($data.progress + '%');
+               
+               // worker is still busy with it's business, carry on
+               if ($data.progress < 100) {
+                       // send request for next loop
+                       $.ajax({
+                               url: 'index.php?action=WorkerProxy&t=' + SECURITY_TOKEN + SID_ARG_2ND,
+                               type: 'POST',
+                               data: {
+                                       className: $data.className,
+                                       loopCount: $data.loopCount
+                               },
+                               success: $.proxy(function(data) {
+                                       this._dialog.data('responseData', data);
+                                       this._handleResponse();
+                               }, this),
+                               error: function(transport) {
+                                       alert(transport.responseText);
+                               }
+                       });
+               }
+       }
 };
\ No newline at end of file
diff --git a/wcfsetup/install/files/acp/templates/worker.tpl b/wcfsetup/install/files/acp/templates/worker.tpl
new file mode 100644 (file)
index 0000000..be1668e
--- /dev/null
@@ -0,0 +1,14 @@
+<div id="workerContainer">
+       <header class="mainHeading">
+               <img src="{@RELATIVE_WCF_DIR}icon/workerL.png" alt="" />
+               <hgroup>
+                       <h1>Aufgaben werden ausgeführt &hellip;</h1>
+                       <h2>Aktueller Schritt: <span id="workerAction">{lang}wcf.package.installation.step.prepare{/lang}</span></h2>
+                       <p><progress id="workerProgress" value="0" max="100" style="width: 200px;">0%</progress></p>
+               </hgroup>
+       </header>
+       
+       <div id="workerInnerContentContainer" class="border content" style="display: none;">
+               <div id="workerInnerContent" class="container-1"></div>
+       </div>
+</div>
diff --git a/wcfsetup/install/files/lib/acp/action/WorkerProxyAction.class.php b/wcfsetup/install/files/lib/acp/action/WorkerProxyAction.class.php
new file mode 100644 (file)
index 0000000..3bb3f90
--- /dev/null
@@ -0,0 +1,142 @@
+<?php
+namespace wcf\acp\action;
+use wcf\action\AbstractSecureAction;
+use wcf\system\exception\AJAXException;
+use wcf\system\exception\SystemException;
+use wcf\system\WCF;
+use wcf\util\ClassUtil;
+use wcf\util\JSON;
+
+/**
+ * Handles worker actions.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage acp.action
+ * @category   Community Framework
+ */
+class WorkerProxyAction extends AbstractSecureAction {
+       /**
+        * worker class name
+        * @var string
+        */
+       protected $className = '';
+       
+       /**
+        * loop counter
+        * @var integer
+        */
+       protected $loopCount = 0;
+       
+       /**
+        * parameters for worker action
+        * @var array
+        */
+       protected $parameters = array();
+       
+       /**
+        * worker object
+        * @var wcf\system\worker\IWorker
+        */
+       protected $worker = null;
+       
+       /**
+        * @see wcf\action\AbstractAction::_construct()
+        */
+       public function __construct() {
+               try {
+                       parent::__construct();
+               }
+               catch (\Exception $e) {
+                       if ($e instanceof AJAXException) {
+                               throw $e;
+                       }
+                       else {
+                               throw new AJAXException($e->getMessage());
+                       }
+               }
+       }
+       
+       /**
+        * @see wcf\action\IAction::readParameters()
+        */
+       public function readParameters() {
+               parent::readParameters();
+               
+               if (isset($_POST['className'])) $this->className = $_POST['className'];
+               if (isset($_POST['loopCount'])) $this->loopCount = intval($_POST['loopCount']);
+               if (isset($_POST['parameters']) && is_array($_POST['parameters'])) $this->parameters = $_POST['parameters'];
+               
+               $this->validate();
+       }
+       
+       /**
+        * Validates class name.
+        */
+       protected function validate() {
+               if (empty($this->className)) {
+                       throw new SystemException("class name cannot be empty.");
+               }
+               
+               if (!ClassUtil::isInstanceOf($this->className, 'wcf\system\worker\IWorker')) {
+                       throw new SystemException("class '".$this->className."' should implement the interface 'wcf\system\worker\IWorker'");
+               }
+       }
+       
+       /**
+        * @see wcf\action\IAction::execute()
+        */
+       public function execute() {
+               parent::execute();
+               
+               // initialize worker
+               if ($this->loopCount == 0) {
+                       $this->sendResponse();
+               }
+               
+               $this->worker = new $this->className($this->parameters);
+               $this->worker->setLoopCount($this->loopCount);
+               $this->worker->validate();
+               $returnValues = array();
+               
+               $progress = $this->worker->getProgress();
+               $returnValues['progress'] = $progress;
+               if ($progress < 100) {
+                       $this->worker->execute();
+               }
+               
+               // send current state
+               $this->sendResponse($progress, $this->worker->getParameters());
+               
+       }
+       
+       /**
+        * Sends a JSON-encoded response.
+        * 
+        * @param       integer         $progress
+        * @param       array           $parameters
+        */
+       protected function sendResponse($progress = 0, array $parameters = null) {
+               if ($parameters === null) $parameters = $this->parameters;
+               
+               // build return values
+               $returnValues = array(
+                       'className' => $this->className,
+                       'loopCount' => $this->loopCount,
+                       'parameters' => $parameters,
+                       'progress' => $progress
+               );
+               
+               // include template on startup
+               if ($progress == 0) {
+                       $returnValues['template'] = WCF::getTPL()->fetch('worker');
+               }
+               
+               // send JSON-encoded response
+               header('Content-type: application/json');
+               echo JSON::encode($returnValues);
+               exit;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/worker/AbstractWorker.class.php b/wcfsetup/install/files/lib/system/worker/AbstractWorker.class.php
new file mode 100644 (file)
index 0000000..7c65c8a
--- /dev/null
@@ -0,0 +1,77 @@
+<?php
+namespace wcf\system\worker;
+
+/**
+ * Basic implementation for workers.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 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
+ */
+abstract class AbstractWorker implements IWorker {
+       /**
+        * count of total actions (limited by $limit per loop)
+        * @var integer
+        */
+       protected $count = 0;
+       
+       /**
+        * limit of actions per loop
+        * @var integer
+        */
+       protected $limit = 0;
+       
+       /**
+        * current loop count
+        * @var integer
+        */
+       protected $loopCount = 0;
+       
+       /**
+        * list of additional parameters
+        * @var array
+        */
+       protected $parameters = array();
+       
+       /**
+        * @see wcf\system\worker\IWorker::__construct()
+        */
+       public function __construct(array $parameters) {
+               $this->parameters = $parameters;
+       }
+       
+       /**
+        * @see wcf\system\worker\IWorker::getLoopCount()
+        */
+       public function setLoopCount($loopCount) {
+               $this->loopCount = $loopCount;
+       }
+       
+       /**
+        * Counts objects applicable for worker action.
+        */
+       abstract protected function countObjects();
+       
+       /**
+        * @see wcf\system\worker\IWorker::getProgress()
+        */
+       public function getProgress() {
+               $this->countObjects();
+               
+               if (!$this->count) {
+                       return 100;
+               }
+               
+               return ceil(($this->loopCount / ceil($this->count / $this->limit)) * 100);
+       }
+       
+       /**
+        * @see wcf\system\worker\IWorker::getParameters()
+        */
+       public function getParameters() {
+               return $this->parameters;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/worker/IWorker.class.php b/wcfsetup/install/files/lib/system/worker/IWorker.class.php
new file mode 100644 (file)
index 0000000..b30c396
--- /dev/null
@@ -0,0 +1,53 @@
+<?php
+namespace wcf\system\worker;
+
+/**
+ * Basic interface for workers.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 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
+ */
+interface IWorker {
+       /**
+        * Creates a new worker object with additional parameters
+        * 
+        * @param       array           $parameters
+        */
+       public function __construct(array $parameters);
+       
+       /**
+        * Sets current loop count.
+        * 
+        * @param       integer         $loopCount
+        */
+       public function setLoopCount($loopCount);
+       
+       /**
+        * Gets current process, integer between 0 and 100. If the progress
+        * hits 100 the worker will terminate.
+        * 
+        * @return      integer
+        */
+       public function getProgress();
+       
+       /**
+        * Executes worker action.
+        */
+       public function execute();
+       
+       /**
+        * Returns parameters previously given within __construct().
+        * 
+        * @return      array
+        */
+       public function getParameters();
+       
+       /**
+        * Validates parameters.
+        */
+       public function validate();
+}
diff --git a/wcfsetup/install/files/lib/system/worker/MailWorker.class.php b/wcfsetup/install/files/lib/system/worker/MailWorker.class.php
new file mode 100644 (file)
index 0000000..1a2f114
--- /dev/null
@@ -0,0 +1,134 @@
+<?php
+namespace wcf\system\worker;
+use wcf\system\clipboard\ClipboardHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\SystemException;
+use wcf\system\mail\Mail;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Worker implementation for sending mails.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2011 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 MailWorker extends AbstractWorker {
+       /**
+        * condition builder object
+        * @var wcf\system\database\util\PreparedStatementConditionBuilder
+        */
+       protected $conditions = null;
+       
+       /**
+        * @see wcf\system\worker\AbstractWorker::$limit
+        */
+       protected $limit = 50;
+       
+       /**
+        * mail data
+        * @var array
+        */
+       protected $mailData = null;
+       
+       /**
+        * @see wcf\system\worker\IWorker::validate()
+        */
+       public function validate() {
+               WCF::getSession()->checkPermission('admin.user.canMailUser');
+               
+               if (!isset($this->parameters['mailID'])) {
+                       throw new SystemException("mailID missing");
+               }
+               
+               $userMailData = WCF::getSession()->getVar('userMailData');
+               if (!isset($userMailData[$this->parameters['mailID']])) {
+                       throw new SystemException("mailID '" . $this->parameters['mailID'] . "' is invalid");
+               }
+               
+               $this->mailData = $userMailData[$this->parameters['mailID']];
+       }
+       
+       /**
+        * @see wcf\system\worker\IWorker::countObjects()
+        */
+       public function countObjects() {
+               $this->conditions = new PreparedStatementConditionBuilder();
+               if ($this->mailData['action'] == '') {
+                       $this->conditions->add("user.userID IN (?)", array($this->mailData['userIDs']));
+               }
+               else if ($this->mailData['action'] == 'group') {
+                       $this->conditions->add("user.userID IN (SELECT userID FROM wcf".WCF_N."_user_to_group WHERE groupID IN (?))", array($this->mailData['groupIDs']));
+               }
+               
+               $sql = "SELECT  COUNT(*) AS count
+                       FROM    wcf".WCF_N."_user user
+                       ".$this->conditions;
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute($this->conditions->getParameters());
+               $row = $statement->fetchArray();
+               
+               $this->count = $row['count'];
+       }
+       
+       /**
+        * @see wcf\system\worker\IWorker::getProgress()
+        */
+       public function getProgress() {
+               $progress = parent::getProgress();
+               
+               if ($progress == 100) {
+                       // clear markings
+                       $typeID = ClipboardHandler::getInstance()->getTypeID('com.woltlab.wcf.user');
+                       ClipboardHandler::getInstance()->removeItems($typeID);
+                       
+                       // clear session
+                       $userMailData = WCF::getSession()->getVar('userMailData');
+                       unset($userMailData[$this->parameters['mailID']]);
+                       WCF::getSession()->register('userMailData', $userMailData);
+               }
+               
+               return $progress;
+       }
+       
+       /**
+        * @see wcf\system\worker\IWorker::execute()
+        */
+       public function execute() {
+               // get users
+               $sql = "SELECT          user_option.*, user.*
+                       FROM            wcf".WCF_N."_user user
+                       LEFT JOIN       wcf".WCF_N."_user_option_value user_option
+                       ON              (user_option.userID = user.userID)
+                       ".$this->conditions."
+                       ORDER BY        user.userID";
+               $statement = WCF::getDB()->prepareStatement($sql, $this->limit, ($this->limit * $this->loopCount));
+               $statement->execute($this->conditions->getParameters());
+               while ($row = $statement->fetchArray()) {
+                       $user = new User(null, $row);
+                       $adminCanMail = $user->adminCanMail;
+                       if ($adminCanMail === null || $adminCanMail) {
+                               $this->sendMail($user);
+                       }
+               }
+       }
+       
+       /**
+        * Sends the mail to given user.
+        * 
+        * @param       wcf\data\user\User      $user
+        */
+       protected function sendMail(User $user) {
+               // send mail
+               try {
+                       $mail = new Mail(array($user->username => $user->email), $this->userMailData['subject'], StringUtil::replace('{$username}', $user->username, $this->mailData['text']), $this->mailData['from']);
+                       if ($this->mailData['enableHTML']) $mail->setContentType('text/html');
+                       $mail->send();
+               }
+               catch (SystemException $e) {} // ignore errors
+       }
+}