Attachments.php 30 KB

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