'emfield.admin.inc', 'title' => 'Embedded Media Field configuration', 'description' => 'Configure Embedded Media Field: Allow content types to use various 3rd party providers, enter API keys, etc.', 'page callback' => 'drupal_get_form', 'page arguments' => array('emfield_settings'), 'access arguments' => array('administer site configuration'), ); $items['admin/content/emfield/media'] = array( 'file' => 'emfield.admin.inc', 'title' => 'Media settings', 'description' => 'Configure Embedded Media Field: Allow content types to use various 3rd party providers, enter API keys, etc.', 'page callback' => 'drupal_get_form', 'page arguments' => array('emfield_settings'), 'access arguments' => array('administer site configuration'), 'type' => MENU_DEFAULT_LOCAL_TASK, ); if (module_exists('job_queue')) { $items['admin/content/emfield/reload'] = array( 'file' => 'emfield.admin.inc', 'title' => 'Reload data', 'description' => 'Reload emfield fields in bulk', 'page callback' => 'drupal_get_form', 'page arguments' => array('emfield_settings_jobqueue'), 'access arguments' => array('administer site configuration'), 'type' => MENU_LOCAL_TASK, ); } return $items; } function emfield_field_columns($field) { module_load_include('inc', 'emfield', 'emfield.cck'); return _emfield_field_columns($field); } /** * A helper function for hook_field(), called by the various sub-modules. */ function emfield_emfield_field($op, &$node, $field, &$items, $teaser, $page, $module) { module_load_include('inc', 'emfield', 'emfield.cck'); return _emfield_emfield_field($op, $node, $field, $items, $teaser, $page, $module); } /** * Helper function to implement hook_content_is_empty. */ function emfield_emfield_content_is_empty($item, $field) { return empty($item['value']); } /** * Return a list of providers allowed for a specific field. */ function emfield_allowed_providers($field, $module) { $allowed_providers = emfield_system_list($module); // $field sometimes is the whole $field and sometimes just the $widget, // depending on where it is called from, so check which one we have. $providers = isset($field['widget']['providers']) ? $field['widget']['providers'] : (isset($field['providers']) ? $field['providers'] : array()); $allow_all = TRUE; foreach ($allowed_providers as $test) { if (!variable_get('emfield_'. $module .'_allow_'. $test->name, TRUE)) { unset($allowed_providers[$test->name]); } else { $allow_all &= empty($providers[$test->name]); } } if (!$allow_all) { foreach ($allowed_providers as $test) { if (empty($providers[$test->name])) { unset($allowed_providers[$test->name]); } } } return $allowed_providers; } /** * This returns a list of content types that are implemented by emfield. */ function emfield_implement_types($cached = TRUE) { static $types; if (!isset($types) || !$cached) { // If it's a cachable request, try to load a cached value. if ($cached && $cache = cache_get('emfield_implement_types', 'cache')) { $types = $cache->data; } else { $system_types = _content_type_info(); $content_types = $system_types['content types']; $field_types = $system_types['field types']; // The array that will store type/field information for provider import. $types = array(); $modules = array(); foreach (module_implements('emfield_info', TRUE) as $module) { $modules[$module] = $module; } // Settings per content type for the module. foreach ($content_types as $content_type => $type) { // Determine which content types implement this module. foreach ($type['fields'] as $field_type => $field) { // If this field type is defined by this module, then include it here. if (!empty($modules[$field_types[$field['type']]['module']])) { // Settings per content type per module. $module = $modules[$field_types[$field['type']]['module']]; $types[$module][$content_type][$field_type] = $field; } } } } } cache_set('emfield_implement_types', $types, 'cache', CACHE_PERMANENT); return $types; } /** * This will parse the url or embedded code pasted by the node submitter. * * @return * Either an empty array (if no match), or an array of 'provider' and 'value'. */ function emfield_parse_embed($field, $embed = '', $module) { $providers = emfield_allowed_providers($field, $module); foreach ($providers as $provider) { $success = emfield_include_invoke($module, $provider->name, 'extract', $embed, $field); // We've been given an array of regex strings, so try to find a match. if (is_array($success)) { foreach ($success as $regex) { $matches = NULL; if (preg_match($regex, $embed, $matches)) { return array('provider' => $provider->name, 'value' => $matches[1]); } } } // the invoked include module did its own parsing and found a match else if ($success) { return array('provider' => $provider->name, 'value' => $success); } } // We found no match. return array(); } /** * Extract the id from embedded code or url. */ function _emfield_field_validate_id($field, $item, $error_field, $module, $delta = 0) { // Load the provider info. $item = _emfield_field_submit_id($field, $item, $module, $error_field); // Ensure we have proper provider info. if ($item['embed'] && !$item['value']) { form_set_error($error_field, t('You have specified an invalid media URL or embed code.')); } return $item; } /** * Replace embedded code with the extracted id. this goes in the field 'value'. * This also allows you to grab directly from the URL to display the content from field 'provider'. */ function _emfield_field_submit_id($field, $item, $module) { // TODO Find the right fix for this. // When the embedded media field is used in the default value widget // when editing field settings, the $item has no value for 'provider', // causing an error message. // This is not the right way to fix this, but I don't know why that // value has been left out of the form element returned by hook_widget_settings // and this will at least keep the error message from being displayed. if (!isset($item['provider'])) { $item['provider'] = ''; } $item = array_merge($item, emfield_parse_embed($field, $item['embed'], $module)); $item['data'] = (array)emfield_include_invoke($module, $item['provider'], 'data', $field, $item); return $item; } /** * Format our field for display. This is actually called originally by each helper module * that implements hook_field_formatter, which then call this. */ function emfield_emfield_field_formatter($field, $item, $formatter, $node, $module) { // If we're coming from a preview, we need to extract our new embedded value. if (isset($node->in_preview)) { $item = emfield_parse_embed($field, $item['embed'], $module); } // If we have no value, then return an empty string. if (!isset($item['value'])) { return ''; } // Unfortunately, when we come from a view, we don't get all the widget fields. if (!$node->type) { $type = content_types($field['type_name']); $field['widget'] = $type['fields'][$field['field_name']]['widget']; } // Sometimes our data is still unserialized, again from views. if (!is_array($item['data'])) { $item['data'] = (array)unserialize($item['data']); } // The individual modules actually define the theme for the formatter. $output = ''; $output .= theme($module .'_'. $formatter, $field, $item, $formatter, $node); return $output; } /** Widgets **/ function emfield_emfield_widget_settings($op, $widget, $module) { module_load_include('inc', 'emfield', 'emfield.cck'); return _emfield_emfield_widget_settings($op, $widget, $module); } function emfield_emfield_widget(&$form, &$form_state, $field, $items, $delta = 0, $module) { module_load_include('inc', 'emfield', 'emfield.cck'); return _emfield_emfield_widget($form, $form_state, $field, $items, $delta, $module); } /** * Implementation of hook_node_operations(). */ function emfield_node_operations() { $operations = array( 'emfield_reload' => array( 'label' => t('Reload Embedded Media Data'), 'callback' => 'emfield_operations_reload', ), ); return $operations; } /** * This reloads and saves the data for all the nid's in the array. */ function emfield_operations_reload($nids = array(), $show_message = TRUE) { foreach ($nids as $nid) { if ($node = node_load($nid)) { $type = content_types($node->type); foreach ($type['fields'] as $field) { if (module_hook($field['type'], 'emfield_info')) { $message = "Reloaded %node-title's %field."; $message_variables = array('%node-title' => $node->title, '%field' => $field['type_name']); watchdog('emfield reload data', $message, $message_variables, WATCHDOG_NOTICE, l($node->title, 'node/'. $node->nid)); if ($show_message) { drupal_set_message(t($message, $message_variables)); } $items = $node->{$field['field_name']}; emfield_emfield_field('submit', $node, $field, $items, FALSE, FALSE, $field['type']); $node->{$field['field_name']} = $items; node_save($node); } } } } } /** * Implementation of hook_nodeapi(). */ function emfield_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { switch ($op) { case 'rss item': module_load_include('inc', 'emfield', 'emfield.rss'); return _emfield_nodeapi_rss($node, $op, $teaser, $page); } } /** * When an include file requires to read an xml to receive information, such as for thumbnails, * this script can be used to request the xml and return it as an array. * @TODO convert to simplexml * @param $provider * the string of the third party provider, such as 'youtube', 'flikr', or 'google' * @param $url * the url for the xml request * @param $args * an array of args to pass to the xml url * @param $cached * optional; if TRUE, the result of this xml request will be cached. good to play nice w/ * the third party folks so they don't stop providing service to your site... * @param $return_errors * optional; if TRUE and an error is encountered, a descriptive error array will be returned with elements for * code, message and stat => 'error' * @param $hash_extra * optional; The key for caching is created from the arguments. If your provider does not use arguments * (or uses the same arguments for each media item, you must pass a unique string as $hash_extra. Currently * this is only used by bliptv and archive.org * @param $serialized * optional; Most uses of this function are expecting an XML file to be returned. However some providers (Flickr) * can instead return a serialized PHP array. In this case set $serialized to TRUE. * @param $json * If TRUE, then the result will be a json encoded string. * @return * the xml results returned as an array */ function emfield_request_xml($provider, $url, $args = array(), $cached = TRUE, $return_errors = FALSE, $hash_extra = FALSE, $serialized = FALSE, $json = FALSE) { ksort($args); // Build an argument hash that we'll use for the cache id and api signing. $arghash = $provider .':'; foreach ($args as $k => $v) { $arghash .= $k . $v; } // Build the url. foreach ( $args as $k => $v) { $encoded_params[] = urlencode($k) .'='. urlencode($v); } if (!empty($encoded_params)) { $url .= '?'. implode('&', $encoded_params); } // some providers, such as bliptv, actually change the url, and not just the queries. // we provide an extra section for a unique identifier in that case if (isset($hash_extra)) { $arghash .= ':'. $hash_extra; } // if it's a cachable request, try to load a cached value if ($cached && $cache = cache_get($arghash, 'cache')) { return $cache->data; } // connect and fetch a value $result = drupal_http_request($url); if ($result->code != 200) { if ($return_errors) { return array( 'stat' => 'error', 'code' => $result->code, 'message' => 'HTTP Error: '. $result->error, ); } emfield_set_error(t("Could not connect to @provider, HTTP error @error", array('@error' => $result->code, '@provider' => $provider))); return array(); } if ($json) { $response = (array)json_decode($result->data); } else if ($serialized) { // Flickr gives us a serialized php array. Make sure it unserializes. $response = unserialize($result->data); if (!$response) { if ($return_errors) { return array( 'stat' => 'error', 'code' => '-1', 'message' => 'The response was corrupted, it could not be unserialized.', ); } emfield_set_error(t("The response from @provider was corrupted and could not be unserialized.", array('@provider' => $provider))); return array(); } } else { $parser = drupal_xml_parser_create($result->data); $vals = array(); $index = array(); xml_parse_into_struct($parser, $result->data, $vals, $index); xml_parser_free($parser); $response = array(); $response['_emfield_arghash'] = $arghash; $level = array(); $start_level = 1; foreach ($vals as $xml_elem) { if ($xml_elem['type'] == 'open') { if (array_key_exists('attributes', $xml_elem)) { list($level[$xml_elem['level']], $extra) = array_values($xml_elem['attributes']); } else { $level[$xml_elem['level']] = $xml_elem['tag']; } } if ($xml_elem['type'] == 'complete') { $php_stmt = '$response'; while ($start_level < $xml_elem['level']) { $php_stmt .= '[$level['. $start_level .']]'; $start_level++; } $php_stmt .= '[$xml_elem[\'tag\']][] = $xml_elem[\'value\'];'. $php_stmt .'[$xml_elem[\'tag\']][] = $xml_elem[\'attributes\'];'; eval($php_stmt); $start_level--; } } } if ($cached) { cache_set($arghash, $response, 'cache', time() + variable_get('emfield_cache_duration', 3600)); } return $response; } /** * Get the HTTP Header of media, for mime-type and length. * * @param $provider * The string of the third party provider, such as 'youtube', 'flikr', or 'google'. * @param $url * The url for the media. * @param $cached * Optional; if TRUE, the result of this xml request will be cached. good to play nice w/ * the third party folks so they don't stop providing service to your site... * @return * The header results, returned as an array. */ function emfield_request_header($provider, $url, $cached = TRUE, $return_errors = TRUE) { // if it's cacheable, try to load a cached value if ($cached && $cache = cache_get($url, 'cache')) { return $cache->data; } $result = _emfield_http_request_header($url); if ($result->code != 200) { if ($return_errors) { return $result; } emfield_set_error(t("Could not connect to @provider, HTTP error @error", array('@error' => $result->code, '@provider' => $provider))); return array(); } // @todo does this not want to be changing 'cache' to 'cache_emfield' or similar cache_set($url, $result, 'cache', time() + variable_get('emfield_cache_duration', 3600)); return $result; } /** * HTTP HEAD - just request the header. */ function _emfield_http_request_header($url, $retry = 4) { $result = drupal_http_request($url, array(), 'HEAD', NULL, 0); switch ($result->code) { // the intention here is to retry if the correct information isn't available // so far it's just tuned for YouTube // it's possible/probable that Moved Temporarily will give the headers required elsewhere // and it maybe best to test on the content of the header case 200: // OK case 304: // Not modified - this shouldn't happen here break; case 301: // Moved permanently case 302: // Moved temporarily case 303: // See Other <-- drupal_http_request doesn't deal with this we need this for youtube case 307: // Moved temporarily $location = $result->headers['Location']; if ($retry > 0) { $result = _emfield_http_request_header($result->headers['Location'], --$retry); $result->redirect_code = $result->code; } $result->redirect_url = $location; break; default: } return $result; } function emfield_set_error($error) { watchdog('emfield', '!error', array('!error' => $error), WATCHDOG_WARNING); } /** * Return an array of installed .inc files and/or loads them upon request. * This routine is modeled after drupal_system_listing() (and also depends on it). * It's major difference, however, is that it loads .inc files by default. * * @param $provider * Optional; name of the passed $provider to find (e.g. "youtube", "google", etc.). * @param $load * Defaults to TRUE; whether to include matching files into memory. * @return * An array of file objects optionally matching $provider. */ function emfield_system_list($module, $provider = NULL, $load = TRUE) { $override_files = module_invoke_all('emfield_providers', $module, $provider); $files = drupal_system_listing("$provider\.inc$", drupal_get_path('module', $module) ."/providers", 'name', 0); $files = array_merge($files, $override_files); ksort($files); if ($load) { foreach ($files as $file) { emfield_include_list($file); } } return $files; } /** * Maintains a list of all loaded include files. * * @param $file * Optional; a file object (from emfield_system_list()) to be included. * @return * An array of all loaded includes (without the .inc extension). */ function emfield_include_list($file = NULL) { static $list = array(); if ($file && !isset($list[$file->filename])) { include_once('./'. $file->filename); $list[$file->filename] = $file->name; } return $list; } /** * Determine whether an include implements a hook, cf. module_hook. * * @param $provider * The name of the provider file (without the .inc extension), such as 'youtube' or 'google'. * @param $hook * The name of the hook (e.g. "thumbnail", "settings", etc.). * @return * TRUE if the provider is loaded and the hook is implemented. */ function emfield_include_hook($module, $provider, $hook) { return function_exists($module .'_'. $provider .'_'. $hook); } /** * Invoke hook in a particular include. * * @param $module * the helper module * @param $provider * The name of the provider (without the .inc extension). * @param $hook * The name of the hook (e.g. "settings", "thumbnail", etc.). * @param ... * Arguments to pass to the hook implementation. * @return * The return value of the hook implementation. */ function emfield_include_invoke() { $args = func_get_args(); $module = array_shift($args); $provider = array_shift($args); $hook = array_shift($args); $function = $module .'_'. $provider .'_'. $hook; emfield_system_list($module, $provider); return emfield_include_hook($module, $provider, $hook) ? call_user_func_array($function, $args) : NULL; } /** * Custom filter for provider NOT NULL. */ function emfield_views_handler_filter_is_not_null($op, $filter, $filterinfo, &$query) { if ($op == 'handler') { $query->ensure_table($filterinfo['table']); if ($filter['value']) { $qs = "%s.%s <> '' AND %s.%s IS NOT NULL"; } else { $qs = "%s.%s = '' OR %s.%s IS NULL"; } $query->add_where($qs, $filterinfo['table'], $filterinfo['content_db_info']['columns']['provider']['column'], $filterinfo['table'], $filterinfo['content_db_info']['columns']['provider']['column']); } } /** * Create a list of providers. */ function emfield_views_handler_filter_provider_list($op) { $providers = array(); foreach (emfield_system_list('emvideo') as $provider => $info) { $providers[$provider] = $info->name; } return $providers; } /** * Views handler for the provider list filter. */ function emfield_views_handler_filter_provider($op, $filter, $filterinfo, &$query) { if ($op == 'handler') { $query->ensure_table($filterinfo['table']); if ($filter['operator'] == 'OR') { foreach ($filter['value'] as $provider) { $items[] = "%s.%s = '$provider'"; $where[] = $filterinfo['table']; $where[] = $filterinfo['content_db_info']['columns']['provider']['column']; } $qs = implode(' OR ', $items); } else { foreach ($filter['value'] as $provider) { $items[] = "%s.%s <> '$provider'"; $where[] = $filterinfo['table']; $where[] = $filterinfo['content_db_info']['columns']['provider']['column']; } $qs = implode(' AND ', $items); } $query->add_where($qs, $where); } } /** * Handle the provider argument. This is called from a wrapper that includes the module. */ function _emfield_handler_arg_provider($op, &$query, $argtype, $arg = '', $module = '') { if ($op == 'filter') { $field_name = drupal_substr($argtype['type'], 10); } else { $field_name = drupal_substr($argtype, 10); } $field = content_fields($field_name); $db_info = content_database_info($field); // TODO: This hook no longer exists in D6. $main_column = $db_info['columns']['provider']; // The table name used here is the Views alias for the table, not the actual // table name. $table = 'node_data_'. $field['field_name']; switch ($op) { case 'summary': $query->ensure_table($table); $query->add_field($main_column['column'], $table); $query->add_groupby($table .'.'. $main_column['column']); return array('field' => $table .'.'. $main_column['column']); case 'sort': $query->ensure_table($table); $query->add_orderby($table, $main_column['column'], $argtype); break; case 'filter': $query->ensure_table($table); $where = db_escape_string($arg); $query->add_where($table .'.'. $main_column['column'] ." = '%s'", $where); break; case 'link': $provider = emfield_system_list($module, $query->$main_column['column']); $info = emfield_include_invoke($module, $provider[$query]->name, 'info'); $title = $info['name']; return l($title, $arg .'/'. $query->$main_column['column']); case 'title': $provider = emfield_system_list($module, $query); $info = emfield_include_invoke($module, $provider[$query]->name, 'info'); $title = $info['name']; return $title ? $title : check_plain($query); } } function emfield_provider_themes($module, $provider = NULL) { $themes = array(); if ($provider && $subthemes = emfield_include_invoke($module, $provider, 'subtheme')) { $themes = $subthemes; } $providers = emfield_system_list($module); foreach ($providers as $provider) { if ($subthemes = emfield_include_invoke($module, $provider->name, 'emfield_subtheme')) { $themes += $subthemes; } } return $themes; } function emfield_provider_menus($module, $provider = NULL) { $menus = array(); if ($provider && $submenus = emfield_include_invoke($module, $provider, 'submenu')) { $menus = $submenus; } $providers = module_invoke('emfield', 'system_list', $module); foreach ($providers as $provider) { if ($submenus = emfield_include_invoke($module, $provider->name, 'emfield_submenu')) { $menus += $submenus; } } return $menus; }