3 namespace wcf\system\clipboard
;
5 use wcf\data\DatabaseObject
;
6 use wcf\data\DatabaseObjectList
;
7 use wcf\data\
object\type\ObjectType
;
8 use wcf\data\
object\type\ObjectTypeCache
;
9 use wcf\system\cache\builder\ClipboardActionCacheBuilder
;
10 use wcf\system\cache\builder\ClipboardPageCacheBuilder
;
11 use wcf\system\clipboard\action\IClipboardAction
;
12 use wcf\system\database\util\PreparedStatementConditionBuilder
;
13 use wcf\system\exception\ImplementationException
;
14 use wcf\system\exception\SystemException
;
15 use wcf\system\SingletonFactory
;
19 * Handles clipboard-related actions.
21 * @author Alexander Ebert
22 * @copyright 2001-2019 WoltLab GmbH
23 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
24 * @package WoltLabSuite\Core\System\Clipboard
26 class ClipboardHandler
extends SingletonFactory
29 * cached list of actions
32 protected $actionCache;
35 * cached list of clipboard item types
41 * list of marked items
42 * @var DatabaseObject[][]
44 protected $markedItems;
47 * cached list of page actions
53 * list of page class names
56 protected $pageClasses = [];
62 protected $pageObjectID = 0;
67 protected function init()
71 'objectTypeNames' => [],
73 $cache = ObjectTypeCache
::getInstance()->getObjectTypes('com.woltlab.wcf.clipboardItem');
74 foreach ($cache as $objectType) {
75 $this->cache
['objectTypes'][$objectType->objectTypeID
] = $objectType;
76 $this->cache
['objectTypeNames'][$objectType->objectType
] = $objectType->objectTypeID
;
79 $this->pageCache
= ClipboardPageCacheBuilder
::getInstance()->getData();
85 protected function loadActionCache()
87 if ($this->actionCache
!== null) {
91 $this->actionCache
= ClipboardActionCacheBuilder
::getInstance()->getData();
95 * Marks objects as marked.
97 * @param array $objectIDs
98 * @param int $objectTypeID
100 public function mark(array $objectIDs, $objectTypeID)
102 // remove existing entries first, prevents conflict with INSERT
103 $this->unmark($objectIDs, $objectTypeID);
105 $sql = "INSERT INTO wcf" . WCF_N
. "_clipboard_item
106 (objectTypeID, userID, objectID)
108 $statement = WCF
::getDB()->prepareStatement($sql);
109 foreach ($objectIDs as $objectID) {
110 $statement->execute([
112 WCF
::getUser()->userID
,
119 * Removes an object marking.
121 * @param array $objectIDs
122 * @param int $objectTypeID
124 public function unmark(array $objectIDs, $objectTypeID)
126 $conditions = new PreparedStatementConditionBuilder();
127 $conditions->add("objectTypeID = ?", [$objectTypeID]);
128 $conditions->add("objectID IN (?)", [$objectIDs]);
129 $conditions->add("userID = ?", [WCF
::getUser()->userID
]);
131 $sql = "DELETE FROM wcf" . WCF_N
. "_clipboard_item
133 $statement = WCF
::getDB()->prepareStatement($sql);
134 $statement->execute($conditions->getParameters());
138 * Unmarks all items of given type.
140 * @param int $objectTypeID
142 public function unmarkAll($objectTypeID)
144 $sql = "DELETE FROM wcf" . WCF_N
. "_clipboard_item
145 WHERE objectTypeID = ?
147 $statement = WCF
::getDB()->prepareStatement($sql);
148 $statement->execute([
150 WCF
::getUser()->userID
,
155 * Returns the id of the clipboard object type with the given name or `null` if no such
156 * clipboard object type exists.
158 * @param string $typeName
161 public function getObjectTypeID($typeName)
163 return $this->cache
['objectTypeNames'][$typeName] ??
null;
167 * Returns the clipboard object type with the given id or `null` if no such
168 * clipboard object type exists.
170 * @param int $objectTypeID
171 * @return ObjectType|null
173 public function getObjectType($objectTypeID)
175 return $this->cache
['objectTypes'][$objectTypeID] ??
null;
179 * Returns the id of the clipboard object type with the given name or `null` if no such
180 * clipboard object type exists.
182 * @param string $objectType
185 public function getObjectTypeByName($objectType)
187 foreach ($this->cache
['objectTypes'] as $objectTypeID => $objectTypeObj) {
188 if ($objectTypeObj->objectType
== $objectType) {
189 return $objectTypeID;
197 * Loads a list of marked items grouped by type name.
199 * @param int $objectTypeID
200 * @throws SystemException
202 protected function loadMarkedItems($objectTypeID = null)
204 if ($this->markedItems
=== null) {
205 $this->markedItems
= [];
208 if ($objectTypeID !== null) {
209 $objectType = $this->getObjectType($objectTypeID);
210 if ($objectType === null) {
211 throw new SystemException("object type id " . $objectTypeID . " is invalid");
214 if (!isset($this->markedItems
[$objectType->objectType
])) {
215 $this->markedItems
[$objectType->objectType
] = [];
219 $conditions = new PreparedStatementConditionBuilder();
220 $conditions->add("userID = ?", [WCF
::getUser()->userID
]);
221 if ($objectTypeID !== null) {
222 $conditions->add("objectTypeID = ?", [$objectTypeID]);
226 $sql = "SELECT objectTypeID, objectID
227 FROM wcf" . WCF_N
. "_clipboard_item
229 $statement = WCF
::getDB()->prepareStatement($sql);
230 $statement->execute($conditions->getParameters());
232 // group object ids by type name
234 while ($row = $statement->fetchArray()) {
235 $objectType = $this->getObjectType($row['objectTypeID']);
236 if ($objectType === null) {
240 if (!isset($data[$objectType->objectType
])) {
241 /** @noinspection PhpUndefinedFieldInspection */
242 $listClassName = $objectType->listclassname
;
243 if ($listClassName == '') {
244 throw new SystemException("Missing list class for object type '" . $objectType->objectType
. "'");
247 $data[$objectType->objectType
] = [
248 'className' => $listClassName,
253 $data[$objectType->objectType
]['objectIDs'][] = $row['objectID'];
257 foreach ($data as $objectType => $objectData) {
258 /** @var DatabaseObjectList $objectList */
259 $objectList = new $objectData['className']();
260 $objectList->getConditionBuilder()->add(
261 $objectList->getDatabaseTableAlias() . "." . $objectList->getDatabaseTableIndexName() . " IN (?)",
262 [$objectData['objectIDs']]
264 $objectList->readObjects();
266 $this->markedItems
[$objectType] = $objectList->getObjects();
268 // validate object ids against loaded items (check for zombie object ids)
269 $indexName = $objectList->getDatabaseTableIndexName();
270 foreach ($this->markedItems
[$objectType] as $object) {
271 /** @noinspection PhpVariableVariableInspection */
272 $index = \array_search
($object->{$indexName}, $objectData['objectIDs']);
273 unset($objectData['objectIDs'][$index]);
276 if (!empty($objectData['objectIDs'])) {
277 $conditions = new PreparedStatementConditionBuilder();
278 $conditions->add("objectTypeID = ?", [$this->getObjectTypeByName($objectType)]);
279 $conditions->add("userID = ?", [WCF
::getUser()->userID
]);
280 $conditions->add("objectID IN (?)", [$objectData['objectIDs']]);
282 $sql = "DELETE FROM wcf" . WCF_N
. "_clipboard_item
284 $statement = WCF
::getDB()->prepareStatement($sql);
285 $statement->execute($conditions->getParameters());
291 * Loads a list of marked items grouped by type name.
293 * @param int $objectTypeID
296 public function getMarkedItems($objectTypeID = null)
298 if ($this->markedItems
=== null) {
299 $this->loadMarkedItems($objectTypeID);
302 if ($objectTypeID !== null) {
303 $objectType = $this->getObjectType($objectTypeID);
304 if (!isset($this->markedItems
[$objectType->objectType
])) {
305 $this->loadMarkedItems($objectTypeID);
308 return $this->markedItems
[$objectType->objectType
];
311 return $this->markedItems
;
315 * Returns `true` if the object with the given data is marked.
317 public function isMarked(int $objectTypeID, int $objectID): bool
319 return isset($this->getMarkedItems($objectTypeID)[$objectID]);
323 * Returns the data of the items for clipboard editor or `null` if no items are marked.
325 * @param string|string[] $page
326 * @param int $pageObjectID
328 * @throws ImplementationException
330 public function getEditorItems($page, $pageObjectID)
333 if (!\
is_array($pages)) {
337 $this->pageClasses
= [];
338 $this->pageObjectID
= 0;
341 $this->loadMarkedItems();
342 if (empty($this->markedItems
)) {
346 $this->pageClasses
= $pages;
347 $this->pageObjectID
= $pageObjectID;
350 $this->loadActionCache();
352 foreach ($pages as $page) {
353 foreach ($this->pageCache
[$page] as $actionID) {
354 if (isset($this->actionCache
[$actionID])) {
355 $actionIDs[] = $actionID;
359 $actionIDs = \array_unique
($actionIDs);
363 foreach ($actionIDs as $actionID) {
364 $actionObject = $this->actionCache
[$actionID];
365 $actionClassName = $actionObject->actionClassName
;
366 if (!isset($actions[$actionClassName])) {
368 if (!\
is_subclass_of($actionClassName, IClipboardAction
::class)) {
369 throw new ImplementationException($actionClassName, IClipboardAction
::class);
372 $actions[$actionClassName] = [
374 'object' => new $actionClassName(),
378 $actions[$actionClassName]['actions'][] = $actionObject;
383 foreach ($actions as $actionData) {
384 /** @var IClipboardAction $clipboardAction */
385 $clipboardAction = $actionData['object'];
387 // get accepted objects
388 $typeName = $clipboardAction->getTypeName();
389 if (!isset($this->markedItems
[$typeName]) ||
empty($this->markedItems
[$typeName])) {
393 if (!isset($editorData[$typeName])) {
394 $editorData[$typeName] = [
395 'label' => $clipboardAction->getEditorLabel($this->markedItems
[$typeName]),
397 'reloadPageOnSuccess' => $clipboardAction->getReloadPageOnSuccess(),
400 $editorData[$typeName]['reloadPageOnSuccess'] = \array_unique
(\array_merge
(
401 $editorData[$typeName]['reloadPageOnSuccess'],
402 $clipboardAction->getReloadPageOnSuccess()
406 foreach ($actionData['actions'] as $actionObject) {
407 $data = $clipboardAction->execute($this->markedItems
[$typeName], $actionObject);
408 if ($data === null) {
412 $editorData[$typeName]['items'][$actionObject->showOrder
] = $data;
420 * Removes items from clipboard.
424 public function removeItems($typeID = null)
426 $conditions = new PreparedStatementConditionBuilder();
427 $conditions->add("userID = ?", [WCF
::getUser()->userID
]);
428 if ($typeID !== null) {
429 $conditions->add("objectTypeID = ?", [$typeID]);
432 $sql = "DELETE FROM wcf" . WCF_N
. "_clipboard_item
434 $statement = WCF
::getDB()->prepareStatement($sql);
435 $statement->execute($conditions->getParameters());
439 * Returns true (1) if at least one item (of the given object type) is marked.
441 * @param int $objectTypeID
444 public function hasMarkedItems($objectTypeID = null)
446 if (!WCF
::getUser()->userID
) {
450 $conditionBuilder = new PreparedStatementConditionBuilder();
451 $conditionBuilder->add("userID = ?", [WCF
::getUser()->userID
]);
452 if ($objectTypeID !== null) {
453 $conditionBuilder->add("objectTypeID = ?", [$objectTypeID]);
456 $sql = "SELECT COUNT(*)
457 FROM wcf" . WCF_N
. "_clipboard_item
458 " . $conditionBuilder;
459 $statement = WCF
::getDB()->prepareStatement($sql);
460 $statement->execute($conditionBuilder->getParameters());
462 return $statement->fetchSingleColumn() ?
1 : 0;
466 * Returns the list of page class names.
470 public function getPageClasses()
472 return $this->pageClasses
;
476 * Returns page object id.
480 public function getPageObjectID()
482 return $this->pageObjectID
;