Attachments.php 30 KB

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