Index: modules/image/image.test =================================================================== RCS file: /cvs/drupal/drupal/modules/image/image.test,v retrieving revision 1.8 diff -u -r1.8 image.test --- modules/image/image.test 26 Aug 2009 03:20:39 -0000 1.8 +++ modules/image/image.test 12 Oct 2009 04:42:42 -0000 @@ -335,6 +335,7 @@ // Edit effect form. // Revisit each form to make sure the effect was saved. + drupal_static_reset('image_styles'); $style = image_style_load($style_name); foreach ($style['effects'] as $ieid => $effect) { Index: modules/image/image.module =================================================================== RCS file: /cvs/drupal/drupal/modules/image/image.module,v retrieving revision 1.18 diff -u -r1.18 image.module --- modules/image/image.module 9 Oct 2009 01:00:00 -0000 1.18 +++ modules/image/image.module 12 Oct 2009 04:42:41 -0000 @@ -6,6 +6,9 @@ * Exposes global functionality for creating image styles. */ +// Load all Field module hooks for Image. +require_once DRUPAL_ROOT . '/modules/image/image.field.inc'; + /** * Implement of hook_help(). */ @@ -126,6 +129,7 @@ */ function image_theme() { return array( + // Theme functions in image.module. 'image_style' => array( 'arguments' => array( 'style_name' => NULL, @@ -136,6 +140,8 @@ 'getsize' => TRUE, ), ), + + // Theme functions in image.admin.inc. 'image_style_list' => array( 'arguments' => array('styles' => NULL), ), @@ -160,6 +166,20 @@ 'image_rotate_summary' => array( 'arguments' => array('data' => NULL), ), + + // Theme functions in image.field.inc. + 'image_widget' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_image' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_image_link_content' => array( + 'arguments' => array('element' => NULL), + ), + 'field_formatter_image_link_file' => array( + 'arguments' => array('element' => NULL), + ), ); } @@ -187,9 +207,12 @@ * * Control the access to files underneath the styles directory. */ -function image_file_download($filepath) { - if (strpos($filepath, 'styles/') === 0) { - $args = explode('/', $filepath); +function image_file_download($uri) { + $path = file_uri_target($uri); + + // Private file access for image style derivatives. + if (strpos($path, 'styles/') === 0) { + $args = explode('/', $path); // Discard the first part of the path (styles). array_shift($args); // Get the style name from the second part. @@ -198,7 +221,7 @@ $original_path = implode('/', $args); // Check that the file exists and is an image. - if ($info = image_get_info($filepath)) { + if ($info = image_get_info($uri)) { // Check the permissions of the original to grant access to this image. $headers = module_invoke_all('file_download', $original_path); if (!in_array(-1, $headers)) { @@ -217,6 +240,17 @@ } return -1; } + + // Private file access for the original files. Note that we only + // check access for non-temporary images, since file.module will + // grant access for all temporary files. + $files = file_load_multiple(array(), array('uri' => $uri)); + if (count($files)) { + $file = reset($files); + if ($file->status) { + return file_file_download($uri, 'image'); + } + } } /** @@ -236,6 +270,14 @@ } /** + * Implement hook_file_references(). + */ +function image_file_references($file) { + $count = file_get_file_reference_count($file, NULL, 'image'); + return $count ? array('image' => $count) : NULL; +} + +/** * Clear cached versions of a specific file in all styles. * * @param $path @@ -464,7 +506,7 @@ // acquiring the lock. $success = file_exists($destination) || image_style_create_derivative($style, $path, $destination); - if ($lock_acquired) { + if (!empty($lock_acquired)) { lock_release($lock_name); } @@ -600,17 +642,16 @@ * * @param $style_name * The name of the style to be used with this image. - * @param $path - * The path to the image. + * @param $uri + * The URI or path to the image. * @return * The path to an image style image relative to Drupal's root. * @see image_style_url() */ -function image_style_path($style_name, $path) { - if ($target = file_uri_target($path)) { - $path = $target; - } - return variable_get('file_default_scheme', 'public') . '://styles/' . $style_name . '/' . $path; +function image_style_path($style_name, $uri) { + $path = ($path = file_uri_target($uri)) ? $path : $uri; + $scheme = ($scheme = file_uri_scheme($uri)) ? $scheme : variable_get('file_default_scheme', 'public'); + return $scheme . '://styles/' . $style_name . '/' . $path; } /** @@ -773,6 +814,7 @@ * TRUE on success. FALSE if unable to perform the image effect on the image. */ function image_effect_apply($image, $effect) { + module_load_include('inc', 'image', 'image.effects'); if (function_exists($effect['effect callback'])) { return call_user_func($effect['effect callback'], $image, $effect['data']); } @@ -811,6 +853,7 @@ $style_path = image_style_url($style_name, $path); } $variables['path'] = file_create_url($style_path); + $variables['getsize'] = FALSE; return theme('image', $variables); } Index: modules/image/image.info =================================================================== RCS file: /cvs/drupal/drupal/modules/image/image.info,v retrieving revision 1.2 diff -u -r1.2 image.info --- modules/image/image.info 21 Jul 2009 07:09:46 -0000 1.2 +++ modules/image/image.info 12 Oct 2009 04:42:41 -0000 @@ -7,5 +7,6 @@ files[] = image.module files[] = image.admin.inc files[] = image.effects.inc +files[] = image.field.inc files[] = image.install files[] = image.test Index: modules/file/file.field.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/file/file.field.inc,v retrieving revision 1.5 diff -u -r1.5 file.field.inc --- modules/file/file.field.inc 9 Oct 2009 00:59:58 -0000 1.5 +++ modules/file/file.field.inc 12 Oct 2009 04:42:41 -0000 @@ -18,12 +18,12 @@ 'display_field' => 0, 'display_default' => 0, 'uri_scheme' => 'public', - 'default_file' => 0, ), 'instance_settings' => array( 'file_extensions' => 'txt', 'file_directory' => '', 'max_filesize' => '', + 'description_field' => 0, ), 'default_widget' => 'file_file', 'default_formatter' => 'file_default', @@ -51,11 +51,10 @@ 'not null' => TRUE, 'default' => 1, ), - 'data' => array( - 'description' => 'Serialized additional data about the file, such as a description.', + 'description' => array( + 'description' => 'A description of the file.', 'type' => 'text', 'not null' => FALSE, - 'serialize' => TRUE, ), ), 'indexes' => array( @@ -101,14 +100,6 @@ '#disabled' => $has_data, ); - $form['default_file'] = array( - '#title' => t('Default file'), - '#type' => 'managed_file', - '#description' => t('If no file is uploaded, this file will be used on display.'), - '#default_value' => $field['settings']['default_file'], - '#upload_location' => 'public://default_files/', - ); - return $form; } @@ -118,15 +109,12 @@ function file_field_instance_settings_form($field, $instance) { $settings = $instance['settings']; - $form['#attached']['js'][] = drupal_get_path('module', 'file') . '/file.js'; - - $form['max_filesize'] = array( + $form['file_directory'] = array( '#type' => 'textfield', - '#title' => t('Maximum upload size'), - '#default_value' => $settings['max_filesize'], - '#description' => t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit %limit).', array('%limit' => format_size(file_upload_max_size()))), - '#size' => 10, - '#element_validate' => array('_file_generic_settings_max_filesize'), + '#title' => t('File directory'), + '#default_value' => $settings['file_directory'], + '#description' => t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.', array('%directory' => variable_get('file_directory_path', 'files') . '/')), + '#element_validate' => array('_file_generic_settings_file_directory_validate'), '#weight' => 3, ); @@ -136,26 +124,28 @@ '#type' => 'textfield', '#title' => t('Allowed file extensions'), '#default_value' => $extensions, - '#size' => 64, '#description' => t('Separate extensions with a space or comma and do not include the leading dot. Leaving this blank will allow users to upload a file with any extension.'), '#element_validate' => array('_file_generic_settings_extensions'), - '#weight' => 4, + '#weight' => 1, ); - $form['destination'] = array( - '#type' => 'fieldset', - '#title' => t('Upload destination'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, + $form['max_filesize'] = array( + '#type' => 'textfield', + '#title' => t('Maximum upload size'), + '#default_value' => $settings['max_filesize'], + '#description' => t('Enter a value like "512" (bytes), "80 KB" (kilobytes) or "50 MB" (megabytes) in order to restrict the allowed file size. If left empty the file sizes will be limited only by PHP\'s maximum post and file upload sizes (current limit %limit).', array('%limit' => format_size(file_upload_max_size()))), + '#size' => 10, + '#element_validate' => array('_file_generic_settings_max_filesize'), '#weight' => 5, ); - $form['destination']['file_directory'] = array( - '#type' => 'textfield', - '#title' => t('File directory'), - '#default_value' => $settings['file_directory'], - '#description' => t('Optional subdirectory within the upload destination where files will be stored. Do not include preceding or trailing slashes.', array('%directory' => variable_get('file_directory_path', 'files') . '/')), - '#element_validate' => array('_file_generic_settings_file_directory_validate'), - '#parents' => array('instance', 'settings', 'file_directory'), + + $form['description_field'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Description field'), + '#default_value' => isset($settings['description_field']) ? $settings['description_field'] : '', + '#description' => t('The description field allows users to enter a description about the uploaded file.'), + '#parents' => array('instance', 'settings', 'description_field'), + '#weight' => 11, ); return $form; @@ -230,9 +220,7 @@ if (empty($item['fid']) || !isset($files[$item['fid']])) { $items[$obj_id][$delta] = NULL; } - // Unserialize the data column. else { - $item['data'] = unserialize($item['data']); $items[$obj_id][$delta] = array_merge($item, (array) $files[$item['fid']]); } } @@ -243,19 +231,10 @@ * Implement hook_field_sanitize(). */ function file_field_sanitize($obj_type, $object, $field, $instance, $langcode, &$items) { - // If there are no files specified at all, use the default. - if (empty($items) && $field['settings']['default_file']) { - if ($file = file_load($field['settings']['default_file'])) { - $items[0] = (array) $file; - $items[0]['is_default'] = TRUE; - } - } // Remove files from being displayed if they're not displayed. - else { - foreach ($items as $delta => $item) { - if (!file_field_displayed($item, $field)) { - unset($items[$delta]); - } + foreach ($items as $delta => $item) { + if (!file_field_displayed($item, $field)) { + unset($items[$delta]); } } @@ -274,11 +253,6 @@ * Implement hook_field_update(). */ function file_field_update($obj_type, $object, $field, $instance, $langcode, &$items) { - // Serialize the data column before storing. - foreach ($items as $delta => $item) { - $items[$delta]['data'] = serialize($item['data']); - } - // Check for files that have been removed from the object. // On new revisions, old files are always maintained in the previous revision. @@ -416,11 +390,10 @@ function file_field_widget_info() { return array( 'file_generic' => array( - 'label' => t('Generic file'), + 'label' => t('File'), 'field types' => array('file'), 'settings' => array( 'progress_indicator' => 'throbber', - 'description_field' => 0, ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, @@ -446,26 +419,10 @@ ), '#default_value' => $settings['progress_indicator'], '#description' => t('The throbber display does not show the status of uploads but takes up space. The progress bar is helpful for monitoring progress on large uploads.'), - '#weight' => 2, + '#weight' => 16, '#access' => file_progress_implementation(), ); - $form['additional'] = array( - '#type' => 'fieldset', - '#title' => t('Additional fields'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#weight' => 10, - ); - - $form['additional']['description_field'] = array( - '#type' => 'checkbox', - '#title' => t('Enable Description field'), - '#default_value' => $settings['description_field'], - '#description' => t('The description field allows users to enter a description about the uploaded file.'), - '#parents' => array('instance', 'widget', 'settings', 'description_field'), - ); - return $form; } @@ -477,8 +434,8 @@ $defaults = array( 'fid' => 0, - 'display' => $field['settings']['display_default'], - 'data' => array('description' => ''), + 'display' => !empty($field['settings']['display_default']), + 'description' => '', ); // Retrieve any values set in $form_state, as will be the case during AJAX @@ -632,7 +589,7 @@ $return += array( 'fid' => 0, 'display' => 1, - 'data' => array(), + 'description' => '', ); return $return; @@ -653,17 +610,8 @@ $element['#theme'] = 'file_widget'; - // Data placeholder for widgets to store additional data. - $element['data'] = array( - '#tree' => TRUE, - '#title' => t('File data'), - '#collapsible' => TRUE, - '#collapsed' => TRUE, - '#access' => (bool) $item['fid'], - ); - // Add the display field if enabled. - if ($field['settings']['display_field'] && $item['fid']) { + if (!empty($field['settings']['display_field']) && $item['fid']) { $element['display'] = array( '#type' => empty($item['fid']) ? 'hidden' : 'checkbox', '#title' => t('Include file in display'), @@ -679,11 +627,11 @@ } // Add the description field if enabled. - if ($settings['description_field'] && $item['fid']) { - $element['data']['description'] = array( + if (!empty($instance['settings']['description_field']) && $item['fid']) { + $element['description'] = array( '#type' => 'textfield', '#title' => t('Description'), - '#value' => isset($item['data']['description']) ? $item['data']['description'] : '', + '#value' => isset($item['description']) ? $item['description'] : '', '#type' => variable_get('file_description_type', 'textfield'), '#maxlength' => variable_get('file_description_length', 128), '#description' => t('The description may be used as the label of the link to the file.'), @@ -769,6 +717,7 @@ $element = $variables['element']; $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); // Get our list of widgets in order. $widgets = array(); @@ -784,7 +733,7 @@ // Build up a table of applicable fields. $headers = array(); $headers[] = t('File information'); - if ($field['settings']['display_field']) { + if (!empty($field['settings']['display_field'])) { $headers[] = array( 'data' => t('Display'), 'class' => array('checkbox'), @@ -812,7 +761,7 @@ // Render the "Display" option in its own own column. $display = ''; - if ($field['settings']['display_field']) { + if (!empty($field['settings']['display_field'])) { unset($element[$key]['display']['#title']); $display = array( 'data' => drupal_render($element[$key]['display']), @@ -830,7 +779,7 @@ $row = array(); $row[] = $information; - if ($field['settings']['display_field']) { + if (!empty($field['settings']['display_field'])) { $row[] = $display; } $row[] = $weight; Index: modules/file/file.module =================================================================== RCS file: /cvs/drupal/drupal/modules/file/file.module,v retrieving revision 1.6 diff -u -r1.6 file.module --- modules/file/file.module 9 Oct 2009 07:31:38 -0000 1.6 +++ modules/file/file.module 12 Oct 2009 04:42:41 -0000 @@ -87,8 +87,11 @@ /** * Implement hook_file_download(). + * + * This function takes an extra parameter $field_type so that it may + * be re-used by other File-like modules, such as Image. */ -function file_file_download($uri) { +function file_file_download($uri, $field_type = 'file') { global $user; // Get the file record based on the URI. If not in the database just return. @@ -101,11 +104,11 @@ } // Find out which (if any) file fields contain this file. - $references = file_get_file_references($file); + $references = file_get_file_references($file, NULL, FIELD_LOAD_REVISION, $field_type); // TODO: Check field-level access if available here. - $denied = NULL; + $denied = $file->status ? NULL : FALSE; // Check access to content containing the file fields. If access is allowed // to any of this content, allow the download. foreach ($references as $field_name => $field_references) { @@ -610,11 +613,11 @@ ); // Use the description as the link text if available. - if (empty($file->data['description'])) { + if (empty($file->description)) { $link_text = check_plain($file->filename); } else { - $link_text = check_plain($file->data['description']); + $link_text = check_plain($file->description); $options['attributes']['title'] = check_plain($file->filename); } @@ -838,62 +841,28 @@ */ /** - * Return an array of file fields in an bundle or by field name. - * - * @param $bundle_type - * (optional) The bundle type on which to filter the list of fields. In the - * case of nodes, this is the node type. - * @param $field - * (optional) A field array or name on which to filter the list. - */ -function file_get_field_list($bundle_type = NULL, $field = NULL) { - // Build the list of fields to be used for retrieval. - if (isset($field)) { - if (is_string($field)) { - $field = field_info_field($field); - } - $fields = array($field['field_name'] => $field); - } - elseif (isset($bundle_type)) { - $instances = field_info_instances($bundle_type); - $fields = array(); - foreach ($instances as $field_name => $instance) { - $fields[$field_name] = field_info_field($field_name); - } - } - else { - $fields = field_info_fields(); - } - - // Filter down the list to just file fields. - foreach ($fields as $key => $field) { - if ($field['type'] != 'file') { - unset($fields[$key]); - } - } - - return $fields; -} - -/** - * Count the number of times the file is referenced within a field. + * Count the number of times the file is referenced. * * @param $file * A file object. * @param $field - * Optional. The CCK field array or field name as a string. + * (optional) A CCK field array or field name as a string. If provided, + * limits the reference check to the given field. + * @param $field_type + * (optional) The name of a field type. If provided, limits the reference + * check to fields of the given type. * @return * An integer value. */ -function file_get_file_reference_count($file, $field = NULL) { - $fields = file_get_field_list(NULL, $field); +function file_get_file_reference_count($file, $field = NULL, $field_type = 'file') { + $fields = field_info_fields(NULL, $field, $field_type); $types = field_info_fieldable_types(); $reference_count = 0; foreach ($fields as $field) { // TODO: Use a more efficient mechanism rather than actually retrieving // all the references themselves, such as using a COUNT() query. - $references = file_get_file_references($file, $field); + $references = file_get_file_references($file, $field, FIELD_LOAD_REVISION, $field_type); foreach ($references as $obj_type => $type_references) { $reference_count += count($type_references); } @@ -925,22 +894,26 @@ /** - * Get a list of references to a file by bundle and ID. + * Get a list of references to a file. * * @param $file * A file object. * @param $field - * (optional) A field array to be used for this check. + * (optional) A field array to be used for this check. If given, limits the + * reference check to the given field. * @param $age * (optional) A constant that specifies which references to count. Use * FIELD_LOAD_REVISION to retrieve all references within all revisions or * FIELD_LOAD_CURRENT to retrieve references only in the current revisions. + * @param $field_type + * Optional. The name of a field type. If given, limits the reference check to + * fields of the given type. * @return * An integer value. */ -function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION) { +function file_get_file_references($file, $field = NULL, $age = FIELD_LOAD_REVISION, $field_type = 'file') { $references = drupal_static(__FUNCTION__, array()); - $fields = file_get_field_list(NULL, $field); + $fields = field_info_fields(NULL, $field, $field_type); foreach ($fields as $field_name => $file_field) { if (!isset($references[$field_name])) { Index: modules/file/tests/file.test =================================================================== RCS file: /cvs/drupal/drupal/modules/file/tests/file.test,v retrieving revision 1.4 diff -u -r1.4 file.test --- modules/file/tests/file.test 11 Oct 2009 03:07:18 -0000 1.4 +++ modules/file/tests/file.test 12 Oct 2009 04:42:41 -0000 @@ -288,10 +288,10 @@ 'display_field' => '1', 'display_default' => '1', ); - $instance_settings = array(); - $widget_settings = array( + $instance_settings = array( 'description_field' => '1', ); + $widget_settings = array(); $this->createFileField($field_name, $type_name, $field_settings, $instance_settings, $widget_settings); $field = field_info_field($field_name); $instance = field_info_instance($field_name, $type_name); Index: modules/field/field.info.inc =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.info.inc,v retrieving revision 1.19 diff -u -r1.19 field.info.inc --- modules/field/field.info.inc 27 Sep 2009 12:52:55 -0000 1.19 +++ modules/field/field.info.inc 12 Oct 2009 04:42:40 -0000 @@ -507,14 +507,48 @@ /** * Return array of all field data, keyed by field name. * + * @param $bundle_type + * (optional) The bundle type on which to filter the list of fields. In the + * case of nodes, this is the node type. + * @param $field + * (optional) A field array or name on which to filter the list. + * @param $field_type + * (optional) A field type on which to filter the list. * @return * An array of Field objects. Each Field object has an additional * property, bundles, which is an array of all the bundles to which * this field belongs. */ -function field_info_fields() { - $info = _field_info_collate_fields(); - return $info['fields']; +function field_info_fields($bundle_type = NULL, $field = NULL, $field_type = NULL) { + // Build the list of fields to be used for retrieval. + if (isset($field)) { + if (is_string($field)) { + $field = field_info_field($field); + } + $fields = array($field['field_name'] => $field); + } + elseif (isset($bundle_type)) { + $instances = field_info_instances($bundle_type); + $fields = array(); + foreach ($instances as $field_name => $instance) { + $fields[$field_name] = field_info_field($field_name); + } + } + else { + $info = _field_info_collate_fields(); + $fields = $info['fields']; + } + + // If a field type was given, filter the list down to fields of that type. + if (isset($field_type)) { + foreach ($fields as $key => $field) { + if ($field['type'] != $field_type) { + unset($fields[$key]); + } + } + } + + return $fields; } /** Index: modules/field/field.module =================================================================== RCS file: /cvs/drupal/drupal/modules/field/field.module,v retrieving revision 1.39 diff -u -r1.39 field.module --- modules/field/field.module 9 Oct 2009 19:22:56 -0000 1.39 +++ modules/field/field.module 12 Oct 2009 04:42:40 -0000 @@ -170,9 +170,12 @@ ); $field_formatters = field_info_formatter_types(NULL); foreach ($field_formatters as $key => $field_formatter) { - $items["field_formatter_$key"] = array( + $items['field_formatter_' . $key] = array( 'arguments' => array('element' => NULL), ); + if (isset($field_formatter['theme'])) { + $items['field_formatter_' . $key] += $field_formatter['theme']; + } } return $items; } Index: modules/image/image.field.inc =================================================================== RCS file: modules/image/image.field.inc diff -N modules/image/image.field.inc --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/image/image.field.inc 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,490 @@ + array( + 'label' => t('Image'), + 'description' => t('This field stores the ID of an image file as an integer value.'), + 'settings' => array( + 'uri_scheme' => 'public', + 'default_image' => 0, + ), + 'instance_settings' => array( + 'file_extensions' => 'png gif jpg jpeg', + 'file_directory' => '', + 'max_filesize' => '', + 'alt_field' => 0, + 'title_field' => 0, + 'max_resolution' => '', + 'min_resolution' => '', + ), + 'default_widget' => 'image_image', + 'default_formatter' => 'image', + ), + ); +} + +/** + * Implement hook_field_schema(). + */ +function image_field_schema($field) { + return array( + 'columns' => array( + 'fid' => array( + 'description' => 'The {files}.fid being referenced in this field.', + 'type' => 'int', + 'not null' => FALSE, + 'unsigned' => TRUE, + ), + 'alt' => array( + 'description' => "Alternative image text, for the image's 'alt' attribute.", + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ), + 'title' => array( + 'description' => "Image title text, for the image's 'title' attribute.", + 'type' => 'varchar', + 'length' => 128, + 'not null' => FALSE, + ), + ), + 'indexes' => array( + 'fid' => array('fid'), + ), + ); +} + +/** + * Implement hook_field_settings_form(). + */ +function image_field_settings_form($field, $instance) { + $defaults = field_info_field_settings($field['type']); + $settings = array_merge($defaults, $field['settings']); + + $scheme_options = array(); + foreach (file_get_stream_wrappers() as $scheme => $stream_wrapper) { + if ($scheme != 'temporary') { + $scheme_options[$scheme] = $stream_wrapper['name']; + } + } + $form['uri_scheme'] = array( + '#type' => 'radios', + '#title' => t('Upload destination'), + '#options' => $scheme_options, + '#default_value' => $settings['uri_scheme'], + '#description' => t('Select where the final files should be stored. Private file storage has significantly more overhead than public files, but allows restricted access to files within this field.'), + ); + + $form['default_image'] = array( + '#title' => t('Default image'), + '#type' => 'managed_file', + '#description' => t('If no image is uploaded, this image will be shown on display.'), + '#default_value' => $field['settings']['default_image'], + '#upload_location' => 'public://default_images/', + ); + + return $form; + +} + +/** + * Implement hook_field_instance_settings_form(). + */ +function image_field_instance_settings_form($field, $instance) { + $settings = $instance['settings']; + + // Use the file field instance settings form as a basis. + $form = file_field_instance_settings_form($field, $instance); + + // Add maximum and minimum resolution settings. + $max_resolution = explode('x', $settings['max_resolution']) + array('', ''); + $form['max_resolution'] = array( + '#title' => t('Maximum image resolution'), + '#element_validate' => array('_image_field_resolution_validate'), + '#theme_wrappers' => array('form_element'), + '#weight' => 4.1, + '#description' => t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Leave blank for no restriction. If a larger image is uploaded, it will be resized to reflect the given width and height. Resizing images on upload will cause the loss of EXIF data in the image.'), + ); + $form['max_resolution']['x'] = array( + '#type' => 'textfield', + '#default_value' => $max_resolution[0], + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => ' x ', + '#theme_wrappers' => array(), + ); + $form['max_resolution']['y'] = array( + '#type' => 'textfield', + '#default_value' => $max_resolution[1], + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => ' ' . t('pixels'), + '#theme_wrappers' => array(), + ); + + $min_resolution = explode('x', $settings['min_resolution']) + array('', ''); + $form['min_resolution'] = array( + '#title' => t('Minimum image resolution'), + '#element_validate' => array('_image_field_resolution_validate'), + '#theme_wrappers' => array('form_element'), + '#weight' => 4.2, + '#description' => t('The minimum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Leave blank for no restriction. If a smaller image is uploaded, it will be rejected.'), + ); + $form['min_resolution']['x'] = array( + '#type' => 'textfield', + '#default_value' => $min_resolution[0], + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => ' x ', + '#theme_wrappers' => array(), + ); + $form['min_resolution']['y'] = array( + '#type' => 'textfield', + '#default_value' => $min_resolution[1], + '#size' => 5, + '#maxlength' => 5, + '#field_suffix' => ' ' . t('pixels'), + '#theme_wrappers' => array(), + ); + + // Remove the description option. + unset($form['description_field']); + + // Add title and alt configuration options. + $form['alt_field'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Alt field'), + '#default_value' => $settings['alt_field'], + '#description' => t('The alt attribute may be used by search engines, screen readers, and when the image cannot be loaded.'), + '#weight' => 10, + ); + $form['title_field'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Title field'), + '#default_value' => $settings['title_field'], + '#description' => t('The title attribute is used as a tooltip when the mouse hovers over the image.'), + '#weight' => 11, + ); + + return $form; +} + +/** + * Element validate function for resolution fields. + */ +function _image_field_resolution_validate($element, &$form_state) { + if (!empty($element['x']['#value']) || !empty($element['y']['#value'])) { + foreach (array('x', 'y') as $dimension) { + $value = $element[$dimension]['#value']; + if (!is_numeric($value)) { + form_error($element[$dimension], t('Height and width values must be numeric.')); + return; + } + if (intval($value) == 0) { + form_error($element[$dimension], t('Both a height and width value must be specified in the !name field.', array('!name' => $element['#title']))); + return; + } + } + form_set_value($element, intval($element['x']['#value']) . 'x' . intval($element['y']['#value']), $form_state); + } + else { + form_set_value($element, '', $form_state); + } +} + +/** + * Implement hook_field_load(). + */ +function image_field_load($obj_type, $objects, $field, $instances, $langcode, &$items, $age) { + file_field_load($obj_type, $objects, $field, $instances, $langcode, $items, $age); +} + +/** + * Implement hook_field_sanitize(). + */ +function image_field_sanitize($obj_type, $object, $field, $instance, $langcode, &$items) { + // If there are no files specified at all, use the default. + if (empty($items) && $field['settings']['default_image']) { + if ($file = file_load($field['settings']['default_image'])) { + $items[0] = (array) $file + array( + 'is_default' => TRUE, + 'alt' => '', + 'title' => '', + ); + } + } +} + +/** + * Implement hook_field_insert(). + */ +function image_field_insert($obj_type, $object, $field, $instance, $langcode, &$items) { + image_field_update($obj_type, $object, $field, $instance, $langcode, $items); +} + +/** + * Implement hook_field_update(). + */ +function image_field_update($obj_type, $object, $field, $instance, $langcode, &$items) { + file_field_update($obj_type, $object, $field, $instance, $langcode, $items); +} + +/** + * Implement hook_field_delete(). + */ +function image_field_delete($obj_type, $object, $field, $instance, $langcode, &$items) { + file_field_delete($obj_type, $object, $field, $instance, $langcode, $items); +} + +/** + * Implement hook_field_delete_revision(). + */ +function image_field_delete_revision($obj_type, $object, $field, $instance, $langcode, &$items) { + file_field_delete_revision($obj_type, $object, $field, $instance, $langcode, $items); +} + +/** + * Implement hook_field_is_empty(). + */ +function image_field_is_empty($item, $field) { + return file_field_is_empty($item, $field); +} + +/** + * Implement hook_field_widget_info(). + */ +function image_field_widget_info() { + return array( + 'image_image' => array( + 'label' => t('Image'), + 'field types' => array('image'), + 'settings' => array( + 'progress_indicator' => 'throbber', + 'preview_image_style' => 'thumbnail', + ), + 'behaviors' => array( + 'multiple values' => FIELD_BEHAVIOR_CUSTOM, + 'default value' => FIELD_BEHAVIOR_NONE, + ), + ), + ); +} + +/** + * Implement hook_field_widget_settings_form(). + */ +function image_field_widget_settings_form($field, $instance) { + $widget = $instance['widget']; + $settings = $widget['settings']; + + // Use the file widget settings form. + $form = file_field_widget_settings_form($field, $instance); + + $form['preview_image_style'] = array( + '#title' => t('Preview image style'), + '#type' => 'select', + '#options' => array('' => '<' . t('no preview') . '>') + image_style_options(FALSE), + '#default_value' => $settings['preview_image_style'], + '#description' => t('The preview image will be shown while editing the content.'), + '#weight' => 15, + ); + + return $form; +} + +/** + * Implementation of hook_field_widget(). + */ +function image_field_widget(&$form, &$form_state, $field, $instance, $items, $delta = 0) { + $elements = file_field_widget($form, $form_state, $field, $instance, $items, $delta); + $settings = $instance['settings']; + + foreach (element_children($elements) as $delta) { + // Add upload resolution validation. + if ($settings['max_resolution'] || $settings['min_resolution']) { + $elements[$delta]['#upload_validators']['file_validate_image_resolution'] = array($settings['max_resolution'], $settings['min_resolution']); + } + + // If not using custom extension validation, ensure this is an image. + $supported_extensions = array('png', 'gif', 'jpg', 'jpeg'); + $extensions = isset($elements[$delta]['#upload_validators']['file_validate_extensions'][0]) ? $elements[$delta]['#upload_validators']['file_validate_extensions'][0] : implode(' ', $supported_extensions); + $extensions = array_intersect(explode(' ', $extensions), $supported_extensions); + $elements[$delta]['#upload_validators']['file_validate_extensions'][0] = implode(' ', $extensions); + + // Add all extra functionality provided by the image widget. + $elements[$delta]['#process'][] = 'image_field_widget_process'; + } + + if ($field['cardinality'] == 1) { + // If there's only one field, return it as delta 0. + if (empty($elements[0]['#default_value']['fid'])) { + $elements[0]['#description'] = theme('file_upload_help', array('description' => $instance['description'], 'upload_validators' => $elements[0]['#upload_validators'])); + } + } + else { + $elements['#file_upload_description'] = theme('file_upload_help', array('upload_validators' => $elements[0]['#upload_validators'])); + } + return $elements; +} + +/** + * An element #process callback for the image_image field type. + * + * Expands the image_image type to include the alt and title fields. + */ +function image_field_widget_process($element, &$form_state, $form) { + $item = $element['#value']; + $item['fid'] = $element['fid']['#value']; + + $field = field_info_field($element['#field_name']); + $instance = field_info_instance($element['#field_name'], $element['#bundle']); + $settings = $instance['settings']; + $widget_settings = $instance['widget']['settings']; + + $element['#theme'] = 'image_widget'; + $element['#attached']['css'][] = drupal_get_path('module', 'image') . '/image.css'; + + // Add the image preview. + if ($element['#file'] && $widget_settings['preview_image_style']) { + $element['preview'] = array( + '#type' => 'markup', + '#markup' => theme('image_style', array('style_name' => $widget_settings['preview_image_style'], 'path' => $element['#file']->uri, 'getsize' => FALSE)), + ); + } + + // Add the additional alt and title fields. + $element['alt'] = array( + '#title' => t('Alternate text'), + '#type' => 'textfield', + '#default_value' => isset($item['alt']) ? $item['alt'] : '', + '#description' => t('This text will be used by screen readers, search engines, or when the image cannot be loaded.'), + '#maxlength' => variable_get('image_alt_length', 80), // See http://www.gawds.org/show.php?contentid=28. + '#weight' => -2, + '#access' => (bool) $item['fid'] && $settings['alt_field'], + ); + $element['title'] = array( + '#type' => 'textfield', + '#title' => t('Title'), + '#default_value' => isset($item['title']) ? $item['title'] : '', + '#description' => t('The title is used as a tool tip when the user hovers the mouse over the image.'), + '#maxlength' => variable_get('image_title_length', 500), + '#weight' => -1, + '#access' => (bool) $item['fid'] && $settings['title_field'], + ); + + return $element; +} + +/** + * Theme the display of the image field widget. + */ +function theme_image_widget($variables) { + $element = $variables['element']; + $output = ''; + $output .= '
'; + + if (isset($element['preview'])) { + $output .= '
'; + $output .= drupal_render($element['preview']); + $output .= '
'; + } + + $output .= '
'; + if ($element['fid']['#value'] != 0) { + $element['filename']['#markup'] .= ' (' . format_size($element['#file']->filesize) . ') '; + } + $output .= drupal_render_children($element); + $output .= '
'; + $output .= '
'; + + return $output; +} + +/** + * Implement hook_field_formatter_info(). + */ +function image_field_formatter_info() { + $formatters = array( + 'image' => array( + 'label' => t('Image'), + 'field types' => array('image'), + ), + 'image_link_content' => array( + 'label' => t('Image linked to content'), + 'field types' => array('image'), + ), + 'image_link_file' => array( + 'label' => t('Image linked to file'), + 'field types' => array('image'), + ), + ); + + foreach (image_styles() as $style) { + $formatters['image__' . $style['name']] = array( + 'label' => t('Image "@style"', array('@style' => $style['name'])), + 'field types' => array('image'), + 'theme' => array('function' => 'theme_field_formatter_image'), + ); + $formatters['image_link_content__' . $style['name']] = array( + 'label' => t('Image "@style" linked to content', array('@style' => $style['name'])), + 'field types' => array('image'), + 'theme' => array('function' => 'theme_field_formatter_image_link_content'), + ); + $formatters['image_link_file__' . $style['name']] = array( + 'label' => t('Image "@style" linked to file', array('@style' => $style['name'])), + 'field types' => array('image'), + 'theme' => array('function' => 'theme_field_formatter_image_link_file'), + ); + } + + return $formatters; +} + +/** + * Theme function for 'image' image field formatter. + */ +function theme_field_formatter_image($variables) { + $element = $variables['element']; + $image = array( + 'path' => $element['#item']['uri'], + 'alt' => $element['#item']['alt'], + 'title' => $element['#item']['title'], + ); + + // Check if this requires a particular image style. + $matches = array(); + if (preg_match('/__([a-z0-9_]+)/', $element['#formatter'], $matches)) { + $image['style_name'] = $matches[1]; + return theme('image_style', $image); + } + else { + return theme('image', $image); + } +} + +/** + * Theme function for 'image_link_content' image field formatter. + */ +function theme_field_formatter_image_link_content($variables) { + $element = $variables['element']; + list($id, $vid, $bundle) = field_extract_ids($element['#object_type'], $element['#object']); + return l(theme('field_formatter_image', $variables), $element['#object_type'] . '/' . $id, array('html' => TRUE)); +} + +/** + * Theme function for 'image_link_file' image field formatter. + */ +function theme_field_formatter_image_link_file($variables) { + $element = $variables['element']; + return l(theme('field_formatter_image', $variables), file_create_url($element['#item']['uri']), array('html' => TRUE)); +} Index: modules/image/image-rtl.css =================================================================== RCS file: modules/image/image-rtl.css diff -N modules/image/image-rtl.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/image/image-rtl.css 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,12 @@ +/* $Id$ */ + +/** + * Image upload widget. + */ +div.image-preview { + float: right; /* RTL */ + padding: 0 0 10px 10px; /* RTL */ +} +div.image-widget-data { + float: right; /* RTL */ +} Index: modules/image/image.css =================================================================== RCS file: modules/image/image.css diff -N modules/image/image.css --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ modules/image/image.css 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,15 @@ +/* $Id$ */ + +/** + * Image upload widget. + */ +div.image-preview { + float: left; /* RTL */ + padding: 0 10px 10px 0; /* RTL */ +} +div.image-widget-data { + float: left; /* RTL */ +} +div.image-widget-data input.text-field { + width: auto; +}