array(), 'display' => 1, 'description' => '', ); return $return; } /** * Validation callback for upload element on file widget. Checks if user has * uploaded more files than allowed. * * This validator is used only when cardinality not set to 1 or unlimited. */ function file_field_widget_multiple_count_validate($element, &$form_state, $form) { $parents = $element['#parents']; $entity_type = $element['#entity_type']; $field_name = $element['#field_name']; $values = NestedArray::getValue($form_state['values'], $parents); array_pop($parents); $current = count(element_children(NestedArray::getValue($form, $parents))) - 1; $field = field_info_field($entity_type, $field_name); $cardinality = $field->getCardinality(); $uploaded = count($values['fids']); $count = $uploaded + $current; if ($count > $cardinality) { $keep = $uploaded - $count + $cardinality; $removed_files = array_slice($values['fids'], $keep); $removed_names = array(); foreach ($removed_files as $fid) { $file = file_load($fid); $removed_names[] = $file->getFilename(); } drupal_set_message( t( 'Field %field can only hold @max values but there were @count uploaded. The following files have been omitted as a result: %list.', array( '%field' => $field_name, '@max' => $cardinality, '@count' => $keep, '%list' => implode(', ', $removed_names), ) ), 'warning' ); $values['fids'] = array_slice($values['fids'], 0, $keep); NestedArray::setValue($form_state['values'], $element['#parents'], $values); } } /** * Render API callback: Processes a file_generic field element. * * Expands the file_generic type to include the description and display fields. * * This function is assigned as a #process callback in file_field_widget_form(). */ function file_field_widget_process($element, &$form_state, $form) { $item = $element['#value']; $item['fids'] = $element['fids']['#value']; $element['#theme'] = 'file_widget'; // Add the display field if enabled. if ($element['#display_field'] && $item['fids']) { $element['display'] = array( '#type' => empty($item['fids']) ? 'hidden' : 'checkbox', '#title' => t('Include file in display'), '#value' => isset($item['display']) ? $item['display'] : $element['#display_default'], '#attributes' => array('class' => array('file-display')), ); } else { $element['display'] = array( '#type' => 'hidden', '#value' => '1', ); } // Add the description field if enabled. if ($element['#description_field'] && $item['fids']) { $config = \Drupal::config('file.settings'); $element['description'] = array( '#type' => $config->get('description.type'), '#title' => t('Description'), '#value' => isset($item['description']) ? $item['description'] : '', '#maxlength' => $config->get('description.length'), '#description' => t('The description may be used as the label of the link to the file.'), ); } // Adjust the Ajax settings so that on upload and remove of any individual // file, the entire group of file fields is updated together. if ($element['#cardinality'] != 1) { $parents = array_slice($element['#array_parents'], 0, -1); $new_path = 'file/ajax'; $new_options = array( 'query' => array( 'element_parents' => implode('/', $parents), 'form_build_id' => $form['form_build_id']['#value'], ), ); $field_element = NestedArray::getValue($form, $parents); $new_wrapper = $field_element['#id'] . '-ajax-wrapper'; foreach (element_children($element) as $key) { if (isset($element[$key]['#ajax'])) { $element[$key]['#ajax']['path'] = $new_path; $element[$key]['#ajax']['options'] = $new_options; $element[$key]['#ajax']['wrapper'] = $new_wrapper; } } unset($element['#prefix'], $element['#suffix']); } // Add another submit handler to the upload and remove buttons, to implement // functionality needed by the field widget. This submit handler, along with // the rebuild logic in file_field_widget_form() requires the entire field, // not just the individual item, to be valid. foreach (array('upload_button', 'remove_button') as $key) { $element[$key]['#submit'][] = 'file_field_widget_submit'; $element[$key]['#limit_validation_errors'] = array(array_slice($element['#parents'], 0, -1)); } return $element; } /** * Render API callback: Processes a group of file_generic field elements. * * 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. * * This function is assigned as a #process callback in file_field_widget_form(). */ function file_field_widget_process_multiple($element, &$form_state, $form) { $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; } /** * Retrieves the file description from a field field element. * * This helper function is used by file_field_widget_process_multiple(). * * @param $element * The element being processed. * * @return * A description of the file suitable for use in the administrative interface. */ function _file_field_get_description_from_element($element) { // Use the actual file description, if it's available. if (!empty($element['#default_value']['description'])) { return $element['#default_value']['description']; } // Otherwise, fall back to the filename. if (!empty($element['#default_value']['filename'])) { return $element['#default_value']['filename']; } // This is probably a newly uploaded file; no description is available. return FALSE; } /** * Form submission handler for upload/remove button of file_field_widget_form(). * * This runs in addition to and after file_managed_file_submit(). * * @see file_managed_file_submit() * @see file_field_widget_form() * @see file_field_widget_process() */ function file_field_widget_submit($form, &$form_state) { // During the form rebuild, file_field_widget_form() will create field item // widget elements using re-indexed deltas, so clear out $form_state['input'] // to avoid a mismatch between old and new deltas. The rebuilt elements will // have #default_value set appropriately for the current state of the field, // so nothing is lost in doing this. $parents = array_slice($form_state['triggering_element']['#parents'], 0, -2); NestedArray::setValue($form_state['input'], $parents, NULL); $button = $form_state['triggering_element']; // Go one level up in the form, to the widgets container. $element = NestedArray::getValue($form, array_slice($button['#array_parents'], 0, -1)); $field_name = $element['#field_name']; $parents = $element['#field_parents']; $submitted_values = NestedArray::getValue($form_state['values'], array_slice($button['#parents'], 0, -2)); foreach ($submitted_values as $delta => $submitted_value) { if (empty($submitted_value['fids'])) { unset($submitted_values[$delta]); } } // If there are more files uploaded via the same widget, we have to separate // them, as we display each file in it's own widget. $new_values = array(); foreach ($submitted_values as $delta => $submitted_value) { if (is_array($submitted_value['fids'])) { foreach ($submitted_value['fids'] as $fid) { $new_value = $submitted_value; $new_value['fids'] = array($fid); $new_values[] = $new_value; } } else { $new_value = $submitted_value; } } // Re-index deltas after removing empty items. $submitted_values = array_values($new_values); // Update form_state values. NestedArray::setValue($form_state['values'], array_slice($button['#parents'], 0, -2), $submitted_values); // Update items. $field_state = field_form_get_state($parents, $field_name, $form_state); $field_state['items'] = $submitted_values; field_form_set_state($parents, $field_name, $form_state, $field_state); } /** * Returns HTML for an individual file upload widget. * * @param $variables * An associative array containing: * - element: A render element representing the widget. * * @ingroup themeable */ function theme_file_widget($variables) { $element = $variables['element']; $output = ''; // The "form-managed-file" class is required for proper Ajax functionality. $output .= '
'; if (!empty($element['fids']['#value'])) { // Add the file size after the file name. $file = reset($element['#files']); $element['file_' . $file->id()]['filename']['#suffix'] = ' (' . format_size($file->getSize()) . ') '; } $output .= drupal_render_children($element); $output .= '
'; return $output; } /** * Returns HTML for a group of file upload widgets. * * @param $variables * An associative array containing: * - element: A render element representing the widgets. * * @ingroup themeable */ function theme_file_widget_multiple($variables) { $element = $variables['element']; // Special ID and classes for draggable tables. $weight_class = $element['#id'] . '-weight'; $table_id = $element['#id'] . '-table'; // Build up a table of applicable fields. $headers = array(); $headers[] = t('File information'); if ($element['#display_field']) { $headers[] = array( 'data' => t('Display'), 'class' => array('checkbox'), ); } $headers[] = t('Weight'); $headers[] = t('Operations'); // Get our list of widgets in order (needed when the form comes back after // preview or failed validation). $widgets = array(); foreach (element_children($element) as $key) { $widgets[] = &$element[$key]; } usort($widgets, '_field_sort_items_value_helper'); $rows = array(); foreach ($widgets as $key => &$widget) { // Save the uploading row for last. if (empty($widget['#files'])) { $widget['#title'] = $element['#file_upload_title']; $widget['#description'] = drupal_render($element['#file_upload_description']); continue; } // Delay rendering of the buttons, so that they can be rendered later in the // "operations" column. $operations_elements = array(); foreach (element_children($widget) as $sub_key) { if (isset($widget[$sub_key]['#type']) && $widget[$sub_key]['#type'] == 'submit') { hide($widget[$sub_key]); $operations_elements[] = &$widget[$sub_key]; } } // Delay rendering of the "Display" option and the weight selector, so that // each can be rendered later in its own column. if ($element['#display_field']) { hide($widget['display']); } hide($widget['_weight']); // Render everything else together in a column, without the normal wrappers. $widget['#theme_wrappers'] = array(); $information = drupal_render($widget); // Render the previously hidden elements, using render() instead of // drupal_render(), to undo the earlier hide(). $operations = ''; foreach ($operations_elements as $operation_element) { $operations .= render($operation_element); } $display = ''; if ($element['#display_field']) { unset($widget['display']['#title']); $display = array( 'data' => render($widget['display']), 'class' => array('checkbox'), ); } $widget['_weight']['#attributes']['class'] = array($weight_class); $weight = render($widget['_weight']); // Arrange the row with all of the rendered columns. $row = array(); $row[] = $information; if ($element['#display_field']) { $row[] = $display; } $row[] = $weight; $row[] = $operations; $rows[] = array( 'data' => $row, 'class' => isset($widget['#attributes']['class']) ? array_merge($widget['#attributes']['class'], array('draggable')) : array('draggable'), ); } $build = array( '#type' => 'table', '#header' => $headers, '#rows' => $rows, '#attributes' => array( 'id' => $table_id, ), '#tabledrag' => array( array( 'action' => 'order', 'relationship' => 'sibling', 'group' => $weight_class, ), ), ); $output = empty($rows) ? '' : drupal_render($build); $output .= drupal_render_children($element); return $output; } /** * Returns HTML for help text based on file upload validators. * * @param $variables * An associative array containing: * - description: The normal description for this field, specified by the * user. * - upload_validators: An array of upload validators as used in * $element['#upload_validators']. * * @ingroup themeable */ function theme_file_upload_help($variables) { $description = $variables['description']; $upload_validators = $variables['upload_validators']; $cardinality = $variables['cardinality']; $descriptions = array(); if (strlen($description)) { $descriptions[] = _filter_htmlcorrector($description); } if (isset($cardinality)) { if ($cardinality == -1) { $descriptions[] = t('Unlimited number of files can be uploaded to this field.'); } else { $descriptions[] = format_plural($cardinality, 'One file only.', 'Maximum @count files.'); } } if (isset($upload_validators['file_validate_size'])) { $descriptions[] = t('!size limit.', array('!size' => format_size($upload_validators['file_validate_size'][0]))); } if (isset($upload_validators['file_validate_extensions'])) { $descriptions[] = t('Allowed types: !extensions.', array('!extensions' => check_plain($upload_validators['file_validate_extensions'][0]))); } if (isset($upload_validators['file_validate_image_resolution'])) { $max = $upload_validators['file_validate_image_resolution'][0]; $min = $upload_validators['file_validate_image_resolution'][1]; if ($min && $max && $min == $max) { $descriptions[] = t('Images must be exactly !size pixels.', array('!size' => '' . $max . '')); } elseif ($min && $max) { $descriptions[] = t('Images must be between !min and !max pixels.', array('!min' => '' . $min . '', '!max' => '' . $max . '')); } elseif ($min) { $descriptions[] = t('Images must be larger than !min pixels.', array('!min' => '' . $min . '')); } elseif ($max) { $descriptions[] = t('Images must be smaller than !max pixels.', array('!max' => '' . $max . '')); } } return implode(' ', $descriptions); } /** * Determine whether a field references files stored in {file_managed}. * * @param Drupal\field\FieldInterface $field * A field definition. * * @return * The field column if the field references {file_managed}.fid, typically * fid, FALSE if it doesn't. */ function file_field_find_file_reference_column(FieldInterface $field) { $schema = $field->getSchema(); foreach ($schema['foreign keys'] as $data) { if ($data['table'] == 'file_managed') { foreach ($data['columns'] as $field_column => $column) { if ($column == 'fid') { return $field_column; } } } } return FALSE; } /** * Returns HTML for a file attachments table. * * @param $variables * An associative array containing: * - items: An array of file attachments. * * @ingroup themeable */ function theme_file_formatter_table($variables) { $header = array(t('Attachment'), t('Size')); $rows = array(); foreach ($variables['items'] as $delta => $item) { if ($item->isDisplayed() && $item->entity) { $rows[] = array( array( 'data' => array( '#theme' => 'file_link', '#file' => $item->entity, ), ), format_size($item->entity->getSize()), ); } } $build = array( '#theme' => 'table', '#header' => $header, '#rows' => $rows, ); return empty($rows) ? '' : drupal_render($build); }