Subs-Attachments.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842
  1. <?php
  2. /**
  3. * This file handles the uploading and creation of attachments
  4. * as well as the auto management of the attachment directories.
  5. *
  6. * Simple Machines Forum (SMF)
  7. *
  8. * @package SMF
  9. * @author Simple Machines
  10. *
  11. * @copyright 2013 Simple Machines and individual contributors
  12. * @license http://www.simplemachines.org/about/smf/license.php BSD
  13. *
  14. * @version 2.1 Alpha 1
  15. */
  16. if (!defined('SMF'))
  17. die('No direct access...');
  18. /**
  19. * Check if the current directory is still valid or not.
  20. * If not creates the new directory
  21. *
  22. * @return (bool) false if any error occurred
  23. */
  24. function automanage_attachments_check_directory()
  25. {
  26. global $boarddir, $modSettings, $context;
  27. // Not pretty, but since we don't want folders created for every post. It'll do unless a better solution can be found.
  28. if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'admin')
  29. $doit = true;
  30. elseif (empty($modSettings['automanage_attachments']))
  31. return;
  32. elseif (!isset($_FILES))
  33. return;
  34. elseif (isset($_FILES['attachment']))
  35. foreach ($_FILES['attachment']['tmp_name'] as $dummy)
  36. if (!empty($dummy))
  37. {
  38. $doit = true;
  39. break;
  40. }
  41. if (!isset($doit))
  42. return;
  43. $year = date('Y');
  44. $month = date('m');
  45. $day = date('d');
  46. $rand = md5(mt_rand());
  47. $rand1 = $rand[1];
  48. $rand = $rand[0];
  49. if (!empty($modSettings['attachment_basedirectories']) && !empty($modSettings['use_subdirectories_for_attachments']))
  50. {
  51. if (!is_array($modSettings['attachment_basedirectories']))
  52. $modSettings['attachment_basedirectories'] = unserialize($modSettings['attachment_basedirectories']);
  53. $base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
  54. }
  55. else
  56. $base_dir = 0;
  57. if ($modSettings['automanage_attachments'] == 1)
  58. {
  59. if (!isset($modSettings['last_attachments_directory']))
  60. $modSettings['last_attachments_directory'] = array();
  61. if (!is_array($modSettings['last_attachments_directory']))
  62. $modSettings['last_attachments_directory'] = unserialize($modSettings['last_attachments_directory']);
  63. if (!isset($modSettings['last_attachments_directory'][$base_dir]))
  64. $modSettings['last_attachments_directory'][$base_dir] = 0;
  65. }
  66. $basedirectory = (!empty($modSettings['use_subdirectories_for_attachments']) ? ($modSettings['basedirectory_for_attachments']) : $boarddir);
  67. //Just to be sure: I don't want directory separators at the end
  68. $sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
  69. $basedirectory = rtrim($basedirectory, $sep);
  70. switch ($modSettings['automanage_attachments'])
  71. {
  72. case 1:
  73. $updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . (isset($modSettings['last_attachments_directory'][$base_dir]) ? $modSettings['last_attachments_directory'][$base_dir] : 0);
  74. break;
  75. case 2:
  76. $updir = $basedirectory . DIRECTORY_SEPARATOR . $year;
  77. break;
  78. case 3:
  79. $updir = $basedirectory . DIRECTORY_SEPARATOR . $year . DIRECTORY_SEPARATOR . $month;
  80. break;
  81. case 4:
  82. $updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand;
  83. break;
  84. case 5:
  85. $updir = $basedirectory . DIRECTORY_SEPARATOR . (empty($modSettings['use_subdirectories_for_attachments']) ? 'attachments-' : 'random_') . $rand . DIRECTORY_SEPARATOR . $rand1;
  86. break;
  87. default :
  88. $updir = '';
  89. }
  90. if (!is_array($modSettings['attachmentUploadDir']))
  91. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
  92. if (!in_array($updir, $modSettings['attachmentUploadDir']) && !empty($updir))
  93. $outputCreation = automanage_attachments_create_directory($updir);
  94. elseif (in_array($updir, $modSettings['attachmentUploadDir']))
  95. $outputCreation = true;
  96. if ($outputCreation)
  97. {
  98. $modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
  99. $context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
  100. updateSettings(array(
  101. 'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
  102. ));
  103. }
  104. return $outputCreation;
  105. }
  106. /**
  107. * Creates a directory
  108. *
  109. * @param $updir: the directory to be created
  110. *
  111. * @return (bool) false on errors
  112. */
  113. function automanage_attachments_create_directory($updir)
  114. {
  115. global $modSettings, $initial_error, $context, $boarddir;
  116. $tree = get_directory_tree_elements($updir);
  117. $count = count($tree);
  118. $directory = attachments_init_dir($tree, $count);
  119. if ($directory === false)
  120. {
  121. // Maybe it's just the folder name
  122. $tree = get_directory_tree_elements($boarddir . DIRECTORY_SEPARATOR . $updir);
  123. $count = count($tree);
  124. $directory = attachments_init_dir($tree, $count);
  125. if ($directory === false)
  126. return false;
  127. }
  128. $directory .= DIRECTORY_SEPARATOR . array_shift($tree);
  129. while (!@is_dir($directory) || $count != -1)
  130. {
  131. if (!@is_dir($directory))
  132. {
  133. if (!@mkdir($directory,0755))
  134. {
  135. $context['dir_creation_error'] = 'attachments_no_create';
  136. return false;
  137. }
  138. }
  139. $directory .= DIRECTORY_SEPARATOR . array_shift($tree);
  140. $count--;
  141. }
  142. // @todo: chmod (especially with some servers) is usually bad
  143. if (!is_writable($directory))
  144. {
  145. chmod($directory, 0755);
  146. if (!is_writable($directory))
  147. {
  148. chmod($directory, 0775);
  149. if (!is_writable($directory))
  150. {
  151. chmod($directory, 0777);
  152. if (!is_writable($directory))
  153. {
  154. $context['dir_creation_error'] = 'attachments_no_write';
  155. return false;
  156. }
  157. }
  158. }
  159. }
  160. // Everything seems fine...let's create the .htaccess
  161. if (!file_exists($directory . DIRECTORY_SEPARATOR . '.htaccess'))
  162. secureDirectory($updir, true);
  163. $sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
  164. $updir = rtrim($updir, $sep);
  165. // Only update if it's a new directory
  166. if (!in_array($updir, $modSettings['attachmentUploadDir']))
  167. {
  168. $modSettings['currentAttachmentUploadDir'] = max(array_keys($modSettings['attachmentUploadDir'])) + 1;
  169. $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']] = $updir;
  170. updateSettings(array(
  171. 'attachmentUploadDir' => serialize($modSettings['attachmentUploadDir']),
  172. 'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
  173. ), true);
  174. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
  175. }
  176. $context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
  177. return true;
  178. }
  179. /**
  180. * Called when a directory space limit is reached.
  181. * Creates a new directory and increments the directory suffix number.
  182. *
  183. * @return (bool) false on erros
  184. */
  185. function automanage_attachments_by_space()
  186. {
  187. global $modSettings, $boarddir, $context;
  188. if (!isset($modSettings['automanage_attachments']) || (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] != 1))
  189. return;
  190. $basedirectory = !empty($modSettings['use_subdirectories_for_attachments']) ? $modSettings['basedirectory_for_attachments'] : $boarddir;
  191. //Just to be sure: I don't want directory separators at the end
  192. $sep = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? '\/' : DIRECTORY_SEPARATOR;
  193. $basedirectory = rtrim($basedirectory, $sep);
  194. // Get the current base directory
  195. if (!empty($modSettings['use_subdirectories_for_attachments']) && !empty($modSettings['attachment_basedirectories']))
  196. {
  197. $base_dir = array_search($modSettings['basedirectory_for_attachments'], $modSettings['attachment_basedirectories']);
  198. $base_dir = !empty($modSettings['automanage_attachments']) ? $base_dir : 0;
  199. }
  200. else
  201. $base_dir = 0;
  202. // Get the last attachment directory for that base directory
  203. if (empty($modSettings['last_attachments_directory'][$base_dir]))
  204. $modSettings['last_attachments_directory'][$base_dir] = 0;
  205. // And increment it.
  206. $modSettings['last_attachments_directory'][$base_dir]++;
  207. $updir = $basedirectory . DIRECTORY_SEPARATOR . 'attachments_' . $modSettings['last_attachments_directory'][$base_dir];
  208. if (automanage_attachments_create_directory($updir))
  209. {
  210. $modSettings['currentAttachmentUploadDir'] = array_search($updir, $modSettings['attachmentUploadDir']);
  211. updateSettings(array(
  212. 'last_attachments_directory' => serialize($modSettings['last_attachments_directory']),
  213. 'currentAttachmentUploadDir' => $modSettings['currentAttachmentUploadDir'],
  214. ));
  215. $modSettings['last_attachments_directory'] = unserialize($modSettings['last_attachments_directory']);
  216. return true;
  217. }
  218. else
  219. return false;
  220. }
  221. /**
  222. * Split a path into a list of all directories and subdirectories
  223. *
  224. * @param $directory a path
  225. *
  226. * @return (mixed) an array of all the directories and subdirectories or false on failure
  227. */
  228. function get_directory_tree_elements ($directory)
  229. {
  230. /*
  231. In Windows server both \ and / can be used as directory separators in paths
  232. In Linux (and presumably *nix) servers \ can be part of the name
  233. So for this reasons:
  234. * in Windows we need to explode for both \ and /
  235. * while in linux should be safe to explode only for / (aka DIRECTORY_SEPARATOR)
  236. */
  237. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
  238. $tree = preg_split('#[\\\/]#', $directory);
  239. else
  240. {
  241. if (substr($directory, 0, 1) != DIRECTORY_SEPARATOR)
  242. return false;
  243. $tree = explode(DIRECTORY_SEPARATOR, trim($directory,DIRECTORY_SEPARATOR));
  244. }
  245. return $tree;
  246. }
  247. /**
  248. * Return the first part of a path (i.e. c:\ or / + the first directory), used by automanage_attachments_create_directory
  249. *
  250. * @param $tree an array
  251. * @param $count the number of elements in $tree
  252. *
  253. * @return (string)
  254. */
  255. function attachments_init_dir (&$tree, &$count)
  256. {
  257. $directory = '';
  258. // If on Windows servers the first part of the path is the drive (e.g. "C:")
  259. if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN')
  260. {
  261. //Better be sure that the first part of the path is actually a drive letter...
  262. //...even if, I should check this in the admin page...isn't it?
  263. //...NHAAA Let's leave space for users' complains! :P
  264. if (preg_match('/^[a-z]:$/i',$tree[0]))
  265. $directory = array_shift($tree);
  266. else
  267. return false;
  268. $count--;
  269. }
  270. return $directory;
  271. }
  272. /**
  273. * Moves an attachment to the proper directory and set the relevant data into $_SESSION['temp_attachments']
  274. */
  275. function processAttachments()
  276. {
  277. global $context, $modSettings, $smcFunc, $txt, $user_info;
  278. // Make sure we're uploading to the right place.
  279. if (!empty($modSettings['automanage_attachments']))
  280. automanage_attachments_check_directory();
  281. if (!is_array($modSettings['attachmentUploadDir']))
  282. $modSettings['attachmentUploadDir'] = unserialize($modSettings['attachmentUploadDir']);
  283. $context['attach_dir'] = $modSettings['attachmentUploadDir'][$modSettings['currentAttachmentUploadDir']];
  284. // Is the attachments folder actualy there?
  285. if (!empty($context['dir_creation_error']))
  286. $initial_error = $context['dir_creation_error'];
  287. elseif (!is_dir($context['attach_dir']))
  288. {
  289. $initial_error = 'attach_folder_warning';
  290. log_error(sprintf($txt['attach_folder_admin_warning'], $context['attach_dir']), 'critical');
  291. }
  292. if (!isset($initial_error) && !isset($context['attachments']))
  293. {
  294. // If this isn't a new post, check the current attachments.
  295. if (isset($_REQUEST['msg']))
  296. {
  297. $request = $smcFunc['db_query']('', '
  298. SELECT COUNT(*), SUM(size)
  299. FROM {db_prefix}attachments
  300. WHERE id_msg = {int:id_msg}
  301. AND attachment_type = {int:attachment_type}',
  302. array(
  303. 'id_msg' => (int) $_REQUEST['msg'],
  304. 'attachment_type' => 0,
  305. )
  306. );
  307. list ($context['attachments']['quantity'], $context['attachments']['total_size']) = $smcFunc['db_fetch_row']($request);
  308. $smcFunc['db_free_result']($request);
  309. }
  310. else
  311. $context['attachments'] = array(
  312. 'quantity' => 0,
  313. 'total_size' => 0,
  314. );
  315. }
  316. // Hmm. There are still files in session.
  317. $ignore_temp = false;
  318. if (!empty($_SESSION['temp_attachments']['post']['files']) && count($_SESSION['temp_attachments']) > 1)
  319. {
  320. // Let's try to keep them. But...
  321. $ignore_temp = true;
  322. // If new files are being added. We can't ignore those
  323. foreach ($_FILES['attachment']['tmp_name'] as $dummy)
  324. if (!empty($dummy))
  325. {
  326. $ignore_temp = false;
  327. break;
  328. }
  329. // Need to make space for the new files. So, bye bye.
  330. if (!$ignore_temp)
  331. {
  332. foreach ($_SESSION['temp_attachments'] as $attachID => $attachment)
  333. if (strpos($attachID, 'post_tmp_' . $user_info['id']) !== false)
  334. unlink($attachment['tmp_name']);
  335. $context['we_are_history'] = $txt['error_temp_attachments_flushed'];
  336. $_SESSION['temp_attachments'] = array();
  337. }
  338. }
  339. if (!isset($_FILES['attachment']['name']))
  340. $_FILES['attachment']['tmp_name'] = array();
  341. if (!isset($_SESSION['temp_attachments']))
  342. $_SESSION['temp_attachments'] = array();
  343. // Remember where we are at. If it's anywhere at all.
  344. if (!$ignore_temp)
  345. $_SESSION['temp_attachments']['post'] = array(
  346. 'msg' => !empty($_REQUEST['msg']) ? $_REQUEST['msg'] : 0,
  347. 'last_msg' => !empty($_REQUEST['last_msg']) ? $_REQUEST['last_msg'] : 0,
  348. 'topic' => !empty($topic) ? $topic : 0,
  349. 'board' => !empty($board) ? $board : 0,
  350. );
  351. // If we have an itital error, lets just display it.
  352. if (!empty($initial_error))
  353. {
  354. $_SESSION['temp_attachments']['initial_error'] = $initial_error;
  355. // And delete the files 'cos they ain't going nowhere.
  356. foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
  357. if (file_exists($_FILES['attachment']['tmp_name'][$n]))
  358. unlink($_FILES['attachment']['tmp_name'][$n]);
  359. $_FILES['attachment']['tmp_name'] = array();
  360. }
  361. // Loop through $_FILES['attachment'] array and move each file to the current attachments folder.
  362. foreach ($_FILES['attachment']['tmp_name'] as $n => $dummy)
  363. {
  364. if ($_FILES['attachment']['name'][$n] == '')
  365. continue;
  366. // First, let's first check for PHP upload errors.
  367. $errors = array();
  368. if (!empty($_FILES['attachment']['error'][$n]))
  369. {
  370. if ($_FILES['attachment']['error'][$n] == 2)
  371. $errors[] = array('file_too_big', array($modSettings['attachmentSizeLimit']));
  372. elseif ($_FILES['attachment']['error'][$n] == 6)
  373. log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_6'], 'critical');
  374. else
  375. log_error($_FILES['attachment']['name'][$n] . ': ' . $txt['php_upload_error_' . $_FILES['attachment']['error'][$n]]);
  376. if (empty($errors))
  377. $errors[] = 'attach_php_error';
  378. }
  379. // Try to move and rename the file before doing any more checks on it.
  380. $attachID = 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand());
  381. $destName = $context['attach_dir'] . '/' . $attachID;
  382. if (empty($errors))
  383. {
  384. $_SESSION['temp_attachments'][$attachID] = array(
  385. 'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n])),
  386. 'tmp_name' => $destName,
  387. 'size' => $_FILES['attachment']['size'][$n],
  388. 'type' => $_FILES['attachment']['type'][$n],
  389. 'id_folder' => $modSettings['currentAttachmentUploadDir'],
  390. 'errors' => array(),
  391. );
  392. // Move the file to the attachments folder with a temp name for now.
  393. if (@move_uploaded_file($_FILES['attachment']['tmp_name'][$n], $destName))
  394. @chmod($destName, 0644);
  395. else
  396. {
  397. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_timeout';
  398. if (file_exists($_FILES['attachment']['tmp_name'][$n]))
  399. unlink($_FILES['attachment']['tmp_name'][$n]);
  400. }
  401. }
  402. else
  403. {
  404. $_SESSION['temp_attachments'][$attachID] = array(
  405. 'name' => htmlspecialchars(basename($_FILES['attachment']['name'][$n])),
  406. 'tmp_name' => $destName,
  407. 'errors' => $errors,
  408. );
  409. if (file_exists($_FILES['attachment']['tmp_name'][$n]))
  410. unlink($_FILES['attachment']['tmp_name'][$n]);
  411. }
  412. // If there's no errors to this pont. We still do need to apply some addtional checks before we are finished.
  413. if (empty($_SESSION['temp_attachments'][$attachID]['errors']))
  414. attachmentChecks($attachID);
  415. }
  416. // Mod authors, finally a hook to hang an alternate attachment upload system upon
  417. // Upload to the current attachment folder with the file name $attachID or 'post_tmp_' . $user_info['id'] . '_' . md5(mt_rand())
  418. // Populate $_SESSION['temp_attachments'][$attachID] with the following:
  419. // name => The file name
  420. // tmp_name => Path to the temp file ($context['attach_dir'] . '/' . $attachID).
  421. // size => File size (required).
  422. // type => MIME type (optional if not available on upload).
  423. // id_folder => $modSettings['currentAttachmentUploadDir']
  424. // errors => An array of errors (use the index of the $txt variable for that error).
  425. // Template changes can be done using "integrate_upload_template".
  426. call_integration_hook('integrate_attachment_upload', array());
  427. }
  428. /**
  429. * Performs various checks on an uploaded file.
  430. * - Requires that $_SESSION['temp_attachments'][$attachID] be properly populated.
  431. *
  432. * @param $attachID
  433. */
  434. function attachmentChecks($attachID)
  435. {
  436. global $modSettings, $context, $sourcedir, $smcFunc;
  437. // No data or missing data .... Not necessarily needed, but in case a mod author missed something.
  438. if ( empty($_SESSION['temp_attachments'][$attachID]))
  439. $errror = '$_SESSION[\'temp_attachments\'][$attachID]';
  440. elseif (empty($attachID))
  441. $errror = '$attachID';
  442. elseif (empty($context['attachments']))
  443. $errror = '$context[\'attachments\']';
  444. elseif (empty($context['attach_dir']))
  445. $errror = '$context[\'attach_dir\']';
  446. // Let's get their attention.
  447. if (!empty($error))
  448. fatal_lang_error('attach_check_nag', 'debug', array($error));
  449. // These are the only valid image types for SMF.
  450. $validImageTypes = array(
  451. 1 => 'gif',
  452. 2 => 'jpeg',
  453. 3 => 'png',
  454. 5 => 'psd',
  455. 6 => 'bmp',
  456. 7 => 'tiff',
  457. 8 => 'tiff',
  458. 9 => 'jpeg',
  459. 14 => 'iff'
  460. );
  461. // Just in case this slipped by the first checks, we stop it here and now
  462. if ($_SESSION['temp_attachments'][$attachID]['size'] == 0)
  463. {
  464. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'attach_0_byte_file';
  465. return false;
  466. }
  467. // First, the dreaded security check. Sorry folks, but this should't be avoided
  468. $size = @getimagesize($_SESSION['temp_attachments'][$attachID]['tmp_name']);
  469. if (isset($validImageTypes[$size[2]]))
  470. {
  471. require_once($sourcedir . '/Subs-Graphics.php');
  472. if (!checkImageContents($_SESSION['temp_attachments'][$attachID]['tmp_name'], !empty($modSettings['attachment_image_paranoid'])))
  473. {
  474. // It's bad. Last chance, maybe we can re-encode it?
  475. if (empty($modSettings['attachment_image_reencode']) || (!reencodeImage($_SESSION['temp_attachments'][$attachID]['tmp_name'], $size[2])))
  476. {
  477. // Nothing to do: not allowed or not successful re-encoding it.
  478. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'bad_attachment';
  479. return false;
  480. }
  481. // Success! However, successes usually come for a price:
  482. // we might get a new format for our image...
  483. $old_format = $size[2];
  484. $size = @getimagesize($attachmentOptions['tmp_name']);
  485. if (!(empty($size)) && ($size[2] != $old_format))
  486. {
  487. if (isset($validImageTypes[$size[2]]))
  488. $_SESSION['temp_attachments'][$attachID]['type'] = 'image/' . $validImageTypes[$size[2]];
  489. }
  490. }
  491. }
  492. // Is there room for this sucker?
  493. if (!empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
  494. {
  495. // Check the folder size and count. If it hasn't been done already.
  496. if (empty($context['dir_size']) || empty($context['dir_files']))
  497. {
  498. $request = $smcFunc['db_query']('', '
  499. SELECT COUNT(*), SUM(size)
  500. FROM {db_prefix}attachments
  501. WHERE id_folder = {int:folder_id}
  502. AND attachment_type != {int:type}',
  503. array(
  504. 'folder_id' => $modSettings['currentAttachmentUploadDir'],
  505. 'type' => 1,
  506. )
  507. );
  508. list ($context['dir_files'], $context['dir_size']) = $smcFunc['db_fetch_row']($request);
  509. $smcFunc['db_free_result']($request);
  510. }
  511. $context['dir_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
  512. $context['dir_files']++;
  513. // Are we about to run out of room? Let's notify the admin then.
  514. if (empty($modSettings['attachment_full_notified']) && !empty($modSettings['attachmentDirSizeLimit']) && $modSettings['attachmentDirSizeLimit'] > 4000 && $context['dir_size'] > ($modSettings['attachmentDirSizeLimit'] - 2000) * 1024
  515. || (!empty($modSettings['attachmentDirFileLimit']) && $modSettings['attachmentDirFileLimit'] * .95 < $context['dir_files'] && $modSettings['attachmentDirFileLimit'] > 500))
  516. {
  517. require_once($sourcedir . '/Subs-Admin.php');
  518. emailAdmins('admin_attachments_full');
  519. updateSettings(array('attachment_full_notified' => 1));
  520. }
  521. // // No room left.... What to do now???
  522. if (!empty($modSettings['attachmentDirFileLimit']) && $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit']
  523. || (!empty($modSettings['attachmentDirSizeLimit']) && $context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024))
  524. {
  525. if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1)
  526. {
  527. // Move it to the new folder if we can.
  528. if (automanage_attachments_by_space())
  529. {
  530. rename($_SESSION['temp_attachments'][$attachID]['tmp_name'], $context['attach_dir'] . '/' . $attachID);
  531. $_SESSION['temp_attachments'][$attachID]['tmp_name'] = $context['attach_dir'] . '/' . $attachID;
  532. $_SESSION['temp_attachments'][$attachID]['id_folder'] = $modSettings['currentAttachmentUploadDir'];
  533. $context['dir_size'] = 0;
  534. $context['dir_files'] = 0;
  535. }
  536. // Or, let the user know that it ain't gonna happen.
  537. else
  538. {
  539. if (isset($context['dir_creation_error']))
  540. $_SESSION['temp_attachments'][$attachID]['errors'][] = $context['dir_creation_error'];
  541. else
  542. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
  543. }
  544. }
  545. else
  546. $_SESSION['temp_attachments'][$attachID]['errors'][] = 'ran_out_of_space';
  547. }
  548. }
  549. // Is the file too big?
  550. $context['attachments']['total_size'] += $_SESSION['temp_attachments'][$attachID]['size'];
  551. if (!empty($modSettings['attachmentSizeLimit']) && $_SESSION['temp_attachments'][$attachID]['size'] > $modSettings['attachmentSizeLimit'] * 1024)
  552. $_SESSION['temp_attachments'][$attachID]['errors'][] = array('file_too_big', array(comma_format($modSettings['attachmentSizeLimit'], 0)));
  553. // Check the total upload size for this post...
  554. if (!empty($modSettings['attachmentPostLimit']) && $context['attachments']['total_size'] > $modSettings['attachmentPostLimit'] * 1024)
  555. $_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)));
  556. // Have we reached the maximum number of files we are allowed?
  557. $context['attachments']['quantity']++;
  558. // Set a max limit if none exists
  559. if (empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] >= 50)
  560. $modSettings['attachmentNumPerPostLimit'] = 50;
  561. if (!empty($modSettings['attachmentNumPerPostLimit']) && $context['attachments']['quantity'] > $modSettings['attachmentNumPerPostLimit'])
  562. $_SESSION['temp_attachments'][$attachID]['errors'][] = array('attachments_limit_per_post', array($modSettings['attachmentNumPerPostLimit']));
  563. // File extension check
  564. if (!empty($modSettings['attachmentCheckExtensions']))
  565. {
  566. $allowed = explode(',', strtolower($modSettings['attachmentExtensions']));
  567. foreach ($allowed as $k => $dummy)
  568. $allowed[$k] = trim($dummy);
  569. if (!in_array(strtolower(substr(strrchr($_SESSION['temp_attachments'][$attachID]['name'], '.'), 1)), $allowed))
  570. {
  571. $allowed_extensions = strtr(strtolower($modSettings['attachmentExtensions']), array(',' => ', '));
  572. $_SESSION['temp_attachments'][$attachID]['errors'][] = array('cant_upload_type', array($allowed_extensions));
  573. }
  574. }
  575. // Undo the math if there's an error
  576. if (!empty($_SESSION['temp_attachments'][$attachID]['errors']))
  577. {
  578. if (isset($context['dir_size']))
  579. $context['dir_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
  580. if (isset($context['dir_files']))
  581. $context['dir_files']--;
  582. $context['attachments']['total_size'] -= $_SESSION['temp_attachments'][$attachID]['size'];
  583. $context['attachments']['quantity']--;
  584. return false;
  585. }
  586. return true;
  587. }
  588. /**
  589. * Create an attachment, with the given array of parameters.
  590. * - Adds any addtional or missing parameters to $attachmentOptions.
  591. * - Renames the temporary file.
  592. * - Creates a thumbnail if the file is an image and the option enabled.
  593. *
  594. * @param array $attachmentOptions
  595. */
  596. function createAttachment(&$attachmentOptions)
  597. {
  598. global $modSettings, $sourcedir, $smcFunc, $context;
  599. global $txt, $boarddir;
  600. require_once($sourcedir . '/Subs-Graphics.php');
  601. // These are the only valid image types for SMF.
  602. $validImageTypes = array(
  603. 1 => 'gif',
  604. 2 => 'jpeg',
  605. 3 => 'png',
  606. 5 => 'psd',
  607. 6 => 'bmp',
  608. 7 => 'tiff',
  609. 8 => 'tiff',
  610. 9 => 'jpeg',
  611. 14 => 'iff'
  612. );
  613. // If this is an image we need to set a few additional parameters.
  614. $size = @getimagesize($attachmentOptions['tmp_name']);
  615. list ($attachmentOptions['width'], $attachmentOptions['height']) = $size;
  616. // If it's an image get the mime type right.
  617. if (empty($attachmentOptions['mime_type']) && $attachmentOptions['width'])
  618. {
  619. // Got a proper mime type?
  620. if (!empty($size['mime']))
  621. $attachmentOptions['mime_type'] = $size['mime'];
  622. // Otherwise a valid one?
  623. elseif (isset($validImageTypes[$size[2]]))
  624. $attachmentOptions['mime_type'] = 'image/' . $validImageTypes[$size[2]];
  625. }
  626. // Get the hash if no hash has been given yet.
  627. if (empty($attachmentOptions['file_hash']))
  628. $attachmentOptions['file_hash'] = getAttachmentFilename($attachmentOptions['name'], false, null, true);
  629. // Assuming no-one set the extension let's take a look at it.
  630. if (empty($attachmentOptions['fileext']))
  631. {
  632. $attachmentOptions['fileext'] = strtolower(strrpos($attachmentOptions['name'], '.') !== false ? substr($attachmentOptions['name'], strrpos($attachmentOptions['name'], '.') + 1) : '');
  633. if (strlen($attachmentOptions['fileext']) > 8 || '.' . $attachmentOptions['fileext'] == $attachmentOptions['name'])
  634. $attachmentOptions['fileext'] = '';
  635. }
  636. $smcFunc['db_insert']('',
  637. '{db_prefix}attachments',
  638. array(
  639. 'id_folder' => 'int', 'id_msg' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
  640. 'size' => 'int', 'width' => 'int', 'height' => 'int',
  641. 'mime_type' => 'string-20', 'approved' => 'int',
  642. ),
  643. array(
  644. (int) $attachmentOptions['id_folder'], (int) $attachmentOptions['post'], $attachmentOptions['name'], $attachmentOptions['file_hash'], $attachmentOptions['fileext'],
  645. (int) $attachmentOptions['size'], (empty($attachmentOptions['width']) ? 0 : (int) $attachmentOptions['width']), (empty($attachmentOptions['height']) ? '0' : (int) $attachmentOptions['height']),
  646. (!empty($attachmentOptions['mime_type']) ? $attachmentOptions['mime_type'] : ''), (int) $attachmentOptions['approved'],
  647. ),
  648. array('id_attach')
  649. );
  650. $attachmentOptions['id'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
  651. // @todo Add an error here maybe?
  652. if (empty($attachmentOptions['id']))
  653. return false;
  654. // Now that we have the attach id, let's rename this sucker and finish up.
  655. $attachmentOptions['destination'] = getAttachmentFilename(basename($attachmentOptions['name']), $attachmentOptions['id'], $attachmentOptions['id_folder'], false, $attachmentOptions['file_hash']);
  656. rename($attachmentOptions['tmp_name'], $attachmentOptions['destination']);
  657. // If it's not approved then add to the approval queue.
  658. if (!$attachmentOptions['approved'])
  659. $smcFunc['db_insert']('',
  660. '{db_prefix}approval_queue',
  661. array(
  662. 'id_attach' => 'int', 'id_msg' => 'int',
  663. ),
  664. array(
  665. $attachmentOptions['id'], (int) $attachmentOptions['post'],
  666. ),
  667. array()
  668. );
  669. if (empty($modSettings['attachmentThumbnails']) || (empty($attachmentOptions['width']) && empty($attachmentOptions['height'])))
  670. return true;
  671. // Like thumbnails, do we?
  672. if (!empty($modSettings['attachmentThumbWidth']) && !empty($modSettings['attachmentThumbHeight']) && ($attachmentOptions['width'] > $modSettings['attachmentThumbWidth'] || $attachmentOptions['height'] > $modSettings['attachmentThumbHeight']))
  673. {
  674. if (createThumbnail($attachmentOptions['destination'], $modSettings['attachmentThumbWidth'], $modSettings['attachmentThumbHeight']))
  675. {
  676. // Figure out how big we actually made it.
  677. $size = @getimagesize($attachmentOptions['destination'] . '_thumb');
  678. list ($thumb_width, $thumb_height) = $size;
  679. if (!empty($size['mime']))
  680. $thumb_mime = $size['mime'];
  681. elseif (isset($validImageTypes[$size[2]]))
  682. $thumb_mime = 'image/' . $validImageTypes[$size[2]];
  683. // Lord only knows how this happened...
  684. else
  685. $thumb_mime = '';
  686. $thumb_filename = $attachmentOptions['name'] . '_thumb';
  687. $thumb_size = filesize($attachmentOptions['destination'] . '_thumb');
  688. $thumb_file_hash = getAttachmentFilename($thumb_filename, false, null, true);
  689. $thumb_path = $attachmentOptions['destination'] . '_thumb';
  690. // We should check the file size and count here since thumbs are added to the existing totals.
  691. if (!empty($modSettings['automanage_attachments']) && $modSettings['automanage_attachments'] == 1 && !empty($modSettings['attachmentDirSizeLimit']) || !empty($modSettings['attachmentDirFileLimit']))
  692. {
  693. $context['dir_size'] = isset($context['dir_size']) ? $context['dir_size'] += $thumb_size : $context['dir_size'] = 0;
  694. $context['dir_files'] = isset($context['dir_files']) ? $context['dir_files']++ : $context['dir_files'] = 0;
  695. // If the folder is full, try to create a new one and move the thumb to it.
  696. if ($context['dir_size'] > $modSettings['attachmentDirSizeLimit'] * 1024 || $context['dir_files'] + 2 > $modSettings['attachmentDirFileLimit'])
  697. {
  698. if (automanage_attachments_by_space())
  699. {
  700. rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
  701. $thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
  702. $context['dir_size'] = 0;
  703. $context['dir_files'] = 0;
  704. }
  705. }
  706. }
  707. // If a new folder has been already created. Gotta move this thumb there then.
  708. if ($modSettings['currentAttachmentUploadDir'] != $attachmentOptions['id_folder'])
  709. {
  710. rename($thumb_path, $context['attach_dir'] . '/' . $thumb_filename);
  711. $thumb_path = $context['attach_dir'] . '/' . $thumb_filename;
  712. }
  713. // To the database we go!
  714. $smcFunc['db_insert']('',
  715. '{db_prefix}attachments',
  716. array(
  717. 'id_folder' => 'int', 'id_msg' => 'int', 'attachment_type' => 'int', 'filename' => 'string-255', 'file_hash' => 'string-40', 'fileext' => 'string-8',
  718. 'size' => 'int', 'width' => 'int', 'height' => 'int', 'mime_type' => 'string-20', 'approved' => 'int',
  719. ),
  720. array(
  721. $modSettings['currentAttachmentUploadDir'], (int) $attachmentOptions['post'], 3, $thumb_filename, $thumb_file_hash, $attachmentOptions['fileext'],
  722. $thumb_size, $thumb_width, $thumb_height, $thumb_mime, (int) $attachmentOptions['approved'],
  723. ),
  724. array('id_attach')
  725. );
  726. $attachmentOptions['thumb'] = $smcFunc['db_insert_id']('{db_prefix}attachments', 'id_attach');
  727. if (!empty($attachmentOptions['thumb']))
  728. {
  729. $smcFunc['db_query']('', '
  730. UPDATE {db_prefix}attachments
  731. SET id_thumb = {int:id_thumb}
  732. WHERE id_attach = {int:id_attach}',
  733. array(
  734. 'id_thumb' => $attachmentOptions['thumb'],
  735. 'id_attach' => $attachmentOptions['id'],
  736. )
  737. );
  738. rename($thumb_path, getAttachmentFilename($thumb_filename, $attachmentOptions['thumb'], $modSettings['currentAttachmentUploadDir'], false, $thumb_file_hash));
  739. }
  740. }
  741. }
  742. return true;
  743. }
  744. ?>