'media_ahah_formatter_load', 'access arguments' => array('access content'), 'file' => 'media_ahah.inc', ); $items['media/metadata/js'] = array( 'page callback' => 'media_ahah_metadata_ahah', 'access arguments' => array('access content'), 'file' => 'media_ahah.inc', ); // Default settings, for content types that do not have their own. $items['admin/settings/media'] = array( 'title' => 'Media settings', 'description' => 'Configure Global Media settings, including default content type settings.', 'page callback' => 'drupal_get_form', 'page arguments' => array('media_settings_global', 'global'), 'access arguments' => array('administer media'), 'file' => 'media_settings.inc', 'weight' => 3, ); return $items; } /** * Implementation of hook_form_alter(). * @param $form * @param $form_state * @param $form_id */ function media_form_alter(&$form, $form_state, $form_id) { global $user; // Load content-type settings if ($form_id == 'node_type_form') { include_once('media_settings.inc'); media_settings_content_type($form, $form['#node_type']->type); } // Add the media browser on the node add/edit screen. if (strstr($form_id, 'node_form') ) { // Is the media browser enabled on this node type? Type-specific options override the default. if (variable_get('media_'. $form['type']['#value'] .'_override', NULL) !== NULL) { $enabled = variable_get('media_'. $form['type']['#value'] .'_enabled', NULL); } else { $enabled = variable_get('media_global_enabled', TRUE); } if ($enabled) { // Get the fields we need to enable on this module. $fields = media_active_fields_for_node_type($form['type']['#value']); // Iterate through each field and add a browser form. foreach ($fields as $field => $registration_ids) { // Add the media browser form. $form[$field]['media'] = media_build_browser_form($form_state, $registration_ids, $form['type']['#value'], $field, $user->uid); // Add .media and .replace class to the field CSS class attributes. $form[$field][0]['#attributes'] = array('class' => 'media replace'); } } } } /** * Implements hook_theme(). * Register theming functions * * @return array */ function media_theme() { return array( // The media file browser form. 'media_file_browser' => array( 'file' => 'media_theme.inc', 'arguments' => array('element' => NULL), ), // The default media file list form element. 'media_file_list' => array( 'file' => 'media_theme.inc', 'arguments' => array('element' => NULL), ), // The media browser pane. 'media_browser_pane' => array( 'file' => 'media_theme.inc', 'arguments' => array('form' => array()), ), ); } /* ***************************************** */ /* Media hook calls */ /* ***************************************** */ /** * Gets all of the modules which register with Media. * * @param array $ids * (Optional) If this contains an array of id strings, then return only the * specified ids. * @param boolean $reset * (Optional) If TRUE, then reset the static cache. * @return array * An array of registrations, keyed by implementing function name, in the * form of: * 'module' => The module implementing the hook. * 'uri' => The scheme the stream wrapper handles, default is 'public://'. * 'types' => The mime types this module handles, defaults to * (all). * 'name' => A human readable name, displayed on forms. * 'kind' => Kind of functionality: 'resource' or 'format'. * 'description' => A verbose description of functionality. * 'callbacks' => An array of key => functions called for data. * 'fields' => What fields does this functionality operate on? * 'validation' => @todo * 'display' => @todo */ function media_get_registered_modules($ids = NULL, $reset = FALSE) { static $registrations; // Only build cache the first time the function is called, or if we reset it. if (is_NULL($registrations) || $reset) { $registrations = array(); // Get all the modules which implement hook_media_register(). foreach (module_implements('media_register') as $module) { $function = $module .'_media_register'; // Get all the registrations defined by the module. $registration = $function(); // TODO: Fix this. Why are we calling $function twice (jmstacey 6/2/09)? // Iterate through each registration defined by the implementing module. foreach (array_keys($function()) as $key) { // Add the module name to each registration. // Default the 'module' key of the registration to the implementing module. $registration[$key]['module'] = $registration[$key]['module'] ? $registration[$key]['module'] : $module; // translate strings now $registration[$key]['name'] = t($registration[$key]['name']); $registration[$key]['description'] = t($registration[$key]['description']); // Default the 'uri' to public://. if (!$registraton[$key]['uri']) { $registration[$key]['uri'] = MEDIA_RESOURCE_URI_DEFAULT; } // Default 'types' to * (all). if (!$registration[$key]['types']) { $registration[$key]['types'] = MEDIA_TYPES_DEFAULT; } } // Add the new registrations to our total. $registrations = array_merge($registrations, $registration); } } // Return requested registrations. if ($ids) { foreach ($ids as $id) { $return[$id] = $registrations[$id]; } return $return; } return $registrations; } /* ***************************************** */ /* Media API Functions */ /* ***************************************** */ /** * Build a list of possible registration types. * 'resource' handles building file resource lists. * 'formatter' displays a list of file resources for the file browser. * 'attach' passes an FID to the registered module for handling. * * @return array */ function media_registration_kinds() { return array('resource', 'formatter'); } /** * Get all fields that can be enabled on a field type. * * @param string $field_type * The field type to get items for. * @param string $function_type * The kind of functionality being looked for. * @return array * Array of full registration objects. */ function media_get_fields($field_type, $function_type = 'resource') { static $data; // Do we have cached version? if ($data[$field_type][$function_type]) { return $data[$field_type][$function_type]; } $data = array(); // Get all the registered modules. foreach (media_get_registered_modules() as $id => $registration) { // Check to see if this registration supports this function type. if ($registration['kind'] == $function_type) { // Now look for the fields. if ($registration['fields']) { foreach ($registration['fields'] as $field) { // If this registration supports this field type, add it to the // returned array. if ($field == $field_type) { $data[$field_type][$function_type][$id] = $registration; } } } } } return $data[$field_type][$function_type]; } /** * Select registrations for use by matching against uri and file extension. * * @param array $registrations * Array of extensions. * @param string $uri * The kind of uri being used. * @param string $file_extension * The current file extension. * @return array * An array of applicable formatters. */ function media_get_applicable_formatters($registrations, $file_extension) { // Do we have a file extension? if ($file_extension) { foreach ($registrations as $id => $formatter) { // Does this formatter use any file type? If not, then we have to dig. if (!$formatter['types'] == MEDIA_TYPES_DEFAULT) { // We need to see if this file type is supported specifically. if (!in_array($file_extension, $registration['types'])) { // This registration is not useful. unset($registrations[$id]); } } } } return $registrations; } /* ***************************************** */ /* Media Internal Functions */ /* ***************************************** */ /** * Parsing function for the registrations to hand back the kinds of modules * registering. Used to select all formatters, resources, etc. * * @param string $kinds * Return all the matching registrations of this kind. * @return array * An array of matching registrations. */ function media_get_registration_kinds($kind = NULL) { $kinds = array(); // Get the registered modules. $registrations = media_get_registered_modules(); // Parse the registrations. foreach ($registrations as $id => $registration) { if ($kind) { // Get the kind that is being looked for. if ($registration['kind'][$kind]) { $kinds[$id] = $registration; } } else { $kinds[$id] = $registration; } } return $kinds; } /** * Return a set of formatters which can format the specified item. * * If $description is NULL, all formatters will be returned. A set of * registered modules can be passed in to narrow the formatter options. * * @param string $description * File extension to return. * @param array $registrations * (Optional) If specified, then match only against these registrations. * @return array * An array of formatter ids keyed by module. */ function media_registration_item_formatters($description = '*', $registrations = NULL) { $formatters = array(); // Get all the registrations if we don't have any. if (is_NULL($registrations)) { $registrations = media_get_registered_modules(); } // Iterate through each of the registered modules and find the formatters. foreach ($registrations as $id => $registration) { // Look for the formatter, or just take all (wildcard being *). if ((is_array($registration['kind']['formatter']['types']) && in_array($registration['kind']['formatter']['types'], $description)) || $description == '*' || $registration['kind']['formatter']['types'] == '*') { $formatters[$registration['module']] = $id; } } return $formatters; } /** * Parsing function for the registrations to hand back the kinds of modules * registering. * * @param string $type * Only hand back data for the specified type. * @return array * @TODO: finish this function. */ function media_registration_types($type = NULL) { // get the registered modules $registrations = media_get_registered_modules(); // parse the registrations foreach ($registrations as $registration) { // @TODO } } /** * Parsing function for the registrations to hand back the kinds of modules * registering. * * @param string $array * Name of the element we want to get data from. * @return array */ function media_registration_data($name) { $data = array(); // Get the registered modules. $registrations = media_get_registered_modules(); // Parse the registrations. foreach ($registrations as $id => $registration) { // Do we have this data in this registration? if ($registration[$name]) { // Get the item that was requested. $data[$registration['module']] = array( $id => array( $name => $registration[$name], 'description' => $registration['description'] ) ); } } return $data; } /** * Fetch all resources registered and call the specified callback. I * * Also add item to the media browser as a horizontal tab. * * @TODO Implement admin weighting here somehow. * * @param array $registration_ids * Array of registration ids to be loaded. * @param string $node_type * Drupal node type. * @param field $field * CCK field name. * @param int $uid * Drupal {user} id. * @return array */ function media_get_resources($registration_ids, $node_type, $field, $uid) { // Get all the registrations that define the resources. $registrations = media_get_registered_modules($registration_ids); foreach ($registrations as $id => $registration) { // Get the callback function. $function = $registration['callbacks']['resource']; if (function_exists($function)) { // Get the results of the callback function. $item = $function($node_type, $field, $uid); $tab_name = check_plain(key($item)); // Add a resource_id to the item. $item[$tab_name][key($item[$tab_name])]['resource_id'] = array( '#type' => 'value', '#value' => $id, ); // Add a resource module to the item so that we don't have to figure it out later $item[$tab_name][key($item[$tab_name])]['registered_module'] = array( '#type' => 'value', '#value' => $registration['module'], ); // Add tabs under the tab name. $items[$tab_name][key($item[$tab_name])] = $item[$tab_name][key($item[$tab_name])]; } } return $items; } /** * Get a list of fields for the requested node type. * * @param string $type_name * Drupal {node} type. * @param string $function * Either 'resource' or 'formatter'. * @return array * An array of field names. */ function media_active_fields_for_node_type($type_name, $function = 'resource') { // Need to know if the type-specific or global settings are going to be used. // At least one of them is enabled at this point. $type_override = variable_get('media_'. $type_name .'_override', FALSE); $items = array(); $type = _media_content_field_types($type_name); // extract the fields for this node type foreach ((array)$type['fields'] as $field_name => $field) { // Ignore the content-type specific per-field setting unless the override is set $fields_enabled = FALSE; if ($type_override) { $fields_enabled = variable_get('media_'. $type_name .'_'. $field['field_name'] .'_'. $function, FALSE); } else { //@TODO: Right now, if it's using the default it will be enabled for all fields. // Eventually it will probably be better to allow per-field controls at a global level as well. // However, this line will probably just work once the actual variables exist. $fields_enabled = variable_get('media_global_'. $field['field_name'] .'_'. $function, 'default'); // Handle the default special, because we want the default to be on for all fields but don't know what the fields are yet. if ($fields_enabled === 'default') { $fields_enabled = array(); foreach (media_registration_kinds() as $kind) { // get all the kinds that match this field if ($registrations = media_get_fields($field['type'], $kind)) { foreach ($registrations as $id => $registration) { $compound_id = $field['field_name'] .'--'. $id; $fields_enabled[$compound_id] = $compound_id; } } } } } if (!empty($fields_enabled)) { $items[] = $fields_enabled; } } $data = array(); foreach ($items as $item) { foreach ($item as $id => $value) { // we need to split the $id into $field_name and $media registration id if ($value) { $id = explode('--', $id); $data[$id[0]][] = $id[1]; } } } return $data; } /** * Sanitize the incoming name to be used for a html #id. * * @param string $drawer_name * @return string */ function media_create_id($drawer_name) { return str_replace(array(' ', "'", '"', "%", "<", ">"), '', $drawer_name); } /** * Get a list of content field types. * * Use CCK if available. * * @param string $type_name * A string representing the content type. * @return array * A form structured array is returned. */ function _media_content_field_types($type_name) { if (module_exists('content')) { $type = content_types($type_name); } else { // Get the specific content type. $type = (array)node_get_types('type', $type_name); } // Recognize the upload/attachment form and create an artificial field. // This should be the only one we ever have to do this for. if (module_exists('upload')) { $type['fields']['attachments'] = array( 'field_name' => 'attachments', 'type' => 'attachments', 'widget' => array( 'label' => $type['extra']['attachments']['label'], ), ); } return $type; } /* *************************************************** */ /* Media forms */ /* *************************************************** */ /** * Build data for the media browser display. * * @TODO Clean this form up and use a form theme function. * * Note: The FAPI docs say a submit element event defaults to 'click', * but as of d6.10, it defaults to 'mousedown', so we need to override. * 'event' => 'click', * * @param array $registration_ids * Array of registrations to call. * @param string $node_type * @param string $field * @param uid $uid * @return array * Drupal FAPI form array. */ function media_build_browser_form($form_state, $registration_ids, $node_type, $field, $uid) { static $id; // We need a static counter for our form element wrapper. $id += 1; // Load our css. $path = drupal_get_path('module', 'media'); drupal_add_css($path .'/media.css'); // Load our specific js for the file selector drupal_add_js($path .'/javascript/media.js'); // Load the md5 library so we can hash the upload filename for use in the meta form. drupal_add_js($path .'/javascript/jquery.md5.js'); $items = array(); $form = array(); $form['media_browser_activate'] = array( '#type' => 'markup', '#value' => '
'. t('Add files') .'
', ); // We are using a tab form type. $form['media_browser'] = array( '#type' => 'tabset', '#attributes' => array('class' => 'media browser wrapper'), ); // Get all the active resources $resources = media_get_resources($registration_ids, $node_type, $field, $uid); // Store the tab & drawer names for the js form selector. $drawer_options = array(); // loop through the form and start pulling out the data to // create tabs -> panes -> drawers foreach ($resources as $tab_name => $data) { // create a tab id $tab_id = strtolower(str_replace(' ', '_', $tab_name)); // create tab $form['media_browser'][$tab_id] = array( '#type' => 'tabpage', '#title' => $tab_name, '#theme' => 'media_browser_pane', ); $drawer_options[$tab_id] = $tab_name; // build the drawers for this tab $drawer_list = array(); $active_drawer = TRUE; // check to see if we do have children- we should, but just in case if (is_array($data)) { $drawers = array(); foreach ($data as $drawer_name => $drawer_data) { // @TODO check drawer access permissions here to make // sure we should present this to the user // the drawer id needs to have additional data on it to prevent // name space conflicts with ids $drawer_id = strtolower(str_replace(' ', '_', $drawer_name)) .'_display'; // create a link with a specific id to call $drawers_link = ''. $drawer_name .''; $drawer_list[] = array('data' => $drawers_link, 'class' => ($active_drawer ? 'active' : '') ); // add the drawer form element $form['media_browser'][$tab_id][$drawer_name] = $drawer_data; // add classes to the drawer display item $form['media_browser'][$tab_id][$drawer_name]['#prefix'] = '
'; $form['media_browser'][$tab_id][$drawer_name]['#suffix'] = '
'; // no longer on the first drawer $active_drawer = FALSE; $drawer_options[$tab_id .'|'. $drawer_id] = '- '. $drawer_name; } // change the drawers to a list for easer display $form['media_browser'][$tab_id]['drawers'] = array( '#type' => 'markup', '#value' => theme('item_list', $drawer_list, NULL, 'ul', array('class' => 'drawers')) ); } } $form['media_browser']['drawer_select'] = array( '#type' => 'select', '#title' => t('Drawer select'), '#description' => t('Oh bother, you really should have JavaScript enabled, you know...'), '#options' => $drawer_options, '#prefix' => '
', '#suffix' => '
', ); // Container for the progress indicator. $form['media_browser']['media_browser_file_progress'] = array( '#prefix' => '
', '#suffix' => '
', ); $form['media_browser']['media_browser_file_progress']['file_progress_message'] = array( '#type' => 'item', '#title' => theme('image', variable_get('media_file_progress_image', $path .'/images/uploading-gradient.gif')) . t('Please wait while your file is attached...'), ); // Our AHAH enabled submit button. $form['media_browser']['media_browser_submit'] = array( '#type' => 'submit', '#value' => t('Add file'), '#description' => t("Add the selected file."), '#submit' => array('media_browser_submit'), // If no javascript action. '#validate' => array('media_browser_validate'), '#attributes' => array('class' => 'media-browser-submit'), '#ahah' => array( 'path' => 'media/js', 'wrapper' => 'media-browser-file-progress-'. $id, 'method' => 'replace', 'effect' => 'fade', 'event' => 'click', ), ); // Container for the metadata submission message. $form['media_browser']['media_browser_metadata_message'] = array( '#prefix' => '
', '#suffix' => '
', ); $form['media_browser']['media_browser_metadata_message']['message'] = array( '#type' => 'item', '#value' => '', ); $form['media_browser']['media_browser_metadata'] = array( '#type' => 'tabset', '#attributes' => array('class' => 'media-browser-metadata-wrapper'), ); // @TODO: This all goes in the form creation, actually, // to create our metadata form... // $uri = $form_state['values']['media_files']; // $file_extension = pathinfo($uri, PATHINFO_EXTENSION); // // // Get the file creator for this item. // $file_creator = media_get_registered_modules(array($registration_id)); // // // Get the formaters for this node type. // $formatters = media_active_fields_for_node_type($node_type, 'formatter'); // // // Get the registrations. // $registrations = media_get_registered_modules($formatters[$field]); // // // Remove any non-applying registrations. // $registrations = media_get_applicable_formatters($registrations, $file_extension); // // // Get all the formatting forms. // $formatter_options = array(); // $forms = array(); // foreach ($registrations as $id => $registration) { // $formatter_options[$id] = $registration['name']; // $function = $registration['callbacks']['form']; // if (function_exists($function)) { // $forms[$id] = $function($node_type, $field, $file_extension, $uri); // } // } // @TODO: This is placeholder only. foreach (array('Video', 'Image', 'Audio', 'PDF') as $mime_type) { $form['media_browser']['media_browser_metadata'][$mime_type] = array( '#type' => 'tabpage', '#title' => $mime_type, ); $form['media_browser']['media_browser_metadata'][$mime_type][$mime_type .'_title'] = array( '#type' => 'textfield', '#title' => t('Title'), ); } // Our AHAH enabled submit button for the metadata. $form['media_browser']['media_browser_metadata_submit'] = array( '#type' => 'submit', '#value' => t('Add metadata'), '#description' => t("Add the selected file."), '#submit' => array('media_browser_metadata_submit'), // If no javascript action. '#validate' => array('media_browser_metadata_validate'), '#attributes' => array('class' => 'media-browser-metadata-submit'), '#ahah' => array( 'path' => 'media/metadata/js', 'wrapper' => 'media-browser-metadata-message-'. $id, 'method' => 'replace', 'effect' => 'fade', 'event' => 'click', ), ); // build the tabs into a single form element // @TODO make sure we have children for each tab and remove any that // we don't have data for $form['tabs'] = array( '#type' => 'markup', '#value' => theme('item_list', $tabs, NULL, 'ul', array('class' => 'tabs')), ); return $form; } /** * Display files in a form element. * * This is a generic for other modules to make use of. * * @param array $files * array of (uri => uri, filename => filename, meta => array(key => value)) * @param string $title * option title argument * @return array * Form array containing a select list populated with files. */ function media_resource_display_user_files_form($files, $title = NULL) { // Pass files into options array. $options = array(); foreach ($files as $file) { // TODO: Use FID rather than URI $options[$file['uri']] = $file['filename']; } // Parse files into form element. $form['media_files'] = array( '#type' => 'select', '#options' => $options, '#title' => $title ? $title : '', '#attributes' => array('class' => 'resource select'), '#size' => variable_get('media_file_list_size', 10), ); return $form; } /** * Display the upload form for the tab. * * @return array * Form array containing a file field. */ function media_resource_display_upload_form() { $form['media_upload']['upload'] = array( '#type' => 'file', '#title' => t('Upload your file'), '#size' => 30, '#attributes' => array('class' => 'resource select'), ); return $form; } /* ***************************************** */ /* Media Hook Implementations */ /* ***************************************** */ /** * Implementation of hook_media_elements(). * * A Media File List element is created with the following FAPI: * '#type' => 'media_file_list', * '#options' => $options, // An associative array of filepaths, keyed by FID. * '#title' => $title, // The translated title, displayed in the tab. * '#description' => $description, // A translated description, to be displayed below the title. */ function media_elements() { $elements = array(); $elements['media_file_list'] = array( '#input' => TRUE, '#process' => array('media_file_list_element_process'), '#element_validate' => array('media_file_list_element_validate'), '#submit' => array('media_file_list_element_submit'), ); return $elements; } /* ***************************************** */ /* Callbacks */ /* ***************************************** */ /** * Process callback for the media_browser element. * * @param $element * @param $edit * @param $form_state * @param $form * @return array */ function media_file_list_element_process($element, $edit, $form_state, $form) { $element['list'] = array( '#type' => 'select', '#options' => $element['#options'], '#size' => variable_get('media_file_list_size', 10), ); return $element; } /** * Submit callback for the media_browser element. * * @param array $form * @param $form_state */ function media_file_list_element_submit(&$form, $form_state) { drupal_set_message('Successful submit of the Media File List...'); } /** * Validate callback for the media_browser element. * * @param $element * @param $form_state */ function media_file_list_element_validate($element, $form_state) { drupal_set_message('Successful validation of the Media File List...'); return $element; } /** * Validate callback for the media_browser metadata. * * @param array $form * @param $form_state */ function media_browser_metadata_validate(&$form, $form_state) { drupal_set_message('validating metadata... media_browser_metadata_validate needs to pass it to the registered module.'); } /** * Submit callback for the media_browser metadata. * * @param array $form * @param $form_state */ function media_browser_metadata_submit(&$form, $form_state) { drupal_set_message('media_browser_metadata_submit: last step... we now have metadata, and need to send that info to the registered module w/ the file info.'); } /** * Validate callback for the media_browser. * * @param array $form * @param $form_state */ function media_browser_validate(&$form, $form_state) { drupal_set_message('media_browser_validate is validating... needs to go to registered module from here.'); } /** * Submit callback for the media_browser. * * @param array $form * @param $form_state */ function media_browser_submit(&$form, $form_state) { drupal_set_message(theme('image', variable_get('media_file_progress_success', drupal_get_path('module', 'media') .'/images/check-green-blah.png')) . t('media_browser_submit File attachment: !file successful.', array('!file' => l($form_state['values']['media_files'], $form_state['values']['media_files'])))); drupal_set_message('(media_browser_submit will need to actually process the file here, sending it to the original registered module to store. we also need to ensure the file is kept in synch with the metadata & formatter coming up next.)'); // To consider: Use module_implements to call all modules which implement this hook. // This might allow the modules to determine their own chaining, but at the expense of performance. // Order of operations and state would be an issue... // // Another alternative: Create a mechanism for before and after requirements. Example: a thumbnail creator would register // an action and with the registration provide a list of modules/functions that must run before the action it provides is run. // This would mean that for every action fire we would have to pull all of this together and build an action chain. // That could get horribly messy in the event of a race condition. // // For now just look for the specific module's hook and fire it off. $hook_media_action = $form_state['values']['registered_module'] .'_media_action'; if (function_exists($hook_media_action)) { $hook_media_action($form, $form_state, 'media_browser_submit'); } else { drupal_set_message(t('Media: No action handler found.'), 'error'); } // dsm($form_state['values']); }