PageRenderTime 50ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/sources/subs/Attachments.subs.php

https://github.com/Arantor/Elkarte
PHP | 1839 lines | 1266 code | 218 blank | 355 comment | 242 complexity | 13535e9d26eb4f8b3eaa2eb86a708bcd MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-3.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /**
  3. * @name ElkArte Forum
  4. * @copyright ElkArte Forum contributors
  5. * @license BSD http://opensource.org/licenses/BSD-3-Clause
  6. *
  7. * This software is a derived product, based on:
  8. *
  9. * Simple Machines Forum (SMF)
  10. * copyright: 2011 Simple Machines (http://www.simplemachines.org)
  11. * license: BSD, See included LICENSE.TXT for terms and conditions.
  12. *
  13. * @version 1.0 Alpha
  14. *
  15. * This file handles the uploading and creation of attachments
  16. * as well as the auto management of the attachment directories.
  17. * Note to enhance documentation later:
  18. * attachment_type = 3 is a thumbnail, etc.
  19. *
  20. */
  21. if (!defined('ELKARTE'))
  22. die('No access...');
  23. function automanage_attachments_check_directory()
  24. {
  25. global $modSettings, $context;
  26. // Not pretty, but since we don't want folders created for every post. It'll do unless a better solution can be found.
  27. if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'admin')
  28. $doit = true;
  29. elseif (empty($modSettings['automanage_attachments']))
  30. return;
  31. elseif (!isset($_FILES))
  32. return;
  33. elseif (isset($_FILES['attachment']))
  34. {
  35. foreach ($_FILES['attachment']['tmp_name'] as $dummy)
  36. {
  37. if (!empty($dummy))
  38. {
  39. $doit = true;
  40. break;
  41. }
  42. }
  43. }
  44. if (!isset($doit))
  45. return;
  46. // get our date and random numbers for the directory choices
  47. $year = date('Y');
  48. $month = date('m');
  49. $day = date('d');
  50. $rand = md5(mt_rand());
  51. $rand1 = $rand[1];
  52. $rand = $rand[0];
  53. if (!empty($modSettings['attachment_basedirectories']) && !empty($modSettings['use_subdirectories_for_attachments']))
  54. {
  55. if (!is_array($modSettings['attachment_basedirectories']))
  56. $modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']);
  57. $base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
  58. }
  59. else
  60. $base_dir = 0;
  61. if ($modSettings['automanage_attachments'] == 1)
  62. {
  63. if (!isset($modSettings['last_attachments_directory']))
  64. $modSettings['last_attachments_directory'] = array();
  65. if (!is_array($modSettings['last_attachments_directory']))
  66. $modSettings['last_attachments_directory'] = unserialize($modSettings['last_attachments_directory']);
  67. if (!isset($modSettings['last_attachments_directory'][$base_dir]))
  68. $modSettings['last_attachments_directory'][$base_dir] = 0;
  69. }
  70. $basedirectory = (!empty($modSettings['use_subdirectories_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : BOARDDIR);
  71. //Just to be sure: I don't want directory separators at the end
  72. $sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
  73. $basedirectory = rtrim($basedirectory, $sep);
  74. switch ($modSettings['automanage_attachments'])
  75. {
  76. case 1:
  77. $updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . (isset($modSettings['last_attachments_directory'][$base_dir]) ? $modSettings['last_attachments_directory'][$base_dir] : 0);
  78. break;
  79. case 2:
  80. $updir = $basedirectory . DIRECTORY_SEPARATOR . $year;
  81. break;
  82. case 3:
  83. $updir = $basedirectory . DIRECTORY_SEPARATOR . $year . DIRECTORY_SEPARATOR . $month;
  84. break;
  85. case 4:
  86. $updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand;
  87. break;
  88. case 5:
  89. $updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand . DIRECTORY_SEPARATOR . $rand1;
  90. break;
  91. default :
  92. $updir = '';
  93. }
  94. if (!is_array($modSettings['attachmentUploadDir']))
  95. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
  96. if (!in_array($updir, $modSettings['attachmentUploadDir']) && !empty($updir))
  97. $outputCreation = automanage_attachments_create_directory($updir);
  98. elseif (in_array($updir, $modSettings['attachmentUploadDir']))
  99. $outputCreation = true;
  100. if ($outputCreation)
  101. {
  102. $modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
  103. $context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
  104. updateSettings(array(
  105. 'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
  106. ));
  107. }
  108. return $outputCreation;
  109. }
  110. /**
  111. * Creates a directory as defined by the admin attach options
  112. * Attempts to make the directory writable
  113. * Places an .htaccess in new directories for security
  114. *
  115. * @param type $updir
  116. * @return boolean
  117. */
  118. function automanage_attachments_create_directory($updir)
  119. {
  120. global $modSettings, $initial_error, $context;
  121. $tree = get_directory_tree_elements($updir);
  122. $count = count($tree);
  123. $directory = attachments_init_dir($tree, $count);
  124. if ($directory === false)
  125. {
  126. // Maybe it's just the folder name
  127. $tree = get_directory_tree_elements(BOARDDIR . DIRECTORY_SEPARATOR . $updir);
  128. $count = count($tree);
  129. $directory = attachments_init_dir($tree, $count);
  130. if ($directory === false)
  131. return false;
  132. }
  133. $directory .= DIRECTORY_SEPARATOR . array_shift($tree);
  134. while (!@is_dir($directory) || $count != -1)
  135. {
  136. if (!@is_dir($directory))
  137. {
  138. if (!@mkdir($directory, 0755))
  139. {
  140. $context['dir_creation_error'] = 'attachments_no_create';
  141. return false;
  142. }
  143. }
  144. $directory .= DIRECTORY_SEPARATOR . array_shift($tree);
  145. $count--;
  146. }
  147. // try to make it writable
  148. if (!is_writable($directory))
  149. {
  150. chmod($directory, 0755);
  151. if (!is_writable($directory))
  152. {
  153. chmod($directory, 0775);
  154. if (!is_writable($directory))
  155. {
  156. chmod($directory, 0777);
  157. if (!is_writable($directory))
  158. {
  159. $context['dir_creation_error'] = 'attachments_no_write';
  160. return false;
  161. }
  162. }
  163. }
  164. }
  165. // Everything seems fine...let's create the .htaccess
  166. if (!file_exists($directory . DIRECTORY_SEPARATOR . '.htaccess'))
  167. secureDirectory($updir, true);
  168. $sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
  169. $updir = rtrim($updir, $sep);
  170. // Only update if it's a new directory
  171. if (!in_array($updir, $modSettings['attachmentUploadDir']))
  172. {
  173. $modSettings['currentAttachmentUploadDir'] = max(array_keys($modSettings['attachmentUploadDir'])) + 1;
  174. $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']] = $updir;
  175. updateSettings(array(
  176. 'attachmentUploadDir' => serialize($modSettings['attachmentUploadDir']),
  177. 'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
  178. ), true);
  179. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
  180. }
  181. $context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
  182. return true;
  183. }
  184. /**
  185. * Determines the current base directory and attachment directory
  186. * Increments the above directory to the next availble slot
  187. * Uses automanage_attachments_create_directory to create the incremental directory
  188. *
  189. * @return boolean
  190. */
  191. function automanage_attachments_by_space()
  192. {
  193. global $modSettings, $context;
  194. if (!isset($modSettings['automanage_attachments']) || (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] != 1))
  195. return;
  196. $basedirectory = (!empty($modSettings['use_subdirectories_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : BOARDDIR);
  197. // Just to be sure: I don't want directory separators at the end
  198. $sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
  199. $basedirectory = rtrim($basedirectory, $sep);
  200. // Get the current base directory
  201. if (!empty($modSettings['use_subdirectories_for_attachments']) && !empty($modSettings['attachment_basedirectories']))
  202. {
  203. $base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
  204. $base_dir = !empty($modSettings['automanage_attachments']) ? $base_dir : 0;
  205. }
  206. else
  207. $base_dir = 0;
  208. // Get the last attachment directory for that base directory
  209. if (empty($modSettings['last_attachments_directory'][$base_dir]))
  210. $modSettings['last_attachments_directory'][$base_dir] = 0;
  211. // And increment it.
  212. $modSettings['last_attachments_directory'][$base_dir]++;
  213. $updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . $modSettings['last_attachments_directory'][$base_dir];
  214. // make sure it exists and is writable
  215. if (automanage_attachments_create_directory($updir))
  216. {
  217. $modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
  218. updateSettings(array(
  219. 'last_attachments_directory' => serialize($modSettings['last_attachments_directory']),
  220. 'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
  221. ));
  222. $modSettings['last_attachments_directory'] = unserialize($modSettings['last_attachments_directory']);
  223. return true;
  224. }
  225. else
  226. return false;
  227. }
  228. /**
  229. * Finds the current directory tree for the supplied base directory
  230. *
  231. * @param type $directory
  232. * @return boolean on fail else array of directory names
  233. */
  234. function get_directory_tree_elements ($directory)
  235. {
  236. /*
  237. In Windows server both \ and / can be used as directory separators in paths
  238. In Linux (and presumably *nix) servers \ can be part of the name
  239. So for this reasons:
  240. * in Windows we need to explode for both \ and /
  241. * while in linux should be safe to explode only for / (aka DIRECTORY_SEPARATOR)
  242. */
  243. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
  244. $tree = preg_split('#[\\\/]#', $directory);
  245. else
  246. {
  247. if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR)
  248. return false;
  249. $tree = explode(DIRECTORY_SEPARATOR, trim($directory, DIRECTORY_SEPARATOR));
  250. }
  251. return $tree;
  252. }
  253. /**
  254. * Helper function for automanage_attachments_create_directory
  255. * Gets the directory w/o drive letter for windows
  256. *
  257. * @param array $tree
  258. * @param int $count
  259. * @return boolean
  260. */
  261. function attachments_init_dir (&$tree, &$count)
  262. {
  263. $directory = '';
  264. // If on Windows servers the first part of the path is the drive (e.g. "C:")
  265. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
  266. {
  267. // Better be sure that the first part of the path is actually a drive letter...
  268. // ...even if, I should check this in the admin page...isn't it?
  269. // ...NHAAA Let's leave space for users' complains! :P
  270. if (preg_match('/^[a-z]:$/i', $tree[0]))
  271. $directory = array_shift($tree);
  272. else
  273. return false;
  274. $count--;
  275. }
  276. return $directory;
  277. }
  278. /**
  279. * Handles the actual saving of attachments to a directory
  280. * Loops through $_FILES['attachment'] array and saves each file to the current attachments folder
  281. * Validates the save location actually exists
  282. */
  283. function processAttachments()
  284. {
  285. global $context, $modSettings, $smcFunc, $txt, $user_info;
  286. // Make sure we're uploading to the right place.
  287. if (!empty($modSettings['automanage_attachments']))
  288. automanage_attachments_check_directory();
  289. if (!is_array($modSettings['attachmentUploadDir']))
  290. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
  291. $context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
  292. // Is the attachments folder actualy there?
  293. if (!empty($context['dir_creation_error']))
  294. $initial_error = $context['dir_creation_error'];
  295. elseif (!is_dir($context['attach_dir']))
  296. {
  297. $initial_error = 'attach_folder_warning';
  298. log_error(sprintf($txt['attach_folder_admin_warning'], $context['attach_dir']), 'critical');
  299. }
  300. if (!isset($initial_error) && !isset($context['attachments']))
  301. {
  302. // If this isn't a new post, check the current attachments.
  303. if (isset($_REQUEST['msg']))
  304. {
  305. $request = $smcFunc['db_query']('', '
  306. SELECT COUNT(*), SUM(size)
  307. FROM {db_prefix}attachments
  308. WHERE id_msg = {int:id_msg}
  309. AND attachment_type = {int:attachment_type}',
  310. array(
  311. 'id_msg' => (int) $_REQUEST['msg'],
  312. 'attachment_type' => 0,
  313. )
  314. );
  315. list ($context['attachments']['quantity'], $context['attachments']['total_size']) = $smcFunc['db_fetch_row']($request);
  316. $smcFunc['db_free_result']($request);
  317. }
  318. else
  319. $context['attachments'] = array(
  320. 'quantity' => 0,
  321. 'total_size' => 0,
  322. );
  323. }
  324. // Hmm. There are still files in session.
  325. $ignore_temp = false;
  326. if (!empty($_SESSION['temp_attachments']['post']['files']) && count($_SESSION['temp_attachments']) > 1)
  327. {
  328. // Let's try to keep them. But...
  329. $ignore_temp = true;
  330. // If new files are being added. We can't ignore those
  331. foreach ($_FILES['attachment']['tmp_name'] as $dummy)
  332. {
  333. if (!empty($dummy))
  334. {
  335. $ignore_temp = false;
  336. break;
  337. }
  338. }
  339. // Need to make space for the new files. So, bye bye.
  340. if (!$ignore_temp)
  341. {
  342. foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
  343. {
  344. if (strpos($attachID, 'post_tmp_' . $user_info['id']) !== false)
  345. unlink($attachment['tmp_name']);
  346. }
  347. $context['we_are_history'] = $txt['error_temp_attachments_flushed'];
  348. $_SESSION['temp_attachments'] = array();
  349. }
  350. }
  351. if (!isset($_FILES['attachment']['name']))
  352. $_FILES['attachment']['tmp_name'] = array();
  353. if (!isset($_SESSION['temp_attachments']))
  354. $_SESSION['temp_attachments'] = array();
  355. // Remember where we are at. If it's anywhere at all.
  356. if (!$ignore_temp)
  357. $_SESSION['temp_attachments']['post'] = array(
  358. 'msg' => !empty($_REQUEST['msg']) ? $_REQUEST['msg'] : 0,
  359. 'last_msg' => !empty($_REQUEST['last_msg']) ? $_REQUEST['last_msg'] : 0,
  360. 'topic' => !empty($topic) ? $topic : 0,
  361. 'board' => !empty($board) ? $board : 0,
  362. );
  363. // If we have an initial error, lets just display it.
  364. if (!empty($initial_error))
  365. {
  366. $_SESSION['temp_attachments']['initial_error'] = $initial_error;
  367. // And delete the files 'cos they ain't going nowhere.
  368. foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
  369. {
  370. if (file_exists($_FILES['attachment']['tmp_name'][$n]))
  371. unlink($_FILES['attachment']['tmp_name'][$n]);
  372. }
  373. $_FILES['attachment']['tmp_name'] = array();
  374. }
  375. // Loop through $_FILES['attachment'] array and move each file to the current attachments folder.
  376. foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
  377. {
  378. if ($_FILES['attachment']['name'][$n] == '')
  379. continue;
  380. // First, let's first check for PHP upload errors.
  381. $errors = array();
  382. if (!empty($_FILES['attachment']['error'][$n]))
  383. {
  384. if ($_FILES['attachment']['error'][$n] == 2)
  385. $errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
  386. elseif ($_FILES['attachment']['error'][$n] == 6)
  387. log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_6'], 'critical');
  388. else
  389. log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$n]]);
  390. if (empty($errors))
  391. $errors[] = 'attach_php_error';
  392. }
  393. // Try to move and rename the file before doing any more checks on it.
  394. $attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand());
  395. $destName = $context['attach_dir'] . '/' . $attachID;
  396. if (empty($errors))
  397. {
  398. $_SESSION['temp_attachments'][$attachID] = array(
  399. 'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n])),
  400. 'tmp_name' => $destName,
  401. 'size' => $_FILES['attachment']['size'][$n],
  402. 'type' => $_FILES['attachment']['type'][$n],
  403. 'id_folder' => $modSettings['currentAttachmentUploadDir'],
  404. 'errors' => array(),
  405. );
  406. // Move the file to the attachments folder with a temp name for now.
  407. if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName))
  408. @chmod($destName, 0644);
  409. else
  410. {
  411. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout';
  412. if (file_exists($_FILES['attachment']['tmp_name'][$n]))
  413. unlink($_FILES['attachment']['tmp_name'][$n]);
  414. }
  415. }
  416. else
  417. {
  418. $_SESSION['temp_attachments'][$attachID] = array(
  419. 'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n])),
  420. 'tmp_name' => $destName,
  421. 'errors' => $errors,
  422. );
  423. if (file_exists($_FILES['attachment']['tmp_name'][$n]))
  424. unlink($_FILES['attachment']['tmp_name'][$n]);
  425. }
  426. // If there's no errors to this pont. We still do need to apply some addtional checks before we are finished.
  427. if (empty($_SESSION['temp_attachments'][$attachID]['errors']))
  428. attachmentChecks($attachID);
  429. }
  430. // Mod authors, finally a hook to hang an alternate attachment upload system upon
  431. // Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand())
  432. // Populate $_SESSION['temp_attachments'][$attachID] with the following:
  433. // name => The file name
  434. // tmp_name => Path to the temp file ($context['attach_dir'] . '/' . $attachID).
  435. // size => File size (required).
  436. // type => MIME type (optional if not available on upload).
  437. // id_folder => $modSettings['currentAttachmentUploadDir']
  438. // errors => An array of errors (use the index of the $txt variable for that error).
  439. // Template changes can be done using "integrate_upload_template".
  440. call_integration_hook('integrate_attachment_upload');
  441. }
  442. /**
  443. * Performs various checks on an uploaded file.
  444. * - Requires that $_SESSION['temp_attachments'][$attachID] be properly populated.
  445. *
  446. * @param int $attachID
  447. */
  448. function attachmentChecks($attachID)
  449. {
  450. global $modSettings, $context, $smcFunc;
  451. // No data or missing data .... Not necessarily needed, but in case a mod author missed something.
  452. if ( empty($_SESSION['temp_attachments'][$attachID]))
  453. $error = '$_SESSION[\'temp_attachments\'][$attachID]';
  454. elseif (empty($attachID))
  455. $error = '$attachID';
  456. elseif (empty($context['attachments']))
  457. $error = '$context[\'attachments\']';
  458. elseif (empty($context['attach_dir']))
  459. $error = '$context[\'attach_dir\']';
  460. // Let's get their attention.
  461. if (!empty($error))
  462. fatal_lang_error('attach_check_nag', 'debug', array($error));
  463. // These are the only valid image types.
  464. $validImageTypes = array(
  465. 1 => 'gif',
  466. 2 => 'jpeg',
  467. 3 => 'png',
  468. 5 => 'psd',
  469. 6 => 'bmp',
  470. 7 => 'tiff',
  471. 8 => 'tiff',
  472. 9 => 'jpeg',
  473. 14 => 'iff'
  474. );
  475. // Just in case this slipped by the first checks, we stop it here and now
  476. if ($_SESSION['temp_attachments'][$attachID]['size'] == 0)
  477. {
  478. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_0_byte_file';
  479. return false;
  480. }
  481. // First, the dreaded security check. Sorry folks, but this should't be avoided
  482. $size = @getimagesize($_SESSION['temp_attachments'][$attachID]['tmp_name']);
  483. if (isset($validImageTypes[$size[2]]))
  484. {
  485. require_once(SUBSDIR . '/Graphics.subs.php');
  486. if (!checkImageContents($_SESSION['temp_attachments'][$attachID]['tmp_name'], !empty($modSettings['attachment_image_paranoid'])))
  487. {
  488. // It's bad. Last chance, maybe we can re-encode it?
  489. if (empty($modSettings['attachment_image_reencode']) || (!reencodeImage($_SESSION['temp_attachments'][$attachID]['tmp_name'], $size[2])))
  490. {
  491. // Nothing to do: not allowed or not successful re-encoding it.
  492. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'bad_attachment';
  493. return false;
  494. }
  495. // Success! However, successes usually come for a price:
  496. // we might get a new format for our image...
  497. $old_format = $size[2];
  498. $size = @getimagesize($attachmentOptions['tmp_name']);
  499. if (!(empty($size)) && ($size[2] != $old_format))
  500. {
  501. if (isset($validImageTypes[$size[2]]))
  502. $_SESSION['temp_attachments'][$attachID]['type'] = 'image/' . $validImageTypes[$size[2]];
  503. }
  504. }
  505. }
  506. // Is there room for this sucker?
  507. if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
  508. {
  509. // Check the folder size and count. If it hasn't been done already.
  510. if (empty($context['dir_size']) || empty($context['dir_files']))
  511. {
  512. $request = $smcFunc['db_query']('', '
  513. SELECT COUNT(*), SUM(size)
  514. FROM {db_prefix}attachments
  515. WHERE id_folder = {int:folder_id}
  516. AND attachment_type != {int:type}',
  517. array(
  518. 'folder_id' => $modSettings['currentAttachmentUploadDir'],
  519. 'type' => 1,
  520. )
  521. );
  522. list ($context['dir_files'], $context['dir_size']) = $smcFunc['db_fetch_row']($request);
  523. $smcFunc['db_free_result']($request);
  524. }
  525. $context['dir_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
  526. $context['dir_files']++;
  527. // Are we about to run out of room? Let's notify the admin then.
  528. if (empty($modSettings['attachment_full_notified']) && !empty($modSettings['attachmentDirSizeLimit']) && $modSettings['attachmentDirSizeLimit'] > 4000 && $context['dir_size'] > ($modSettings['attachmentDirSizeLimit'] - 2000) * 1024
  529. || (!empty($modSettings['attachmentDirFileLimit']) && $modSettings['attachmentDirFileLimit'] * .95 < $context['dir_files'] && $modSettings['attachmentDirFileLimit'] > 500))
  530. {
  531. require_once(SUBSDIR . '/Admin.subs.php');
  532. emailAdmins('admin_attachments_full');
  533. updateSettings(array('attachment_full_notified' => 1));
  534. }
  535. // No room left.... What to do now???
  536. if (!empty($modSettings['attachmentDirFileLimit']) && $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit']
  537. || (!empty($modSettings['attachmentDirSizeLimit']) && $context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024))
  538. {
  539. if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1)
  540. {
  541. // Move it to the new folder if we can.
  542. if (automanage_attachments_by_space())
  543. {
  544. rename($_SESSION['temp_attachments'][$attachID]['tmp_name'], $context['attach_dir'] . '/' . $attachID);
  545. $_SESSION['temp_attachments'][$attachID]['tmp_name'] = $context['attach_dir'] . '/' . $attachID;
  546. $_SESSION['temp_attachments'][$attachID]['id_folder'] = $modSettings['currentAttachmentUploadDir'];
  547. $context['dir_size'] = 0;
  548. $context['dir_files'] = 0;
  549. }
  550. // Or, let the user know that it ain't gonna happen.
  551. else
  552. {
  553. if (isset($context['dir_creation_error']))
  554. $_SESSION['temp_attachments'][$attachID]['errors'][] = $context['dir_creation_error'];
  555. else
  556. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
  557. }
  558. }
  559. else
  560. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
  561. }
  562. }
  563. // Is the file too big?
  564. $context['attachments']['total_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
  565. if (!empty($modSettings['attachmentSizeLimit']) && $_SESSION['temp_attachments'][$attachID]['size'] > $modSettings['attachmentSizeLimit'] * 1024)
  566. $_SESSION['temp_attachments'][$attachID]['errors'][] = array('file_too_big', array(comma_format($modSettings['attachmentSizeLimit'], 0)));
  567. // Check the total upload size for this post...
  568. if (!empty($modSettings['attachmentPostLimit']) && $context['attachments']['total_size'] > $modSettings['attachmentPostLimit'] * 1024)
  569. $_SESSION['temp_attachments'][$attachID]['errors'][] = array('attach_max_total_file_size', array(comma_format($modSettings['attachmentPostLimit'], 0), comma_format($modSettings['attachmentPostLimit'] - (($context['attachments']['total_size'] - $_SESSION['temp_attachments'][$attachID]['size']) / 1024), 0)));
  570. // Have we reached the maximum number of files we are allowed?
  571. $context['attachments']['quantity']++;
  572. // Set a max limit if none exists
  573. if (empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] >= 50)
  574. $modSettings['attachmentNumPerPostLimit'] = 50;
  575. if (!empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] > $modSettings['attachmentNumPerPostLimit'])
  576. $_SESSION['temp_attachments'][$attachID]['errors'][] = array('attachments_limit_per_post', array($modSettings['attachmentNumPerPostLimit']));
  577. // File extension check
  578. if (!empty($modSettings['attachmentCheckExtensions']))
  579. {
  580. $allowed = explode(',', strtolower($modSettings['attachmentExtensions']));
  581. foreach ($allowed as $k => $dummy)
  582. $allowed[$k] = trim($dummy);
  583. if (!in_array(strtolower(substr(strrchr($_SESSION['temp_attachments'][$attachID]['name'], '.'), 1)), $allowed))
  584. {
  585. $allowed_extensions = strtr(strtolower($modSettings['attachmentExtensions']), array(',' => ', '));
  586. $_SESSION['temp_attachments'][$attachID]['errors'][] = array('cant_upload_type', array($allowed_extensions));
  587. }
  588. }
  589. // Undo the math if there's an error
  590. if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
  591. {
  592. if (isset($context['dir_size']))
  593. $context['dir_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
  594. if (isset($context['dir_files']))
  595. $context['dir_files']--;
  596. $context['attachments']['total_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
  597. $context['attachments']['quantity']--;
  598. return false;
  599. }
  600. return true;
  601. }
  602. /**
  603. * Create an attachment, with the given array of parameters.
  604. * - Adds any addtional or missing parameters to $attachmentOptions.
  605. * - Renames the temporary file.
  606. * - Creates a thumbnail if the file is an image and the option enabled.
  607. *
  608. * @param array $attachmentOptions
  609. */
  610. function createAttachment(&$attachmentOptions)
  611. {
  612. global $modSettings, $smcFunc, $context;
  613. global $txt;
  614. require_once(SUBSDIR . '/Graphics.subs.php');
  615. // These are the only valid image types.
  616. $validImageTypes = array(
  617. 1 => 'gif',
  618. 2 => 'jpeg',
  619. 3 => 'png',
  620. 5 => 'psd',
  621. 6 => 'bmp',
  622. 7 => 'tiff',
  623. 8 => 'tiff',
  624. 9 => 'jpeg',
  625. 14 => 'iff'
  626. );
  627. // If this is an image we need to set a few additional parameters.
  628. $size = @getimagesize($attachmentOptions['tmp_name']);
  629. list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
  630. // If it's an image get the mime type right.
  631. if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
  632. {
  633. // Got a proper mime type?
  634. if (!empty($size['mime']))
  635. $attachmentOptions['mime_type'] = $size['mime'];
  636. // Otherwise a valid one?
  637. elseif (isset($validImageTypes[$size[2]]))
  638. $attachmentOptions['mime_type'] = 'image/' . $validImageTypes[$size[2]];
  639. }
  640. // Get the hash if no hash has been given yet.
  641. if (empty($attachmentOptions['file_hash']))
  642. $attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], false, null, true);
  643. // Assuming no-one set the extension let's take a look at it.
  644. if (empty($attachmentOptions['fileext']))
  645. {
  646. $attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
  647. if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
  648. $attachmentOptions['fileext'] = '';
  649. }
  650. $smcFunc['db_insert']('',
  651. '{db_prefix}attachments',
  652. array(
  653. 'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
  654. 'size' => 'int', 'width' => 'int', 'height' => 'int',
  655. 'mime_type' => 'string-20', 'approved' => 'int',
  656. ),
  657. array(
  658. (int) $attachmentOptions['id_folder'], (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'],
  659. (int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
  660. (!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'],
  661. ),
  662. array('id_attach')
  663. );
  664. $attachmentOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
  665. // @todo Add an error here maybe?
  666. if (empty($attachmentOptions['id']))
  667. return false;
  668. // Now that we have the attach id, let's rename this sucker and finish up.
  669. $attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $attachmentOptions['id_folder'], false, $attachmentOptions['file_hash']);
  670. rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']);
  671. // If it's not approved then add to the approval queue.
  672. if (!$attachmentOptions['approved'])
  673. $smcFunc['db_insert']('',
  674. '{db_prefix}approval_queue',
  675. array(
  676. 'id_attach' => 'int', 'id_msg' => 'int',
  677. ),
  678. array(
  679. $attachmentOptions['id'], (int) $attachmentOptions['post'],
  680. ),
  681. array()
  682. );
  683. if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
  684. return true;
  685. // Like thumbnails, do we?
  686. if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
  687. {
  688. if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
  689. {
  690. // Figure out how big we actually made it.
  691. $size = @getimagesize($attachmentOptions['destination'] . '_thumb');
  692. list ($thumb_width, $thumb_height) = $size;
  693. if (!empty($size['mime']))
  694. $thumb_mime = $size['mime'];
  695. elseif (isset($validImageTypes[$size[2]]))
  696. $thumb_mime = 'image/' . $validImageTypes[$size[2]];
  697. // Lord only knows how this happened...
  698. else
  699. $thumb_mime = '';
  700. $thumb_filename = $attachmentOptions['name'] . '_thumb';
  701. $thumb_size = filesize($attachmentOptions['destination'] . '_thumb');
  702. $thumb_file_hash = getAttachmentFilename($thumb_filename, false, null, true);
  703. $thumb_path = $attachmentOptions['destination'] . '_thumb';
  704. // We should check the file size and count here since thumbs are added to the existing totals.
  705. if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1 && !empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
  706. {
  707. $context['dir_size'] = isset($context['dir_size']) ? $context['dir_size'] += $thumb_size : $context['dir_size'] = 0;
  708. $context['dir_files'] = isset($context['dir_files']) ? $context['dir_files']++ : $context['dir_files'] = 0;
  709. // If the folder is full, try to create a new one and move the thumb to it.
  710. if ($context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024 || $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit'])
  711. {
  712. if (automanage_attachments_by_space())
  713. {
  714. rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
  715. $thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
  716. $context['dir_size'] = 0;
  717. $context['dir_files'] = 0;
  718. }
  719. }
  720. }
  721. // If a new folder has been already created. Gotta move this thumb there then.
  722. if ($modSettings['currentAttachmentUploadDir'] != $attachmentOptions['id_folder'])
  723. {
  724. rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
  725. $thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
  726. }
  727. // To the database we go!
  728. $smcFunc['db_insert']('',
  729. '{db_prefix}attachments',
  730. array(
  731. 'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
  732. 'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int',
  733. ),
  734. array(
  735. $modSettings['currentAttachmentUploadDir'], (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
  736. $thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'],
  737. ),
  738. array('id_attach')
  739. );
  740. $attachmentOptions['thumb'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
  741. if (!empty($attachmentOptions['thumb']))
  742. {
  743. $smcFunc['db_query']('', '
  744. UPDATE {db_prefix}attachments
  745. SET id_thumb = {int:id_thumb}
  746. WHERE id_attach = {int:id_attach}',
  747. array(
  748. 'id_thumb' => $attachmentOptions['thumb'],
  749. 'id_attach' => $attachmentOptions['id'],
  750. )
  751. );
  752. rename($thumb_path, getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $modSettings['currentAttachmentUploadDir'], false, $thumb_file_hash));
  753. }
  754. }
  755. }
  756. return true;
  757. }
  758. /**
  759. * Get the avatar with the specified ID.
  760. *
  761. * @param int $id_attach
  762. * @return array, the avatar data as array
  763. */
  764. function getAvatar($id_attach)
  765. {
  766. global $smcFunc;
  767. // Use our cache when possible
  768. if (($cache = cache_get_data('getAvatar_id-' . $id_attach)) !== null)
  769. $avatarData = $cache;
  770. else
  771. {
  772. $request = $smcFunc['db_query']('', '
  773. SELECT id_folder, filename, file_hash, fileext, id_attach, attachment_type, mime_type, approved, id_member
  774. FROM {db_prefix}attachments
  775. WHERE id_attach = {int:id_attach}
  776. AND id_member > {int:blank_id_member}
  777. LIMIT 1',
  778. array(
  779. 'id_attach' => $id_attach,
  780. 'blank_id_member' => 0,
  781. )
  782. );
  783. $avatarData = array();
  784. if ($smcFunc['db_num_rows']($request) != 0)
  785. $avatarData = $smcFunc['db_fetch_row']($request);
  786. $smcFunc['db_free_result']($request);
  787. cache_put_data('getAvatar_id-' . $id_attach, $avatarData, 900);
  788. }
  789. return $avatarData;
  790. }
  791. /**
  792. * Get the specified attachment. This includes a check of the topic
  793. * (it only returns the attachment if it's indeed attached to a message
  794. * in the topic given as parameter), and query_see_board...
  795. *
  796. * @param int $id_attach
  797. * @param int $id_topic
  798. */
  799. function getAttachmentFromTopic($id_attach, $id_topic)
  800. {
  801. global $smcFunc;
  802. // Make sure this attachment is on this board.
  803. $request = $smcFunc['db_query']('', '
  804. SELECT a.id_folder, a.filename, a.file_hash, a.fileext, a.attachment_type, a.mime_type, a.approved, m.id_member
  805. FROM {db_prefix}attachments AS a
  806. INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg AND m.id_topic = {int:current_topic})
  807. INNER JOIN {db_prefix}boards AS b ON (b.id_board = m.id_board AND {query_see_board})
  808. WHERE a.id_attach = {int:attach}
  809. LIMIT 1',
  810. array(
  811. 'attach' => $id_attach,
  812. 'current_topic' => $id_topic,
  813. )
  814. );
  815. $attachmentData = array();
  816. if ($smcFunc['db_num_rows']($request) != 0)
  817. $attachmentData = $smcFunc['db_fetch_row']($request);
  818. $smcFunc['db_free_result']($request);
  819. return $attachmentData;
  820. }
  821. /**
  822. * Increase download counter for id_attach.
  823. * Does not check if it's a thumbnail.
  824. *
  825. * @param int $id_attach
  826. */
  827. function increaseDownloadCounter($id_attach)
  828. {
  829. global $smcFunc;
  830. $smcFunc['db_query']('attach_download_increase', '
  831. UPDATE LOW_PRIORITY {db_prefix}attachments
  832. SET downloads = downloads + 1
  833. WHERE id_attach = {int:id_attach}',
  834. array(
  835. 'id_attach' => $id_attach,
  836. )
  837. );
  838. }
  839. /**
  840. * Approve an attachment, or maybe even more - no permission check!
  841. *
  842. * @param array $attachments
  843. */
  844. function approveAttachments($attachments)
  845. {
  846. global $smcFunc;
  847. if (empty($attachments))
  848. return 0;
  849. // For safety, check for thumbnails...
  850. $request = $smcFunc['db_query']('', '
  851. SELECT
  852. a.id_attach, a.id_member, IFNULL(thumb.id_attach, 0) AS id_thumb
  853. FROM {db_prefix}attachments AS a
  854. LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)
  855. WHERE a.id_attach IN ({array_int:attachments})
  856. AND a.attachment_type = {int:attachment_type}',
  857. array(
  858. 'attachments' => $attachments,
  859. 'attachment_type' => 0,
  860. )
  861. );
  862. $attachments = array();
  863. while ($row = $smcFunc['db_fetch_assoc']($request))
  864. {
  865. // Update the thumbnail too...
  866. if (!empty($row['id_thumb']))
  867. $attachments[] = $row['id_thumb'];
  868. $attachments[] = $row['id_attach'];
  869. }
  870. $smcFunc['db_free_result']($request);
  871. if (empty($attachments))
  872. return 0;
  873. // Approving an attachment is not hard - it's easy.
  874. $smcFunc['db_query']('', '
  875. UPDATE {db_prefix}attachments
  876. SET approved = {int:is_approved}
  877. WHERE id_attach IN ({array_int:attachments})',
  878. array(
  879. 'attachments' => $attachments,
  880. 'is_approved' => 1,
  881. )
  882. );
  883. // In order to log the attachments, we really need their message and filename
  884. $request = $smcFunc['db_query']('', '
  885. SELECT m.id_msg, a.filename
  886. FROM {db_prefix}attachments AS a
  887. INNER JOIN {db_prefix}messages AS m ON (a.id_msg = m.id_msg)
  888. WHERE a.id_attach IN ({array_int:attachments})
  889. AND a.attachment_type = {int:attachment_type}',
  890. array(
  891. 'attachments' => $attachments,
  892. 'attachment_type' => 0,
  893. )
  894. );
  895. while ($row = $smcFunc['db_fetch_assoc']($request))
  896. logAction(
  897. 'approve_attach',
  898. array(
  899. 'message' => $row['id_msg'],
  900. 'filename' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($row['filename'])),
  901. )
  902. );
  903. $smcFunc['db_free_result']($request);
  904. // Remove from the approval queue.
  905. $smcFunc['db_query']('', '
  906. DELETE FROM {db_prefix}approval_queue
  907. WHERE id_attach IN ({array_int:attachments})',
  908. array(
  909. 'attachments' => $attachments,
  910. )
  911. );
  912. call_integration_hook('integrate_approve_attachments', array($attachments));
  913. }
  914. /**
  915. * Removes attachments or avatars based on a given query condition.
  916. * Called by remove avatar/attachment functions.
  917. * It removes attachments based that match the $condition.
  918. * It allows query_types 'messages' and 'members', whichever is need by the
  919. * $condition parameter.
  920. * It does no permissions check.
  921. *
  922. * @param array $condition
  923. * @param string $query_type
  924. * @param bool $return_affected_messages = false
  925. * @param bool $autoThumbRemoval = true
  926. */
  927. function removeAttachments($condition, $query_type = '', $return_affected_messages = false, $autoThumbRemoval = true)
  928. {
  929. global $modSettings, $smcFunc;
  930. // @todo This might need more work!
  931. $new_condition = array();
  932. $query_parameter = array(
  933. 'thumb_attachment_type' => 3,
  934. );
  935. $do_logging = array();
  936. if (is_array($condition))
  937. {
  938. foreach ($condition as $real_type => $restriction)
  939. {
  940. // Doing a NOT?
  941. $is_not = substr($real_type, 0, 4) == 'not_';
  942. $type = $is_not ? substr($real_type, 4) : $real_type;
  943. if (in_array($type, array('id_member', 'id_attach', 'id_msg')))
  944. $new_condition[] = 'a.' . $type . ($is_not ? ' NOT' : '') . ' IN (' . (is_array($restriction) ? '{array_int:' . $real_type . '}' : '{int:' . $real_type . '}') . ')';
  945. elseif ($type == 'attachment_type')
  946. $new_condition[] = 'a.attachment_type = {int:' . $real_type . '}';
  947. elseif ($type == 'poster_time')
  948. $new_condition[] = 'm.poster_time < {int:' . $real_type . '}';
  949. elseif ($type == 'last_login')
  950. $new_condition[] = 'mem.last_login < {int:' . $real_type . '}';
  951. elseif ($type == 'size')
  952. $new_condition[] = 'a.size > {int:' . $real_type . '}';
  953. elseif ($type == 'id_topic')
  954. $new_condition[] = 'm.id_topic IN (' . (is_array($restriction) ? '{array_int:' . $real_type . '}' : '{int:' . $real_type . '}') . ')';
  955. // Add the parameter!
  956. $query_parameter[$real_type] = $restriction;
  957. if ($type == 'do_logging')
  958. $do_logging = $condition['id_attach'];
  959. }
  960. $condition = implode(' AND ', $new_condition);
  961. }
  962. // Delete it only if it exists...
  963. $msgs = array();
  964. $attach = array();
  965. $parents = array();
  966. // Get all the attachment names and id_msg's.
  967. $request = $smcFunc['db_query']('', '
  968. SELECT
  969. a.id_folder, a.filename, a.file_hash, a.attachment_type, a.id_attach, a.id_member' . ($query_type == 'messages' ? ', m.id_msg' : ', a.id_msg') . ',
  970. thumb.id_folder AS thumb_folder, IFNULL(thumb.id_attach, 0) AS id_thumb, thumb.filename AS thumb_filename, thumb.file_hash AS thumb_file_hash, thumb_parent.id_attach AS id_parent
  971. FROM {db_prefix}attachments AS a' .($query_type == 'members' ? '
  972. INNER JOIN {db_prefix}members AS mem ON (mem.id_member = a.id_member)' : ($query_type == 'messages' ? '
  973. INNER JOIN {db_prefix}messages AS m ON (m.id_msg = a.id_msg)' : '')) . '
  974. LEFT JOIN {db_prefix}attachments AS thumb ON (thumb.id_attach = a.id_thumb)
  975. LEFT JOIN {db_prefix}attachments AS thumb_parent ON (thumb.attachment_type = {int:thumb_attachment_type} AND thumb_parent.id_thumb = a.id_attach)
  976. WHERE ' . $condition,
  977. $query_parameter
  978. );
  979. while ($row = $smcFunc['db_fetch_assoc']($request))
  980. {
  981. // Figure out the "encrypted" filename and unlink it ;).
  982. if ($row['attachment_type'] == 1)
  983. {
  984. // if attachment_type = 1, it's... an avatar in a custom avatar directory.
  985. // wasn't it obvious? :P
  986. // @todo look again at this.
  987. @unlink($modSettings['custom_avatar_dir'] . '/' . $row['filename']);
  988. }
  989. else
  990. {
  991. $filename = getAttachmentFilename($row['filename'], $row['id_attach'], $row['id_folder'], false, $row['file_hash']);
  992. @unlink($filename);
  993. // If this was a thumb, the parent attachment should know about it.
  994. if (!empty($row['id_parent']))
  995. $parents[] = $row['id_parent'];
  996. // If this attachments has a thumb, remove it as well.
  997. if (!empty($row['id_thumb']) && $autoThumbRemoval)
  998. {
  999. $thumb_filename = getAttachmentFilename($row['thumb_filename'], $row['id_thumb'], $row['thumb_folder'], false, $row['thumb_file_hash']);
  1000. @unlink($thumb_filename);
  1001. $attach[] = $row['id_thumb'];
  1002. }
  1003. }
  1004. // Make a list.
  1005. if ($return_affected_messages && empty($row['attachment_type']))
  1006. $msgs[] = $row['id_msg'];
  1007. $attach[] = $row['id_attach'];
  1008. }
  1009. $smcFunc['db_free_result']($request);
  1010. // Removed attachments don't have to be updated anymore.
  1011. $parents = array_diff($parents, $attach);
  1012. if (!empty($parents))
  1013. $smcFunc['db_query']('', '
  1014. UPDATE {db_prefix}attachments
  1015. SET id_thumb = {int:no_thumb}
  1016. WHERE id_attach IN ({array_int:parent_attachments})',
  1017. array(
  1018. 'parent_attachments' => $parents,
  1019. 'no_thumb' => 0,
  1020. )
  1021. );
  1022. if (!empty($do_logging))
  1023. {
  1024. // In order to log the attachments, we really need their message and filename
  1025. $request = $smcFunc['db_query']('', '
  1026. SELECT m.id_msg, a.filename
  1027. FROM {db_prefix}attachments AS a
  1028. INNER JOIN {db_prefix}messages AS m ON (a.id_msg = m.id_msg)
  1029. WHERE a.id_attach IN ({array_int:attachments})
  1030. AND a.attachment_type = {int:attachment_type}',
  1031. array(
  1032. 'attachments' => $do_logging,
  1033. 'attachment_type' => 0,
  1034. )
  1035. );
  1036. while ($row = $smcFunc['db_fetch_assoc']($request))
  1037. logAction(
  1038. 'remove_attach',
  1039. array(
  1040. 'message' => $row['id_msg'],
  1041. 'filename' => preg_replace('~&amp;#(\\d{1,7}|x[0-9a-fA-F]{1,6});~', '&#\\1;', $smcFunc['htmlspecialchars']($row['filename'])),
  1042. )
  1043. );
  1044. $smcFunc['db_free_result']($request);
  1045. }
  1046. if (!empty($attach))
  1047. $smcFunc['db_query']('', '
  1048. DELETE FROM {db_prefix}attachments
  1049. WHERE id_attach IN ({array_int:attachment_list})',
  1050. array(
  1051. 'attachment_list' => $attach,
  1052. )
  1053. );
  1054. call_integration_hook('integrate_remove_attachments', array($attach));
  1055. if ($return_affected_messages)
  1056. return array_unique($msgs);
  1057. }
  1058. /**
  1059. * Saves a file and stores it locally for avatar use by id_member.
  1060. * - supports GIF, JPG, PNG, BMP and WBMP formats.
  1061. * - detects if GD2 is available.
  1062. * - uses resizeImageFile() to resize to max_width by max_height, and saves the result to a file.
  1063. * - updates the database info for the member's avatar.
  1064. * - returns whether the download and resize was successful.
  1065. * @uses subs/Graphics.subs.php
  1066. *
  1067. * @param string $temporary_path the full path to the temporary file
  1068. * @param int $memID member ID
  1069. * @param int $max_width
  1070. * @param int $max_height
  1071. * @return boolean whether the download and resize was successful.
  1072. *
  1073. */
  1074. function saveAvatar($temporary_path, $memID, $max_width, $max_height)
  1075. {
  1076. global $modSettings, $smcFunc;
  1077. $ext = !empty($modSettings['avatar_download_png']) ? 'png' : 'jpeg';
  1078. $destName = 'avatar_' . $memID . '_' . time() . '.' . $ext;
  1079. // Just making sure there is a non-zero member.
  1080. if (empty($memID))
  1081. return false;
  1082. removeAttachments(array('id_member' => $memID));
  1083. $id_folder = getAttachmentPathID();
  1084. $avatar_hash = empty($modSettings['custom_avatar_enabled']) ? getAttachmentFilename($destName, false, null, true) : '';
  1085. $smcFunc['db_insert']('',
  1086. '{db_prefix}attachments',
  1087. array(
  1088. 'id_member' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-255', 'fileext' => 'string-8', 'size' => 'int',
  1089. 'id_folder' => 'int',
  1090. ),
  1091. array(
  1092. $memID, empty($modSettings['custom_avatar_enabled']) ? 0 : 1, $destName, $avatar_hash, $ext, 1,
  1093. $id_folder,
  1094. ),
  1095. array('id_attach')
  1096. );
  1097. $attachID = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
  1098. // First, the temporary file will have the .tmp extension.
  1099. $tempName = getAvatarPath() . '/' . $destName . '.tmp';
  1100. // The destination filename will depend on whether custom dir for avatars has been set
  1101. $destName = getAvatarPath() . '/' . $destName;
  1102. $path = getAttachmentPath();
  1103. $destName = empty($avatar_hash) ? $destName : $path . '/' . $attachID . '_' . $avatar_hash;
  1104. // Resize it.
  1105. require_once(SUBSDIR . '/Graphics.subs.php');
  1106. if (!empty($modSettings['avatar_download_png']))
  1107. $success = resizeImageFile($temporary_path, $tempName, $max_width, $max_height, 3);
  1108. else
  1109. $success = resizeImageFile($temporary_path, $tempName, $max_width, $max_height);
  1110. if ($success)
  1111. {
  1112. // Remove the .tmp extension from the attachment.
  1113. if (rename($tempName, $destName))
  1114. {
  1115. list ($width, $height) = getimagesize($destName);
  1116. $mime_type = 'image/' . $ext;
  1117. // Write filesize in the database.
  1118. $smcFunc['db_query']('', '
  1119. UPDATE {db_prefix}attachments
  1120. SET size = {int:filesize}, width = {int:width}, height = {int:height},
  1121. mime_type = {string:mime_type}
  1122. WHERE id_attach = {int:current_attachment}',
  1123. array(
  1124. 'filesize' => filesize($destName),
  1125. 'width' => (int) $width,
  1126. 'height' => (int) $height,
  1127. 'current_attachment' => $attachID,
  1128. 'mime_type' => $mime_type,
  1129. )
  1130. );
  1131. // Retain this globally in case the script wants it.
  1132. $modSettings['new_avatar_data'] = array(
  1133. 'id' => $attachID,
  1134. 'filename' => $destName,
  1135. 'type' => empty($modSettings['custom_avatar_enabled']) ? 0 : 1,
  1136. );
  1137. return true;
  1138. }
  1139. else
  1140. return false;
  1141. }
  1142. else
  1143. {
  1144. $smcFunc['db_query']('', '
  1145. DELETE FROM {db_prefix}attachments
  1146. WHERE id_attach = {int:current_attachment}',
  1147. array(
  1148. 'current_attachment' => $attachID,
  1149. )
  1150. );
  1151. @unlink($tempName);
  1152. return false;
  1153. }
  1154. }
  1155. /**
  1156. * Get the size of a specified image with better error handling.
  1157. * @todo see if it's better in subs/Graphics.subs.php, but one step at the time.
  1158. * Uses getimagesize() to determine the size of a file.
  1159. * Attempts to connect to the server first so it won't time out.
  1160. *
  1161. * @param string $url
  1162. * @return array or false, the image size as array (width, height), or false on failure
  1163. */
  1164. function url_image_size($url)
  1165. {
  1166. // Make sure it is a proper URL.
  1167. $url = str_replace(' ', '%20', $url);
  1168. // Can we pull this from the cache... please please?
  1169. if (($temp = cache_get_data('url_image_size-' . md5($url), 240)) !== null)
  1170. return $temp;
  1171. $t = microtime(true);
  1172. // Get the host to pester...
  1173. preg_match('~^\w+://(.+?)/(.*)$~', $url, $match);
  1174. // Can't figure it out, just try the image size.
  1175. if ($url == '' || $url == 'http://' || $url == 'https://')
  1176. {
  1177. return false;
  1178. }
  1179. elseif (!isset($match[1]))
  1180. {
  1181. $size = @getimagesize($url);
  1182. }
  1183. else
  1184. {
  1185. // Try to connect to the server... give it half a second.
  1186. $temp = 0;
  1187. $fp = @fsockopen($match[1], 80, $temp, $temp, 0.5);
  1188. // Successful? Continue...
  1189. if ($fp != false)
  1190. {
  1191. // Send the HEAD request (since we don't have to worry about chunked, HTTP/1.1 is fine here.)
  1192. fwrite($fp, 'HEAD /' . $match[2] . ' HTTP/1.1' . "\r\n" . 'Host: ' . $match[1] . "\r\n" . 'User-Agent: PHP/ELKARTE' . "\r\n" . 'Connection: close' . "\r\n\r\n");
  1193. // Read in the HTTP/1.1 or whatever.
  1194. $test = substr(fgets($fp, 11), -1);
  1195. fclose($fp);
  1196. // See if it returned a 404/403 or something.
  1197. if ($test < 4)
  1198. {
  1199. $size = @getimagesize($url);
  1200. // This probably means allow_url_fopen is off, let's try GD.
  1201. if ($size === false && function_exists('imagecreatefromstring'))
  1202. {
  1203. include_once(SUBSDIR . '/Package.subs.php');
  1204. // It's going to hate us for doing this, but another request...
  1205. $image = @imagecreatefromstring(fetch_web_data($url));
  1206. if ($image !== false)
  1207. {
  1208. $size = array(imagesx($image), imagesy($image));
  1209. imagedestroy($image);
  1210. }
  1211. }
  1212. }
  1213. }
  1214. }
  1215. // If we didn't get it, we failed.
  1216. if (!isset($size))
  1217. $size = false;
  1218. // If this took a long time, we may never have to do it again, but then again we might...
  1219. if (microtime(true) - $t > 0.8)
  1220. cache_put_data('url_image_size-' . md5($url), $size, 240);
  1221. // Didn't work.
  1222. return $size;
  1223. }
  1224. /**
  1225. * The current attachments path:
  1226. * - BOARDDIR . '/attachments', if nothing is set yet.
  1227. * - if the forum is using multiple attachments directories,
  1228. * then the current path is stored as unserialize($modSettings['attachmentUploadDir'])[$modSettings['currentAttachmentUploadDir']]
  1229. * - otherwise, the current path is $modSettings['attachmentUploadDir'].
  1230. */
  1231. function getAttachmentPath()
  1232. {
  1233. global $modSettings;
  1234. // Make sure this thing exists and it is unserialized
  1235. if (empty($modSettings['attachmentUploadDir']))
  1236. $attachmentDir = BOARDDIR . '/attachments';
  1237. elseif (!empty($modSettings['currentAttachmentUploadDir']) && !is_array($modSettings['attachmentUploadDir']))
  1238. $attachmentDir = unserialize($modSettings['attachmentUploadDir']);
  1239. else
  1240. $attachmentDir = $modSettings['attachmentUploadDir'];
  1241. return is_array($attachmentDir) ? $attachmentDir[$modSettings['currentAttachmentUploadDir']] : $attachmentDir;
  1242. }
  1243. /**
  1244. * Return an array of attachments directories.
  1245. * @see getAttachmentPath()
  1246. */
  1247. function attachmentPaths()
  1248. {
  1249. global $modSettings, $boarddir;
  1250. if (empty($modSettings['attachmentUploadDir']))
  1251. return array($boarddir . '/attachments');
  1252. elseif (!empty($modSettings['currentAttachmentUploadDir']))
  1253. {
  1254. // we have more directories
  1255. if (!is_array($modSettings['attachmentUploadDir']))
  1256. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
  1257. return $modSettings['attachmentUploadDir'];
  1258. }
  1259. else
  1260. return array($modSettings['attachmentUploadDir']);
  1261. }
  1262. /**
  1263. * The avatars path: if custom avatar directory is set, that's it.
  1264. * Otherwise, it's attachments path.
  1265. */
  1266. function getAvatarPath()
  1267. {
  1268. global $modSettings;
  1269. return empty($modSettings['custom_ava…

Large files files are truncated, but you can click here to view the full file