· 8 years ago · Jul 06, 2018, 04:30 PM
1<?php
2// $Id: emfield.module,v 1.12.4.32 2009/05/21 00:53:50 aaron Exp $
3
4/**
5 * @file
6 * Embedded Media Field is a CCK-based framework for 3rd party media files.
7 */
8
9/**
10 * Implementation of hook_menu().
11 */
12function emfield_menu() {
13 $items['admin/content/emfield'] = array(
14 'file' => 'emfield.admin.inc',
15 'title' => 'Embedded Media Field configuration',
16 'description' => 'Configure Embedded Media Field: Allow content types to use various 3rd party providers, enter API keys, etc.',
17 'page callback' => 'drupal_get_form',
18 'page arguments' => array('emfield_settings'),
19 'access arguments' => array('administer site configuration'),
20 );
21 $items['admin/content/emfield/media'] = array(
22 'file' => 'emfield.admin.inc',
23 'title' => 'Media settings',
24 'description' => 'Configure Embedded Media Field: Allow content types to use various 3rd party providers, enter API keys, etc.',
25 'page callback' => 'drupal_get_form',
26 'page arguments' => array('emfield_settings'),
27 'access arguments' => array('administer site configuration'),
28 'type' => MENU_DEFAULT_LOCAL_TASK,
29 );
30
31 if (module_exists('job_queue')) {
32 $items['admin/content/emfield/reload'] = array(
33 'file' => 'emfield.admin.inc',
34 'title' => 'Reload data',
35 'description' => 'Reload emfield fields in bulk',
36 'page callback' => 'drupal_get_form',
37 'page arguments' => array('emfield_settings_jobqueue'),
38 'access arguments' => array('administer site configuration'),
39 'type' => MENU_LOCAL_TASK,
40 );
41 }
42
43 return $items;
44}
45
46function emfield_field_columns($field) {
47 module_load_include('inc', 'emfield', 'emfield.cck');
48
49 return _emfield_field_columns($field);
50}
51
52/**
53 * A helper function for hook_field(), called by the various sub-modules.
54 */
55function emfield_emfield_field($op, &$node, $field, &$items, $teaser, $page, $module) {
56 module_load_include('inc', 'emfield', 'emfield.cck');
57 return _emfield_emfield_field($op, $node, $field, $items, $teaser, $page, $module);
58}
59
60/**
61 * Helper function to implement hook_content_is_empty.
62 */
63function emfield_emfield_content_is_empty($item, $field) {
64 return empty($item['value']);
65}
66
67/**
68 * Return a list of providers allowed for a specific field.
69 */
70function emfield_allowed_providers($field, $module) {
71 $allowed_providers = emfield_system_list($module);
72
73 // $field sometimes is the whole $field and sometimes just the $widget,
74 // depending on where it is called from, so check which one we have.
75 $providers = isset($field['widget']['providers']) ? $field['widget']['providers'] : (isset($field['providers']) ? $field['providers'] : array());
76 $allow_all = TRUE;
77 foreach ($allowed_providers as $test) {
78 if (!variable_get('emfield_'. $module .'_allow_'. $test->name, TRUE)) {
79 unset($allowed_providers[$test->name]);
80 }
81 else {
82 $allow_all &= empty($providers[$test->name]);
83 }
84 }
85 if (!$allow_all) {
86 foreach ($allowed_providers as $test) {
87 if (empty($providers[$test->name])) {
88 unset($allowed_providers[$test->name]);
89 }
90 }
91 }
92 return $allowed_providers;
93}
94
95/**
96 * This returns a list of content types that are implemented by emfield.
97 */
98function emfield_implement_types($cached = TRUE) {
99 static $types;
100
101 if (!isset($types) || !$cached) {
102 // If it's a cachable request, try to load a cached value.
103 if ($cached && $cache = cache_get('emfield_implement_types', 'cache')) {
104 $types = $cache->data;
105 }
106 else {
107 $system_types = _content_type_info();
108 $content_types = $system_types['content types'];
109 $field_types = $system_types['field types'];
110
111 // The array that will store type/field information for provider import.
112 $types = array();
113 $modules = array();
114
115 foreach (module_implements('emfield_info', TRUE) as $module) {
116 $modules[$module] = $module;
117 }
118
119 // Settings per content type for the module.
120 foreach ($content_types as $content_type => $type) {
121 // Determine which content types implement this module.
122 foreach ($type['fields'] as $field_type => $field) {
123
124 // If this field type is defined by this module, then include it here.
125 if (!empty($modules[$field_types[$field['type']]['module']])) {
126 // Settings per content type per module.
127 $module = $modules[$field_types[$field['type']]['module']];
128 $types[$module][$content_type][$field_type] = $field;
129 }
130 }
131 }
132 }
133 }
134
135 cache_set('emfield_implement_types', $types, 'cache', CACHE_PERMANENT);
136
137 return $types;
138}
139
140/**
141 * This will parse the url or embedded code pasted by the node submitter.
142 *
143 * @return
144 * Either an empty array (if no match), or an array of 'provider' and 'value'.
145 */
146function emfield_parse_embed($field, $embed = '', $module) {
147 $providers = emfield_allowed_providers($field, $module);
148 foreach ($providers as $provider) {
149 $success = emfield_include_invoke($module, $provider->name, 'extract', $embed, $field);
150
151 // We've been given an array of regex strings, so try to find a match.
152 if (is_array($success)) {
153 foreach ($success as $regex) {
154 $matches = NULL;
155 if (preg_match($regex, $embed, $matches)) {
156 return array('provider' => $provider->name, 'value' => $matches[1]);
157 }
158 }
159 }
160 // the invoked include module did its own parsing and found a match
161 else if ($success) {
162 return array('provider' => $provider->name, 'value' => $success);
163 }
164 }
165
166 // We found no match.
167 return array();
168}
169
170/**
171 * Extract the id from embedded code or url.
172 */
173function _emfield_field_validate_id($field, $item, $error_field, $module, $delta = 0) {
174 // Load the provider info.
175 $item = _emfield_field_submit_id($field, $item, $module, $error_field);
176 // Ensure we have proper provider info.
177 if ($item['embed'] && !$item['value']) {
178 form_set_error($error_field, t('You have specified an invalid media URL or embed code.'));
179 }
180
181 return $item;
182}
183
184/**
185 * Replace embedded code with the extracted id. this goes in the field 'value'.
186 * This also allows you to grab directly from the URL to display the content from field 'provider'.
187 */
188function _emfield_field_submit_id($field, $item, $module) {
189
190 // TODO Find the right fix for this.
191 // When the embedded media field is used in the default value widget
192 // when editing field settings, the $item has no value for 'provider',
193 // causing an error message.
194 // This is not the right way to fix this, but I don't know why that
195 // value has been left out of the form element returned by hook_widget_settings
196 // and this will at least keep the error message from being displayed.
197
198 if (!isset($item['provider'])) {
199 $item['provider'] = '';
200 }
201 $item = array_merge($item, emfield_parse_embed($field, $item['embed'], $module));
202 $item['data'] = (array)emfield_include_invoke($module, $item['provider'], 'data', $field, $item);
203
204 return $item;
205}
206
207/**
208 * Format our field for display. This is actually called originally by each helper module
209 * that implements hook_field_formatter, which then call this.
210 */
211function emfield_emfield_field_formatter($field, $item, $formatter, $node, $module) {
212 // If we're coming from a preview, we need to extract our new embedded value.
213 if (isset($node->in_preview)) {
214 $item = emfield_parse_embed($field, $item['embed'], $module);
215 }
216
217 // If we have no value, then return an empty string.
218 if (!isset($item['value'])) {
219 return '';
220 }
221
222 // Unfortunately, when we come from a view, we don't get all the widget fields.
223 if (!$node->type) {
224 $type = content_types($field['type_name']);
225 $field['widget'] = $type['fields'][$field['field_name']]['widget'];
226 }
227
228 // Sometimes our data is still unserialized, again from views.
229 if (!is_array($item['data'])) {
230 $item['data'] = (array)unserialize($item['data']);
231 }
232
233 // The individual modules actually define the theme for the formatter.
234 $output = '';
235 $output .= theme($module .'_'. $formatter, $field, $item, $formatter, $node);
236
237 return $output;
238}
239
240/** Widgets **/
241function emfield_emfield_widget_settings($op, $widget, $module) {
242 module_load_include('inc', 'emfield', 'emfield.cck');
243 return _emfield_emfield_widget_settings($op, $widget, $module);
244}
245
246function emfield_emfield_widget(&$form, &$form_state, $field, $items, $delta = 0, $module) {
247 module_load_include('inc', 'emfield', 'emfield.cck');
248
249 return _emfield_emfield_widget($form, $form_state, $field, $items, $delta, $module);
250}
251
252/**
253 * Implementation of hook_node_operations().
254 */
255function emfield_node_operations() {
256 $operations = array(
257 'emfield_reload' => array(
258 'label' => t('Reload Embedded Media Data'),
259 'callback' => 'emfield_operations_reload',
260 ),
261 );
262
263 return $operations;
264}
265
266/**
267 * This reloads and saves the data for all the nid's in the array.
268 */
269function emfield_operations_reload($nids = array(), $show_message = TRUE) {
270 foreach ($nids as $nid) {
271 if ($node = node_load($nid)) {
272 $type = content_types($node->type);
273 foreach ($type['fields'] as $field) {
274 if (module_hook($field['type'], 'emfield_info')) {
275 $message = "Reloaded %node-title's %field.";
276 $message_variables = array('%node-title' => $node->title, '%field' => $field['type_name']);
277 watchdog('emfield reload data', $message, $message_variables, WATCHDOG_NOTICE, l($node->title, 'node/'. $node->nid));
278 if ($show_message) {
279 drupal_set_message(t($message, $message_variables));
280 }
281 $items = $node->{$field['field_name']};
282 emfield_emfield_field('submit', $node, $field, $items, FALSE, FALSE, $field['type']);
283 $node->{$field['field_name']} = $items;
284 node_save($node);
285 }
286 }
287 }
288 }
289}
290
291/**
292 * Implementation of hook_nodeapi().
293 */
294function emfield_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) {
295 switch ($op) {
296 case 'rss item':
297 module_load_include('inc', 'emfield', 'emfield.rss');
298 return _emfield_nodeapi_rss($node, $op, $teaser, $page);
299 }
300}
301
302/**
303 * When an include file requires to read an xml to receive information, such as for thumbnails,
304 * this script can be used to request the xml and return it as an array.
305 * @TODO convert to simplexml
306 * @param $provider
307 * the string of the third party provider, such as 'youtube', 'flikr', or 'google'
308 * @param $url
309 * the url for the xml request
310 * @param $args
311 * an array of args to pass to the xml url
312 * @param $cached
313 * optional; if TRUE, the result of this xml request will be cached. good to play nice w/
314 * the third party folks so they don't stop providing service to your site...
315 * @param $return_errors
316 * optional; if TRUE and an error is encountered, a descriptive error array will be returned with elements for
317 * code, message and stat => 'error'
318 * @param $hash_extra
319 * optional; The key for caching is created from the arguments. If your provider does not use arguments
320 * (or uses the same arguments for each media item, you must pass a unique string as $hash_extra. Currently
321 * this is only used by bliptv and archive.org
322 * @param $serialized
323 * optional; Most uses of this function are expecting an XML file to be returned. However some providers (Flickr)
324 * can instead return a serialized PHP array. In this case set $serialized to TRUE.
325 * @param $json
326 * If TRUE, then the result will be a json encoded string.
327 * @return
328 * the xml results returned as an array
329 */
330function emfield_request_xml($provider, $url, $args = array(), $cached = TRUE, $return_errors = FALSE, $hash_extra = FALSE, $serialized = FALSE, $json = FALSE) {
331 ksort($args);
332
333 // Build an argument hash that we'll use for the cache id and api signing.
334 $arghash = $provider .':';
335 foreach ($args as $k => $v) {
336 $arghash .= $k . $v;
337 }
338
339 // Build the url.
340 foreach ( $args as $k => $v) {
341 $encoded_params[] = urlencode($k) .'='. urlencode($v);
342 }
343 if (!empty($encoded_params)) {
344 $url .= '?'. implode('&', $encoded_params);
345 }
346
347 // some providers, such as bliptv, actually change the url, and not just the queries.
348 // we provide an extra section for a unique identifier in that case
349 if (isset($hash_extra)) {
350 $arghash .= ':'. $hash_extra;
351 }
352
353 // if it's a cachable request, try to load a cached value
354 if ($cached && $cache = cache_get($arghash, 'cache')) {
355 return $cache->data;
356 }
357
358 // connect and fetch a value
359 $result = drupal_http_request($url);
360
361 if ($result->code != 200) {
362 if ($return_errors) {
363 return array(
364 'stat' => 'error',
365 'code' => $result->code,
366 'message' => 'HTTP Error: '. $result->error,
367 );
368 }
369 emfield_set_error(t("Could not connect to @provider, HTTP error @error", array('@error' => $result->code, '@provider' => $provider)));
370
371 return array();
372 }
373
374 if ($json) {
375 $response = (array)json_decode($result->data);
376 }
377 else if ($serialized) {
378 // Flickr gives us a serialized php array. Make sure it unserializes.
379 $response = unserialize($result->data);
380 if (!$response) {
381 if ($return_errors) {
382 return array(
383 'stat' => 'error',
384 'code' => '-1',
385 'message' => 'The response was corrupted, it could not be unserialized.',
386 );
387 }
388
389 emfield_set_error(t("The response from @provider was corrupted and could not be unserialized.", array('@provider' => $provider)));
390
391 return array();
392 }
393 }
394 else {
395 $parser = drupal_xml_parser_create($result->data);
396 $vals = array();
397 $index = array();
398 xml_parse_into_struct($parser, $result->data, $vals, $index);
399 xml_parser_free($parser);
400
401 $response = array();
402 $response['_emfield_arghash'] = $arghash;
403 $level = array();
404 $start_level = 1;
405 foreach ($vals as $xml_elem) {
406 if ($xml_elem['type'] == 'open') {
407 if (array_key_exists('attributes', $xml_elem)) {
408 list($level[$xml_elem['level']], $extra) = array_values($xml_elem['attributes']);
409 }
410 else {
411 $level[$xml_elem['level']] = $xml_elem['tag'];
412 }
413 }
414 if ($xml_elem['type'] == 'complete') {
415 $php_stmt = '$response';
416 while ($start_level < $xml_elem['level']) {
417 $php_stmt .= '[$level['. $start_level .']]';
418 $start_level++;
419 }
420 $php_stmt .= '[$xml_elem[\'tag\']][] = $xml_elem[\'value\'];'. $php_stmt .'[$xml_elem[\'tag\']][] = $xml_elem[\'attributes\'];';
421 eval($php_stmt);
422 $start_level--;
423 }
424 }
425 }
426
427 if ($cached) {
428 cache_set($arghash, $response, 'cache', time() + variable_get('emfield_cache_duration', 3600));
429 }
430
431 return $response;
432}
433
434/**
435 * Get the HTTP Header of media, for mime-type and length.
436 *
437 * @param $provider
438 * The string of the third party provider, such as 'youtube', 'flikr', or 'google'.
439 * @param $url
440 * The url for the media.
441 * @param $cached
442 * Optional; if TRUE, the result of this xml request will be cached. good to play nice w/
443 * the third party folks so they don't stop providing service to your site...
444 * @return
445 * The header results, returned as an array.
446 */
447function emfield_request_header($provider, $url, $cached = TRUE, $return_errors = TRUE) {
448 // if it's cacheable, try to load a cached value
449 if ($cached && $cache = cache_get($url, 'cache')) {
450 return $cache->data;
451 }
452
453 $result = _emfield_http_request_header($url);
454
455 if ($result->code != 200) {
456 if ($return_errors) {
457 return $result;
458 }
459
460 emfield_set_error(t("Could not connect to @provider, HTTP error @error", array('@error' => $result->code, '@provider' => $provider)));
461
462 return array();
463 }
464
465 // @todo does this not want to be changing 'cache' to 'cache_emfield' or similar
466 cache_set($url, $result, 'cache', time() + variable_get('emfield_cache_duration', 3600));
467
468 return $result;
469}
470
471/**
472 * HTTP HEAD - just request the header.
473 */
474function _emfield_http_request_header($url, $retry = 4) {
475 $result = drupal_http_request($url, array(), 'HEAD', NULL, 0);
476
477 switch ($result->code) {
478 // the intention here is to retry if the correct information isn't available
479 // so far it's just tuned for YouTube
480 // it's possible/probable that Moved Temporarily will give the headers required elsewhere
481 // and it maybe best to test on the content of the header
482 case 200: // OK
483 case 304: // Not modified - this shouldn't happen here
484 break;
485 case 301: // Moved permanently
486 case 302: // Moved temporarily
487 case 303: // See Other <-- drupal_http_request doesn't deal with this we need this for youtube
488 case 307: // Moved temporarily
489 $location = $result->headers['Location'];
490 if ($retry > 0) {
491 $result = _emfield_http_request_header($result->headers['Location'], --$retry);
492 $result->redirect_code = $result->code;
493 }
494 $result->redirect_url = $location;
495
496 break;
497 default:
498 }
499
500 return $result;
501}
502
503function emfield_set_error($error) {
504 watchdog('emfield', '!error', array('!error' => $error), WATCHDOG_WARNING);
505}
506
507/**
508 * Return an array of installed .inc files and/or loads them upon request.
509 * This routine is modeled after drupal_system_listing() (and also depends on it).
510 * It's major difference, however, is that it loads .inc files by default.
511 *
512 * @param $provider
513 * Optional; name of the passed $provider to find (e.g. "youtube", "google", etc.).
514 * @param $load
515 * Defaults to TRUE; whether to include matching files into memory.
516 * @return
517 * An array of file objects optionally matching $provider.
518 */
519function emfield_system_list($module, $provider = NULL, $load = TRUE) {
520 $override_files = module_invoke_all('emfield_providers', $module, $provider);
521 $files = drupal_system_listing("$provider\.inc$", drupal_get_path('module', $module) ."/providers", 'name', 0);
522 $files = array_merge($files, $override_files);
523
524 ksort($files);
525
526 if ($load) {
527 foreach ($files as $file) {
528 emfield_include_list($file);
529 }
530 }
531
532 return $files;
533}
534
535/**
536 * Maintains a list of all loaded include files.
537 *
538 * @param $file
539 * Optional; a file object (from emfield_system_list()) to be included.
540 * @return
541 * An array of all loaded includes (without the .inc extension).
542 */
543function emfield_include_list($file = NULL) {
544 static $list = array();
545
546 if ($file && !isset($list[$file->filename])) {
547 include_once('./'. $file->filename);
548 $list[$file->filename] = $file->name;
549 }
550
551 return $list;
552}
553
554/**
555 * Determine whether an include implements a hook, cf. module_hook.
556 *
557 * @param $provider
558 * The name of the provider file (without the .inc extension), such as 'youtube' or 'google'.
559 * @param $hook
560 * The name of the hook (e.g. "thumbnail", "settings", etc.).
561 * @return
562 * TRUE if the provider is loaded and the hook is implemented.
563 */
564function emfield_include_hook($module, $provider, $hook) {
565 return function_exists($module .'_'. $provider .'_'. $hook);
566}
567
568/**
569 * Invoke hook in a particular include.
570 *
571 * @param $module
572 * the helper module
573 * @param $provider
574 * The name of the provider (without the .inc extension).
575 * @param $hook
576 * The name of the hook (e.g. "settings", "thumbnail", etc.).
577 * @param ...
578 * Arguments to pass to the hook implementation.
579 * @return
580 * The return value of the hook implementation.
581 */
582function emfield_include_invoke() {
583 $args = func_get_args();
584 $module = array_shift($args);
585 $provider = array_shift($args);
586 $hook = array_shift($args);
587 $function = $module .'_'. $provider .'_'. $hook;
588 emfield_system_list($module, $provider);
589 return emfield_include_hook($module, $provider, $hook) ? call_user_func_array($function, $args) : NULL;
590}
591
592/**
593 * Custom filter for provider NOT NULL.
594 */
595function emfield_views_handler_filter_is_not_null($op, $filter, $filterinfo, &$query) {
596 if ($op == 'handler') {
597 $query->ensure_table($filterinfo['table']);
598 if ($filter['value']) {
599 $qs = "%s.%s <> '' AND %s.%s IS NOT NULL";
600 }
601 else {
602 $qs = "%s.%s = '' OR %s.%s IS NULL";
603 }
604 $query->add_where($qs, $filterinfo['table'], $filterinfo['content_db_info']['columns']['provider']['column'], $filterinfo['table'], $filterinfo['content_db_info']['columns']['provider']['column']);
605 }
606}
607
608/**
609 * Create a list of providers.
610 */
611function emfield_views_handler_filter_provider_list($op) {
612 $providers = array();
613 foreach (emfield_system_list('emvideo') as $provider => $info) {
614 $providers[$provider] = $info->name;
615 }
616 return $providers;
617}
618
619/**
620 * Views handler for the provider list filter.
621 */
622function emfield_views_handler_filter_provider($op, $filter, $filterinfo, &$query) {
623 if ($op == 'handler') {
624 $query->ensure_table($filterinfo['table']);
625 if ($filter['operator'] == 'OR') {
626 foreach ($filter['value'] as $provider) {
627 $items[] = "%s.%s = '$provider'";
628 $where[] = $filterinfo['table'];
629 $where[] = $filterinfo['content_db_info']['columns']['provider']['column'];
630 }
631 $qs = implode(' OR ', $items);
632 }
633 else {
634 foreach ($filter['value'] as $provider) {
635 $items[] = "%s.%s <> '$provider'";
636 $where[] = $filterinfo['table'];
637 $where[] = $filterinfo['content_db_info']['columns']['provider']['column'];
638 }
639 $qs = implode(' AND ', $items);
640 }
641 $query->add_where($qs, $where);
642 }
643}
644
645/**
646 * Handle the provider argument. This is called from a wrapper that includes the module.
647 */
648function _emfield_handler_arg_provider($op, &$query, $argtype, $arg = '', $module = '') {
649 if ($op == 'filter') {
650 $field_name = drupal_substr($argtype['type'], 10);
651 }
652 else {
653 $field_name = drupal_substr($argtype, 10);
654 }
655
656 $field = content_fields($field_name);
657 $db_info = content_database_info($field); // TODO: This hook no longer exists in D6.
658 $main_column = $db_info['columns']['provider'];
659
660 // The table name used here is the Views alias for the table, not the actual
661 // table name.
662 $table = 'node_data_'. $field['field_name'];
663
664 switch ($op) {
665 case 'summary':
666 $query->ensure_table($table);
667 $query->add_field($main_column['column'], $table);
668 $query->add_groupby($table .'.'. $main_column['column']);
669 return array('field' => $table .'.'. $main_column['column']);
670 case 'sort':
671 $query->ensure_table($table);
672 $query->add_orderby($table, $main_column['column'], $argtype);
673 break;
674 case 'filter':
675 $query->ensure_table($table);
676 $where = db_escape_string($arg);
677 $query->add_where($table .'.'. $main_column['column'] ." = '%s'", $where);
678 break;
679 case 'link':
680 $provider = emfield_system_list($module, $query->$main_column['column']);
681 $info = emfield_include_invoke($module, $provider[$query]->name, 'info');
682 $title = $info['name'];
683 return l($title, $arg .'/'. $query->$main_column['column']);
684 case 'title':
685 $provider = emfield_system_list($module, $query);
686 $info = emfield_include_invoke($module, $provider[$query]->name, 'info');
687 $title = $info['name'];
688 return $title ? $title : check_plain($query);
689 }
690}
691
692function emfield_provider_themes($module, $provider = NULL) {
693 $themes = array();
694
695 if ($provider && $subthemes = emfield_include_invoke($module, $provider, 'subtheme')) {
696 $themes = $subthemes;
697 }
698 $providers = emfield_system_list($module);
699
700 foreach ($providers as $provider) {
701 if ($subthemes = emfield_include_invoke($module, $provider->name, 'emfield_subtheme')) {
702 $themes += $subthemes;
703 }
704 }
705
706 return $themes;
707}
708
709function emfield_provider_menus($module, $provider = NULL) {
710 $menus = array();
711
712 if ($provider && $submenus = emfield_include_invoke($module, $provider, 'submenu')) {
713 $menus = $submenus;
714 }
715 $providers = module_invoke('emfield', 'system_list', $module);
716
717 foreach ($providers as $provider) {
718 if ($submenus = emfield_include_invoke($module, $provider->name, 'emfield_submenu')) {
719 $menus += $submenus;
720 }
721 }
722
723 return $menus;
724}