3 namespace wcf\data\attachment
;
5 use wcf\data\AbstractDatabaseObjectAction
;
6 use wcf\data\ISortableAction
;
7 use wcf\data\IUploadAction
;
8 use wcf\data\
object\type\ObjectTypeCache
;
9 use wcf\system\attachment\AttachmentHandler
;
10 use wcf\system\database\util\PreparedStatementConditionBuilder
;
11 use wcf\system\event\EventHandler
;
12 use wcf\system\exception\PermissionDeniedException
;
13 use wcf\system\exception\UserInputException
;
14 use wcf\system\upload\DefaultUploadFileSaveStrategy
;
15 use wcf\system\upload\DefaultUploadFileValidationStrategy
;
16 use wcf\system\upload\UploadFile
;
18 use wcf\util\ArrayUtil
;
19 use wcf\util\FileUtil
;
22 * Executes attachment-related actions.
25 * @copyright 2001-2019 WoltLab GmbH
26 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
27 * @package WoltLabSuite\Core\Data\Attachment
29 * @method Attachment create()
30 * @method AttachmentEditor[] getObjects()
31 * @method AttachmentEditor getSingleObject()
33 class AttachmentAction
extends AbstractDatabaseObjectAction
implements ISortableAction
, IUploadAction
38 protected $allowGuestAccess = ['delete', 'updatePosition', 'upload'];
43 protected $className = AttachmentEditor
::class;
46 * current attachment object, used to communicate with event listeners
49 public $eventAttachment;
52 * current data, used to communicate with event listeners.
55 public $eventData = [];
60 public function validateDelete()
63 if (empty($this->objects
)) {
66 if (empty($this->objects
)) {
67 throw new UserInputException('objectIDs');
71 foreach ($this->getObjects() as $attachment) {
72 if ($attachment->tmpHash
) {
73 if ($attachment->userID
!= WCF
::getUser()->userID
) {
74 throw new PermissionDeniedException();
76 } elseif (!$attachment->canDelete()) {
77 // admin can always delete attachments (unless they are private)
78 if (!WCF
::getSession()->getPermission('admin.attachment.canManageAttachment') || ObjectTypeCache
::getInstance()->getObjectType($attachment->objectTypeID
)->private) {
79 throw new PermissionDeniedException();
88 public function validateUpload()
91 if (isset($_POST['isFallback'])) {
92 $this->parameters
['objectType'] = $_POST['objectType'] ??
'';
93 $this->parameters
['objectID'] = $_POST['objectID'] ??
0;
94 $this->parameters
['parentObjectID'] = $_POST['parentObjectID'] ??
0;
95 $this->parameters
['tmpHash'] = $_POST['tmpHash'] ??
'';
99 $this->readString('objectType');
100 $this->readInteger('objectID', true);
101 $this->readInteger('parentObjectID', true);
102 $this->readString('tmpHash');
104 // validate object type
105 $objectType = ObjectTypeCache
::getInstance()->getObjectTypeByName(
106 'com.woltlab.wcf.attachment.objectType',
107 $this->parameters
['objectType']
109 if ($objectType === null) {
110 throw new UserInputException('objectType');
114 $processor = $objectType->getProcessor();
116 // check upload permissions
118 !$processor->canUpload(
119 (!empty($this->parameters
['objectID']) ? \
intval($this->parameters
['objectID']) : 0),
120 (!empty($this->parameters
['parentObjectID']) ? \
intval($this->parameters
['parentObjectID']) : 0)
123 throw new PermissionDeniedException();
126 // check max count of uploads
127 $handler = new AttachmentHandler(
128 $this->parameters
['objectType'],
129 \
intval($this->parameters
['objectID']),
130 $this->parameters
['tmpHash']
132 /** @noinspection PhpUndefinedMethodInspection */
133 if ($handler->count() + \
count($this->parameters
['__files']->getFiles()) > $processor->getMaxCount()) {
134 throw new UserInputException('files', 'exceededQuota', [
135 'current' => $handler->count(),
136 'quota' => $processor->getMaxCount(),
140 // check max filesize, allowed file extensions etc.
141 /** @noinspection PhpUndefinedMethodInspection */
142 $this->parameters
['__files']->validateFiles(new DefaultUploadFileValidationStrategy(
143 $processor->getMaxSize(),
144 $processor->getAllowedExtensions()
151 public function upload()
154 $objectType = ObjectTypeCache
::getInstance()->getObjectTypeByName(
155 'com.woltlab.wcf.attachment.objectType',
156 $this->parameters
['objectType']
160 $saveStrategy = new DefaultUploadFileSaveStrategy(self
::class, [
161 'generateThumbnails' => true,
162 'rotateImages' => true,
164 'objectID' => \
intval($this->parameters
['objectID']),
165 'objectTypeID' => $objectType->objectTypeID
,
166 'tmpHash' => !$this->parameters
['objectID'] ?
$this->parameters
['tmpHash'] : '',
169 /** @noinspection PhpUndefinedMethodInspection */
170 $this->parameters
['__files']->saveFiles($saveStrategy);
172 /** @var Attachment[] $attachments */
173 $attachments = $saveStrategy->getObjects();
176 $result = ['attachments' => [], 'errors' => []];
177 if (!empty($attachments)) {
178 // get attachment ids
179 $attachmentIDs = $attachmentToFileID = [];
180 foreach ($attachments as $internalFileID => $attachment) {
181 $attachmentIDs[] = $attachment->attachmentID
;
182 $attachmentToFileID[$attachment->attachmentID
] = $internalFileID;
185 // get attachments from database (check thumbnail status)
186 $attachmentList = new AttachmentList();
187 $attachmentList->setObjectIDs($attachmentIDs);
188 $attachmentList->readObjects();
190 foreach ($attachmentList as $attachment) {
191 $result['attachments'][$attachmentToFileID[$attachment->attachmentID
]] = [
192 'filename' => $attachment->filename
,
193 'filesize' => $attachment->filesize
,
194 'formattedFilesize' => FileUtil
::formatFilesize($attachment->filesize
),
195 'isImage' => $attachment->isImage
,
196 'attachmentID' => $attachment->attachmentID
,
197 'tinyURL' => $attachment->tinyThumbnailType ?
$attachment->getThumbnailLink('tiny') : '',
198 'thumbnailURL' => $attachment->thumbnailType ?
$attachment->getThumbnailLink('thumbnail') : '',
199 'url' => $attachment->getLink(),
200 'height' => $attachment->height
,
201 'width' => $attachment->width
,
202 'iconName' => $attachment->getIconName(),
207 /** @noinspection PhpUndefinedMethodInspection */
208 /** @var UploadFile[] $files */
209 $files = $this->parameters
['__files']->getFiles();
210 foreach ($files as $file) {
211 if ($file->getValidationErrorType()) {
212 $result['errors'][$file->getInternalFileID()] = [
213 'filename' => $file->getFilename(),
214 'filesize' => $file->getFilesize(),
215 'errorType' => $file->getValidationErrorType(),
216 'additionalData' => $file->getValidationErrorAdditionalData(),
225 * Generates thumbnails.
227 public function generateThumbnails()
229 if (empty($this->objects
)) {
230 $this->readObjects();
233 $saveStrategy = new DefaultUploadFileSaveStrategy(self
::class);
235 foreach ($this->getObjects() as $attachment) {
236 if (!$attachment->isImage
) {
237 // create thumbnails for every file that isn't an image
238 $this->eventAttachment
= $attachment;
239 $this->eventData
= [];
241 EventHandler
::getInstance()->fireAction($this, 'generateThumbnail');
243 if (!empty($this->eventData
)) {
244 $attachment->update($this->eventData
);
250 $saveStrategy->generateThumbnails($attachment->getDecoratedObject());
257 public function validateUpdatePosition()
259 $this->readInteger('objectID', true);
260 $this->readString('objectType');
261 $this->readString('tmpHash', true);
263 if (empty($this->parameters
['objectID']) && empty($this->parameters
['tmpHash'])) {
264 throw new UserInputException('objectID');
267 // validate object type
268 $objectType = ObjectTypeCache
::getInstance()->getObjectTypeByName(
269 'com.woltlab.wcf.attachment.objectType',
270 $this->parameters
['objectType']
272 if ($objectType === null) {
273 throw new UserInputException('objectType');
276 if (!empty($this->parameters
['objectID'])) {
277 // check upload permissions
278 if (!$objectType->getProcessor()->canUpload($this->parameters
['objectID'])) {
279 throw new PermissionDeniedException();
283 if (!isset($this->parameters
['attachmentIDs']) ||
!\
is_array($this->parameters
['attachmentIDs'])) {
284 throw new UserInputException('attachmentIDs');
287 $this->parameters
['attachmentIDs'] = ArrayUtil
::toIntegerArray($this->parameters
['attachmentIDs']);
289 // check attachment ids
290 $conditions = new PreparedStatementConditionBuilder();
291 $conditions->add("attachmentID IN (?)", [$this->parameters
['attachmentIDs']]);
292 $conditions->add("objectTypeID = ?", [$objectType->objectTypeID
]);
294 if (!empty($this->parameters
['objectID'])) {
295 $conditions->add("objectID = ?", [$this->parameters
['objectID']]);
297 $conditions->add("tmpHash = ?", [$this->parameters
['tmpHash']]);
300 $sql = "SELECT attachmentID
301 FROM wcf" . WCF_N
. "_attachment
303 $statement = WCF
::getDB()->prepareStatement($sql);
304 $statement->execute($conditions->getParameters());
305 $attachmentIDs = $statement->fetchAll(\PDO
::FETCH_COLUMN
);
307 foreach ($this->parameters
['attachmentIDs'] as $attachmentID) {
308 if (!\
in_array($attachmentID, $attachmentIDs)) {
309 throw new UserInputException('attachmentIDs');
317 public function updatePosition()
319 $sql = "UPDATE wcf" . WCF_N
. "_attachment
321 WHERE attachmentID = ?";
322 $statement = WCF
::getDB()->prepareStatement($sql);
324 WCF
::getDB()->beginTransaction();
326 foreach ($this->parameters
['attachmentIDs'] as $attachmentID) {
327 $statement->execute([
332 WCF
::getDB()->commitTransaction();
336 * Copies attachments from one object id to another.
338 public function copy()
340 $sourceObjectType = ObjectTypeCache
::getInstance()->getObjectTypeByName(
341 'com.woltlab.wcf.attachment.objectType',
342 $this->parameters
['sourceObjectType']
344 $targetObjectType = ObjectTypeCache
::getInstance()->getObjectTypeByName(
345 'com.woltlab.wcf.attachment.objectType',
346 $this->parameters
['targetObjectType']
349 $attachmentList = new AttachmentList();
350 $attachmentList->getConditionBuilder()->add("attachment.objectTypeID = ?", [$sourceObjectType->objectTypeID
]);
351 $attachmentList->getConditionBuilder()->add("attachment.objectID = ?", [$this->parameters
['sourceObjectID']]);
352 $attachmentList->readObjects();
354 $newAttachmentIDs = [];
355 foreach ($attachmentList as $attachment) {
356 $newAttachment = AttachmentEditor
::create([
357 'objectTypeID' => $targetObjectType->objectTypeID
,
358 'objectID' => $this->parameters
['targetObjectID'],
359 'userID' => $attachment->userID
,
360 'filename' => $attachment->filename
,
361 'filesize' => $attachment->filesize
,
362 'fileType' => $attachment->fileType
,
363 'fileHash' => $attachment->fileHash
,
364 'isImage' => $attachment->isImage
,
365 'width' => $attachment->width
,
366 'height' => $attachment->height
,
367 'tinyThumbnailType' => $attachment->tinyThumbnailType
,
368 'tinyThumbnailSize' => $attachment->tinyThumbnailSize
,
369 'tinyThumbnailWidth' => $attachment->tinyThumbnailWidth
,
370 'tinyThumbnailHeight' => $attachment->tinyThumbnailHeight
,
371 'thumbnailType' => $attachment->thumbnailType
,
372 'thumbnailSize' => $attachment->thumbnailSize
,
373 'thumbnailWidth' => $attachment->thumbnailWidth
,
374 'thumbnailHeight' => $attachment->thumbnailHeight
,
375 'downloads' => $attachment->downloads
,
376 'lastDownloadTime' => $attachment->lastDownloadTime
,
377 'uploadTime' => $attachment->uploadTime
,
378 'showOrder' => $attachment->showOrder
,
382 @\
copy($attachment->getLocation(), $newAttachment->getLocation());
384 if ($attachment->tinyThumbnailSize
) {
385 @\
copy($attachment->getTinyThumbnailLocation(), $newAttachment->getTinyThumbnailLocation());
387 if ($attachment->thumbnailSize
) {
388 @\
copy($attachment->getThumbnailLocation(), $newAttachment->getThumbnailLocation());
391 $newAttachmentIDs[$attachment->attachmentID
] = $newAttachment->attachmentID
;
395 'attachmentIDs' => $newAttachmentIDs,