video_filter.module 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. <?php
  2. /**
  3. * @file
  4. * Video filter is a highly flexible and easy extendable filter module to embed
  5. * any type of video in your site using a simple tag.
  6. */
  7. module_load_include('inc', 'video_filter', 'video_filter.codecs');
  8. /**
  9. * Implements hook_filter_info().
  10. */
  11. function video_filter_filter_info() {
  12. $filters = array();
  13. $filters['video_filter'] = array(
  14. 'title' => t('Video Filter'),
  15. 'description' => t('Substitutes [video:URL] with embedded HTML.'),
  16. 'process callback' => '_video_filter_process',
  17. 'settings callback' => '_video_filter_settings',
  18. 'default settings' => array(
  19. 'video_filter_width' => '400',
  20. 'video_filter_height' => '400',
  21. 'video_filter_autoplay' => 1,
  22. 'video_filter_related' => 1,
  23. 'video_filter_html5' => 1,
  24. ),
  25. 'tips callback' => '_video_filter_tips',
  26. // See http://drupal.org/node/1061244.
  27. 'weight' => -1,
  28. );
  29. return $filters;
  30. }
  31. function _video_filter_settings($form, &$form_state, $filter, $format, $defaults, $filters) {
  32. $settings['video_filter_width'] = array(
  33. '#type' => 'textfield',
  34. '#title' => t('Default width setting'),
  35. '#default_value' => isset($filter->settings['video_filter_width']) ? $filter->settings['video_filter_width'] : $defaults['video_filter_width'],
  36. '#maxlength' => 4,
  37. );
  38. $settings['video_filter_height'] = array(
  39. '#type' => 'textfield',
  40. '#title' => t('Default height setting'),
  41. '#default_value' => isset($filter->settings['video_filter_height']) ? $filter->settings['video_filter_height'] : $defaults['video_filter_height'],
  42. '#maxlength' => 4,
  43. );
  44. $settings['video_filter_autoplay'] = array(
  45. '#type' => 'radios',
  46. '#title' => t('Default autoplay setting'),
  47. '#description' => t('Not all video formats support this setting.'),
  48. '#default_value' => isset($filter->settings['video_filter_autoplay']) ? $filter->settings['video_filter_autoplay'] : $defaults['video_filter_autoplay'],
  49. '#options' => array(
  50. 0 => t('No'),
  51. 1 => t('Yes'),
  52. ),
  53. );
  54. $settings['video_filter_related'] = array(
  55. '#type' => 'radios',
  56. '#title' => t('Related videos setting'),
  57. '#description' => t('Show "related videos"? Not all video formats support this setting.'),
  58. '#default_value' => isset($filter->settings['video_filter_related']) ? $filter->settings['video_filter_related'] : $defaults['video_filter_related'],
  59. '#options' => array(
  60. 0 => t('No'),
  61. 1 => t('Yes'),
  62. ),
  63. );
  64. $settings['video_filter_html5'] = array(
  65. '#type' => 'radios',
  66. '#title' => t('Use HTML5'),
  67. '#description' => t('Use HTML5 if the codec provides it. Makes your videos more device agnostic.'),
  68. '#default_value' => isset($filter->settings['video_filter_html5']) ? $filter->settings['video_filter_html5'] : $defaults['video_filter_html5'],
  69. '#options' => array(
  70. 0 => t('No'),
  71. 1 => t('Yes'),
  72. ),
  73. );
  74. return $settings;
  75. }
  76. function _video_filter_tips($filter, $format, $long = FALSE) {
  77. if ($long) {
  78. $codecs = video_filter_get_codec_info();
  79. $supported = array();
  80. $instructions = array();
  81. foreach ($codecs as $codec) {
  82. $supported[] = $codec['name'];
  83. $instructions[] = isset($codec['instructions']) ? '<li>' . $codec['name'] . ':<br/>' . $codec['instructions'] . '</li>' : '';
  84. }
  85. return t('
  86. <p><strong>Video Filter</strong></p>
  87. <p>You may insert videos from popular video sites by using a simple tag <code>[video:URL]</code>.</p>
  88. <p>Examples:</p>
  89. <ul>
  90. <li>Single video:<br /><code>[video:http://www.youtube.com/watch?v=uN1qUeId]</code></li>
  91. <li>Random video out of multiple:<br /><code>[video:http://www.youtube.com/watch?v=uN1qUeId1,http://www.youtube.com/watch?v=uN1qUeId2]</code></li>
  92. <li>Override default autoplay setting: <code>[video:http://www.youtube.com/watch?v=uN1qUeId autoplay:1]</code></li>
  93. <li>Override default width and height:<br /><code>[video:http://www.youtube.com/watch?v=uN1qUeId width:X height:Y]</code></li>
  94. <li>Override default aspect ratio:<br /><code>[video:http://www.youtube.com/watch?v=uN1qUeId ratio:4/3]</code></li>
  95. <li>Align the video:<br /><code>[video:http://www.youtube.com/watch?v=uN1qUeId align:right]</code></li>
  96. </ul>
  97. <p>Supported sites: @codecs.</p>
  98. <p>Special instructions:</p>
  99. <small>Some codecs need special input. You\'ll find those instructions here.</small>
  100. <ul>!instructions</ul>', array(
  101. '@codecs' => implode(', ', $supported),
  102. '!instructions' => implode('', $instructions),
  103. )
  104. );
  105. }
  106. else {
  107. return t('You may insert videos with [video:URL]');
  108. }
  109. }
  110. function _video_filter_process($text, $filter, $format, $langcode, $cache, $cache_id) {
  111. if (preg_match_all('/\[video(\:(.+))?( .+)?\]/isU', $text, $matches_code)) {
  112. foreach ($matches_code[0] as $ci => $code) {
  113. $video = array(
  114. 'source' => $matches_code[2][$ci],
  115. 'autoplay' => $filter->settings['video_filter_autoplay'],
  116. 'related' => $filter->settings['video_filter_related'],
  117. );
  118. // Pick random out of multiple sources separated by comma (,).
  119. if (strstr($video['source'], ',')) {
  120. $sources = explode(',', $video['source']);
  121. $random = array_rand($sources, 1);
  122. $video['source'] = $sources[$random];
  123. }
  124. // Load all codecs.
  125. $codecs = video_filter_get_codec_info();
  126. // Find codec.
  127. foreach ($codecs as $codec_name => $codec) {
  128. if (!is_array($codec['regexp'])) {
  129. $codec['regexp'] = array($codec['regexp']);
  130. }
  131. // Try different regular expressions.
  132. foreach ($codec['regexp'] as $delta => $regexp) {
  133. if (preg_match($regexp, $video['source'], $matches)) {
  134. $video['codec'] = $codec;
  135. $video['codec']['delta'] = $delta;
  136. $video['codec']['matches'] = $matches;
  137. // Used in theme function:
  138. $video['codec']['codec_name'] = $codec_name;
  139. break 2;
  140. }
  141. }
  142. }
  143. // Codec found.
  144. if (isset($video['codec'])) {
  145. // Override default attributes.
  146. if ($matches_code[3][$ci] && preg_match_all('/\s+([a-zA-Z_]+)\:(\s+)?([0-9a-zA-Z\/]+)/i', $matches_code[3][$ci], $matches_attributes)) {
  147. foreach ($matches_attributes[0] as $ai => $attribute) {
  148. $video[$matches_attributes[1][$ai]] = $matches_attributes[3][$ai];
  149. }
  150. }
  151. // Use configured ratio if present, otherwise use that from the codec,
  152. // if set. Fall back to 1.
  153. $ratio = 1;
  154. if (isset($video['ratio']) && preg_match('/(\d+)\/(\d+)/', $video['ratio'], $tratio)) {
  155. // Validate given ratio parameter.
  156. $ratio = $tratio[1] / $tratio[2];
  157. }
  158. elseif (isset($video['codec']['ratio'])) {
  159. $ratio = $video['codec']['ratio'];
  160. }
  161. // Sets video width & height after any user input has been parsed.
  162. // First, check if user has set a width.
  163. if (isset($video['width']) && !isset($video['height'])) {
  164. $video['height'] = $filter->settings['video_filter_height'];
  165. }
  166. // Else, if user has set height.
  167. elseif (isset($video['height']) && !isset($video['width'])) {
  168. $video['width'] = $video['height'] * $ratio;
  169. }
  170. // Maybe both?
  171. elseif (isset($video['height']) && isset($video['width'])) {
  172. $video['width'] = $video['width'];
  173. $video['height'] = $video['height'];
  174. }
  175. // Fall back to defaults.
  176. elseif (!isset($video['height']) && !isset($video['width'])) {
  177. $video['width'] = $filter->settings['video_filter_width'] != '' ? $filter->settings['video_filter_width'] : 400;
  178. $video['height'] = $filter->settings['video_filter_height'] != '' ? $filter->settings['video_filter_height'] : 400;
  179. }
  180. // Default value for control bar height.
  181. $control_bar_height = 0;
  182. if (isset($video['control_bar_height'])) {
  183. // Respect control_bar_height option if present.
  184. $control_bar_height = $video['control_bar_height'];
  185. }
  186. elseif (isset($video['codec']['control_bar_height'])) {
  187. // Respect setting provided by codec otherwise.
  188. $control_bar_height = $video['codec']['control_bar_height'];
  189. }
  190. // Resize to fit within width and height repecting aspect ratio.
  191. if ($ratio) {
  192. $scale_factor = min(array(
  193. ($video['height'] - $control_bar_height),
  194. $video['width'] / $ratio,
  195. ));
  196. $video['height'] = round($scale_factor + $control_bar_height);
  197. $video['width'] = round($scale_factor * $ratio);
  198. }
  199. $video['autoplay'] = (bool) $video['autoplay'];
  200. $video['align'] = (isset($video['align']) && in_array($video['align'], array(
  201. 'left',
  202. 'right',
  203. 'center',
  204. ))) ? $video['align'] : NULL;
  205. // Let modules have final say on video parameters.
  206. drupal_alter('video_filter_video', $video);
  207. if (isset($video['codec']['html5_callback']) && $filter->settings['video_filter_html5'] == 1 && is_callable($video['codec']['html5_callback'], FALSE)) {
  208. $replacement = call_user_func($video['codec']['html5_callback'], $video);
  209. }
  210. elseif (is_callable($video['codec']['callback'], FALSE)) {
  211. $replacement = call_user_func($video['codec']['callback'], $video);
  212. }
  213. else {
  214. // Invalid callback.
  215. $replacement = '<!-- VIDEO FILTER - INVALID CALLBACK IN: ' . $pattern . ' -->';
  216. }
  217. }
  218. // Invalid format.
  219. else {
  220. $replacement = '<!-- VIDEO FILTER - INVALID CODEC IN: ' . $code . ' -->';
  221. }
  222. $text = str_replace($code, $replacement, $text);
  223. }
  224. }
  225. return $text;
  226. }
  227. /**
  228. * Wrapper that calls the theme function.
  229. */
  230. function video_filter_flash($video, $params = array()) {
  231. return theme('video_filter_flash', array('video' => $video, 'params' => $params));
  232. }
  233. /**
  234. * Wrapper that calls the theme function.
  235. */
  236. function video_filter_iframe($video) {
  237. return theme('video_filter_iframe', array('video' => $video));
  238. }
  239. function video_filter_get_codec_info() {
  240. static $codecs;
  241. if (!isset($codecs)) {
  242. $codecs = module_invoke_all('codec_info');
  243. drupal_alter('video_filter_codec_info', $codecs);
  244. }
  245. return $codecs;
  246. }
  247. /**
  248. * Function that outputs the <object> element.
  249. *
  250. * @ingroup themeable
  251. */
  252. function theme_video_filter_flash($variables) {
  253. $output = '';
  254. $video = $variables['video'];
  255. $params = isset($variables['params']) ? $variables['params'] : array();
  256. $classes = video_filter_get_classes($video);
  257. $output .= '<object class="' . implode(' ', $classes) . '" type="application/x-shockwave-flash" ';
  258. $output .= 'width="' . $video['width'] . '" height="' . $video['height'] . '" data="' . $video['source'] . '">' . "\n";
  259. $defaults = array(
  260. 'movie' => $video['source'],
  261. 'wmode' => 'transparent',
  262. 'allowFullScreen' => 'true',
  263. );
  264. $params = array_merge($defaults, (is_array($params) && count($params)) ? $params : array());
  265. foreach ($params as $name => $value) {
  266. $output .= ' <param name="' . $name . '" value="' . $value . '" />' . "\n";
  267. }
  268. $output .= '</object>' . "\n";
  269. return $output;
  270. }
  271. /**
  272. * Function that outputs HTML5 compatible iFrame for codecs that support it.
  273. *
  274. * @ingroup themeable
  275. */
  276. function theme_video_filter_iframe($variables) {
  277. $video = $variables['video'];
  278. $classes = video_filter_get_classes($video);
  279. $output = '<iframe src="' . $video['source'] . '" width="' . $video['width'] . '" height="' . $video['height'] . '" class="' . implode(' ', $classes) . '" frameborder="0"></iframe>';
  280. return $output;
  281. }
  282. /**
  283. * Implements hook_theme().
  284. */
  285. function video_filter_theme($existing, $type, $theme, $path) {
  286. return array(
  287. 'video_filter_flash' => array(
  288. 'variables' => array('video' => NULL, 'params' => NULL),
  289. ),
  290. 'video_filter_iframe' => array(
  291. 'variables' => array('video' => NULL, 'params' => NULL),
  292. ),
  293. 'video_filter_dashboard' => array(
  294. 'variables' => array('form' => NULL),
  295. 'template' => 'video_filter_dashboard',
  296. ),
  297. );
  298. }
  299. /**
  300. * Helper function that extracts some classes from $video.
  301. */
  302. function video_filter_get_classes($video) {
  303. $classes = array(
  304. 'video-filter',
  305. // Add codec name.
  306. 'video-' . $video['codec']['codec_name'],
  307. );
  308. // Add alignment.
  309. if (isset($video['align'])) {
  310. $classes[] = 'video-' . $video['align'];
  311. }
  312. // First match is the URL, we don't want that as a class.
  313. unset($video['codec']['matches'][0]);
  314. foreach ($video['codec']['matches'] as $match) {
  315. $classes[] = 'vf-' . strtolower(preg_replace('/[^a-zA-Z0-9]/', '', $match));
  316. }
  317. return $classes;
  318. }
  319. /**
  320. * Implements hook_menu().
  321. */
  322. function video_filter_menu() {
  323. $items = array();
  324. $items['video_filter/dashboard/%'] = array(
  325. 'title' => 'Videofilter',
  326. 'description' => 'Dashboard',
  327. 'page callback' => 'video_filter_dashboard_page',
  328. 'access arguments' => array('access content'),
  329. 'type' => MENU_CALLBACK,
  330. 'page arguments' => array(2),
  331. 'theme callback' => '_video_filter_dashboard_theme',
  332. );
  333. return $items;
  334. }
  335. /**
  336. * Return the theme name to be used when showing linkit dashboard
  337. */
  338. function _video_filter_dashboard_theme() {
  339. return variable_get('admin_theme', 'seven');
  340. }
  341. /**
  342. * Template preprocess function for video_filter_dashboard().
  343. */
  344. function template_preprocess_video_filter_dashboard(&$variables) {
  345. // Construct page title.
  346. $variables['head_title'] = t('Video filter dashboard');
  347. $variables['head'] = drupal_get_html_head();
  348. $variables['help'] = theme('help');
  349. $variables['language'] = $GLOBALS['language'];
  350. $variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
  351. $variables['messages'] = isset($variables['show_messages']) ? theme('status_messages') : '';
  352. $variables['css'] = drupal_add_css();
  353. $variables['styles'] = drupal_get_css();
  354. $variables['scripts'] = drupal_get_js();
  355. }
  356. /**
  357. * Creates the dashboard.
  358. */
  359. function video_filter_dashboard_page($editor) {
  360. module_invoke('admin_menu', 'suppress');
  361. // Add CSS.
  362. drupal_add_css(drupal_get_path('module', 'video_filter') . '/css/video_filter.css');
  363. switch ($editor) {
  364. case 'wysiwyg_tinymce':
  365. // Add JavaScript.
  366. drupal_add_js(wysiwyg_get_path('tinymce') . '/jscripts/tiny_mce/tiny_mce_popup.js');
  367. drupal_add_js(drupal_get_path('module', 'video_filter') . '/editors/tinymce/video_filter.js');
  368. break;
  369. case 'ckeditor':
  370. case 'wysiwyg_ckeditor':
  371. // Add JavaScript.
  372. drupal_add_js(drupal_get_path('module', 'video_filter') . '/editors/ckeditor/video_filter_dialog.js');
  373. break;
  374. case 'fckeditor':
  375. case 'wysiwyg_fckeditor':
  376. // Add JavaScript.
  377. drupal_add_js(drupal_get_path('module', 'video_filter') . '/editors/fckeditor/video_filter/video_filter_dialog.js');
  378. break;
  379. }
  380. print theme('video_filter_dashboard', array('form' => render(drupal_get_form('_video_filter_form'))));
  381. exit();
  382. }
  383. function _video_filter_form() {
  384. $form['video_filter'] = array(
  385. '#type' => 'fieldset',
  386. '#title' => t('Insert Video'),
  387. '#weight' => 0,
  388. '#collapsible' => FALSE,
  389. '#collapsed' => FALSE,
  390. '#attributes' => array('class' => array('clearfix')),
  391. );
  392. $form['video_filter']['file_url'] = array(
  393. '#type' => 'textfield',
  394. '#title' => t('Video URL'),
  395. '#maxlength' => 255,
  396. '#size' => 80,
  397. '#default_value' => '',
  398. '#weight' => 1,
  399. );
  400. $form['video_filter']['width'] = array(
  401. '#type' => 'textfield',
  402. '#title' => t('Width'),
  403. '#maxlength' => 255,
  404. '#size' => 80,
  405. '#default_value' => '',
  406. '#weight' => 2,
  407. );
  408. $form['video_filter']['height'] = array(
  409. '#type' => 'textfield',
  410. '#title' => t('Height'),
  411. '#maxlength' => 255,
  412. '#size' => 80,
  413. '#default_value' => '',
  414. '#weight' => 3,
  415. );
  416. $form['video_filter']['align'] = array(
  417. '#type' => 'select',
  418. '#title' => t('Align'),
  419. '#default_value' => 'none',
  420. '#options' => array(
  421. 'none' => t('None'),
  422. 'left' => t('Left'),
  423. 'right' => t('Right'),
  424. 'center' => t('Center'),
  425. ),
  426. '#weight' => 4,
  427. );
  428. $form['video_filter']['autoplay'] = array(
  429. '#type' => 'checkbox',
  430. '#title' => t('Autoplay'),
  431. '#weight' => 5,
  432. );
  433. $form['instructions'] = array(
  434. '#type' => 'fieldset',
  435. '#title' => t('Instructions'),
  436. '#collapsible' => TRUE,
  437. '#collapsed' => TRUE,
  438. '#attributes' => array('class' => array('clearfix')),
  439. '#weight' => 97,
  440. );
  441. $text = '<p>' . t('Insert a 3rd party video from one of the following providers.') . '</p>';
  442. $text .= _video_filter_instructions();
  443. $form['instructions']['text'] = array(
  444. '#type' => 'item',
  445. '#markup' => $text,
  446. );
  447. $form['cancel'] = array(
  448. '#type' => 'button',
  449. '#value' => t('Cancel'),
  450. '#weight' => 98,
  451. );
  452. $form['insert'] = array(
  453. '#type' => 'button',
  454. '#value' => t('Insert'),
  455. '#weight' => 99,
  456. );
  457. return $form;
  458. }
  459. function video_filter_wysiwyg_plugin($editor, $version) {
  460. _video_filter_add_settings('wysiwyg_' . $editor);
  461. $plugins = array();
  462. switch ($editor) {
  463. case 'ckeditor':
  464. $plugins['video_filter'] = array(
  465. 'path' => drupal_get_path('module', 'video_filter') . '/editors/ckeditor/',
  466. 'buttons' => array('video_filter' => t('Video filter')),
  467. 'url' => 'http://drupal.org/project/video_filter',
  468. 'load' => TRUE,
  469. );
  470. break;
  471. case 'fckeditor':
  472. $plugins['video_filter'] = array(
  473. 'path' => drupal_get_path('module', 'video_filter') . '/editors/fckeditor/',
  474. 'buttons' => array('video_filter' => t('Video filter')),
  475. 'url' => 'http://drupal.org/project/video_filter',
  476. 'load' => TRUE,
  477. );
  478. break;
  479. case 'tinymce':
  480. $plugins['video_filter'] = array(
  481. 'path' => drupal_get_path('module', 'video_filter') . '/editors/tinymce',
  482. 'filename' => 'editor_plugin.js',
  483. 'buttons' => array('video_filter' => t('Video filter')),
  484. 'url' => 'http://drupal.org/project/video_filter',
  485. 'load' => TRUE,
  486. );
  487. break;
  488. }
  489. return $plugins;
  490. }
  491. /**
  492. * Implements hook_element_info_alter().
  493. */
  494. function video_filter_element_info_alter(&$types) {
  495. if (isset($types['text_format']['#pre_render']) && is_array($types['text_format']['#pre_render'])) {
  496. if (in_array('ckeditor_pre_render_text_format', $types['text_format']['#pre_render'])) {
  497. _video_filter_add_settings('ckeditor');
  498. }
  499. }
  500. }
  501. function _video_filter_add_settings($editor) {
  502. static $editor_settings_added = array();
  503. static $global_settings_added = FALSE;
  504. if (!isset($editor_settings_added[$editor])) {
  505. $editor_settings_added[$editor] = TRUE;
  506. // Add popup url.
  507. $settings = array(
  508. 'video_filter' => array('url' => array($editor => url('video_filter/dashboard/' . $editor))),
  509. );
  510. drupal_add_js($settings, 'setting');
  511. }
  512. if (!$global_settings_added) {
  513. $global_settings_added = TRUE;
  514. // Add global settings for video_filter.
  515. $settings = array(
  516. 'video_filter' => array(
  517. 'modulepath' => drupal_get_path('module', 'video_filter'),
  518. ),
  519. );
  520. drupal_add_js($settings, 'setting');
  521. }
  522. }
  523. /**
  524. * Parses Codec into instructions for WYSIWYG popup.
  525. */
  526. function _video_filter_instructions() {
  527. $codecs = video_filter_get_codec_info();
  528. $output = '<ul>';
  529. foreach ($codecs as $codec) {
  530. $output .= '<li><strong>' . $codec['name'] . '</strong><br />' . $codec['sample_url'] . '</li>';
  531. }
  532. $output .= '</ul>';
  533. return $output;
  534. }
  535. /**
  536. * Requests data from an oEmbed provider.
  537. *
  538. * Note: This function currently only supports JSON responses.
  539. *
  540. * @param string $endpoint
  541. * The provider endpoint URL.
  542. * @param array $arguments
  543. * An associative array of URL arguments to send the provider.
  544. *
  545. * @return array|FALSE
  546. * An array of data if the request is successful, otherwise FALSE.
  547. *
  548. * @todo Support other response formats than JSON?
  549. */
  550. function video_filter_oembed_request($endpoint, array $arguments) {
  551. // Make HTTP request.
  552. $result = drupal_http_request(url($endpoint, array('query' => $arguments)));
  553. if ($data = json_decode($result->data)) {
  554. // Success.
  555. return (array) $data;
  556. }
  557. else {
  558. // Failure. Either the resource doesn't exist or there was an error with the
  559. // request.
  560. return FALSE;
  561. }
  562. }