PageRenderTime 44ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/phpmyfaq/src/phpMyFAQ/Attachment/AttachmentAbstract.php

http://github.com/thorsten/phpMyFAQ
PHP | 433 lines | 196 code | 54 blank | 183 comment | 15 complexity | b21069bc0709723222ec677a4afd2fe9 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-2.1, LGPL-3.0
  1. <?php
  2. /**
  3. * Abstract attachment class.
  4. *
  5. * This Source Code Form is subject to the terms of the Mozilla Public License,
  6. * v. 2.0. If a copy of the MPL was not distributed with this file, You can
  7. * obtain one at http://mozilla.org/MPL/2.0/.
  8. *
  9. * @package phpMyFAQ
  10. * @author Anatoliy Belsky <ab@php.net>
  11. * @copyright 2009-2021 phpMyFAQ Team
  12. * @license http://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
  13. * @link https://www.phpmyfaq.de
  14. * @since 2009-08-21
  15. */
  16. namespace phpMyFAQ\Attachment;
  17. use phpMyFAQ\Database;
  18. use phpMyFAQ\Database\DatabaseDriver;
  19. /**
  20. * Class AttachmentAbstract
  21. *
  22. * @package phpMyFAQ\Attachment
  23. */
  24. abstract class AttachmentAbstract
  25. {
  26. /**
  27. * Attachment id.
  28. *
  29. * @var int
  30. */
  31. protected $id;
  32. /**
  33. * The key to encrypt with.
  34. *
  35. * @var string
  36. */
  37. protected $key;
  38. /**
  39. * Errors.
  40. *
  41. * @var string[]
  42. */
  43. protected $error = [];
  44. /**
  45. * Database instance.
  46. *
  47. * @var DatabaseDriver
  48. */
  49. protected $db;
  50. /**
  51. * Record ID.
  52. *
  53. * @var int
  54. */
  55. protected $recordId;
  56. /**
  57. * Record language.
  58. *
  59. * @var string
  60. */
  61. protected $recordLang;
  62. /**
  63. * Real file md5 hash.
  64. *
  65. * @var string
  66. */
  67. protected $realHash;
  68. /**
  69. * Virtual unique md5 hash used for encrypted files.
  70. * Must equal real hash for unencrypted files.
  71. *
  72. * @var string
  73. */
  74. protected $virtualHash;
  75. /**
  76. * If this is set, the sh1 hashed key we got must equal to it.
  77. *
  78. * @var string
  79. */
  80. protected $passwordHash;
  81. /**
  82. * Filesize in bytes.
  83. *
  84. * @var int
  85. */
  86. protected $filesize;
  87. /**
  88. * Filename.
  89. *
  90. * @var string
  91. */
  92. protected $filename;
  93. /**
  94. * Encrypted.
  95. *
  96. * @var string
  97. */
  98. protected $encrypted;
  99. /**
  100. * Attachment file mime type.
  101. *
  102. * @var string
  103. */
  104. protected $mimeType;
  105. /**
  106. * Constructor.
  107. *
  108. * @param mixed $id attachment id
  109. */
  110. public function __construct($id = null)
  111. {
  112. $this->db = Database::getInstance();
  113. if (null !== $id) {
  114. $this->id = $id;
  115. $this->getMeta();
  116. }
  117. }
  118. /**
  119. * Get meta data.
  120. *
  121. * @return bool
  122. */
  123. protected function getMeta(): bool
  124. {
  125. $hasMeta = false;
  126. $sql = sprintf(
  127. '
  128. SELECT
  129. record_id, record_lang, real_hash, virtual_hash, password_hash,
  130. filename, filesize, encrypted, mime_type
  131. FROM
  132. %sfaqattachment
  133. WHERE
  134. id = %d',
  135. Database::getTablePrefix(),
  136. (int)$this->id
  137. );
  138. $result = $this->db->query($sql);
  139. if ($result) {
  140. $assoc = $this->db->fetchArray($result);
  141. if (!empty($assoc)) {
  142. $this->recordId = $assoc['record_id'];
  143. $this->recordLang = $assoc['record_lang'];
  144. $this->realHash = $assoc['real_hash'];
  145. $this->virtualHash = $assoc['virtual_hash'];
  146. $this->passwordHash = $assoc['password_hash'];
  147. $this->filename = $assoc['filename'];
  148. $this->filesize = $assoc['filesize'];
  149. $this->encrypted = $assoc['encrypted'];
  150. $this->mimeType = $assoc['mime_type'];
  151. $hasMeta = true;
  152. }
  153. }
  154. return $hasMeta;
  155. }
  156. /**
  157. * Build attachment url.
  158. *
  159. * @param bool $forHTML either to use ampersands directly
  160. *
  161. * @return string
  162. */
  163. public function buildUrl($forHTML = true): string
  164. {
  165. $amp = true === $forHTML ? '&amp;' : '&';
  166. return "index.php?action=attachment{$amp}id={$this->id}";
  167. }
  168. /**
  169. * Set encryption key.
  170. *
  171. * @param string $key encryption key
  172. * @param bool $default if the key is default system wide
  173. */
  174. public function setKey(string $key, $default = true): void
  175. {
  176. $this->key = $key;
  177. $this->encrypted = null !== $key;
  178. // Not default means the key was set explicitly
  179. // for this attachment, so lets hash it
  180. if ($this->encrypted && !$default) {
  181. $this->passwordHash = sha1($key);
  182. }
  183. }
  184. /**
  185. * Set record language.
  186. *
  187. * @param string $lang record language
  188. */
  189. public function setRecordLang(string $lang): void
  190. {
  191. $this->recordLang = $lang;
  192. }
  193. /**
  194. * Get attachment id.
  195. *
  196. * @return int
  197. */
  198. public function getId(): int
  199. {
  200. return $this->id;
  201. }
  202. /**
  203. * Sets attachment id.
  204. *
  205. * @param int $id
  206. */
  207. public function setId(int $id): void
  208. {
  209. $this->id = $id;
  210. }
  211. /**
  212. * Get attachment record id.
  213. *
  214. * @return int
  215. */
  216. public function getRecordId(): int
  217. {
  218. return $this->recordId;
  219. }
  220. /**
  221. * Set record id.
  222. *
  223. * @param int $id record id
  224. */
  225. public function setRecordId(int $id): void
  226. {
  227. $this->recordId = $id;
  228. }
  229. /**
  230. * Save attachment meta data.
  231. *
  232. * @return int saved attachment id
  233. *
  234. * @todo implement update case
  235. */
  236. public function saveMeta(): int
  237. {
  238. $attachmentTableName = sprintf('%sfaqattachment', Database::getTablePrefix());
  239. if (null == $this->id) {
  240. $this->id = $this->db->nextId($attachmentTableName, 'id');
  241. $sql = sprintf(
  242. "
  243. INSERT INTO
  244. %s
  245. (id, record_id, record_lang, real_hash, virtual_hash,
  246. password_hash, filename, filesize, encrypted, mime_type)
  247. VALUES
  248. (%d, %d, '%s', '%s', '%s', '%s', '%s', %d, %d, '%s')",
  249. $attachmentTableName,
  250. $this->id,
  251. $this->recordId,
  252. $this->recordLang,
  253. $this->realHash,
  254. $this->virtualHash,
  255. $this->passwordHash,
  256. $this->filename,
  257. $this->filesize,
  258. $this->encrypted ? 1 : 0,
  259. $this->mimeType
  260. );
  261. $this->db->query($sql);
  262. }
  263. return $this->id;
  264. }
  265. /**
  266. * Returns filename.
  267. *
  268. * @return string
  269. */
  270. public function getFilename(): string
  271. {
  272. return $this->filename;
  273. }
  274. /**
  275. * Returns the MIME type
  276. * @return string
  277. */
  278. public function getMimeType(): string
  279. {
  280. return $this->mimeType;
  281. }
  282. /**
  283. * Update several meta things after it was saved.
  284. */
  285. protected function postUpdateMeta(): void
  286. {
  287. $sql = sprintf(
  288. "
  289. UPDATE
  290. %sfaqattachment
  291. SET virtual_hash = '%s',
  292. mime_type = '%s'
  293. WHERE id = %d",
  294. Database::getTablePrefix(),
  295. $this->virtualHash,
  296. $this->readMimeType(),
  297. $this->id
  298. );
  299. $this->db->query($sql);
  300. }
  301. /**
  302. * The function is supposed to detect the file mime
  303. * type.
  304. *
  305. * @return string
  306. */
  307. protected function readMimeType(): string
  308. {
  309. $ext = pathinfo($this->filename, PATHINFO_EXTENSION);
  310. $this->mimeType = AbstractMimeType::guessByExt($ext);
  311. return $this->mimeType;
  312. }
  313. /**
  314. * Generate hash based on current conditions.
  315. *
  316. * @return string
  317. *
  318. * NOTE The way a file is saved in the filesystem
  319. * is based on md5 hash. If the file is unencrypted,
  320. * it's md5 hash is used directly, otherwise a
  321. * hash based on several tokens gets generated.
  322. *
  323. * @return string|null
  324. * @throws AttachmentException
  325. */
  326. protected function mkVirtualHash(): ?string
  327. {
  328. if ($this->encrypted) {
  329. if (
  330. null === $this->id || null === $this->recordId
  331. || null === $this->realHash || null === $this->filename
  332. || null === $this->key
  333. ) {
  334. throw new AttachmentException(
  335. 'All of id, recordId, hash, filename, key is needed to generate fs hash for encrypted files'
  336. );
  337. }
  338. $src = $this->id . $this->recordId . $this->realHash .
  339. $this->filename . $this->key;
  340. $this->virtualHash = md5($src);
  341. } else {
  342. $this->virtualHash = $this->realHash;
  343. }
  344. return $this->virtualHash;
  345. }
  346. /**
  347. * Check if the same virtual hash exists more than once
  348. * in the database. If so, this means the file
  349. * is already uploaded as unencrypted.
  350. *
  351. * @return bool
  352. */
  353. protected function linkedRecords(): bool
  354. {
  355. $assoc = [];
  356. $sql = sprintf(
  357. "SELECT COUNT(1) AS count FROM %sfaqattachment WHERE virtual_hash = '%s'",
  358. Database::getTablePrefix(),
  359. $this->virtualHash
  360. );
  361. $result = $this->db->query($sql);
  362. if ($result) {
  363. $assoc = $this->db->fetchArray($result);
  364. }
  365. return $assoc['count'] > 1;
  366. }
  367. /**
  368. * Remove meta data from the db.
  369. */
  370. protected function deleteMeta(): void
  371. {
  372. $sql = sprintf(
  373. 'DELETE FROM %sfaqattachment WHERE id = %d',
  374. Database::getTablePrefix(),
  375. $this->id
  376. );
  377. $this->db->query($sql);
  378. }
  379. }