PageRenderTime 74ms CodeModel.GetById 1ms RepoModel.GetById 1ms app.codeStats 0ms

/wcfsetup/install/files/lib/data/attachment/Attachment.class.php

https://github.com/WoltLab/WCF
PHP | 364 lines | 183 code | 36 blank | 145 comment | 24 complexity | 858e297b6fdaaffb7ec8818cbda5826b MD5 | raw file
  1. <?php
  2. namespace wcf\data\attachment;
  3. use wcf\data\DatabaseObject;
  4. use wcf\data\ILinkableObject;
  5. use wcf\data\IThumbnailFile;
  6. use wcf\data\object\type\ObjectTypeCache;
  7. use wcf\system\request\IRouteController;
  8. use wcf\system\request\LinkHandler;
  9. use wcf\system\WCF;
  10. use wcf\util\FileUtil;
  11. /**
  12. * Represents an attachment.
  13. *
  14. * @author Marcel Werk
  15. * @copyright 2001-2019 WoltLab GmbH
  16. * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  17. * @package WoltLabSuite\Core\Data\Attachment
  18. *
  19. * @property-read int $attachmentID unique id of the attachment
  20. * @property-read int $objectTypeID id of the `com.woltlab.wcf.attachment.objectType` object type
  21. * @property-read int|null $objectID id of the attachment container object the attachment belongs to
  22. * @property-read int|null $userID id of the user who uploaded the attachment or `null` if the user does not exist anymore or if the attachment has been uploaded by a guest
  23. * @property-read string $tmpHash temporary hash used to identify uploaded attachments but not associated with an object yet or empty if the attachment has been associated with an object
  24. * @property-read string $filename name of the physical attachment file
  25. * @property-read int $filesize size of the physical attachment file
  26. * @property-read string $fileType type of the physical attachment file
  27. * @property-read string $fileHash hash of the physical attachment file
  28. * @property-read int $isImage is `1` if the attachment is an image, otherwise `0`
  29. * @property-read int $width width of the attachment if `$isImage` is `1`, otherwise `0`
  30. * @property-read int $height height of the attachment if `$isImage` is `1`, otherwise `0`
  31. * @property-read string $tinyThumbnailType type of the tiny thumbnail file for the attachment if `$isImage` is `1`, otherwise empty
  32. * @property-read int $tinyThumbnailSize size of the tiny thumbnail file for the attachment if `$isImage` is `1`, otherwise `0`
  33. * @property-read int $tinyThumbnailWidth width of the tiny thumbnail file for the attachment if `$isImage` is `1`, otherwise `0`
  34. * @property-read int $tinyThumbnailHeight height of the tiny thumbnail file for the attachment if `$isImage` is `1`, otherwise `0`
  35. * @property-read string $thumbnailType type of the thumbnail file for the attachment if `$isImage` is `1`, otherwise empty
  36. * @property-read int $thumbnailSize size of the thumbnail file for the attachment if `$isImage` is `1`, otherwise `0`
  37. * @property-read int $thumbnailWidth width of the thumbnail file for the attachment if `$isImage` is `1`, otherwise `0`
  38. * @property-read int $thumbnailHeight height of the thumbnail file for the attachment if `$isImage` is `1`, otherwise `0`
  39. * @property-read int $downloads number of times the attachment has been downloaded
  40. * @property-read int $lastDownloadTime timestamp at which the attachment has been downloaded the last time
  41. * @property-read int $uploadTime timestamp at which the attachment has been uploaded
  42. * @property-read int $showOrder position of the attachment in relation to the other attachment to the same message
  43. */
  44. class Attachment extends DatabaseObject implements ILinkableObject, IRouteController, IThumbnailFile
  45. {
  46. /**
  47. * indicates if the attachment is embedded
  48. * @var bool
  49. */
  50. protected $embedded = false;
  51. /**
  52. * user permissions for attachment access
  53. * @var bool[]
  54. */
  55. protected $permissions = [];
  56. /**
  57. * @inheritDoc
  58. */
  59. public function getLink()
  60. {
  61. // Do not use `LinkHandler::getControllerLink()` or `forceFrontend` as attachment
  62. // links can be opened in the frontend and in the ACP.
  63. return LinkHandler::getInstance()->getLink('Attachment', [
  64. 'object' => $this,
  65. ]);
  66. }
  67. /**
  68. * Returns true if a user has the permission to download this attachment.
  69. *
  70. * @return bool
  71. */
  72. public function canDownload()
  73. {
  74. return $this->getPermission('canDownload');
  75. }
  76. /**
  77. * Returns true if a user has the permission to view the preview of this
  78. * attachment.
  79. *
  80. * @return bool
  81. */
  82. public function canViewPreview()
  83. {
  84. return $this->getPermission('canViewPreview');
  85. }
  86. /**
  87. * Returns true if a user has the permission to delete the preview of this
  88. * attachment.
  89. *
  90. * @return bool
  91. */
  92. public function canDelete()
  93. {
  94. return $this->getPermission('canDelete');
  95. }
  96. /**
  97. * Checks permissions.
  98. *
  99. * @param string $permission
  100. * @return bool
  101. */
  102. protected function getPermission($permission)
  103. {
  104. if (!isset($this->permissions[$permission])) {
  105. $this->permissions[$permission] = true;
  106. if ($this->tmpHash) {
  107. if ($this->userID && $this->userID != WCF::getUser()->userID) {
  108. $this->permissions[$permission] = false;
  109. }
  110. } else {
  111. $objectType = ObjectTypeCache::getInstance()->getObjectType($this->objectTypeID);
  112. $processor = $objectType->getProcessor();
  113. if ($processor !== null) {
  114. $this->permissions[$permission] = \call_user_func([$processor, $permission], $this->objectID);
  115. }
  116. }
  117. }
  118. return $this->permissions[$permission];
  119. }
  120. /**
  121. * Sets the permissions for attachment access.
  122. *
  123. * @param bool[] $permissions
  124. */
  125. public function setPermissions(array $permissions)
  126. {
  127. $this->permissions = $permissions;
  128. }
  129. /**
  130. * @inheritDoc
  131. */
  132. public function getLocation()
  133. {
  134. return $this->getLocationHelper(self::getStorage() . \substr(
  135. $this->fileHash,
  136. 0,
  137. 2
  138. ) . '/' . $this->attachmentID . '-' . $this->fileHash);
  139. }
  140. /**
  141. * Returns the physical location of the tiny thumbnail.
  142. *
  143. * @return string
  144. */
  145. public function getTinyThumbnailLocation()
  146. {
  147. return $this->getThumbnailLocation('tiny');
  148. }
  149. /**
  150. * @inheritDoc
  151. */
  152. public function getThumbnailLocation($size = '')
  153. {
  154. if ($size == 'tiny') {
  155. $location = self::getStorage() . \substr(
  156. $this->fileHash,
  157. 0,
  158. 2
  159. ) . '/' . $this->attachmentID . '-tiny-' . $this->fileHash;
  160. } else {
  161. $location = self::getStorage() . \substr(
  162. $this->fileHash,
  163. 0,
  164. 2
  165. ) . '/' . $this->attachmentID . '-thumbnail-' . $this->fileHash;
  166. }
  167. return $this->getLocationHelper($location);
  168. }
  169. /**
  170. * Migrates the storage location of this attachment to
  171. * include the `.bin` suffix.
  172. *
  173. * @since 5.2
  174. */
  175. public function migrateStorage()
  176. {
  177. foreach (
  178. [
  179. $this->getLocation(),
  180. $this->getThumbnailLocation(),
  181. $this->getThumbnailLocation('tiny'),
  182. ] as $location
  183. ) {
  184. if (!\str_ends_with($location, '.bin')) {
  185. \rename($location, $location . '.bin');
  186. }
  187. }
  188. }
  189. /**
  190. * Returns the appropriate location with or without extension.
  191. *
  192. * Files are suffixed with `.bin` since 5.2, but they are recognized
  193. * without the extension for backward compatibility.
  194. *
  195. * @param string $location
  196. * @return string
  197. * @since 5.2
  198. */
  199. final protected function getLocationHelper($location)
  200. {
  201. if (\is_readable($location . '.bin')) {
  202. return $location . '.bin';
  203. } elseif (\is_readable($location)) {
  204. return $location;
  205. }
  206. // Assume that the attachment has not been uploaded yet.
  207. return $location . '.bin';
  208. }
  209. /**
  210. * @inheritDoc
  211. */
  212. public function getThumbnailLink($size = '')
  213. {
  214. $parameters = [
  215. 'id' => $this->attachmentID,
  216. ];
  217. if ($size == 'tiny') {
  218. $parameters['tiny'] = 1;
  219. } elseif ($size == 'thumbnail') {
  220. $parameters['thumbnail'] = 1;
  221. }
  222. return LinkHandler::getInstance()->getLink('Attachment', $parameters);
  223. }
  224. /**
  225. * @inheritDoc
  226. */
  227. public function getTitle()
  228. {
  229. return $this->filename;
  230. }
  231. /**
  232. * Marks this attachment as embedded.
  233. */
  234. public function markAsEmbedded()
  235. {
  236. $this->embedded = true;
  237. }
  238. /**
  239. * Returns true if this attachment is embedded.
  240. *
  241. * @return bool
  242. */
  243. public function isEmbedded()
  244. {
  245. return $this->embedded;
  246. }
  247. /**
  248. * Returns true if this attachment should be shown as an image.
  249. *
  250. * @return bool
  251. */
  252. public function showAsImage()
  253. {
  254. if ($this->isImage) {
  255. if (!$this->hasThumbnail() && ($this->width > ATTACHMENT_THUMBNAIL_WIDTH || $this->height > ATTACHMENT_THUMBNAIL_HEIGHT)) {
  256. return false;
  257. }
  258. if ($this->canDownload()) {
  259. return true;
  260. }
  261. if ($this->canViewPreview() && $this->hasThumbnail()) {
  262. return true;
  263. }
  264. }
  265. return false;
  266. }
  267. /**
  268. * Returns true if this attachment has a thumbnail.
  269. *
  270. * @return bool
  271. */
  272. public function hasThumbnail()
  273. {
  274. return $this->thumbnailType ? true : false;
  275. }
  276. /**
  277. * Returns true if this attachment should be shown as a file.
  278. *
  279. * @return bool
  280. */
  281. public function showAsFile()
  282. {
  283. return !$this->showAsImage();
  284. }
  285. /**
  286. * Returns icon name for this attachment.
  287. *
  288. * @return string
  289. */
  290. public function getIconName()
  291. {
  292. if ($iconName = FileUtil::getIconNameByFilename($this->filename)) {
  293. return 'file-' . $iconName . '-o';
  294. }
  295. return 'paperclip';
  296. }
  297. /**
  298. * Returns the storage path.
  299. *
  300. * @return string
  301. */
  302. public static function getStorage()
  303. {
  304. if (ATTACHMENT_STORAGE) {
  305. return FileUtil::addTrailingSlash(ATTACHMENT_STORAGE);
  306. }
  307. return WCF_DIR . 'attachments/';
  308. }
  309. /**
  310. * @inheritDoc
  311. */
  312. public static function getThumbnailSizes()
  313. {
  314. return [
  315. 'tiny' => [
  316. 'height' => 144,
  317. 'retainDimensions' => false,
  318. 'width' => 144,
  319. ],
  320. // standard thumbnail size
  321. '' => [
  322. 'height' => ATTACHMENT_THUMBNAIL_HEIGHT,
  323. 'retainDimensions' => ATTACHMENT_RETAIN_DIMENSIONS,
  324. 'width' => ATTACHMENT_THUMBNAIL_WIDTH,
  325. ],
  326. ];
  327. }
  328. }