diff --git a/modules/file/file.field.inc b/modules/file/file.field.inc
index 1189704..c7485f9 100644
--- a/modules/file/file.field.inc
+++ b/modules/file/file.field.inc
@@ -408,6 +408,17 @@ function file_field_widget_info() {
'default value' => FIELD_BEHAVIOR_NONE,
),
),
+ 'file_mfw' => array(
+ 'label' => t('Multiupload'),
+ 'field types' => array('file'),
+ 'behaviors' => array(
+ 'multiple values' => FIELD_BEHAVIOR_CUSTOM,
+ 'default value' => FIELD_BEHAVIOR_NONE,
+ ),
+ 'settings' => array(
+ 'progress_indicator' => 'throbber',
+ ),
+ ),
);
}
@@ -430,6 +441,7 @@ function file_field_widget_settings_form($field, $instance) {
'#weight' => 16,
'#access' => file_progress_implementation(),
);
+ $form['#attached']['js'] = array(drupal_get_path('module', 'multiupload_filefield_widget') . '/mfw.js');
return $form;
}
@@ -444,28 +456,72 @@ function file_field_widget_form(&$form, &$form_state, $field, $instance, $langco
'display' => !empty($field['settings']['display_default']),
'description' => '',
);
+ if($instance['widget']['type']=="file_generic") {
+ $widget_type = "generic";
+ } elseif($instance['widget']['type']=="file_mfw") {
+ $widget_type = "multi";
+ }
- // Load the items for form rebuilds from the field state as they might not be
- // in $form_state['values'] because of validation limitations. Also, they are
- // only passed in as $items when editing existing entities.
- $field_state = field_form_get_state($element['#field_parents'], $field['field_name'], $langcode, $form_state);
- if (isset($field_state['items'])) {
- $items = $field_state['items'];
- }
-
- // Essentially we use the managed_file type, extended with some enhancements.
- $element_info = element_info('managed_file');
- $element += array(
- '#type' => 'managed_file',
- '#upload_location' => file_field_widget_uri($field, $instance),
- '#upload_validators' => file_field_widget_upload_validators($field, $instance),
- '#value_callback' => 'file_field_widget_value',
- '#process' => array_merge($element_info['#process'], array('file_field_widget_process')),
- '#progress_indicator' => $instance['widget']['settings']['progress_indicator'],
- // Allows this field to return an array instead of a single value.
- '#extended' => TRUE,
- );
+ if($widget_type == "generic") {
+ // Load the items for form rebuilds from the field state as they might not be
+ // in $form_state['values'] because of validation limitations. Also, they are
+ // only passed in as $items when editing existing entities.
+ $field_state = field_form_get_state($element['#field_parents'], $field['field_name'], $langcode, $form_state);
+ if (isset($field_state['items'])) {
+ $items = $field_state['items'];
+ }
+ // Essentially we use the managed_file type, extended with some enhancements.
+ $element_info = element_info('managed_file');
+ $element += array(
+ '#type' => 'managed_file',
+ '#upload_location' => file_field_widget_uri($field, $instance),
+ '#upload_validators' => file_field_widget_upload_validators($field, $instance),
+ '#value_callback' => 'file_field_widget_value',
+ '#process' => array_merge($element_info['#process'], array('file_field_widget_process')),
+ '#progress_indicator' => $instance['widget']['settings']['progress_indicator'],
+ // Allows this field to return an array instead of a single value.
+ '#extended' => TRUE,
+ );
+ } elseif($widget_type == "multi") {
+ // Retrieve any values set in $form_state, as will be the case during AJAX
+ // rebuilds of this form.
+ if (isset($form_state['values'])) {
+ $path = array_merge($element['#field_parents'], array($field['field_name'], $langcode));
+ $path_exists = FALSE;
+ $values = drupal_array_get_nested_value($form_state['values'], $path, $path_exists);
+ if ($path_exists) {
+ $items = $values;
+ drupal_array_set_nested_value($form_state['values'], $path, NULL);
+ }
+ }
+ foreach ($items as $delta => $item) {
+ $items[$delta] = array_merge($defaults, $items[$delta]);
+ // Remove any items from being displayed that are not needed.
+ if ($items[$delta]['fid'] == 0) {
+ unset($items[$delta]);
+ }
+ }
+
+ // Re-index deltas after removing empty items.
+ $items = array_values($items);
+
+ // Update order according to weight.
+ $items = _field_sort_items($field, $items);
+
+ // Essentially we use the mfw_managed_file type, extended with some enhancements.
+ $element_info = element_info('mfw_managed_file');
+ $element += array(
+ '#type' => 'mfw_managed_file',
+ '#default_value' => isset($items[$delta]) ? $items[$delta] : $defaults,
+ '#upload_location' => file_field_widget_uri($field, $instance),
+ '#upload_validators' => file_field_widget_upload_validators($field, $instance),
+ '#value_callback' => 'mfw_field_widget_value',
+ '#process' => array_merge($element_info['#process'], array('mfw_field_widget_process')),
+ // Allows this field to return an array instead of a single value.
+ '#extended' => TRUE,
+ );
+ }
if ($field['cardinality'] == 1) {
// Set the default value.
$element['#default_value'] = !empty($items) ? $items[0] : $defaults;
@@ -477,6 +533,7 @@ function file_field_widget_form(&$form, &$form_state, $field, $instance, $langco
}
else {
// If there are multiple values, add an element for each existing one.
+ $delta = 0;
foreach ($items as $item) {
$elements[$delta] = $element;
$elements[$delta]['#default_value'] = $item;
@@ -496,7 +553,6 @@ function file_field_widget_form(&$form, &$form_state, $field, $instance, $langco
$elements['#file_upload_delta'] = $delta;
$elements['#theme'] = 'file_widget_multiple';
$elements['#theme_wrappers'] = array('fieldset');
- $elements['#process'] = array('file_field_widget_process_multiple');
$elements['#title'] = $element['#title'];
$elements['#description'] = $element['#description'];
$elements['#field_name'] = $element['#field_name'];
@@ -508,8 +564,12 @@ function file_field_widget_form(&$form, &$form_state, $field, $instance, $langco
// a hook_form_alter().
$elements['#file_upload_title'] = t('Add a new file');
$elements['#file_upload_description'] = theme('file_upload_help', array('description' => '', 'upload_validators' => $elements[0]['#upload_validators']));
+ if($widget_type == "generic") {
+ $elements['#process'] = array('file_field_widget_process_multiple');
+ } elseif ($widget_type == "multi") {
+ $elements['#process'] = array('mfw_field_widget_process_multiple');
+ }
}
-
return $elements;
}
@@ -1006,3 +1066,92 @@ function theme_file_formatter_table($variables) {
return empty($rows) ? '' : theme('table', array('header' => $header, 'rows' => $rows));
}
+
+//Multi file processing
+
+/**
+ * The #value_callback for the file_mfw field element.
+ */
+function mfw_field_widget_value($element, $input = FALSE, &$form_state) {
+ if ($input) {
+ // Checkboxes lose their value when empty.
+ // If the display field is present make sure its unchecked value is saved.
+ $field = field_widget_field($element, $form_state);
+ if (empty($input['display'])) {
+ $input['display'] = $field['settings']['display_field'] ? 0 : 1;
+ }
+ }
+
+ // We depend on the mfw managed file element to handle uploads.
+ $return = mfw_managed_file_value($element, $input, $form_state);
+
+ // Ensure that all the required properties are returned even if empty.
+ $return += array(
+ 'fid' => 0,
+ 'display' => 1,
+ 'description' => '',
+ );
+
+ $last_parent = $element['#parents'][count($element['#parents']) - 1];
+ $form_state['values'][$element['#field_name']]['und'][$last_parent] = $return;
+
+ return $return;
+}
+
+/**
+ * An element #process callback for the mfw_file field type.
+ *
+ * Expands the mfw_file type to include the description and display fields.
+ */
+function mfw_field_widget_process($element, &$form_state, &$form) {
+ return file_field_widget_process($element, $form_state, $form);
+}
+
+/**
+ * An element #process callback for a group of mfw_file fields.
+ *
+ * Adds the weight field to each row so it can be ordered and adds a new AJAX
+ * wrapper around the entire group so it can be replaced all at once.
+ */
+function mfw_field_widget_process_multiple($element, &$form_state, $form) {
+ $upload_name = 'files_' . implode('_', $element['#parents']) . '_' . $element['#file_upload_delta'];
+ if (array_key_exists($upload_name, $_FILES)) {
+ $count = count($_FILES[$upload_name]['name']);
+ //Supposing #file_upload_delta is always the last delta this will work
+ for ($i = 1; $i < $count; $i++) {
+ $element[] = $element[$element['#file_upload_delta']];
+ }
+ }
+
+ $element_children = element_children($element, TRUE);
+ $count = count($element_children);
+
+ foreach ($element_children as $delta => $key) {
+ if ($key < $element['#file_upload_delta']) {
+ $description = _file_field_get_description_from_element($element[$key]);
+ $element[$key]['_weight'] = array(
+ '#type' => 'weight',
+ '#title' => $description ? t('Weight for @title', array('@title' => $description)) : t('Weight for new file'),
+ '#title_display' => 'invisible',
+ '#delta' => $count,
+ '#default_value' => $delta,
+ );
+ }
+ else {
+ // The title needs to be assigned to the upload field so that validation
+ // errors include the correct widget label.
+ $element[$key]['#title'] = $element['#title'];
+ $element[$key]['_weight'] = array(
+ '#type' => 'hidden',
+ '#default_value' => $delta,
+ );
+ }
+ }
+
+ // Add a new wrapper around all the elements for AJAX replacement.
+ $element['#prefix'] = '
';
+ $element['#suffix'] = '
';
+
+ return $element;
+}
+
diff --git a/modules/file/file.module b/modules/file/file.module
index 3d351fa..bb3d783 100644
--- a/modules/file/file.module
+++ b/modules/file/file.module
@@ -79,6 +79,25 @@ function file_element_info() {
'js' => array($file_path . '/file.js'),
),
);
+ $types['mfw_managed_file'] = array(
+ '#input' => TRUE,
+ '#process' => array('mfw_managed_file_process'),
+ '#value_callback' => 'mfw_managed_file_value',
+ '#element_validate' => array('file_managed_file_validate'),
+ '#pre_render' => array('file_managed_file_pre_render'),
+ '#theme' => 'file_managed_file',
+ '#theme_wrappers' => array('form_element'),
+ '#progress_indicator' => 'throbber',
+ '#progress_message' => NULL,
+ '#upload_validators' => array(),
+ '#upload_location' => NULL,
+ '#size' => 22,
+ '#extended' => FALSE,
+ '#attached' => array(
+ 'css' => array($file_path . '/file.css'),
+ 'js' => array($file_path . '/mfw.js', $file_path . '/file.js'),
+ ),
+ );
return $types;
}
@@ -1011,3 +1030,341 @@ function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISI
/**
* @} End of "defgroup file-module-api".
*/
+
+// Multiple File Upload helper functions
+
+/**
+ * Process function to expand the mfw_managed_file element type.
+ *
+ * Expands the file type to include Upload and Remove buttons, as well as
+ * support for a default value.
+ */
+function mfw_managed_file_process($element, &$form_state, &$form) {
+ $element = file_managed_file_process($element, $form_state, $form);
+ $element['upload']['#attributes'] = array('multiple' => 'multiple');
+ $element['upload']['#name'] = 'files_' . implode('_', $element['#parents']) . '[]';
+ $element['upload']['#attached']['js'][0]['data']['mfw'] = $element['upload']['#attached']['js'][0]['data']['file'];
+ unset($element['upload']['#attached']['js'][0]['data']['file']);
+ return $element;
+}
+
+/**
+ * The #value_callback for a mfw_managed_file type element.
+ *
+ */
+function mfw_managed_file_value(&$element, $input = FALSE, $form_state = NULL) {
+ $fid = 0;
+
+ // Find the current value of this field from the form state.
+ $form_state_fid = $form_state['values'];
+ foreach ($element['#parents'] as $parent) {
+ $form_state_fid = isset($form_state_fid[$parent]) ? $form_state_fid[$parent] : 0;
+ }
+
+ if ($element['#extended'] && isset($form_state_fid['fid'])) {
+ $fid = $form_state_fid['fid'];
+ }
+ elseif (is_numeric($form_state_fid)) {
+ $fid = $form_state_fid;
+ }
+
+ // Process any input and save new uploads.
+ if ($input !== FALSE) {
+ $return = $input;
+ $element['#file_upload_delta_original'] = isset($form_state['complete form'][$element['#parents'][0]][$element['#parents'][1]]['#file_upload_delta']) ? $form_state['complete form'][$element['#parents'][0]][$element['#parents'][1]]['#file_upload_delta'] : 0;
+
+ // Uploads take priority over all other values.
+ if ($file = mfw_managed_file_save_upload($element)) {
+ $fid = $file->fid;
+ }
+ else {
+ // Check for #filefield_value_callback values.
+ // Because FAPI does not allow multiple #value_callback values like it
+ // does for #element_validate and #process, this fills the missing
+ // functionality to allow File fields to be extended through FAPI.
+ if (isset($element['#file_value_callbacks'])) {
+ foreach ($element['#file_value_callbacks'] as $callback) {
+ $callback($element, $input, $form_state);
+ }
+ }
+ // Load file if the FID has changed to confirm it exists.
+ if (isset($input['fid']) && $file = file_load($input['fid'])) {
+ $fid = $file->fid;
+ }
+ }
+ }
+
+ // If there is no input, set the default value.
+ else {
+ if ($element['#extended']) {
+ $default_fid = isset($element['#default_value']['fid']) ? $element['#default_value']['fid'] : 0;
+ $return = isset($element['#default_value']) ? $element['#default_value'] : array('fid' => 0);
+ }
+ else {
+ $default_fid = isset($element['#default_value']) ? $element['#default_value'] : 0;
+ $return = array('fid' => 0);
+ }
+
+ // Confirm that the file exists when used as a default value.
+ if ($default_fid && $file = file_load($default_fid)) {
+ $fid = $file->fid;
+ }
+ }
+
+ $return['fid'] = $fid;
+
+ return $return;
+}
+
+/**
+ * Given a mfw_managed_file element, save any files that have been uploaded into it.
+ *
+ *
+ * @param $element
+ * The FAPI element whose values are being saved.
+ * @return
+ * The file object representing the file that was saved, or FALSE if no file
+ * was saved.
+ */
+function mfw_managed_file_save_upload($element) {
+ $last_parent = array_pop($element['#parents']);
+ $upload_name = 'files_' . implode('_', $element['#parents']) . '_' . $element['#file_upload_delta_original'];
+ array_push($element['#parents'], $last_parent);
+
+ $file_number = $last_parent - $element['#file_upload_delta_original'];
+
+ if (isset($_FILES[$upload_name]['name'][$file_number])) {
+ $name = $_FILES[$upload_name]['name'][$file_number];
+ if (empty($name)) {
+ return FALSE;
+ }
+
+ $destination = isset($element['#upload_location']) ? $element['#upload_location'] : NULL;
+ if (isset($destination) && !file_prepare_directory($destination, FILE_CREATE_DIRECTORY)) {
+ watchdog('file', 'The upload directory %directory for the file field !name could not be created or is not accessible. A newly uploaded file could not be saved in this directory as a consequence, and the upload was canceled.', array('%directory' => $destination, '!name' => $element['#field_name']));
+ form_set_error($upload_name, t('The file could not be uploaded.'));
+ return FALSE;
+ }
+
+ if (!$file = mfw_file_save_upload($upload_name, $file_number, $element['#upload_validators'], $destination)) {
+ watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name));
+ form_set_error($upload_name, t('The file in the !name field was unable to be uploaded.', array('!name' => $element['#title'])));
+ return FALSE;
+ }
+ return $file;
+ }
+ else {
+ return FALSE;
+ }
+}
+
+
+/**
+ * Saves a file upload to a new location.
+ *
+ * The file will be added to the {file_managed} table as a temporary file.
+ * Temporary files are periodically cleaned. To make the file a permanent file,
+ * assign the status and use file_save() to save the changes.
+ *
+ * @param $source
+ * A string specifying the filepath or URI of the uploaded file to save.
+ * @param $file_number
+ * The array key of the file to save in $_FILES[$source]['name'].
+ * @param $validators
+ * An optional, associative array of callback functions used to validate the
+ * file. See file_validate() for a full discussion of the array format.
+ * If no extension validator is provided it will default to a limited safe
+ * list of extensions which is as follows: "jpg jpeg gif png txt
+ * doc xls pdf ppt pps odt ods odp". To allow all extensions you must
+ * explicitly set the 'file_validate_extensions' validator to an empty array
+ * (Beware: this is not safe and should only be allowed for trusted users, if
+ * at all).
+ * @param $destination
+ * A string containing the URI $source should be copied to.
+ * This must be a stream wrapper URI. If this value is omitted, Drupal's
+ * temporary files scheme will be used ("temporary://").
+ * @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.
+ *
+ * @return
+ * An object containing the file information if the upload succeeded, FALSE
+ * in the event of an error, or NULL if no file was uploaded. The
+ * documentation for the "File interface" group, which you can find under
+ * Related topics, or the header at the top of this file, documents the
+ * components of a file object. In addition to the standard components,
+ * this function adds:
+ * - source: Path to the file before it is moved.
+ * - destination: Path to the file after it is moved (same as 'uri').
+ */
+function mfw_file_save_upload($source, $file_number, $validators = array(), $destination = FALSE, $replace = FILE_EXISTS_RENAME) {
+ global $user;
+ static $upload_cache;
+
+ // Return cached objects without processing since the file will have
+ // already been processed and the paths in _FILES will be invalid.
+ if (isset($upload_cache[$source][$file_number])) {
+ return $upload_cache[$source][$file_number];
+ }
+
+ // Make sure there's an upload to process.
+ if (empty($_FILES[$source]['name'][$file_number])) {
+ return NULL;
+ }
+
+ // Check for file upload errors and return FALSE if a lower level system
+ // error occurred. For a complete list of errors:
+ // See http://php.net/manual/en/features.file-upload.errors.php.
+ switch ($_FILES[$source]['error'][$file_number]) {
+ 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' => $_FILES[$source]['name'][$file_number], '%maxsize' => format_size(file_upload_max_size()))), 'error');
+ return FALSE;
+
+ 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' => $_FILES[$source]['name'][$file_number])), 'error');
+ return FALSE;
+
+ case UPLOAD_ERR_OK:
+ // Final check that this is a valid upload, if it isn't, use the
+ // default error handler.
+ if (is_uploaded_file($_FILES[$source]['tmp_name'][$file_number])) {
+ break;
+ }
+
+ // Unknown error
+ default:
+ drupal_set_message(t('The file %file could not be saved. An unknown error has occurred.', array('%file' => $_FILES[$source]['name'][$source])), 'error');
+ return FALSE;
+ }
+
+ // Begin building file object.
+ $file = new stdClass();
+ $file->uid = $user->uid;
+ $file->status = 0;
+ $file->filename = trim(basename($_FILES[$source]['name'][$file_number]), '.');
+ $file->uri = $_FILES[$source]['tmp_name'][$file_number];
+ $file->filemime = file_get_mimetype($file->filename);
+ $file->filesize = $_FILES[$source]['size'][$file_number];
+
+ $extensions = '';
+ if (isset($validators['file_validate_extensions'])) {
+ if (isset($validators['file_validate_extensions'][0])) {
+ // Build the list of non-munged extensions if the caller provided them.
+ $extensions = $validators['file_validate_extensions'][0];
+ }
+ else {
+ // If 'file_validate_extensions' is set and the list is empty then the
+ // caller wants to allow any extension. In this case we have to remove the
+ // validator or else it will reject all extensions.
+ unset($validators['file_validate_extensions']);
+ }
+ }
+ else {
+ // No validator was provided, so add one using the default list.
+ // Build a default non-munged safe list for file_munge_filename().
+ $extensions = 'jpg jpeg gif png txt doc xls pdf ppt pps odt ods odp';
+ $validators['file_validate_extensions'] = array();
+ $validators['file_validate_extensions'][0] = $extensions;
+ }
+
+ if (!empty($extensions)) {
+ // Munge the filename to protect against possible malicious extension hiding
+ // within an unknown file type (ie: filename.html.foo).
+ $file->filename = file_munge_filename($file->filename, $extensions);
+ }
+
+ // Rename potentially executable files, to help prevent exploits (i.e. will
+ // rename filename.php.foo and filename.php to filename.php.foo.txt and
+ // filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
+ // evaluates to TRUE.
+ if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
+ $file->filemime = 'text/plain';
+ $file->uri .= '.txt';
+ $file->filename .= '.txt';
+ // The .txt extension may not be in the allowed list of extensions. We have
+ // to add it here or else the file upload will fail.
+ if (!empty($extensions)) {
+ $validators['file_validate_extensions'][0] .= ' txt';
+ drupal_set_message(t('For security reasons, your upload has been renamed to %filename.', array('%filename' => $file->filename)));
+ }
+ }
+
+ // If the destination is not provided, use the temporary directory.
+ if (empty($destination)) {
+ $destination = 'temporary://';
+ }
+
+ // Assert that the destination contains a valid stream.
+ $destination_scheme = file_uri_scheme($destination);
+ if (!$destination_scheme || !file_stream_wrapper_valid_scheme($destination_scheme)) {
+ drupal_set_message(t('The file could not be uploaded, because the destination %destination is invalid.', array('%destination' => $destination)), 'error');
+ return FALSE;
+ }
+
+ $file->source = $source;
+ // A URI may already have a trailing slash or look like "public://".
+ if (substr($destination, -1) != '/') {
+ $destination .= '/';
+ }
+ $file->destination = file_destination($destination . $file->filename, $replace);
+ // If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
+ // there's an existing file so we need to bail.
+ if ($file->destination === FALSE) {
+ drupal_set_message(t('The file %source could not be uploaded because a file by that name already exists in the destination %directory.', array('%source' => $source, '%directory' => $destination)), 'error');
+ return FALSE;
+ }
+
+ // Add in our check of the the file name length.
+ $validators['file_validate_name_length'] = array();
+
+ // Call the validation functions specified by this function's caller.
+ $errors = file_validate($file, $validators);
+
+ // Check for errors.
+ if (!empty($errors)) {
+ $message = t('The specified file %name could not be uploaded.', array('%name' => $file->filename));
+ if (count($errors) > 1) {
+ $message .= theme('item_list', array('items' => $errors));
+ }
+ else {
+ $message .= ' ' . array_pop($errors);
+ }
+ form_set_error($source, $message);
+ return FALSE;
+ }
+
+ // Move uploaded files from PHP's upload_tmp_dir to Drupal's temporary
+ // directory. This overcomes open_basedir restrictions for future file
+ // operations.
+ $file->uri = $file->destination;
+ if (!drupal_move_uploaded_file($_FILES[$source]['tmp_name'][$file_number], $file->uri)) {
+ 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->uri));
+ return FALSE;
+ }
+
+ // Set the permissions on the new file.
+ drupal_chmod($file->uri);
+
+ // If we are replacing an existing file re-use its database record.
+ if ($replace == FILE_EXISTS_REPLACE) {
+ $existing_files = file_load_multiple(array(), array('uri' => $file->uri));
+ if (count($existing_files)) {
+ $existing = reset($existing_files);
+ $file->fid = $existing->fid;
+ }
+ }
+
+ // If we made it this far it's safe to record this file in the database.
+ if ($file = file_save($file)) {
+ // Add file to the cache.
+ $upload_cache[$source][$file_number] = $file;
+ return $file;
+ }
+ return FALSE;
+}
\ No newline at end of file