t('Commerce File'), 'description' => t('This field stores the ID of a licensed file as an integer value.'), 'settings' => array( 'uri_scheme' => _commerce_file_default_system_scheme(), ), 'instance_settings' => array( 'file_extensions' => 'mp4 m4v flv wmv mp3 wav jpg jpeg png pdf doc docx ppt pptx xls xlsx', 'file_directory' => '', 'max_filesize' => '', ), 'default_widget' => 'commerce_file_generic', 'default_formatter' => 'commerce_file_access_link', 'property_type' => 'commerce_file', 'property_callbacks' => array('commerce_file_field_property_info_callback'), ); // set license settings default to unlimited $license_info = _commerce_file_collate_license_info(); $instance_defaults = array(); foreach ($license_info as $k => $info) { $instance_defaults[$k] = isset($info['property info']['default']) ? $info['property info']['default'] : COMMERCE_FILE_FIELD_UNLIMITED; } $base['instance_settings'] += array('data' => $instance_defaults); return array( COMMERCE_FILE_FIELD_TYPE => array_merge_recursive($base + array( 'label' => t('Commerce File'), 'description' => t('This field stores the ID of a licensed file as an integer value.'), )), ); } /** * Implements hook_field_formatter_info_alter(). */ function commerce_file_field_formatter_info_alter(&$info) { // Add type to file formatters $info['file_default']['field types'][] = COMMERCE_FILE_FIELD_TYPE; $info['file_table']['field types'][] = COMMERCE_FILE_FIELD_TYPE; $info['file_url_plain']['field types'][] = COMMERCE_FILE_FIELD_TYPE; } /** * Implements hook_filefield_paths_field_type_info() on behalf of image.module. */ function commerce_file_filefield_paths_field_type_info() { return array(COMMERCE_FILE_FIELD_TYPE); } /** * Implements hook_field_settings_form(). */ function commerce_file_field_settings_form($field, $instance, $has_data) { $form = array(); $defaults = field_info_field_settings($field['type']); $settings = array_merge($defaults, $field['settings']); $scheme_options = _commerce_file_get_private_stream_wrappers_options(); if (!empty($scheme_options)) { $form['uri_scheme'] = array( '#type' => 'radios', '#title' => t('Private upload destination'), '#options' => $scheme_options, '#default_value' => $settings['uri_scheme'], '#disabled' => $has_data, ); } else { drupal_set_message(t('Commerce File requires a private file scheme. Visit admin/config/media/file-system to set your private file path. Optionally, a private scheme other than Drupal\'s may be implemented.', array('!url' => url('admin/config/media/file-system'))), 'error'); } return $form; } /** * Implements hook_field_instance_settings_form(). */ function commerce_file_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); // build form _commerce_file_field_add_license_elements($form, 'instance', $settings); // Remove the description option. unset($form['description_field']); return $form; } /** * Implements hook_field_load(). */ function commerce_file_field_load($entity_type, $entities, $field, $instances, $langcode, &$items, $age) { file_field_load($entity_type, $entities, $field, $instances, $langcode, $items, $age); foreach ($entities as $id => $entity) { foreach ($items[$id] as $delta => $item) { if (isset($item) && !empty($item['fid'])) { // Unserialize the data array if necessary. if (empty($item['data'])) { $items[$id][$delta]['data'] = array(); } elseif (!is_array($item['data'])) { $items[$id][$delta]['data'] = unserialize($item['data']); } } } } } /** * Implements hook_field_storage_pre_insert(). */ function commerce_file_field_storage_pre_insert($entity_type, $entity) { _commerce_file_field_serialize_data($entity_type, $entity); } /** * Implements hook_field_storage_pre_update(). */ function commerce_file_field_storage_pre_update($entity_type, $entity) { _commerce_file_field_serialize_data($entity_type, $entity); } /** * Implements hook_field_attach_insert(). * * This hook is used to unserialize the commerce_file field's data array after it has * been inserted, because the data array is serialized before it is saved and * must be unserialized for compatibility with API requests performed during the * same request after the insert occurs. */ function commerce_file_field_attach_insert($entity_type, $entity) { _commerce_file_field_unserialize_data($entity_type, $entity); } /** * Implements hook_field_attach_update(). * * This hook is used to unserialize the commerce_file field's data array after it has * been updated, because the data array is serialized before it is saved and * must be unserialized for compatibility with API requests performed during the * same request after the update occurs. */ function commerce_file_field_attach_update($entity_type, $entity) { _commerce_file_field_unserialize_data($entity_type, $entity); } /** * Converts commerce_file field data to a serialized array. * * @param $entity_type * The entity type variable passed through hook_field_storage_pre_*(). * @param $entity * The entity variable passed through hook_field_storage_pre_*(). */ function _commerce_file_field_serialize_data($entity_type, $entity) { // Loop over all the commerce_file fields attached to this entity. foreach (_commerce_file_get_commerce_file_fields($entity_type, $entity) as $field_name) { // Iterate over the items arrays for each language. foreach (array_keys($entity->{$field_name}) as $langcode) { $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array(); // Serialize data arrays before saving. foreach ($items as $delta => $item) { // Serialize an existing data array. if (!empty($item['data']) && is_array($item['data'])) { $entity->{$field_name}[$langcode][$delta]['data'] = serialize($item['data']); } } } } } /** * Converts saved commerce_file field data columns back to arrays for use in the rest of * the current page request execution. * * @param $entity_type * The entity type variable passed through hook_field_attach_*(). * @param $entity * The entity variable passed through hook_field_attach_*(). */ function _commerce_file_field_unserialize_data($entity_type, $entity) { // Loop over all the commerce_file fields attached to this entity. foreach (_commerce_file_get_commerce_file_fields($entity_type, $entity) as $field_name) { // Iterate over the items arrays for each language. foreach (array_keys($entity->{$field_name}) as $langcode) { $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array(); // For each item in the array, unserialize or initialize its data array. foreach ($items as $delta => $item) { // If we have a non-array $item['data'], unserialize it. if (!empty($item['data']) && !is_array($item['data'])) { $entity->{$field_name}[$langcode][$delta]['data'] = unserialize($item['data']); } // If we have no data element (or an existing empty), create an empty // array. elseif (empty($item['data'])) { $entity->{$field_name}[$langcode][$delta]['data'] = array(); } } } } } /** * Returns an array of commerce_file field names from a specific entity. * * @param $entity_type * The entity type variable passed through hook_field_storage_pre_*() or * hook_field_attach_*(). * @param $entity * The entity variable passed through hook_field_storage_pre_*() or * hook_field_attach_*(). * * @return array * An array of commerce_file field names or an empty array if none are found. */ function _commerce_file_get_commerce_file_fields($entity_type, $entity) { $commerce_file_fields = array(); // Determine the list of instances to iterate on. list(, , $bundle) = entity_extract_ids($entity_type, $entity); $instances = field_info_instances($entity_type, $bundle); $fields = field_info_fields(); // Iterate through the instances and collect results. foreach ($instances as $instance) { $field_name = $instance['field_name']; // If the instance is a commerce_file field with data... if ($fields[$field_name]['type'] == COMMERCE_FILE_FIELD_TYPE && isset($entity->{$field_name})) { $commerce_file_fields[] = $field_name; } } return $commerce_file_fields; } /** * Implements hook_field_presave(). */ function commerce_file_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { file_field_presave($entity_type, $entity, $field, $instance, $langcode, $items); foreach ($items as $delta => $item) { // Serialize an existing data array. if (isset($item['data']) && is_array($item['data'])) { $items[$delta]['data'] = serialize($item['data']); } } } /** * Implements hook_field_insert(). */ function commerce_file_field_insert($entity_type, $entity, $field, $instance, $langcode, &$items) { file_field_insert($entity_type, $entity, $field, $instance, $langcode, $items); } /** * Implements hook_field_update(). */ function commerce_file_field_update($entity_type, $entity, $field, $instance, $langcode, &$items) { file_field_update($entity_type, $entity, $field, $instance, $langcode, $items); } /** * Implements hook_field_delete(). */ function commerce_file_field_delete($entity_type, $entity, $field, $instance, $langcode, &$items) { file_field_delete($entity_type, $entity, $field, $instance, $langcode, $items); } /** * Implements hook_field_delete_revision(). */ function commerce_file_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, &$items) { file_field_delete_revision($entity_type, $entity, $field, $instance, $langcode, $items); } /** * Implements hook_field_is_empty(). */ function commerce_file_field_is_empty($item, $field) { return file_field_is_empty($item, $field); } /** * Implements hook_field_widget_info(). */ function commerce_file_field_widget_info() { $base = array( 'field types' => array('commerce_file'), 'settings' => array( 'progress_indicator' => 'throbber', ), 'behaviors' => array( 'multiple values' => FIELD_BEHAVIOR_CUSTOM, 'default value' => FIELD_BEHAVIOR_NONE, ), ); // set license settings default to inherited $license_info = _commerce_file_collate_license_info(); $base['settings'] += array('data' => array_fill_keys(array_keys($license_info), COMMERCE_FILE_FIELD_INHERIT)); // return widgets return array( 'commerce_file_generic' => array_merge_recursive($base, array( 'label' => t('Commerce File'), )), ); } /** * Implements hook_filefield_sources_widgets(). * * This returns a list of widgets that are compatible with FileField Sources. */ function commerce_file_filefield_sources_widgets() { // Add any widgets that your module supports here. return array('commerce_file_generic'); } /** * Implements hook_field_widget_settings_form(). */ function commerce_file_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); return $form; } /** * Implements hook_field_widget_form(). */ function commerce_file_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) { // Add display_field setting to field because file_field_widget_form() assumes it is set. $field['settings']['display_field'] = 0; // ensure uri scheme on license file field if no items $controlled_field_names = _commerce_file_get_field_names(); if (empty($items) && empty($field['settings']['uri_scheme']) && $instance['field_name'] == $controlled_field_names['license_file']) { $available_scheme_options = _commerce_file_get_private_stream_wrappers_options(); // set uri_scheme to first available $field['settings']['uri_scheme'] = key($available_scheme_options); } // build elements with file_field $elements = file_field_widget_form($form, $form_state, $field, $instance, $langcode, $items, $delta, $element); // add properties to each element for widget process foreach (element_children($elements) as $delta) { $elements[$delta]['#entity_type'] = $instance['entity_type']; $elements[$delta]['#value_callback'] = '_commerce_file_field_widget_value'; $elements[$delta]['#process'][] = '_commerce_file_field_widget_process'; if ($instance['entity_type'] == COMMERCE_FILE_LICENSE_ENTITY_NAME) { $elements[$delta]['#inheritable'] = FALSE; } // add license limit settings $element_settings = isset($elements[$delta]['#default_value']) ? $elements[$delta]['#default_value'] : array(); _commerce_file_field_add_license_elements($elements[$delta], 'widget', $element_settings, $instance['settings']); } return $elements; } /** * An element #process callback for the commerce_file_commerce_file field type. * - add file widget styles * - determine if there is a default file scheme available */ function _commerce_file_field_widget_process($element, &$form_state, $form) { $item = $element['#value']; $item['fid'] = $element['fid']['#value']; $field = field_widget_field($element, $form_state); $instance = field_widget_instance($element, $form_state); // add styles for file widget $element['#theme'] = 'file_widget'; $element['#attached']['css'][] = drupal_get_path('module', 'commerce_file') . '/css/commerce_file.forms.css'; if (isset($element['#attributes']['class'])) { $element['#attributes']['class'] = array_merge($element['#attributes']['class'], array('file-commerce-file')); } else { $element['#attributes']['class'] = array('file-commerce-file'); } // only check scheme if not any field generated on install if (!in_array($instance['field_name'], _commerce_file_get_field_names())) { // get scheme options $available_scheme_options = _commerce_file_get_private_stream_wrappers_options(); $scheme_options = _commerce_file_get_private_stream_wrappers_options(array($field['settings']['uri_scheme'])); // Warn if there is no scheme selected for this field $default_scheme = NULL; if (empty($item['fid']) && empty($scheme_options)) { $element['#disabled'] = TRUE; $error_msg = t('You cannot upload a file. Commerce File requires a private file scheme. Visit admin/config/media/file-system to set your private file path. Optionally, a private scheme other than Drupal\'s may be implemented.', array('!url' => url('admin/config/media/file-system'))); if (!empty($available_scheme_options)) { $error_msg .= t('
There are private file schemes available on your system. Visit the field settings to select a private scheme allowed for this field.'); } form_error($element, $error_msg); } } return $element; } /** * The #value_callback for the commerce_file_generic field element. */ function _commerce_file_field_widget_value($element, $input = FALSE, $form_state) { // call file field's value callback to perform managed_file operations return file_field_widget_value($element, $input, $form_state); } // ----------------------------------------------------------------------- // Form element functions /** * License settings element generator */ function _commerce_file_field_add_license_elements(&$parent, $level = 'field', $settings = array(), $inherited_settings = array()) { $base = array(); $defaults = array(); $license_info = _commerce_file_collate_license_info(); // allow parent element to suggest inheritable $parent_inheritable = isset($parent['#inheritable']) ? $parent['#inheritable'] : TRUE; $have_inherited_settings = !empty($inherited_settings); switch ($level) { case 'widget': $defaults = field_info_instance_settings(COMMERCE_FILE_FIELD_TYPE); break; case 'instance': $defaults = field_info_instance_settings(COMMERCE_FILE_FIELD_TYPE); break; default: $defaults = field_info_field_settings(COMMERCE_FILE_FIELD_TYPE); break; } // Initialize license base elements foreach ($license_info as $k => &$info) { // initialize base element if not defined if (!isset($info['base_element'])) { $info['base_element'] = array(); } // set base element #inheritable property if ($have_inherited_settings) { // allow setting definition to override if explicitly set to NOT inherit // else set to parent's inheritable if (!isset($info['property info']['inheritable']) || $info['property info']['inheritable']) { $info['base_element']['#inheritable'] = $parent_inheritable; } } else { // force to not inheritable if not inherited settings given $info['base_element']['#inheritable'] = FALSE; } } unset($info); // build inherited settings for license settings $inherited = array('descriptions' => array(), 'values' => array()); if ($have_inherited_settings) { $inherited_tokens = array(); // alter license info array foreach ($license_info as $k => &$info) { // skip if non-inheritable if (empty($info['base_element']['#inheritable'])) { continue; } // retrieve inherited value $inherited['values'][$k] = $inherited_value_k = _commerce_file_license_resolve_setting_value(NULL, $inherited_settings['data'][$k]); if (!isset($inherited_value_k)) { continue; } // if no setting value then set to inherit by default if (!isset($settings['data'][$k])) { $settings['data'][$k] = COMMERCE_FILE_FIELD_INHERIT; } } unset($info); } else { $inherited_settings = array(); } // merge all defaults $values = array_merge($defaults, $inherited_settings, $settings); // build form elements $elements = array(); foreach ($license_info as $k => $info) { if (!isset($values['data'][$k])) { continue; } // set default value to inherited value if defined $default_value = $values['data'][$k]; $inherited_value = isset($inherited['values'][$k]) ? $inherited['values'][$k] : NULL; // initialize element with default value, inherited value, and base element $elements[$k] = array( '#default_value' => $default_value, '#limit_inherited_value' => $inherited_value, ) + $info['base_element']; } // update parent with settings elements if (!empty($elements)) { $element_container = array( '#type' => 'fieldset', '#title' => t('Access Limits'), '#collapsible' => TRUE, '#collapsed' => !empty($settings['fid']), '#weight' => 150, '#attributes' => array('class' => array('clearfix')), ); // merge settings into parent element $parent = array_merge($parent, array('data' => ($element_container + $elements))); } } /** * Resolves license settings values with inheritance of instance settings */ function _commerce_file_license_resolve_setting_value($value, $inherited_value) { if (!isset($value) || _commerce_file_field_setting_is_inherited($value)) { // check if inherited value is set to unlimited - blank textfields are unlimited if (!empty($inherited_value) || $inherited_value === '0') { return $inherited_value; } // default to unlimited return COMMERCE_FILE_FIELD_UNLIMITED; } return $value; } /** * Check if field limit setting value is set to Unlimited */ function _commerce_file_field_setting_is_unlimited($value) { return strcmp($value, COMMERCE_FILE_FIELD_UNLIMITED) === 0; } /** * Check if field limit setting value is set to Unlimited */ function _commerce_file_field_setting_is_inherited($value) { return strcmp($value, COMMERCE_FILE_FIELD_INHERIT) === 0; } // ----------------------------------------------------------------------- // Field property info /** * Callback to alter the property info of price fields. * * @see commerce_file_field_info(). */ function commerce_file_field_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) { $name = $field['field_name']; $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$name]; $property['type'] = ($field['cardinality'] != 1) ? 'list' : 'commerce_file'; $property['getter callback'] = 'entity_metadata_field_verbatim_get'; $property['setter callback'] = 'entity_metadata_field_verbatim_set'; $property['validation callback'] = 'commerce_file_field_entity_metadata_validate_item'; $property['auto creation'] = 'commerce_file_field_data_auto_creation'; $property['property info'] = commerce_file_field_data_property_info($name); unset($property['query callback']); } /** * Returns the default array structure for a field for use when creating * new data arrays through an entity metadata wrapper. */ function commerce_file_field_data_auto_creation() { return array('fid' => NULL, 'data' => array()); } /** * Callback for validating commerce_file field $items. * * - Requires 'fid' to be a valid file * - Allows 'data' to be optional * * @see entity_metadata_field_file_validate_item() */ function commerce_file_field_entity_metadata_validate_item($items, $context) { // Allow NULL values. if (!isset($items)) { return TRUE; } // Stream-line $items for multiple vs non-multiple fields. $items = !entity_property_list_extract_type($context['type']) ? array($items) : (array) $items; foreach ($items as $item) { // File-field items require a valid file. if (!isset($item['fid']) || !file_load($item['fid'])) { return FALSE; } } return TRUE; } /** * Defines info for the properties of the Price field data structure. */ function commerce_file_field_data_property_info($name = NULL) { return array( 'file' => array( 'label' => t('File'), 'description' => !empty($name) ? t('The file of field %name', array('%name' => $name)) : '', 'type' => 'file', 'getter callback' => 'entity_metadata_field_file_get', 'setter callback' => 'entity_metadata_field_file_set', 'required' => TRUE, ), 'data' => array( 'label' => t('Data'), 'description' => !empty($name) ? t('License data array of field %name', array('%name' => $name)) : '', 'type' => 'struct', 'getter callback' => 'entity_property_verbatim_get', 'setter callback' => 'entity_property_verbatim_set', 'property info' => _commerce_file_license_data_property_info(), ), ); } // ----------------------------------------------------------------------- // Field item operations /** * Resolve all settings for a given field item */ function _commerce_file_license_resolve_settings($field_item, $instance) { if (empty($field_item['data']) || empty($instance['settings']['data'])) { return $field_item; } // get license setting info $license_info = _commerce_file_collate_license_info(); // resolve only settings defined in license info foreach ($field_item['data'] as $setting_key => &$setting_value) { if (isset($license_info[$setting_key]) && isset($instance['settings']['data'][$setting_key])) { $setting_value = _commerce_file_license_resolve_setting_value($setting_value, $instance['settings']['data'][$setting_key]); } } unset($setting_value); return $field_item; } /** * Aggregate one or more field items' settings into the first field item * - assumes all settings have been resolved for all items */ function _commerce_file_field_aggregate_field_items($field_item1/*, $field_item2, ... */) { $args = func_get_args(); $trunk = array_shift($args); if (empty($args)) { return $trunk; } foreach ($args as $field_item) { if (!isset($field_item['data'])) { continue; } if (!isset($trunk['data'])) { $trunk['data'] = $field_item['data']; } else { $trunk['data'] = _commerce_file_license_property_merge($trunk['data'], $field_item['data']); } } return $trunk; } // ----------------------------------------------------------------------- // Entity field items /** * Return all commerce_file fields for a given entity, indexed by fid */ function _commerce_file_field_aggregate_files($entity, $entity_type = NULL) { $aggregated = array(); $field_type = COMMERCE_FILE_FIELD_TYPE; if (empty($entity_type)) { $entity_type = _commerce_file_get_entity_type($entity); if (empty($entity_type)) { return $aggregated; } } // get some entity info $entity_wrapper = entity_metadata_wrapper($entity_type, $entity); $entity_id = $entity_wrapper->getIdentifier(); $entity_bundle = $entity_wrapper->getBundle(); // get field instances for type and bundle $instances = _commerce_file_field_info_instances($entity_type, $entity_bundle, $field_type); if (empty($instances)) { return $aggregated; } $file_items = array(); foreach ($instances as $field_name => $instance) { $items = $entity_wrapper->{$field_name}->value(); if (empty($items)) { continue; } // make an array for single value if (isset($items['fid'])) { $items = array($items); } // index items on fid foreach ($items as $item) { if (isset($item['fid'])) { $file_items[$item['fid']][] = _commerce_file_license_resolve_settings($item, $instance); } } } // aggregate per file foreach ($file_items as $fid => $items) { $aggregated[$fid] = call_user_func_array('_commerce_file_field_aggregate_field_items', $items); } return $aggregated; } /** * Return all commerce_file field type instances */ function _commerce_file_field_info_instances($entity_type, $entity_bundle, $field_type = COMMERCE_FILE_FIELD_TYPE) { $cache = &drupal_static(__FUNCTION__); $field_type = !empty($field_type) ? $field_type : COMMERCE_FILE_FIELD_TYPE; $cid = "{$entity_type}::{$entity_bundle}::{$field_type}"; if (!isset($cache[$cid])) { $cache[$cid] = array(); // find all fields instances $instances = field_info_instances($entity_type, $entity_bundle); if (!empty($instances)) { // find all instances for field type foreach ($instances as $field_name => $instance) { $field_info = field_info_field($field_name); if ($field_info['type'] == $field_type) { $cache[$cid][$field_name] = $instance; } } } } return $cache[$cid]; }