tripal.upload.inc

File

tripal/includes/tripal.upload.inc
View source
  1. <?php
  2. function tripal_file_upload($type, $filename, $action = NULL, $chunk = 0) {
  3. global $user;
  4. $module = array_key_exists('module', $_GET) ? $_GET['module'] : '';
  5. $file_size = array_key_exists('file_size', $_GET) ? $_GET['file_size'] : '';
  6. $chunk_size = array_key_exists('chunk_size', $_GET) ? $_GET['chunk_size'] : '';
  7. $user_dir = 'public://tripal/users/' . $user->uid;
  8. if (!file_prepare_directory($user_dir, FILE_CREATE_DIRECTORY)) {
  9. $message = 'Could not access the directory on the server for storing this file.';
  10. watchdog('tripal', $message, array(), WATCHDOG_ERROR);
  11. drupal_json_output(array(
  12. 'status' => 'failed',
  13. 'message' => $message,
  14. 'file_id' => '',
  15. ));
  16. return;
  17. }
  18. // Make sure we don't go over the user's quota, but only do this check
  19. // before loading the first chunk so we don't repeat it over and over again.
  20. if ($action == 'check' and $chunk == 0) {
  21. $usage = tripal_get_user_usage($user->uid);
  22. $quota = tripal_get_user_quota($user->uid);
  23. $quota_size = $quota->custom_quota;
  24. if ($file_size + $usage > $quota_size) {
  25. drupal_json_output(array(
  26. 'status' => 'failed',
  27. 'message' => t("Unfortunately, you can not upload this file as the size exceeds the remainder of your quota. See your account page under the 'Uploads' tab to manage your uploaded files."),
  28. 'file_id' => '',
  29. ));
  30. return;
  31. }
  32. // Make sure we don't go over the max file upload size.
  33. $upload_max = variable_get('tripal_upload_max_size', 10000000000);
  34. if ($file_size > $upload_max) {
  35. $message = t("Unfortunately, you can not upload this file as the size exceeds the the maximum file size allowed by this site: " . tripal_format_bytes($upload_max) . '. ');
  36. if (user_access('administer tripal')) {
  37. $message .= t('You can manage the file upload by visiting: Home » Administration » Tripal » User File Management.');
  38. }
  39. drupal_json_output(array(
  40. 'status' => 'failed',
  41. 'message' => $message,
  42. 'file_id' => '',
  43. ));
  44. return;
  45. }
  46. }
  47. // Allow the module that will own the file to make some checks. The module
  48. // is allowed to stop the upload as needed.
  49. $hook_name = $module . '_file_upload_check';
  50. if (function_exists($hook_name)) {
  51. $details = array(
  52. 'filename' => $filename,
  53. 'file_size' => $file_size,
  54. 'chunk_size' => $chunk_size,
  55. );
  56. $message = '';
  57. $status = $hook_name($action, $details, $message);
  58. if ($status === FALSE) {
  59. drupal_json_output(array(
  60. 'status' => 'failed',
  61. 'message' => $message,
  62. 'file_id' => '',
  63. ));
  64. return;
  65. }
  66. }
  67. switch ($action) {
  68. // If the action is 'save' then the callee is sending a chunk of the file
  69. case 'save':
  70. tripal_file_upload_put($filename, $chunk, $user_dir);
  71. break;
  72. case 'check':
  73. tripal_file_upload_verify($filename, $chunk, $user_dir);
  74. break;
  75. case 'merge':
  76. tripal_file_upload_merge($filename, $type, $user_dir);
  77. break;
  78. }
  79. }
  80. /**
  81. * Merges all chunks into a single file
  82. */
  83. function tripal_file_upload_merge($filename, $type, $user_dir) {
  84. global $user;
  85. $module = $_GET['module'];
  86. $status = 'merging';
  87. $message = '';
  88. // Build the paths to the temp directory and merged file.
  89. $temp_dir = $user_dir . '/temp' . '/' . $filename;
  90. $merge_file = $user_dir . '/' . $filename;
  91. // If the temp directory where the chunks are found does not exist and the
  92. // client is requesting merge then most likely the file has already been
  93. // merged and the user hit the upload button again.
  94. if (file_exists($temp_dir)) {
  95. // Get the upload log.
  96. $log = tripal_file_upload_read_log($temp_dir);
  97. // Keep up with the expected file size.
  98. $merge_size = 0;
  99. // Open the new merged file.
  100. $merge_fh = fopen($merge_file, "w");
  101. if ($merge_fh){
  102. if (flock($merge_fh, LOCK_EX)) {
  103. $chunks_written = $log['chunks_written'];
  104. $max_chunk = max(array_keys($chunks_written));
  105. // Iterate through the chunks in order and see if any are missing.
  106. // If so then break out of the loop and fail. Otherwise concatentate
  107. // them together.
  108. for ($i = 0; $i <= $max_chunk; $i++) {
  109. if (!array_key_exists($i, $chunks_written)) {
  110. $status = 'failed';
  111. $message = 'Missing some chunks. Cannot complete file merge.';
  112. break;
  113. }
  114. $merge_size += $chunks_written[$i];
  115. $chunk_file = $temp_dir . '/' . $filename . '.chunk.' . $i;
  116. $cfh = fopen($chunk_file, 'r');
  117. while ($data = fread($cfh, 1024)) {
  118. fwrite($merge_fh, $data);
  119. }
  120. fclose($cfh);
  121. } // end for ($i = 0; $i <= $max_chunk; $i++) { ...
  122. if (filesize($merge_file) != $merge_size) {
  123. $status = 'failed';
  124. $message = 'File was uploaded but final merged size is incorrect: ' . $merge_file . '.';
  125. }
  126. }
  127. else {
  128. $status = 'failed';
  129. $message = 'Cannot lock merged file for writing: ' . $merge_file . '.';
  130. }
  131. }
  132. else {
  133. $status = 'failed';
  134. $message = 'Cannot open merged file: ' . $merge_file . '.';
  135. }
  136. flock($merge_fh, LOCK_UN);
  137. fclose($merge_fh);
  138. }
  139. // Make sure the merged file exists
  140. if (!file_exists($merge_file)) {
  141. $status = 'failed';
  142. $message = 'Merge file is missing after upload ' . $merge_file . '.';
  143. }
  144. $file_id = NULL;
  145. // If the file has been successfully merged then do a few other things...
  146. if ($status != 'failed') {
  147. // See if this file is already managed if so, then it has been uploaded
  148. // before and we don't need to add a managed item again.
  149. $fid = db_select('file_managed', 'fm')
  150. ->fields('fm', ['fid'])
  151. ->condition('uri', $merge_file)
  152. ->execute()
  153. ->fetchField();
  154. // Add the file if it is not already managed.
  155. if (!$fid) {
  156. $file = new stdClass();
  157. $file->uri = $merge_file;
  158. $file->filename = $filename;
  159. $file->filemime = file_get_mimetype($merge_file);
  160. $file->uid = $user->uid;
  161. $file->status = FILE_STATUS_PERMANENT;
  162. $file = file_save($file);
  163. $fid = $file->fid;
  164. }
  165. // Reload the file object to get a full object.
  166. $file_id = $fid;
  167. $file = file_load($fid);
  168. // Set the file as being managed by Tripal.
  169. file_usage_add($file, 'tripal', $type, 0);
  170. // Set the file expiration.
  171. tripal_reset_file_expiration($fid);
  172. // Generate an md5 file the uploaded file.
  173. $full_path = drupal_realpath($file->uri);
  174. $md5sum = md5_file($full_path);
  175. $md5sum_file = fopen("$full_path.md5", "w");
  176. fwrite($md5sum_file, $md5sum);
  177. fclose($md5sum_file);
  178. // Remove the temporary directory.
  179. file_unmanaged_delete_recursive($temp_dir);
  180. // Now let the submitting module deal with it.
  181. $function = $module . '_handle_uploaded_file';
  182. if (function_exists($function)) {
  183. $function($file, $type);
  184. }
  185. $status = 'completed';
  186. }
  187. if ($status == 'failed') {
  188. watchdog('tripal', $message, array(), WATCHDOG_ERROR);
  189. }
  190. drupal_json_output(array(
  191. 'status' => $status,
  192. 'message' => $message,
  193. 'file_id' => $file_id,
  194. ));
  195. }
  196. /**
  197. * Checks the size of a chunk to see if is fully uploaded.
  198. *
  199. * @return
  200. * returns a JSON array with a status, message and the
  201. * current chunk.
  202. */
  203. function tripal_file_upload_verify($filename, $chunk, $user_dir) {
  204. $chunk_size = $_GET['chunk_size'];
  205. // Store the chunked file in a temp folder.
  206. $temp_dir = $user_dir . '/temp' . '/' . $filename;
  207. if (!file_exists($temp_dir)) {
  208. mkdir($temp_dir, 0700, TRUE);
  209. }
  210. // Get the upload log.
  211. $log = tripal_file_upload_read_log($temp_dir);
  212. $chunks_written = $log['chunks_written'];
  213. $max_chunk = 0;
  214. if ($chunks_written) {
  215. $max_chunk = max(array_keys($chunks_written));
  216. }
  217. // Iterate through the chunks in order and see if any are missing.
  218. // If so then break out of the loop and this is the chunk to start
  219. // on.
  220. for ($i = 0; $i <= $max_chunk; $i++) {
  221. if (!array_key_exists($i, $chunks_written)) {
  222. break;
  223. }
  224. }
  225. drupal_json_output(array(
  226. 'status' => 'success',
  227. 'message' => '',
  228. 'curr_chunk' => $i,
  229. ));
  230. }
  231. /**
  232. * Saves the contents of the file being sent to the server.
  233. *
  234. * The file is saved using the filename the chunk number as an
  235. * extension. This function uses file locking to prevent two
  236. * jobs from writing to the same file at the same time.
  237. */
  238. function tripal_file_upload_put($filename, $chunk, $user_dir) {
  239. // Get the HTTP PUT data.
  240. $putdata = fopen("php://input", "r");
  241. $size = $_SERVER['CONTENT_LENGTH'];
  242. // Store the chunked file in a temp folder.
  243. $temp_dir = $user_dir . '/temp/' . $filename;
  244. if (!file_exists($temp_dir)) {
  245. mkdir($temp_dir, 0700, TRUE);
  246. }
  247. // Open the file for writing if doesn't already exist with the proper size.
  248. $chunk_file = $temp_dir . '/' . $filename . '.chunk.' . $chunk;
  249. if (!file_exists($chunk_file) or filesize($chunk_file) != $size) {
  250. // Read the data 1 KB at a time and write to the file
  251. $fh = fopen($chunk_file, "w");
  252. // Lock the file for writing. We don't want two different
  253. // processes trying to write to the same file at the same time.
  254. if (flock($fh, LOCK_EX)) {
  255. while ($data = fread($putdata, 1024)) {
  256. fwrite($fh, $data);
  257. }
  258. flock($fh, LOCK_UN);
  259. fclose($fh);
  260. }
  261. }
  262. fclose($putdata);
  263. // Get the current log, updated and re-write it.
  264. $log = tripal_file_upload_read_log($temp_dir);
  265. $log['chunks_written'][$chunk] = $size;
  266. tripal_file_upoad_write_log($temp_dir, $log);
  267. }
  268. /**
  269. * Reads the upload log file.
  270. *
  271. * The log file is used to keep track of which chunks have been uploaded.
  272. * The format is an array with a key of 'chunks_written' which each element
  273. * a key/value pair containing the chunk index as the key and the chunk size
  274. * as the value.
  275. *
  276. * @param $temp_dir
  277. * The directory where the log file will be written. It must be a unique
  278. * directory where only chunks from a single file are kept.
  279. */
  280. function tripal_file_upload_read_log($temp_dir) {
  281. $log_file = $temp_dir . '/tripal_upload.log';
  282. $log = NULL;
  283. if (file_exists($log_file)) {
  284. $fh = fopen($log_file, "r");
  285. if ($fh and flock($fh, LOCK_EX)) {
  286. $contents = '';
  287. while ($data = fread($fh, 1024)) {
  288. $contents .= $data;
  289. }
  290. $log = unserialize($contents);
  291. }
  292. flock($fh, LOCK_UN);
  293. fclose($fh);
  294. }
  295. if (!is_array($log)) {
  296. $log = array(
  297. 'chunks_written' => array(),
  298. );
  299. }
  300. return $log;
  301. }
  302. /**
  303. * Writes the upload log file.
  304. *
  305. * The log file is used to keep track of which chunks have been uploaded.
  306. * The format is an array with a key of 'chunks_written' which each element
  307. * a key/value pair containing the chunk index as the key and the chunk size
  308. * as the value.
  309. *
  310. * @param $temp_dir
  311. * The directory where the log file will be written. It must be a unique
  312. * directory where only chunks from a single file are kept.
  313. * @param $log
  314. * The log array, that is serialized and then written to the file.
  315. */
  316. function tripal_file_upoad_write_log($temp_dir, $log) {
  317. $log_file = $temp_dir . '/tripal_upload.log';
  318. if (!$log or !is_array($log)) {
  319. $log = array(
  320. 'chunks_written' => array(),
  321. );
  322. }
  323. // Get the last chunk read
  324. $fh = fopen($log_file, "w");
  325. if ($fh and flock($fh, LOCK_EX)) {
  326. fwrite($fh, serialize($log));
  327. }
  328. flock($fh, LOCK_UN);
  329. fclose($fh);
  330. }