From: Alexander Ebert Date: Tue, 6 Sep 2011 14:07:02 +0000 (+0200) Subject: Added clipboard-API (draft) X-Git-Tag: 2.0.0_Beta_1~1791^2~5 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=0d6ea23fe647989641e2c1292e44241b24186e10;p=GitHub%2FWoltLab%2FWCF.git Added clipboard-API (draft) --- diff --git a/XSD/clipboardaction.xsd b/XSD/clipboardaction.xsd new file mode 100644 index 0000000000..09e2147d99 --- /dev/null +++ b/XSD/clipboardaction.xsd @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/XSD/clipboarditemtype.xsd b/XSD/clipboarditemtype.xsd new file mode 100644 index 0000000000..55a781ca4e --- /dev/null +++ b/XSD/clipboarditemtype.xsd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/com.woltlab.wcf/clipboardaction.xml b/com.woltlab.wcf/clipboardaction.xml new file mode 100644 index 0000000000..ced15c163b --- /dev/null +++ b/com.woltlab.wcf/clipboardaction.xml @@ -0,0 +1,36 @@ + + + + + + 1 + + + + + + + + 2 + + + + + + + + 3 + + + + + + + + 4 + + + + + + diff --git a/com.woltlab.wcf/clipboarditemtype.xml b/com.woltlab.wcf/clipboarditemtype.xml new file mode 100644 index 0000000000..b51e7d4d00 --- /dev/null +++ b/com.woltlab.wcf/clipboarditemtype.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/wcfsetup/install/files/acp/js/WCF.ACP.js b/wcfsetup/install/files/acp/js/WCF.ACP.js index c8690e0042..51ad383f02 100644 --- a/wcfsetup/install/files/acp/js/WCF.ACP.js +++ b/wcfsetup/install/files/acp/js/WCF.ACP.js @@ -487,4 +487,45 @@ WCF.ACP.Options.prototype = { } } } +}; + +/** + * Namespace for WCF.ACP.User + */ +WCF.ACP.User = {}; + +/** + * UserList clipboard API + */ +WCF.ACP.User.List = function() { this.init(); }; +WCF.ACP.User.List.prototype = { + /** + * Initializes the UserList clipboard API. + */ + init: function() { + $('body').bind('clipboardAction', $.proxy(this.handleClipboardEvent, this)); + }, + + /** + * Event handler for clipboard editor item actions. + */ + handleClipboardEvent: function(event, type, actionName) { + // ignore unrelated events + if ((type != 'com.woltlab.wcf.user') || (actionName != 'user.delete')) return; + + var $item = $(event.target); + this._delete($item); + }, + + /** + * Handle delete action. + * + * @param jQuery item + */ + _delete: function(item) { + var $confirmMessage = item.data('internalData')['confirmMessage']; + if (confirm($confirmMessage)) { + WCF.Clipboard.sendRequest(item); + } + } }; \ No newline at end of file diff --git a/wcfsetup/install/files/js/WCF.js b/wcfsetup/install/files/js/WCF.js index 00903afa4f..a8f93aa497 100644 --- a/wcfsetup/install/files/js/WCF.js +++ b/wcfsetup/install/files/js/WCF.js @@ -407,6 +407,261 @@ $.extend(WCF, { } }); +/** + * Clipboard API + */ +WCF.Clipboard = { + /** + * action proxy object + * @var WCF.Action.Proxy + */ + _actionProxy: null, + + /** + * current page + * @var string + */ + _page: '', + + /** + * proxy object + * @var WCF.Action.Proxy + */ + _proxy: null, + + /** + * Initializes the clipboard API. + */ + init: function(page) { + this._page = page; + + this._actionProxy = new WCF.Action.Proxy({ + success: $.proxy(this._actionSuccess, this), + url: 'index.php?action=ClipboardProxy&t=' + SECURITY_TOKEN + SID_ARG_2ND + }); + + this._proxy = new WCF.Action.Proxy({ + success: $.proxy(this._success, this), + url: 'index.php?action=Clipboard&t=' + SECURITY_TOKEN + SID_ARG_2ND + }); + + // init containers first + $('.clipboardContainer').each($.proxy(function(index, container) { + this._initContainer(container); + }, this)); + }, + + /** + * Initializes a clipboard container. + * + * @param object container + */ + _initContainer: function(container) { + var $container = $(container); + + // fetch id or assign a random one if none found + var $id = $container.attr('id'); + if (!$id) { + $id = WCF.getRandomID(); + $container.attr('id', $id); + } + + // bind mark all checkboxes + $container.find('.clipboardMarkAll').each($.proxy(function(index, item) { + $(item).data('hasContainer', $id).click($.proxy(this._markAll, this)); + }, this)); + + // bind item checkboxes + $container.find('input.clipboardItem').each($.proxy(function(index, item) { + $(item).data('hasContainer', $id).click($.proxy(this._click, this)); + }, this)); + }, + + /** + * Processes change checkbox state. + * + * @param object event + */ + _click: function(event) { + var $item = $(event.target); + var $objectID = $item.data('objectID'); + var $isMarked = ($item.attr('checked')) ? true : false; + var $objectIDs = [ $objectID ]; + + // item is part of a container + if ($item.data('hasContainer')) { + var $container = $('#' + $item.data('hasContainer')); + var $type = $container.data('type'); + + // check if all items are marked + var $markedAll = true; + $container.find('input.clipboardItem').each(function(index, containerItem) { + var $containerItem = $(containerItem); + if (!$containerItem.attr('checked')) { + $markedAll = false; + } + }); + + // simulate a ticked 'markAll' checkbox + $container.find('.clipboardMarkAll').each(function(index, markAll) { + if ($markedAll) { + $(markAll).attr('checked', 'checked'); + } + else { + $(markAll).removeAttr('checked'); + } + }); + } + else { + // standalone item + var $type = $item.data('type'); + } + + this._saveState($type, $objectIDs, $isMarked); + }, + + /** + * Marks all associated clipboard items as checked. + * + * @param object event + */ + _markAll: function(event) { + var $item = $(event.target); + var $objectIDs = [ ]; + var $isMarked = true; + + // if markAll object is a checkbox, allow toggling + if ($item.getTagName() == 'input') { + $isMarked = $item.attr('checked'); + } + + // handle item containers + if ($item.data('hasContainer')) { + var $container = $('#' + $item.data('hasContainer')); + var $type = $container.data('type'); + + // toggle state for all associated items + $container.find('input.clipboardItem').each(function(index, containerItem) { + var $containerItem = $(containerItem); + if ($isMarked) { + if (!$containerItem.attr('checked')) { + $containerItem.attr('checked', 'checked'); + $objectIDs.push($containerItem.data('objectID')); + } + } + else { + if ($containerItem.attr('checked')) { + $containerItem.removeAttr('checked'); + $objectIDs.push($containerItem.data('objectID')); + } + } + }); + } + + // save new status + this._saveState($type, $objectIDs, $isMarked); + }, + + /** + * Saves clipboard item state. + * + * @param string type + * @param array objectIDs + * @param boolean isMarked + */ + _saveState: function(type, objectIDs, isMarked) { + this._proxy.setOption('data', { + action: (isMarked) ? 'mark' : 'unmark', + objectIDs: objectIDs, + pageClassName: this._page, + type: type + }) + this._proxy.sendRequest(); + }, + + /** + * Updates editor options. + * + * @param object data + * @param string textStatus + * @param jQuery jqXHR + */ + _success: function(data, textStatus, jqXHR) { + // clear all editors first + var $containers = {}; + $('.clipboardEditor').each(function(index, container) { + var $container = $(container); + var $typeName = $container.data('type'); + if ($typeName) { + $containers[$typeName] = $container; + } + + $container.empty(); + }); + + // do not buid new editors + if (!data.items) return; + + // rebuild editors + for (var $typeName in data.items) { + if (!$containers[$typeName]) { + continue; + } + + // create container + var $container = $containers[$typeName]; + var $editor = data.items[$typeName]; + var $label = $('' + $editor.label + '').appendTo($container).click(function(event) { + var $span = $(event.target); + $span.next().toggle(); + }); + var $list = $('
    ').appendTo($container).hide(); + + // create editor items + for (var $itemIndex in $editor.items) { + var $item = $editor.items[$itemIndex]; + var $listItem = $('
  1. ' + $item.label + '
  2. ').appendTo($list); + $listItem.data('actionName', $item.actionName).data('parameters', $item.parameters); + $listItem.data('internalData', $item.internalData).data('url', $item.url).data('type', $typeName); + + // bind event + $listItem.click($.proxy(this._executeAction, this)); + } + } + }, + + /** + * Executes a clipboard editor item action. + * + * @param object event + */ + _executeAction: function(event) { + var $listItem = $(event.target); + var $url = $listItem.data('url'); + if ($url) { + window.location.href = $url; + } + + // fire event + $listItem.trigger('clipboardAction', [ $listItem.data('type'), $listItem.data('actionName') ]); + }, + + /** + * Sends a clipboard proxy request. + * + * @param object item + */ + sendRequest: function(item) { + var $item = $(item); + + this._actionProxy.setOption('data', { + parameters: $item.data('parameters'), + typeName: $item.data('type') + }); + this._actionProxy.sendRequest(); + } +}; + /** * Provides a simple call for periodical executed functions. Based upon * ideas by Prototype's PeriodicalExecuter. diff --git a/wcfsetup/install/files/lib/data/clipboard/action/ClipboardAction.class.php b/wcfsetup/install/files/lib/data/clipboard/action/ClipboardAction.class.php new file mode 100644 index 0000000000..9df83387d8 --- /dev/null +++ b/wcfsetup/install/files/lib/data/clipboard/action/ClipboardAction.class.php @@ -0,0 +1,25 @@ + + * @package com.woltlab.wcf + * @subpackage data.clipboard.action + * @category Community Framework + */ +class ClipboardAction extends DatabaseObject { + /** + * @see wcf\data\DatabaseObject::$databaseTableName + */ + protected static $databaseTableName = 'clipboard_action'; + + /** + * @see wcf\data\DatabaseObject::$databaseTableIndexName + */ + protected static $databaseTableIndexName = 'actionID'; +} diff --git a/wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionAction.class.php b/wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionAction.class.php new file mode 100644 index 0000000000..917a1a0212 --- /dev/null +++ b/wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionAction.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf + * @subpackage data.clipboard.action + * @category Community Framework + */ +class ClipboardActionAction extends AbstractDatabaseObjectAction { + /** + * @see wcf\data\AbstractDatabaseObjectAction::$className + */ + protected $className = 'wcf\data\clipboard\action\ClipboardActionEditor'; +} diff --git a/wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionEditor.class.php b/wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionEditor.class.php new file mode 100644 index 0000000000..fbdced40d1 --- /dev/null +++ b/wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionEditor.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf + * @subpackage data.clipboard.action + * @category Community Framework + */ +class ClipboardActionEditor extends DatabaseObjectEditor { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\clipboard\action\ClipboardAction'; +} diff --git a/wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionList.class.php b/wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionList.class.php new file mode 100644 index 0000000000..a499a68902 --- /dev/null +++ b/wcfsetup/install/files/lib/data/clipboard/action/ClipboardActionList.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf + * @subpackage data.clipboard.action + * @category Community Framework + */ +class ClipboardActionList extends DatabaseObjectList { + /** + * @see wcf\data\DatabaseObjectList::$className + */ + public $className = 'wcf\data\clipboard\action\ClipboardAction'; +} diff --git a/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemType.class.php b/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemType.class.php new file mode 100644 index 0000000000..a9fa4af4c4 --- /dev/null +++ b/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemType.class.php @@ -0,0 +1,25 @@ + + * @package com.woltlab.wcf + * @subpackage data.clipboard.item.type + * @category Community Framework + */ +class ClipboardItemType extends DatabaseObject { + /** + * @see wcf\data\DatabaseObject::$databaseTableName + */ + protected static $databaseTableName = 'clipboard_item_type'; + + /** + * @see wcf\data\DatabaseObject::$databaseTableIndexName + */ + protected static $databaseTableIndexName = 'typeID'; +} diff --git a/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemTypeAction.class.php b/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemTypeAction.class.php new file mode 100644 index 0000000000..088a3bad38 --- /dev/null +++ b/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemTypeAction.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf + * @subpackage data.clipboard.item.type + * @category Community Framework + */ +class ClipboardItemTypeAction extends AbstractDatabaseObjectAction { + /** + * @see wcf\data\AbstractDatabaseObjectAction::$className + */ + protected $className = 'wcf\data\clipboard\item\type\ClipboardItemTypeEditor'; +} diff --git a/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemTypeEditor.class.php b/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemTypeEditor.class.php new file mode 100644 index 0000000000..498b5437dc --- /dev/null +++ b/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemTypeEditor.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf + * @subpackage data.clipboard.item.type + * @category Community Framework + */ +class ClipboardItemTypeEditor extends DatabaseObjectEditor { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\clipboard\item\type\ClipboardItemType'; +} diff --git a/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemTypeList.class.php b/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemTypeList.class.php new file mode 100644 index 0000000000..3adc7bd90a --- /dev/null +++ b/wcfsetup/install/files/lib/data/clipboard/item/type/ClipboardItemTypeList.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf + * @subpackage data.clipboard.item.type + * @category Community Framework + */ +class ClipboardItemTypeList extends DatabaseObjectList { + /** + * @see wcf\data\DatabaseObjectList::$className + */ + public $className = 'wcf\data\clipboard\item\type\ClipboardItemType'; +} diff --git a/wcfsetup/install/files/lib/system/clipboard/ClipboardEditorItem.class.php b/wcfsetup/install/files/lib/system/clipboard/ClipboardEditorItem.class.php new file mode 100644 index 0000000000..0bd933f94d --- /dev/null +++ b/wcfsetup/install/files/lib/system/clipboard/ClipboardEditorItem.class.php @@ -0,0 +1,141 @@ + + * @package com.woltlab.wcf + * @subpackage system.clipboard + * @category Community Framework + */ +final class ClipboardEditorItem { + /** + * internal data + * @var array + */ + protected $internalData = array(); + + /** + * item name + * @var string + */ + protected $name = ''; + + /** + * list of parameters passed to ClipboardProxyAction + * @var array + */ + protected $parameters = array(); + + /** + * redirect url + * @var string + */ + protected $url = ''; + + /** + * Returns internal data. + * + * @return array + */ + public function getInternalData() { + return $this->internalData; + } + + /** + * Returns item name. + * + * @return string + */ + public function getName() { + return $this->name; + } + + /** + * Returns parameters passed to ClipboardProxyAction. + * + * @return array + */ + public function getParameters() { + return $this->parameters; + } + + /** + * Returns redirect url. + * + * @return string + */ + public function getURL() { + return $this->url; + } + + /** + * Adds internal data, values will be left untouched by clipboard API. + * + * @param string $name + * @param mixed $value + */ + public function addInternalData($name, $value) { + if (!preg_match('~^[a-zA-Z]+$~', $name)) { + throw new SystemException("internal data name '".$name."' is invalid"); + } + + if (in_array($name, $this->internalData)) { + throw new SystemException("internal data name '".$name."' is not unique"); + } + + $this->internalData[$name] = $value; + } + + /** + * Adds an parameter passed to ClipboardProxyAction. + * + * @param string $name + * @param mixed $value + */ + public function addParameter($name, $value) { + if (!preg_match('~^[a-zA-Z]+$~', $name)) { + throw new SystemException("parameter name '".$name."' is invalid"); + } + + if (in_array($name, $this->parameters)) { + throw new SystemException("parameter name '".$name."' is not unique"); + } + + $this->parameters[$name] = $value; + } + + /** + * Sets item name. + * + * @param string $name + */ + public function setName($name) { + if (!preg_match('~^[a-zA-Z0-9\.]+$~', $name)) { + throw new SystemException("item name '".$name."' is invalid"); + } + + $this->name = $name; + } + + /** + * Sets redirect url, session id will be appended. + * + * @param string $url + */ + public function setURL($url) { + if (StringUtil::indexOf($url, '?') === false) { + $url .= SID_ARG_1ST; + } + else { + $url .= SID_ARG_2ND_NOT_ENCODED; + } + + $this->url = $url; + } +} diff --git a/wcfsetup/install/files/lib/system/clipboard/ClipboardHandler.class.php b/wcfsetup/install/files/lib/system/clipboard/ClipboardHandler.class.php new file mode 100644 index 0000000000..0cfa703ce0 --- /dev/null +++ b/wcfsetup/install/files/lib/system/clipboard/ClipboardHandler.class.php @@ -0,0 +1,260 @@ + + * @package com.woltlab.wcf + * @subpackage system.clipboard + * @category Community Framework + */ +class ClipboardHandler extends SingletonFactory { + /** + * cached list of actions + * @var array + */ + protected $actionCache = null; + + /** + * cached list of page actions + * @var array + */ + protected $pageCache = null; + + /** + * cached list of clipboard item types + * @var array + */ + protected $typeCache = null; + + /** + * @see wcf\system\SingletonFactory::init() + */ + protected function init() { + CacheHandler::getInstance()->addResource( + 'clipboard-itemType-'.PACKAGE_ID, + WCF_DIR.'cache/cache.clipboard-itemType-'.PACKAGE_ID.'.php', + 'wcf\system\cache\builder\ClipboardItemTypeCacheBuilder' + ); + $this->typeCache = CacheHandler::getInstance()->get('clipboard-itemType-'.PACKAGE_ID); + + CacheHandler::getInstance()->addResource( + 'clipboard-page-'.PACKAGE_ID, + WCF_DIR.'cache/cache.clipboard-page-'.PACKAGE_ID.'.php', + 'wcf\system\cache\builder\ClipboardPageCacheBuilder' + ); + $this->pageCache = CacheHandler::getInstance()->get('clipboard-page-'.PACKAGE_ID); + } + + /** + * Loads action cache. + */ + protected function loadActionCache() { + if ($this->actionCache !== null) return; + + CacheHandler::getInstance()->addResource( + 'clipboard-action-'.PACKAGE_ID, + WCF_DIR.'cache/cache.clipboard-action-'.PACKAGE_ID.'.php', + 'wcf\system\cache\builder\ClipboardActionCacheBuilder' + ); + $this->actionCache = CacheHandler::getInstance()->get('clipboard-action-'.PACKAGE_ID); + } + + /** + * Marks objects as marked. + * + * @param array $objectIDs + * @param integer $typeID + */ + public function mark(array $objectIDs, $typeID) { + // remove existing entries first, prevents conflict with INSERT + $this->unmark($objectIDs, $typeID); + + $sql = "INSERT INTO wcf".WCF_N."_clipboard_item + (typeID, userID, objectID) + VALUES (?, ?, ?)"; + $statement = WCF::getDB()->prepareStatement($sql); + foreach ($objectIDs as $objectID) { + $statement->execute(array( + $typeID, + WCF::getUser()->userID, + $objectID + )); + } + } + + /** + * Removes an object marking. + * + * @param array $objectIDs + * @param integer $typeID + */ + public function unmark(array $objectIDs, $typeID) { + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("typeID = ?", array($typeID)); + $conditions->add("objectID IN (?)", array($objectIDs)); + $conditions->add("userID = ?", array(WCF::getUser()->userID)); + + $sql = "DELETE FROM wcf".WCF_N."_clipboard_item + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + } + + /** + * Returns a type id by name. + * + * @param string $typeName + * @return integer + */ + public function getTypeID($typeName) { + if (isset($this->typeCache['typeNames'][$typeName])) { + return $this->typeCache['typeNames'][$typeName]; + } + + return null; + } + + /** + * Returns a type by type id. + * + * @param integer $typeID + * @return wcf\data\clipboard\item\type\ClipboardItemType + */ + public function getType($typeID) { + if (isset($this->typeCache['types'][$typeID])) { + return $this->typeCache['types'][$typeID]; + } + + return null; + } + + /** + * Returns a list of marked items grouped by type name. + * + * @param integer $typeID + * @return array + */ + public function getMarkedItems($typeID = null) { + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("userID = ?", array(WCF::getUser()->userID)); + if ($typeID !== null) $conditions->add("typeID = ?", array($typeID)); + + // fetch object ids + $sql = "SELECT typeID, objectID + FROM wcf".WCF_N."_clipboard_item + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + + // group object ids by type name + $data = array(); + while ($row = $statement->fetchArray()) { + $type = $this->getType($row['typeID']); + if ($type === null) { + continue; + } + + if (!isset($data[$type->typeName])) { + $data[$type->typeName] = array( + 'className' => $type->className, + 'objectIDs' => array() + ); + } + + $data[$type->typeName]['objectIDs'][] = $row['objectID']; + } + + // read objects + $objects = array(); + foreach ($data as $typeName => $objectData) { + $objectList = new $objectData['className'](); + $objectList->getConditionBuilder()->add($objectList->getDatabaseTableAlias() . "." . $objectList->getDatabaseTableIndexName() . " IN (?)", array($objectData['objectIDs'])); + $objectList->sqlLimit = 0; + $objectList->readObjects(); + + $objects[$typeName] = $objectList->getObjects(); + } + + return $objects; + } + + /** + * Returns items for clipboard editor. + * + * @param string $page + * @return array + */ + public function getEditorItems($page) { + // ignore unknown pages + if (!isset($this->pageCache[$page])) die('page '.$page.' is unknown'); + #return null; + + // get objects + $objects = $this->getMarkedItems(); + if (!count($objects)) return null; + + // fetch action ids + $this->loadActionCache(); + $actionIDs = array(); + foreach ($this->pageCache[$page] as $actionID) { + if (isset($this->actionCache[$actionID])) { + $actionIDs[] = $actionID; + } + } + $actionIDs = array_unique($actionIDs); + + // load actions + $actions = array(); + foreach ($actionIDs as $actionID) { + $actionClassName = $this->actionCache[$actionID]->actionClassName; + $actionName = $this->actionCache[$actionID]->actionName; + if (!isset($actions[$actionClassName])) { + // validate class + if (!ClassUtil::isInstanceOf($actionClassName, 'wcf\system\clipboard\action\IClipboardAction')) { + throw new SystemException("class '".$actionClassName."' does not implement the interface 'wcf\system\clipboard\action\IClipboardAction'."); + } + + $actions[$actionClassName] = array( + 'actions' => array(), + 'object' => new $actionClassName() + ); + } + + $actions[$actionClassName]['actions'][] = $actionName; + } + + // execute actions + $editorData = array(); + foreach ($actions as $actionData) { + // get accepted objects + $typeName = $actionData['object']->getTypeName(); + if (!isset($objects[$typeName])) continue; + + $editorData[$typeName] = array( + 'label' => $actionData['object']->getEditorLabel($objects[$typeName]), + 'items' => array() + ); + + foreach ($actionData['actions'] as $action) { + $data = $actionData['object']->execute($objects[$typeName], $action); + if ($data === null) { + continue; + } + + $editorData[$typeName]['items'][$action] = $data; + } + } + + return $editorData; + } +} diff --git a/wcfsetup/install/files/lib/system/clipboard/action/IClipboardAction.class.php b/wcfsetup/install/files/lib/system/clipboard/action/IClipboardAction.class.php new file mode 100644 index 0000000000..8ef8b1f63d --- /dev/null +++ b/wcfsetup/install/files/lib/system/clipboard/action/IClipboardAction.class.php @@ -0,0 +1,38 @@ + + * @package com.woltlab.wcf + * @subpackage system.clipboard.action + * @category Community Framework + */ +interface IClipboardAction { + /** + * Returns type name identifier. + * + * @return string + */ + public function getTypeName(); + + /** + * Returns action data, return NULL if action is not applicable. + * + * @param array $objects + * @param string $actionName + * @return wcf\system\clipboard\ClipboardEditorItem + */ + public function execute(array $objects, $actionName); + + /** + * Returns label for item editor. + * + * @param array $objects + * @return string + */ + public function getEditorLabel(array $objects); +} diff --git a/wcfsetup/install/files/lib/system/clipboard/action/UserClipboardAction.class.php b/wcfsetup/install/files/lib/system/clipboard/action/UserClipboardAction.class.php new file mode 100644 index 0000000000..361327a6a3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/clipboard/action/UserClipboardAction.class.php @@ -0,0 +1,144 @@ + + * @package com.woltlab.wcf + * @subpackage system.clipboard.action + * @category Community Framework + */ +class UserClipboardAction implements IClipboardAction { + /** + * @see wcf\system\clipboard\action\IClipboardAction::getTypeName() + */ + public function getTypeName() { + return 'com.woltlab.wcf.user'; + } + + /** + * @see wcf\system\clipboard\action\IClipboardAction::execute() + */ + public function execute(array $objects, $actionName) { + $item = new ClipboardEditorItem(); + + // handle actions + switch ($actionName) { + case 'assignToGroup': + $item->setName('user.assignToGroup'); + $item->setURL('index.php?form=UserAssignToGroup'); + break; + + case 'delete': + $count = $this->validateDelete($objects); + if (!$count) { + return null; + } + + // TODO: use language variable + $item->addInternalData('confirmMessage', 'Delete '.$count.' users?'); + $item->addParameter('actionName', 'delete'); + $item->addParameter('className', 'wcf\data\user\UserAction'); + $item->setName('user.delete'); + break; + + case 'exportMailAddress': + $item->setName('user.exportMailAddress'); + $item->setURL('index.php?form=UserEmailAddressExport'); + break; + + case 'sendMail': + $item->setName('user.sendMail'); + $item->setURL('index.php?form=UserMail'); + break; + + default: + throw new SystemException("action '".$actionName."' is invalid"); + break; + } + + return $item; + } + + /** + * Returns number of users which can be deleted. + * + * @param array $objects + * @return integer + */ + protected function validateDelete(array $objects) { + // check permissions + if (!WCF::getSession()->getPermission('admin.user.canDeleteUser')) { + return 0; + } + + // user cannot delete itself + $count = count($objects); + $userIDs = array_keys($objects); + foreach ($userIDs as $index => $userID) { + if ($userID == WCF::getUser()->userID) { + $count--; + unset($objects[$userID]); + unset($userIDs[$index]); + } + } + + // no valid users found + if (!$count) return 0; + + // fetch user to group associations + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("userID IN (?)", array($userIDs)); + + $sql = "SELECT userID, groupID + FROM wcf".WCF_N."_user_to_group + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + + $userToGroup = array(); + while ($row = $statement->fetchArray()) { + if (!isset($userToGroup[$row['userID']])) { + $userToGroup[$row['userID']] = array(); + } + + $userToGroup[$row['userID']][] = $row['groupID']; + } + + // validate if user's group is accessible for current user + $count = count($objects); + foreach ($userIDs as $index => $userID) { + if (!isset($userToGroup[$userID])) { + $count--; + continue; + } + + if (!UserGroup::isAccessibleGroup($userToGroup[$userID])) { + $count--; + } + } + + return $count; + } + + /** + * @see wcf\system\clipboard\action\IClipboardAction::getEditorLabel() + * @todo use language variable + */ + public function getEditorLabel(array $objects) { + $count = count($objects); + if ($count == 1) { + return 'One user marked'; + } + else { + return $count . ' users marked'; + } + } +} diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index 15a1d156aa..7037876840 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -96,6 +96,37 @@ CREATE TABLE wcf1_cleanup_listener ( UNIQUE KEY (className, packageID) ); +DROP TABLE IF EXISTS wcf1_clipboard_action; +CREATE TABLE wcf1_clipboard_action ( + actionID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + packageID INT(10) NOT NULL DEFAULT 0, + actionName VARCHAR(50) NOT NULL DEFAULT '', + actionClassName VARCHAR(200) NOT NULL DEFAULT '', + sortOrder INT(10) NOT NULL DEFAULT 0 +); + +DROP TABLE IF EXISTS wcf1_clipboard_item; +CREATE TABLE wcf1_clipboard_item ( + typeID INT(10) NOT NULL DEFAULT 0, + userID INT(10) NOT NULL DEFAULT 0, + objectID INT(10) NOT NULL DEFAULT 0 +); + +DROP TABLE IF EXISTS wcf1_clipboard_item_type; +CREATE TABLE wcf1_clipboard_item_type ( + typeID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + packageID INT(10) NOT NULL DEFAULT 0, + typeName VARCHAR(80) NOT NULL DEFAULT '', + className VARCHAR(200) NOT NULL DEFAULT '' +); + +DROP TABLE IF EXISTS wcf1_clipboard_page; +CREATE TABLE wcf1_clipboard_page ( + pageClassName VARCHAR(80) NOT NULL DEFAULT '', + packageID INT(10) NOT NULL DEFAULT 0, + actionID INT(10) NOT NULL DEFAULT 0 +); + DROP TABLE IF EXISTS wcf1_cleanup_log; CREATE TABLE wcf1_cleanup_log ( packageID INT(10) NOT NULL DEFAULT 0,