diff --git a/imagefield_zip.module b/imagefield_zip.module index dc1004c..db61fce 100644 --- a/imagefield_zip.module +++ b/imagefield_zip.module @@ -222,32 +222,53 @@ function _imagefield_zip_cck_walker(&$array, &$cck_field, &$zip_field) { * array of image file objects. */ function imagefield_zip_save_and_extract_upload($field_name) { - // File validation array for file_save_upload() - // Has a .zip file extension. - // Is valid zip. - $validators = array( - 'file_validate_extensions' => array('zip'), - 'imagefield_zip_is_valid_zip' => array(), - ); + $files_array = imagefield_zip_files_array_extractor($field_name); $images = array(); - if ($file = file_save_upload($field_name, $validators)) { - // Extract all files. - $extracted = imagefield_zip_extract($file->filepath); - if (!empty($extracted)) { - // Only select valid image files - $images = array_filter($extracted, '_imagefield_zip_is_archived_image'); + foreach ($files_array as $file_array) { + if (substr($file_array['files']['name'][$field_name], -4) === '.zip') { + // File validation array for file_save_upload() + // Has a .zip file extension. + // Is valid zip. + $validators = array( + 'file_validate_extensions' => array('zip'), + 'imagefield_zip_is_valid_zip' => array(), + ); } else { - watchdog('imagefield_zip', 'Unpacked archive appears to be empty.', array(), WATCHDOG_WARNING); + $validators = array(); } - // Delete the zip file as we extracted and used everything we wanted from it. - imagefield_zip_delete_file($file); - } - else { - watchdog('imagefield_zip', 'Failed to save uploaded file', array(), WATCHDOG_ERROR); + $file = imagefield_zip_file_save_upload($field_name, $validators, FALSE, FILE_EXISTS_RENAME, $file_array); + if ($file) { + $extracted = array(); + // Extract all files. + if (substr($file->filepath, -4) === '.zip') { + $extracted = imagefield_zip_extract($file->filepath); + } + else { + $extracted[] = $file; + } + + if (!empty($extracted)) { + // Only select valid image files + $new_images = array_filter($extracted, '_imagefield_zip_is_archived_image'); + $images = array_merge($images, $new_images); + } + else { + watchdog('imagefield_zip', 'Unpacked archive appears to be empty.', array(), WATCHDOG_WARNING); + } + + if (substr($file->filepath, -4) === '.zip') { + // Delete the zip file as we extracted and used everything we wanted from it. + imagefield_zip_delete_file($file); + } + } + else { + watchdog('imagefield_zip', 'Failed to save uploaded file', array(), WATCHDOG_ERROR); + } } + return $images; } @@ -445,12 +466,13 @@ function _imagefield_zip_form(&$form, &$field_name, &$node, &$groups) { $form[$field_name .'_zip'][$field_name . '_upload'] = array( '#type' => 'file', '#title' => t('Zip Upload for @name', array('@name' => $field_name)), - '#name' => "files[{$field_name}_zip]", + '#name' => "files[{$field_name}_zip][]", '#description' => t('To upload multiple images at a time, attach a ZIP file here. The zip must contain only the allowed image formats. Maximum Filesize: @max_filesize', array('@max_filesize' => $max_filesize)), '#attributes' => array( 'accept' => 'zip' ), '#element_validate' => array('_imagefield_zip_element_validate'), + '#attributes' => array('multiple' => 'multiple'), ); // Code adapted from content_multiple_value_form(). @@ -729,3 +751,183 @@ function imagefield_zip_mkdir_recursive($pathname, $mode = 0777) { } return @mkdir($pathname, $mode); } + +/** + * Saves a file upload to a new location. + * + * The source file is validated as a proper upload and handled as such. + * The file will be added to the files table as a temporary file. Temporary + * files are periodically cleaned. To make the file permanent file call + * file_set_status() to change its status. + * + * @param $source + * A string specifying the name of the upload field to save. + * @param $validators + * (optional) An associative array of callback functions used to validate the + * file. The keys are function names and the values arrays of callback + * parameters which will be passed in after the file object. The + * functions should return an array of error messages; an empty array + * indicates that the file passed validation. The functions will be called in + * the order specified. + * @param $dest + * A string containing the directory $source should be copied to. If this is + * not provided or is not writable, the temporary directory will be used. + * @param $replace + * Replace behavior when the destination file already exists: + * - FILE_EXISTS_REPLACE: Replace the existing file. + * - FILE_EXISTS_RENAME: Append _{incrementing number} until the filename + * is unique. + * - FILE_EXISTS_ERROR: Do nothing and return FALSE. + * @param $file_array + * Emulate $_FILES array. + * + * @return + * An object containing the file information, or 0 in the event of an error. + */ +function imagefield_zip_file_save_upload($source, $validators = array(), $dest = FALSE, $replace = FILE_EXISTS_RENAME, $file_array = array()) { + global $user; + + // Add in our check of the the file name length. + $validators['file_validate_name_length'] = array(); + + // Return 0 if the file is not there. + if ( empty($file_array) + || !isset($file_array['files']['name'][$source]) + || !is_uploaded_file($file_array['files']['tmp_name'][$source]) + ) { + return 0; + } + + // Check for file upload errors and return FALSE if a + // lower level system error occurred. + switch ($file_array['files']['error'][$source]) { + // @see http://php.net/manual/en/features.file-upload.errors.php + case UPLOAD_ERR_OK: + break; + + case UPLOAD_ERR_INI_SIZE: + case UPLOAD_ERR_FORM_SIZE: + drupal_set_message(t('The file %file could not be saved, because it exceeds %maxsize, the maximum allowed size for uploads.', array('%file' => $source, '%maxsize' => format_size(file_upload_max_size()))), 'error'); + return 0; + + case UPLOAD_ERR_PARTIAL: + case UPLOAD_ERR_NO_FILE: + drupal_set_message(t('The file %file could not be saved, because the upload did not complete.', array('%file' => $source)), 'error'); + return 0; + + // Unknown error + default: + drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $source)), 'error'); + return 0; + } + + // Build the list of non-munged extensions. + // @todo: this should not be here. we need to figure out the right place. + $extensions = ''; + foreach ($user->roles as $rid => $name) { + $extensions .= ' '. variable_get("upload_extensions_$rid", + variable_get('upload_extensions_default', 'jpg jpeg gif png txt html doc xls pdf ppt pps odt ods odp')); + } + + // Begin building file object. + $file = new stdClass(); + $file->filename = file_munge_filename(trim(basename($file_array['files']['name'][$source]), '.'), $extensions); + $file->filepath = $file_array['files']['tmp_name'][$source]; + $file->filemime = file_get_mimetype($file->filename); + + // If the destination is not provided, or is not writable, then use the + // temporary directory. + if (empty($dest) || file_check_path($dest) === FALSE) { + $dest = file_directory_temp(); + } + + $file->source = $source; + $file->destination = file_destination(file_create_path($dest .'/'. $file->filename), $replace); + $file->filesize = $file_array['files']['size'][$source]; + + // Call the validation functions. + $errors = array(); + foreach ($validators as $function => $args) { + array_unshift($args, $file); + // Make sure $file is passed around by reference. + $args[0] = &$file; + $errors = array_merge($errors, call_user_func_array($function, $args)); + } + + // Rename potentially executable files, to help prevent exploits. + if (preg_match('/\.(php|pl|py|cgi|asp|js)$/i', $file->filename) && (substr($file->filename, -4) != '.txt')) { + $file->filemime = 'text/plain'; + $file->filepath .= '.txt'; + $file->filename .= '.txt'; + // As the file may be named example.php.txt, we need to munge again to + // convert to example.php_.txt, then create the correct destination. + $file->filename = file_munge_filename($file->filename, $extensions); + $file->destination = file_destination(file_create_path($dest .'/'. $file->filename), $replace); + } + + + // Check for validation errors. + if (!empty($errors)) { + $message = t('The selected file %name could not be uploaded.', array('%name' => $file->filename)); + if (count($errors) > 1) { + $message .= ''; + } + else { + $message .= ' '. array_pop($errors); + } + form_set_error($source, $message); + return 0; + } + + // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary directory. + // This overcomes open_basedir restrictions for future file operations. + $file->filepath = $file->destination; + if (!move_uploaded_file($file_array['files']['tmp_name'][$source], $file->filepath)) { + form_set_error($source, t('File upload error. Could not move uploaded file.')); + watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->filepath)); + return 0; + } + + // If we made it this far it's safe to record this file in the database. + $file->uid = $user->uid; + $file->status = FILE_STATUS_TEMPORARY; + $file->timestamp = time(); + drupal_write_record('files', $file); + + return $file; +} + +/** + * Extract each file uploaded to make it look like a single file. + * + * @param $field_name + * A string specifying the name of the upload field. + * + * @return + * An array containing an emulation of a single file in the $_FILES array. + */ +function imagefield_zip_files_array_extractor($field_name) { + $files_array = array(); + if (!is_array($_FILES['files']['name'][$field_name]) && $_FILES['files']['name'][$field_name] != 'array') { + $files_array[] = $_FILES; + } + else { + foreach ($_FILES['files'] as $key_name => $values) { + foreach ($values as $key_field_name => $info) { + if ($key_field_name != $field_name) { + continue; + } + + if (is_array($info)) { + foreach ($info as $delta => $value) { + $files_array[$delta]['files'][$key_name][$key_field_name] = $value; + if ($key_name == 'orig_name' && empty($files_array[$delta]['files']['name'][$key_field_name])) { + $files_array[$delta]['files']['name'][$key_field_name] = $value; + } + } + } + } + } + } + return $files_array; +}