shadowbox.module

Tracking 5.x-2.x branch
  1. drupal
    1. 5 contributions/shadowbox/shadowbox.module
    2. 6 contributions/shadowbox/shadowbox.module

Shadowbox, a JavaScript media viewer application for displaying content in a modal dialogue.

Functions & methods

NameDescription
shadowbox_activeVerify that Shadowbox should be active for the current URL.
shadowbox_automatic_settingsFAPI definition for the Shadowbox automatic image handling settings form.
shadowbox_construct_headerBuild the Shadowbox header by adding the necessary CSS and JS files.
shadowbox_field_formatterImplementation of hook_field_formatter().
shadowbox_field_formatter_infoImplementation of hook_field_formatter_info().
shadowbox_get_settingsConstruct the JS settings array.
shadowbox_get_typesHelper function to get default values for supported media types.
shadowbox_global_settingsFAPI definition for the Shadowbox global settings form.
shadowbox_global_settings_validateValidate admin settings form inputs.
shadowbox_helpImplementation of hook_help().
shadowbox_imagecache_create_urlImplementation of the imagecache_create_url() function for integration with imagecache module versions prior to imagecache 2.
shadowbox_menuImplementation of hook_menu().
shadowbox_permImplementation of hook_perm().
shadowbox_settings_pageThe default Shadowbox settings page.
theme_imagefield_image_imagecache_shadowboxImplementation of theme_imagefield_image_imagecache_shadowbox().
theme_shadowbox_imageTheme function for displaying the shadowbox trigger image in an imagefield.
_array_to_string_valuesFilter values that are not strings from an array.
_explode_to_intConvert numerical value(s) within a delimited string to integer(s).
_validate_hex_colorValidate a hex color value.
_validate_keys_stringValidate a delimited string of keys and/or keycodes.

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Shadowbox, a JavaScript media viewer application for displaying content in a
  5. * modal dialogue.
  6. */
  7. /**
  8. * Implementation of hook_help().
  9. */
  10. function shadowbox_help($section) {
  11. switch ($section) {
  12. case 'admin/help#shadowbox':
  13. $output = '<p>'. t('Shadowbox is a modal media viewer application akin to !lightbox and !thickbox. You can use it to display images, movies, and other web content in a window that is overlaid on top of the originating page.', array('!lightbox' => l('lightbox2', 'http://drupal.org/project/lightbox2', array(), NULL, NULL, TRUE), '!thickbox' => l('thickbox', 'http://drupal.org/project/thickbox', array(), NULL, NULL, TRUE))) .'</p>';
  14. $output .= '<h3>'. t('From the author of Shadowbox:') .'</h3>';
  15. $output .= '<blockquote>'. t('Shadowbox is an online media viewer application that supports all of the web\'s most popular media publishing formats. Shadowbox is written entirely in JavaScript and CSS and is highly customizable. Using Shadowbox, website authors can showcase a wide assortment of media in all major browsers without navigating users away from the linking page.') .'</blockquote>';
  16. $output .= '<h3>'. t('Usage:') .'</h3>';
  17. $output .= '<p>'. t('This module has !global_settings that work on a site-wide basis.', array('!global_settings' => l('global settings', 'admin/settings/shadowbox'))) .'</p>';
  18. $output .= '<p>'. t('Integration with !imagefield 2, !imagecache 2 and !views is provided and the same formatters found in lightbox2 are available in this module.', array('!imagecache' => l('imagecache', 'http://drupal.org/project/imagecache'), '!imagefield' => l('imagefield', 'http://drupal.org/project/imagefield'), '!views' => l('views', 'http://drupal.org/project/views'))) .'</p>';
  19. $output .= '<p>'. t('To manually enable Shadowbox for a link you must add a <strong><code>rel</code></strong> attribute with a value of <strong><code>shadowbox</code></strong> (for compatibility with lightbox2 you can also use a value of <strong><code>lightbox</code></strong>).') .'</p>';
  20. $output .= '<h4>'. t('Single media item:') .'</h4>';
  21. $output .= '<p>'. t('The important thing to note here is the <code>rel</code> attribute. This tells Shadowbox you wish to activate it for this link. The <code>title</code> attribute is optional and can be used to give a caption to the item.') .'</p>';
  22. $output .= '<strong>'. t('Example:') .'</strong>';
  23. $output .= '<p>'. t('<code><pre>&lt;a href=&quot;image1.jpg&quot; rel=&quot;shadowbox&quot; title=&quot;Caption&quot;&gt;Image&lt;/a&gt;</pre></code>') .'</p>';
  24. $output .= '<h4>'. t('Gallery:') .'</h4>';
  25. $output .= '<p>'. t('Media of mixed type can be grouped into a gallery using <code>shadowbox[name]</code> where <code>name</code> is any value you wish to use for the current grouping of media. The value of <code>name</code> is only used internally by Shadowbox and won\'t be shown to the user.') .'</p>';
  26. $output .= '<strong>'. t('Example:') .'</strong>';
  27. $output .= '<p>'. t('<code><pre>&lt;a href=&quot;image1.jpg&quot; rel=&quot;shadowbox[gallery]&quot; title=&quot;Caption 1&quot;&gt;Image 1&lt;/a&gt;
  28. &lt;a href=&quot;image2.jpg&quot; rel=&quot;shadowbox[gallery]&quot; title=&quot;Caption 2&quot;&gt;Image 2&lt;/a&gt;
  29. &lt;a href=&quot;image3.jpg&quot; rel=&quot;shadowbox[gallery]&quot; title=&quot;Caption 3&quot;&gt;Image 3&lt;/a&gt;</pre></code>') .'</p>';
  30. $output .= '<h4>'. t('iFrame:') .'</h4>';
  31. $output .= '<p>'. t('A useful feature of Shadowbox is the ability to open another website directly on top of the current page.') .'</p>';
  32. $output .= '<strong>'. t('Example:') .'</strong>';
  33. $output .= '<p>'. t('<code><pre>&lt;a href=&quot;http://google.com&quot; rel=&quot;shadowbox&quot; title=&quot;Google&quot;&gt;Google&lt;/a&gt;</pre></code>') .'<p>';
  34. $output .= '<h4>'. t('Advanced usage:') .'</h4>';
  35. $output .= '<p>'. t('Shadowbox allows you to specify options on a per-instance basis by using a JSON-formatted parameter. More information on this feature can be found on the !shadowbox_usage', array('!shadowbox_usage' => l('Shadowbox usage page', 'http://mjijackson.com/shadowbox/doc/usage.html#markup', array(), NULL, NULL, TRUE))) .'</p>';
  36. $output .= '<strong>'. t('Example:') .'</strong>';
  37. $output .= '<p>'. t('<code><pre>&lt;a href=&quot;http://google.com&quot; rel=&quot;shadowbox;width=740;height=380;
  38. options={overlayOpacity:1,resizeDuration:0.2};&quot;&gt;Google&lt;/a&gt;</pre></code>') .'</p>';
  39. return $output;
  40. case 'admin/settings/shadowbox':
  41. return '<p>'. t('This page provides access to the Shadowbox settings. The settings here work globally so any changes made here will affect Shadowbox for the entire site.') .'</p>';
  42. }
  43. }
  44. /**
  45. * Implementation of hook_perm().
  46. */
  47. function shadowbox_perm() {
  48. return array('administer shadowbox');
  49. }
  50. /**
  51. * Implementation of hook_menu().
  52. */
  53. function shadowbox_menu($may_cache) {
  54. $items = array();
  55. if ($may_cache) {
  56. $items[] = array(
  57. 'path' => 'admin/settings/shadowbox',
  58. 'title' => 'Shadowbox',
  59. 'callback' => 'shadowbox_settings_page',
  60. 'access' => user_access('administer shadowbox'),
  61. 'description' => t('Configure the settings for Shadowbox.'),
  62. );
  63. $items[] = array('path' => 'admin/settings/shadowbox/global',
  64. 'title' => t('Global'),
  65. 'callback' => 'drupal_get_form',
  66. 'callback arguments' => array('shadowbox_global_settings'),
  67. 'access' => user_access('administer shadowbox'),
  68. 'description' => t('Configure the settings for Shadowbox.'),
  69. 'type' => MENU_DEFAULT_LOCAL_TASK,
  70. 'weight' => 0,
  71. );
  72. $items[] = array('path' => 'admin/settings/shadowbox/automatic',
  73. 'title' => t('Auto image handling'),
  74. 'callback' => 'drupal_get_form',
  75. 'callback arguments' => array('shadowbox_automatic_settings'),
  76. 'access' => user_access('administer shadowbox'),
  77. 'description' => t('Configure the Shadwobox automatic image handling settings.'),
  78. 'type' => MENU_LOCAL_TASK,
  79. 'weight' => 1,
  80. );
  81. }
  82. else {
  83. shadowbox_construct_header();
  84. }
  85. return $items;
  86. }
  87. /**
  88. * The default Shadowbox settings page.
  89. */
  90. function shadowbox_settings_page($op = NULL) {
  91. $output .= drupal_get_form('shadowbox_global_settings');
  92. return $output;
  93. }
  94. /**
  95. * FAPI definition for the Shadowbox global settings form.
  96. *
  97. * @ingroup forms
  98. * @see shadowbox_global_settings_validate()
  99. */
  100. function shadowbox_global_settings() {
  101. $shadowbox_path = drupal_get_path('module', 'shadowbox') .'/shadowbox/';
  102. // Global settings
  103. $form['shadowbox']['shadowbox_enabled'] = array(
  104. '#type' => 'checkbox',
  105. '#title' => t('Global kill-switch'),
  106. '#default_value' => variable_get('shadowbox_enabled', TRUE),
  107. '#description' => t('Check this box to enable Shadowbox for the entire site.'),
  108. );
  109. $form['shadowbox']['active'] = array(
  110. '#type' => 'fieldset',
  111. '#title' => t('Page specific activation settings'),
  112. '#collapsible' => TRUE,
  113. '#collapsed' => FALSE,
  114. );
  115. $form['shadowbox']['active']['shadowbox_active_type'] = array(
  116. '#type' => 'radios',
  117. '#title' => t('Enable Shadowbox on specific pages'),
  118. '#options' => array('disable' => 'Enable on every page except the listed pages.', 'enable' => 'Enable on only the listed pages.'),
  119. '#default_value' => variable_get('shadowbox_active_type', 'disable'),
  120. );
  121. $form['shadowbox']['active']['shadowbox_pages'] = array(
  122. '#type' => 'textarea',
  123. '#title' => t('Pages'),
  124. '#default_value' => variable_get('shadowbox_pages', "admin*\nimg_assist*\nnode/add/*\nnode/*/edit"),
  125. '#description' => t("Enter one page per line as Drupal paths. The '*' character is a wildcard. Example paths are %blog for the blog page and %blog-wildcard for every personal blog. %front is the front page.", array('%blog' => 'blog', '%blog-wildcard' => 'blog/*', '%front' => '<front>')),
  126. );
  127. // Supported media
  128. $form['shadowbox']['shadowbox_supported_media'] = array(
  129. '#type' => 'fieldset',
  130. '#title' => t('Supported media'),
  131. '#collapsible' => TRUE,
  132. '#collapsed' => TRUE,
  133. );
  134. $form['shadowbox']['shadowbox_supported_media']['shadowbox_supported_images'] = array(
  135. '#type' => 'checkboxes',
  136. '#title' => t('Image'),
  137. '#description' => t('Supported image file extensions.'),
  138. '#options' => drupal_map_assoc(shadowbox_get_types('image')),
  139. '#default_value' => variable_get('shadowbox_supported_images', shadowbox_get_types('image')),
  140. );
  141. $form['shadowbox']['shadowbox_supported_media']['shadowbox_supported_swf'] = array(
  142. '#type' => 'checkboxes',
  143. '#title' => t('SWF'),
  144. '#description' => t('Supported SWF file extensions.'),
  145. '#options' => drupal_map_assoc(shadowbox_get_types('swf')),
  146. '#default_value' => variable_get('shadowbox_supported_swf', 'swf'),
  147. );
  148. $form['shadowbox']['shadowbox_supported_media']['shadowbox_supported_flv'] = array(
  149. '#type' => 'checkboxes',
  150. '#title' => t('FLV'),
  151. '#description' => t('Supported FLV file extensions. MP4 and MOV can be opened in the FLV Player rather than Quicktime if desired. Options checked here will override those from Quicktime.'),
  152. '#options' => drupal_map_assoc(shadowbox_get_types('flv')),
  153. '#default_value' => variable_get('shadowbox_supported_flv', 'flv'),
  154. );
  155. $form['shadowbox']['shadowbox_supported_media']['shadowbox_supported_quicktime'] = array(
  156. '#type' => 'checkboxes',
  157. '#title' => t('Quicktime'),
  158. '#description' => t('Movie file extensions supported by QuickTime.'),
  159. '#options' => drupal_map_assoc(shadowbox_get_types('quicktime')),
  160. '#default_value' => variable_get('shadowbox_supported_quicktime', shadowbox_get_types('quicktime')),
  161. );
  162. $form['shadowbox']['shadowbox_supported_media']['shadowbox_supported_wmp'] = array(
  163. '#type' => 'checkboxes',
  164. '#title' => t('Windows Media Player'),
  165. '#description' => t('Movie file extensions supported by Windows Media Player.'),
  166. '#options' => drupal_map_assoc(shadowbox_get_types('wmp')),
  167. '#default_value' => variable_get('shadowbox_supported_wmp', shadowbox_get_types('wmp')),
  168. );
  169. $form['shadowbox']['shadowbox_supported_media']['shadowbox_supported_qtwmp'] = array(
  170. '#type' => 'checkboxes',
  171. '#title' => t('Quicktime and Windows Media Player'),
  172. '#description' => t('Movie file extensions supported by both QuickTime and Windows Media Player.'),
  173. '#options' => drupal_map_assoc(shadowbox_get_types('qtwmp')),
  174. '#default_value' => variable_get('shadowbox_supported_qtwmp', shadowbox_get_types('qtwmp')),
  175. );
  176. $form['shadowbox']['shadowbox_supported_media']['shadowbox_supported_iframe'] = array(
  177. '#type' => 'checkboxes',
  178. '#title' => t('IFrame'),
  179. '#description' => t('File extensions that will be display in an iframe'),
  180. '#options' => drupal_map_assoc(shadowbox_get_types('iframe')),
  181. '#default_value' => variable_get('shadowbox_supported_iframe', array('asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'pl', 'php', 'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml', 'shtml', 'vbs')),
  182. );
  183. // Options
  184. $form['shadowbox']['shadowbox_options'] = array(
  185. '#type' => 'fieldset',
  186. '#title' => t('Options'),
  187. '#description' => t('Shadowbox is highly configurable, but can also be used with little to no configuration at all. The following options may be used to configure Shadowbox on a site-wide basis.'),
  188. '#collapsible' => TRUE,
  189. '#collapsed' => FALSE,
  190. );
  191. // Animation
  192. $form['shadowbox']['shadowbox_options']['animation_settings'] = array(
  193. '#type' => 'fieldset',
  194. '#title' => t('Animation'),
  195. '#collapsible' => TRUE,
  196. '#collapsed' => TRUE,
  197. );
  198. $form['shadowbox']['shadowbox_options']['animation_settings']['shadowbox_animate'] = array(
  199. '#type' => 'checkbox',
  200. '#title' => t('Enable animation'),
  201. '#description' => t('Set this false to disable all fancy animations (except fades). This can improve the overall effect on computers with poor performance.'),
  202. '#default_value' => variable_get('shadowbox_animate', TRUE),
  203. );
  204. $form['shadowbox']['shadowbox_options']['animation_settings']['shadowbox_animate_fade'] = array(
  205. '#type' => 'checkbox',
  206. '#title' => t('Enable fading animations'),
  207. '#description' => t('Set this false to disable all fading animations.'),
  208. '#default_value' => variable_get('shadowbox_animate_fade', TRUE),
  209. );
  210. $form['shadowbox']['shadowbox_options']['animation_settings']['shadowbox_animation_sequence'] = array(
  211. '#type' => 'select',
  212. '#title' => t('Animation sequence'),
  213. '#multiple' => FALSE,
  214. '#description' => t('The animation sequence to use when resizing Shadowbox.'),
  215. '#options' => array(
  216. 'wh' => t('Width then height'),
  217. 'hw' => t('Height then width'),
  218. 'sync' => t('Simultaneously'),
  219. ),
  220. '#default_value' => variable_get('shadowbox_animation_sequence', 'wh'),
  221. );
  222. $form['shadowbox']['shadowbox_options']['animation_settings']['shadowbox_resize_duration'] = array(
  223. '#type' => 'textfield',
  224. '#title' => t('Resize duration'),
  225. '#description' => t('The duration (in seconds) of the resizing animations.'),
  226. '#size' => 5,
  227. '#maxlength' => 4,
  228. '#default_value' => variable_get('shadowbox_resize_duration', 0.35),
  229. );
  230. $form['shadowbox']['shadowbox_options']['animation_settings']['shadowbox_fade_duration'] = array(
  231. '#type' => 'textfield',
  232. '#title' => t('Fade duration'),
  233. '#description' => t('The duration (in seconds) of the overlay fade animation.'),
  234. '#size' => 5,
  235. '#maxlength' => 4,
  236. '#default_value' => variable_get('shadowbox_fade_duration', 0.35),
  237. );
  238. $form['shadowbox']['shadowbox_options']['animation_settings']['shadowbox_initial_height'] = array(
  239. '#type' => 'textfield',
  240. '#title' => t('Initial height'),
  241. '#description' => t('The height of Shadowbox (in pixels) when it first appears on the screen.'),
  242. '#size' => 5,
  243. '#maxlength' => 4,
  244. '#default_value' => variable_get('shadowbox_initial_height', 160),
  245. );
  246. $form['shadowbox']['shadowbox_options']['animation_settings']['shadowbox_initial_width'] = array(
  247. '#type' => 'textfield',
  248. '#title' => t('Initial width'),
  249. '#description' => t('The width of Shadowbox (in pixels) when it first appears on the screen.'),
  250. '#size' => 5,
  251. '#maxlength' => 4,
  252. '#default_value' => variable_get('shadowbox_initial_width', 320),
  253. );
  254. // Overlay
  255. $form['shadowbox']['shadowbox_options']['overlay'] = array(
  256. '#type' => 'fieldset',
  257. '#title' => t('Overlay'),
  258. '#collapsible' => TRUE,
  259. '#collapsed' => TRUE,
  260. );
  261. $form['shadowbox']['shadowbox_options']['overlay']['shadowbox_overlay_color'] = array(
  262. '#type' => 'textfield',
  263. '#title' => t('Overlay color'),
  264. '#description' => t('Select a hexadecimal color value for the overlay (e.g. 000 or 000000 for black).'),
  265. '#size' => 8,
  266. '#maxlength' => 7,
  267. '#default_value' => variable_get('shadowbox_overlay_color', '000'),
  268. '#field_prefix' => '#',
  269. );
  270. $form['shadowbox']['shadowbox_options']['overlay']['shadowbox_overlay_opacity'] = array(
  271. '#type' => 'textfield',
  272. '#title' => t('Overlay opacity'),
  273. '#description' => t('The opacity of the overlay. Accepts values between 0 and 1. 0 is fully transparent, 1 is fully opaque.'),
  274. '#size' => 5,
  275. '#maxlength' => 4,
  276. '#default_value' => variable_get('shadowbox_overlay_opacity', 0.85),
  277. );
  278. $form['shadowbox']['shadowbox_options']['overlay']['shadowbox_flash_background'] = array(
  279. '#type' => 'textfield',
  280. '#title' => t('Flash backround color'),
  281. '#description' => t('The default background color to use for Flash movies.'),
  282. '#size' => 8,
  283. '#maxlength' => 7,
  284. '#default_value' => variable_get('shadowbox_flash_background', '000000'),
  285. '#field_prefix' => '#',
  286. );
  287. $form['shadowbox']['shadowbox_options']['overlay']['shadowbox_viewport_padding'] = array(
  288. '#type' => 'textfield',
  289. '#title' => t('Viewport padding'),
  290. '#description' => t('The amount of padding (in pixels) to maintain around the edge of the browser window.'),
  291. '#size' => 4,
  292. '#maxlength' => 4,
  293. '#default_value' => variable_get('shadowbox_viewport_padding', 20),
  294. );
  295. // Galleries
  296. $form['shadowbox']['shadowbox_options']['gallery'] = array(
  297. '#type' => 'fieldset',
  298. '#title' => t('Galleries'),
  299. '#collapsible' => TRUE,
  300. '#collapsed' => TRUE,
  301. );
  302. $form['shadowbox']['shadowbox_options']['gallery']['shadowbox_display_nav'] = array(
  303. '#type' => 'checkbox',
  304. '#title' => t('Display gallery navigation.'),
  305. '#description' => t('Enable display of the gallery navigation controls.'),
  306. '#default_value' => variable_get('shadowbox_display_nav', TRUE),
  307. );
  308. $form['shadowbox']['shadowbox_options']['gallery']['shadowbox_continuous_galleries'] = array(
  309. '#type' => 'checkbox',
  310. '#title' => t('Enable continuous galleries'),
  311. '#description' => t('By default, the galleries will not let a user go before the first image or after the last. Enabling this feature will let the user go directly to the first image in a gallery from the last one by selecting "Next".'),
  312. '#default_value' => variable_get('shadowbox_continuous_galleries', FALSE),
  313. );
  314. $form['shadowbox']['shadowbox_options']['gallery']['shadowbox_display_counter'] = array(
  315. '#type' => 'checkbox',
  316. '#title' => t('Enable counter'),
  317. '#description' => t('Enable display of the gallery counter. Counters are never displayed on elements that are not part of a gallery.'),
  318. '#default_value' => variable_get('shadowbox_display_counter', TRUE),
  319. );
  320. $form['shadowbox']['shadowbox_options']['gallery']['shadowbox_counter_type'] = array(
  321. '#type' => 'select',
  322. '#title' => t('Counter type'),
  323. '#multiple' => FALSE,
  324. '#description' => t('The mode to use for the gallery counter. May be either \'default\' or \'skip\'. The default counter is a simple \'1 of 5\' message. The skip counter displays a separate link to each piece in the gallery, enabling quick navigation in large galleries.'),
  325. '#options' => array(
  326. 'default' => t('Default'),
  327. 'skip' => t('Skip'),
  328. ),
  329. '#default_value' => variable_get('shadowbox_counter_type', 'default'),
  330. );
  331. $form['shadowbox']['shadowbox_options']['gallery']['shadowbox_counter_limit'] = array(
  332. '#type' => 'textfield',
  333. '#title' => t('Counter limit'),
  334. '#description' => t('Limits the number of counter links that will be displayed in a "skip" style counter. If the actual number of gallery elements is greater than this value, the counter will be restrained to the elements immediately preceding and following the current element.'),
  335. '#default_value' => variable_get('shadowbox_counter_limit', 10),
  336. );
  337. $form['shadowbox']['shadowbox_options']['gallery']['shadowbox_slideshow_delay'] = array(
  338. '#type' => 'textfield',
  339. '#title' => t('Slideshow delay'),
  340. '#description' => t('A delay (in seconds) to use for slideshows. If set to anything other than 0, this value determines an interval at which Shadowbox will automatically proceed to the next piece in the gallery.'),
  341. '#default_value' => variable_get('shadowbox_slideshow_delay', 0),
  342. );
  343. // Movies
  344. $form['shadowbox']['shadowbox_options']['movies'] = array(
  345. '#type' => 'fieldset',
  346. '#title' => t('Movies'),
  347. '#collapsible' => TRUE,
  348. '#collapsed' => TRUE,
  349. );
  350. $form['shadowbox']['shadowbox_options']['movies']['shadowbox_flv_player'] = array(
  351. '#type' => 'textfield',
  352. '#title' => t('FLV player'),
  353. '#description' => t('The URL of the flash video player. This path is relative to the Drupal root.'),
  354. '#size' => 50,
  355. '#maxlength' => 255,
  356. '#default_value' => variable_get('shadowbox_flv_player', $shadowbox_path .'flvplayer.swf'),
  357. );
  358. $form['shadowbox']['shadowbox_options']['movies']['shadowbox_autoplay_movies'] = array(
  359. '#type' => 'checkbox',
  360. '#title' => t('Auto-play movies'),
  361. '#description' => t('Automatically play movies when they are loaded.'),
  362. '#default_value' => variable_get('shadowbox_autoplay_movies', TRUE),
  363. );
  364. $form['shadowbox']['shadowbox_options']['movies']['shadowbox_show_movie_controls'] = array(
  365. '#type' => 'checkbox',
  366. '#title' => t('Enable movie controls'),
  367. '#description' => t('Enable display of QuickTime and Windows Media player movie controls.'),
  368. '#default_value' => variable_get('shadowbox_show_movie_controls', TRUE),
  369. );
  370. // Input controls
  371. $form['shadowbox']['shadowbox_options']['input'] = array(
  372. '#type' => 'fieldset',
  373. '#title' => t('Input controls'),
  374. '#collapsible' => TRUE,
  375. '#collapsed' => TRUE,
  376. );
  377. $form['shadowbox']['shadowbox_options']['input']['shadowbox_overlay_listen'] = array(
  378. '#type' => 'checkbox',
  379. '#title' => t('Mouse click closes overlay.'),
  380. '#description' => t('Enable listening for mouse clicks on the overlay that will close Shadowbox.'),
  381. '#default_value' => variable_get('shadowbox_overlay_listen', TRUE),
  382. );
  383. $form['shadowbox']['shadowbox_options']['input']['shadowbox_enable_keys'] = array(
  384. '#type' => 'checkbox',
  385. '#title' => t('Enable keys'),
  386. '#description' => t('Enable keyboard navigation of galleries.'),
  387. '#default_value' => variable_get('shadowbox_enable_keys', TRUE),
  388. );
  389. // Media handling
  390. $form['shadowbox']['shadowbox_options']['media_handling'] = array(
  391. '#type' => 'fieldset',
  392. '#title' => t('Media handling'),
  393. '#collapsible' => TRUE,
  394. '#collapsed' => TRUE,
  395. );
  396. $form['shadowbox']['shadowbox_options']['media_handling']['shadowbox_handle_oversize'] = array(
  397. '#type' => 'select',
  398. '#title' => t('Handle large images'),
  399. '#multiple' => FALSE,
  400. '#description' => t('The mode to use for handling images that are too large for the viewport. The "none" setting will not alter the image dimensions, though clipping may occur. Setting this to "resize" enables on-the-fly resizing of large content. In this mode, the height and width of large, resizable content will be adjusted so that it may still be viewed in its entirety while maintaining its original aspect ratio. The "drag" mode will display an oversized image at its original resolution, but will allow the user to drag it within the view to see portions that may be clipped.'),
  401. '#options' => array(
  402. 'resize' => t('Resize'),
  403. 'drag' => t('Drag'),
  404. 'none' => t('None'),
  405. ),
  406. '#default_value' => variable_get('shadowbox_handle_oversize', 'resize'),
  407. );
  408. $form['shadowbox']['shadowbox_options']['media_handling']['shadowbox_handle_unsupported'] = array(
  409. '#type' => 'select',
  410. '#title' => t('Handle unsupported'),
  411. '#multiple' => FALSE,
  412. '#description' => t('The mode to use for handling unsupported media. May be either <strong>link</strong> or <strong>remove</strong>. Media are unsupported when the browser plugin required to display the media properly is not installed. The link option will display a user-friendly error message with a link to a page where the needed plugin can be downloaded. The remove option will simply remove any unsupported gallery elements from the gallery before displaying it. With this option, if the element is not part of a gallery, the link will simply be followed.'),
  413. '#options' => array(
  414. 'link' => t('Link'),
  415. 'remove' => t('Remove'),
  416. ),
  417. '#default_value' => variable_get('shadowbox_handle_unsupported', 'link'),
  418. );
  419. return system_settings_form($form);
  420. }
  421. /**
  422. * FAPI definition for the Shadowbox automatic image handling settings form.
  423. *
  424. * @ingroup forms
  425. * @see shadowbox_global_settings_validate()
  426. */
  427. function shadowbox_automatic_settings() {
  428. $form['shadowbox_auto_enable_all_images'] = array(
  429. '#type' => 'checkbox',
  430. '#title' => t('Enable for all image links'),
  431. '#description' => t('Enable this option to automatically add the <code>rel="shadowbox"</code> attribute to all links pointing to an image file.'),
  432. '#default_value' => variable_get('shadowbox_auto_enable_all_images', 0),
  433. );
  434. return system_settings_form($form);
  435. }
  436. /**
  437. * Validate admin settings form inputs.
  438. */
  439. function shadowbox_global_settings_validate($form_id, &$form) {
  440. global $form_values;
  441. $flv_formats = array_values($form_state['values']['shadowbox_supported_flv']);
  442. $flv_player = $form_values['shadowbox_flv_player'];
  443. $color = $form_values['shadowbox_overlay_color'];
  444. $opacity = $form_values['shadowbox_overlay_opacity'];
  445. $flash_background = $form_values['shadowbox_flash_background'];
  446. $slideshow_delay = $form_values['shadowbox_slideshow_delay'];
  447. $resize_duration = $form_values['shadowbox_resize_duration'];
  448. $fade_duration = $form_values['shadowbox_fade_duration'];
  449. $viewport_padding = $form_values['shadowbox_viewport_padding'];
  450. $initial_height = $form_values['shadowbox_initial_height'];
  451. $initial_width = $form_values['shadowbox_initial_width'];
  452. $counter_limit = $form_values['shadowbox_counter_limit'];
  453. if(count(array_intersect($flv_formats, shadowbox_get_types('flv'))) != 0) {
  454. if (!preg_match('!^([a-z0-9_\-\.]+/)*[a-z0-9_\-]+\.swf$!i', $flv_player)) {
  455. form_set_error('shadowbox_flv_player', t('You must enter a valid path to an SWF Flash Player in the form <code>flvplayer.swf</code>.'));
  456. }
  457. elseif (!file_exists($flv_player)) {
  458. form_set_error('shadowbox_flv_player', t('The FLV player does not exist.'));
  459. }
  460. }
  461. if (!is_numeric($resize_duration) || $resize_duration < 0 || $resize_duration > 10) {
  462. form_set_error('shadowbox_resize_duration', t('You must enter a number between 0 and 10.'));
  463. }
  464. if (!is_numeric($fade_duration) || $fade_duration < 0 || $fade_duration > 10) {
  465. form_set_error('shadowbox_fade_duration', t('You must enter a number between 0 and 10.'));
  466. }
  467. if (!is_numeric($initial_height)) {
  468. form_set_error('shadowbox_initial_height', t('You must enter a number.'));
  469. }
  470. else {
  471. $form_values['shadowbox_initial_height'] = floor($initial_height);
  472. }
  473. if (!is_numeric($initial_width)) {
  474. form_set_error('shadowbox_initial_width', t('You must enter a number.'));
  475. }
  476. else {
  477. $form_values['shadowbox_initial_width'] = floor($initial_width);
  478. }
  479. if (!_validate_hex_color($color)) {
  480. form_set_error('shadowbox_overlay_color', t('You must enter a properly formed hex value (e.g. 000 or 000000 for black.)'));
  481. }
  482. if ($opacity == '' || (floor($opacity) != 0 && $opacity != 1)) {
  483. form_set_error('shadowbox_overlay_opacity', t('You must enter a decimal number between 0 and 1.'));
  484. }
  485. if (!_validate_hex_color($flash_background, TRUE)) {
  486. form_set_error('shadowbox_flash_background', t('You must enter a 6 digit hex value (e.g. 000000 for black.)'));
  487. }
  488. if (!is_numeric($viewport_padding) || $viewport_padding < 0 || $viewport_padding > 200) {
  489. form_set_error('shadowbox_viewport_padding', t('You must enter a number between 0 and 200.'));
  490. }
  491. else {
  492. $form_values['shadowbox_viewport_padding'] = (int)$viewport_padding;
  493. }
  494. }
  495. /**
  496. * Implementation of hook_field_formatter_info().
  497. *
  498. * Add certain shadowbox+imagecache formatters to CCK image fields if the
  499. * imagefield.module and the imagecache.module exist.
  500. */
  501. function shadowbox_field_formatter_info() {
  502. $formatter = array();
  503. if (module_exists('imagefield') && module_exists('imagecache')) {
  504. $rules = array();
  505. if (function_exists('imagecache_presets')) {
  506. $presets = imagecache_presets();
  507. foreach ($presets as $preset_id => $preset_info) {
  508. $rules[$preset_id] = $preset_info['presetname'];
  509. }
  510. }
  511. else {
  512. $rules = _imagecache_get_presets();
  513. }
  514. foreach ($rules as $ruleid => $view_rule) {
  515. $formatters['shadowbox][original]['. $view_rule] = array(
  516. 'label' => 'shadowbox: original to '. $view_rule,
  517. 'field types' => array('image'),
  518. );
  519. $formatters['shadowbox]['. $view_rule .'][original'] = array(
  520. 'label' => 'shadowbox: '. $view_rule .' to original',
  521. 'field types' => array('image'),
  522. );
  523. $formatters['shadowbox_gallery][original]['. $view_rule] = array(
  524. 'label' => 'shadowbox gallery: original to '. $view_rule,
  525. 'field types' => array('image'),
  526. );
  527. $formatters['shadowbox_gallery]['. $view_rule .'][original'] = array(
  528. 'label' => 'shadowbox gallery: '. $view_rule .' to original',
  529. 'field types' => array('image'),
  530. );
  531. foreach ($rules as $rid => $shadowbox_rule) {
  532. $formatters['shadowbox]['. $view_rule .']['. $shadowbox_rule] = array(
  533. 'label' => 'shadowbox: '. $view_rule .' to '. $shadowbox_rule,
  534. 'field types' => array('image'),
  535. );
  536. $formatters['shadowbox_gallery]['. $view_rule .']['. $shadowbox_rule] = array(
  537. 'label' => 'shadowbox gallery: '. $view_rule .' to '. $shadowbox_rule,
  538. 'field types' => array('image'),
  539. );
  540. }
  541. }
  542. }
  543. if (is_array($formatters)) {
  544. asort($formatters);
  545. }
  546. return $formatters;
  547. }
  548. /**
  549. * Implementation of hook_field_formatter().
  550. */
  551. function shadowbox_field_formatter($field, $item, $formatter) {
  552. if (module_exists('imagefield') && module_exists('imagecache')) {
  553. if (isset($item['filepath'])) {
  554. $filepath = &$item['filepath'];
  555. }
  556. else if (!empty($item['fid'])) {
  557. $file = _imagefield_file_load($item['fid']);
  558. $filepath = &$file['filepath'];
  559. }
  560. else {
  561. return '';
  562. }
  563. $item = array_merge($item, (array)$file);
  564. $item['filepath'] = $filepath;
  565. if (strpos($formatter, 'shadowbox][') !== FALSE || strpos($formatter, 'shadowbox_gallery][') !== FALSE) {
  566. list($null, $view_preset, $shadowbox_preset) = explode('][', $formatter, 3);
  567. $item['shadowbox_preset'] = $shadowbox_preset;
  568. $rules = array();
  569. if (function_exists('imagecache_presets')) {
  570. $presets = imagecache_presets();
  571. foreach ($presets as $preset_id => $preset_info) {
  572. $rules[$preset_id] = $preset_info['presetname'];
  573. }
  574. }
  575. else {
  576. $rules = _imagecache_get_presets();
  577. }
  578. if ($view_preset == 'original' || in_array($view_preset, (array) $rules)) {
  579. $gallery = (strpos($formatter, 'shadowbox_gallery][') !== FALSE) ? TRUE : FALSE;
  580. return theme('imagefield_image_imagecache_shadowbox', $view_preset, $field, $item, NULL, $gallery);
  581. }
  582. }
  583. }
  584. }
  585. /**
  586. * Implementation of theme_imagefield_image_imagecache_shadowbox().
  587. */
  588. function theme_imagefield_image_imagecache_shadowbox($view_preset, $field, $item, $attributes = NULL, $gallery) {
  589. $filepath = $item['filepath'];
  590. $alt = $item['alt'];
  591. $title = $item['title'];
  592. $rel = ($gallery) ? 'shadowbox['. $view_preset .']' : 'shadowbox';
  593. $link_attributes = array(
  594. 'rel' => $rel,
  595. 'title' => $item['title'] . $node_link,
  596. );
  597. if ($view_preset == 'original') {
  598. $image = theme('shadowbox_image', $filepath, $alt, $title, $attributes);
  599. }
  600. else {
  601. $image = theme('imagecache', $view_preset, $filepath, $alt, $item['title'], $attributes);
  602. }
  603. if ($item['shadowbox_preset'] == 'original') {
  604. $output = l($image, file_create_url($filepath), $link_attributes, NULL, NULL, FALSE, TRUE);
  605. }
  606. else {
  607. $output = l($image, shadowbox_imagecache_create_url($item['shadowbox_preset'], $filepath), $link_attributes, NULL, NULL, FALSE, TRUE);
  608. }
  609. return $output;
  610. }
  611. /**
  612. * Theme function for displaying the shadowbox trigger image in an imagefield.
  613. */
  614. function theme_shadowbox_image($path, $alt = '', $title = '', $attributes = NULL) {
  615. $attributes = drupal_attributes($attributes);
  616. $image = '<img src="'. file_create_url($path) .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. $attributes .' />';
  617. return $image;
  618. }
  619. /**
  620. * Implementation of the imagecache_create_url() function for integration with
  621. * imagecache module versions prior to imagecache 2.
  622. */
  623. function shadowbox_imagecache_create_url($preset, $path) {
  624. if (function_exists('imagecache_create_url')) {
  625. return imagecache_create_url($preset, $path);
  626. }
  627. else {
  628. $dirpath = file_directory_path();
  629. $dirlen = strlen($dirpath);
  630. if (substr($path, 0, $dirlen + 1) == $dirpath .'/') {
  631. $path = substr($path, $dirlen + 1);
  632. }
  633. return file_create_url(file_directory_path() .'/imagecache/'. $preset .'/'. $path);
  634. }
  635. }
  636. /**
  637. * Build the Shadowbox header by adding the necessary CSS and JS files.
  638. */
  639. function shadowbox_construct_header() {
  640. global $locale;
  641. $path = drupal_get_path('module', 'shadowbox');
  642. $images = variable_get('shadowbox_supported_images', shadowbox_get_types('image'));
  643. $swf = variable_get('shadowbox_supported_swf', shadowbox_get_types('swf'));
  644. $flv = variable_get('shadowbox_supported_flv', shadowbox_get_types('flv'));
  645. $quicktime = variable_get('shadowbox_supported_quicktime', shadowbox_get_types('quicktime'));
  646. $wmp = variable_get('shadowbox_supported_wmp', shadowbox_get_types('wmp'));
  647. $qtwmp = variable_get('shadowbox_supported_qtwmp', shadowbox_get_types('qtwmp'));
  648. $iframe = variable_get('shadowbox_supported_iframe', shadowbox_get_types('iframe'));
  649. $image_support = array_filter($images);
  650. $swf_support = array_filter($swf);
  651. $flv_support = array_filter($flv);
  652. $quicktime_support = array_filter($quicktime);
  653. $wmp_support = array_filter($wmp);
  654. $qtwmp_support = array_filter($qtwmp);
  655. $iframe_support = array_filter($iframe);
  656. if (shadowbox_active() && variable_get('shadowbox_enabled', TRUE)) {
  657. drupal_add_js($path .'/shadowbox/build/adapter/shadowbox-jquery.js');
  658. drupal_add_js($path .'/shadowbox/build/shadowbox.js');
  659. drupal_add_js($path .'/shadowbox_auto.js');
  660. drupal_add_js($path .'/shadowbox.js');
  661. drupal_add_js($path .'/shadowbox/src/skin/classic/skin.js');
  662. drupal_add_css($path .'/shadowbox/src/skin/classic/skin.css');
  663. // If language is set to xx (i.e. when using string overrides module) default to English.
  664. if ($locale == 'xx') {
  665. $locale = 'en';
  666. }
  667. drupal_add_js($path .'/shadowbox/build/lang/shadowbox-'. $locale .'.js');
  668. if (!empty($image_support)) {
  669. drupal_add_js($path .'/shadowbox/build/player/shadowbox-img.js');
  670. }
  671. if (!empty($flv_support)) {
  672. drupal_add_js($path .'/shadowbox/build/player/shadowbox-flv.js');
  673. }
  674. if (!empty($iframe_support)) {
  675. drupal_add_js($path .'/shadowbox/build/player/shadowbox-html.js');
  676. drupal_add_js($path .'/shadowbox/build/player/shadowbox-iframe.js');
  677. }
  678. if (!empty($swf_support)) {
  679. drupal_add_js($path .'/shadowbox/build/player/shadowbox-swf.js');
  680. }
  681. if ((!empty($wmp_support)) || (!empty($qtwmp_support))) {
  682. drupal_add_js($path .'/shadowbox/build/player/shadowbox-wmp.js');
  683. }
  684. if (!empty($qtwmp_support)) {
  685. drupal_add_js($path .'/shadowbox/build/player/shadowbox-qt.js');
  686. }
  687. drupal_add_js(shadowbox_get_settings(), 'setting');
  688. }
  689. }
  690. /**
  691. * Construct the JS settings array.
  692. *
  693. * @param $override
  694. * An array of settings to override global values.
  695. *
  696. * @return
  697. * An array containing settings to be used in drupal_add_js.
  698. */
  699. function shadowbox_get_settings($override = array()) {
  700. $module_path = drupal_get_path('module', 'shadowbox');
  701. $shadowbox_path = $module_path .'/shadowbox/';
  702. $images = variable_get('shadowbox_supported_images', shadowbox_get_types('image'));
  703. $swf = variable_get('shadowbox_supported_swf', shadowbox_get_types('swf'));
  704. $flv = variable_get('shadowbox_supported_flv', shadowbox_get_types('flv'));
  705. $quicktime = variable_get('shadowbox_supported_quicktime', shadowbox_get_types('quicktime'));
  706. $wmp = variable_get('shadowbox_supported_wmp', shadowbox_get_types('wmp'));
  707. $qtwmp = variable_get('shadowbox_supported_qtwmp', shadowbox_get_types('qtwmp'));
  708. $iframe = variable_get('shadowbox_supported_iframe', shadowbox_get_types('iframe'));
  709. $settings = array(
  710. 'flv_player' => base_path() . variable_get('shadowbox_flv_player', $shadowbox_path .'flvplayer.swf'),
  711. 'animation' => variable_get('shadowbox_animate', TRUE),
  712. 'animation_fade' => variable_get('shadowbox_animate_fade', TRUE),
  713. 'animation_sequence' => variable_get('shadowbox_animation_sequence', 'wh'),
  714. 'modal' => !variable_get('shadowbox_overlay_listen', TRUE),
  715. 'overlay_color' => variable_get('shadowbox_overlay_color', '000'),
  716. 'overlay_opacity' => variable_get('shadowbox_overlay_opacity', 0.8),
  717. 'flash_background' => variable_get('shadowbox_flash_background', '000000'),
  718. 'autoplay_movies' => variable_get('shadowbox_autoplay_movies', TRUE),
  719. 'show_movie_controls' => variable_get('shadowbox_show_movie_controls', TRUE),
  720. 'slideshow_delay' => variable_get('shadowbox_slideshow_delay', 0),
  721. 'resize_duration' => variable_get('shadowbox_resize_duration', 0.55),
  722. 'fade_duration' => variable_get('shadowbox_fade_duration', 0.35),
  723. 'display_nav' => variable_get('shadowbox_display_nav', TRUE),
  724. 'continuous_galleries' => variable_get('shadowbox_continuous_galleries', FALSE),
  725. 'display_counter' => variable_get('shadowbox_display_counter', TRUE),
  726. 'counter_type' => variable_get('shadowbox_counter_type', 'default'),
  727. 'counter_limit' => variable_get('shadowbox_counter_limit', 10),
  728. 'viewport_padding' => variable_get('shadowbox_viewport_padding', 20),
  729. 'handle_oversize' => variable_get('shadowbox_handle_oversize', 'resize'),
  730. 'handle_unsupported' => variable_get('shadowbox_handle_unsupported', 'link'),
  731. 'initial_height' => variable_get('shadowbox_initial_height', 160),
  732. 'initial_width' => variable_get('shadowbox_initial_width', 320),
  733. 'enable_keys' => variable_get('shadowbox_enable_keys', TRUE),
  734. 'extensions_img' => _array_to_string_values($images),
  735. 'extensions_swf' => _array_to_string_values($swf),
  736. 'extensions_flv' => _array_to_string_values($flv),
  737. 'extensions_qt' => _array_to_string_values($quicktime),
  738. 'extensions_wmp' => _array_to_string_values($wmp),
  739. 'extensions_qtwmp' => _array_to_string_values($qtwmp),
  740. 'extensions_iframe' => _array_to_string_values($iframe),
  741. 'auto_enable_all_images' => variable_get('shadowbox_auto_enable_all_images', 0),
  742. );
  743. $settings = array_merge($settings, $override);
  744. $settings = array('shadowbox' => $settings);
  745. return $settings;
  746. }
  747. /**
  748. * Verify that Shadowbox should be active for the current URL.
  749. *
  750. * @return
  751. * TRUE if Shadowbox should be active for the current page.
  752. */
  753. function shadowbox_active() {
  754. $path = drupal_get_path_alias($_GET['q']);
  755. $regexp = '/^('. preg_replace(array('/(\r\n?|\n)/', '/\\\\\*/', '/(^|\|)\\\\<front\\\\>($|\|)/'), array('|', '.*', '\1'. preg_quote(variable_get('site_frontpage', 'node'), '/') .'\2'), preg_quote(variable_get('shadowbox_pages', "admin*\nimg_assist*\nnode/add/*\nnode/*/edit"), '/')) .')$/';
  756. // Compare with the internal and path alias (if any).
  757. $page_match = preg_match($regexp, $path);
  758. if ($path != $_GET['q']) {
  759. $page_match = $page_match || preg_match($regexp, $_GET['q']);
  760. }
  761. if (variable_get('shadowbox_active_type', 'disable') == 'disable') {
  762. return !$page_match;
  763. }
  764. else {
  765. return $page_match;
  766. }
  767. }
  768. /**
  769. * Helper function to get default values for supported media types.
  770. *
  771. * @param $input
  772. * The type of supported media options to return.
  773. *
  774. * @return
  775. * An array with default values for each type.
  776. */
  777. function shadowbox_get_types($type) {
  778. switch ($type) {
  779. case 'image':
  780. return array('png', 'jpg', 'jpeg', 'gif', 'bmp');
  781. break;
  782. case 'swf':
  783. return array('swf');
  784. break;
  785. case 'flv':
  786. return array('flv', 'mp4', 'mov');
  787. break;
  788. case 'quicktime':
  789. return array('dv', 'mov', 'moov', 'movie', 'mp4');
  790. break;
  791. case 'wmp':
  792. return array('asf', 'wm', 'wmv');
  793. break;
  794. case 'qtwmp':
  795. return array('avi', 'mpg', 'mpeg');
  796. break;
  797. case 'iframe':
  798. return array('asp', 'aspx', 'cgi', 'cfm', 'htm', 'html', 'pdf', 'pl', 'php',
  799. 'php3', 'php4', 'php5', 'phtml', 'rb', 'rhtml', 'shtml', 'txt', 'vbs');
  800. break;
  801. }
  802. }
  803. /**
  804. * Convert numerical value(s) within a delimited string to integer(s).
  805. *
  806. * Explode a space delimited string e.g. 'a b 2' and cast numeric string values
  807. * to integers.
  808. *
  809. * @param $string
  810. * A space delimited string.
  811. *
  812. * @return
  813. * An array containing strings and integers.
  814. */
  815. function _explode_to_int($string) {
  816. foreach (explode(' ', $string) as $value) {
  817. $output[] = (is_numeric($value)) ? (int)$value : $value;
  818. }
  819. return $output;
  820. }
  821. /**
  822. * Validate a delimited string of keys and/or keycodes.
  823. *
  824. * @param $input
  825. * A delimited string of keys and/or keycodes to validate.
  826. *
  827. * @return
  828. * TRUE if $input is valid otherwise FALSE.
  829. */
  830. function _validate_keys_string($input) {
  831. foreach (explode(' ', $input) as $value) {
  832. $items[] = preg_match('!^(?:[a-z]|\d{1,3})$!i', $value);
  833. }
  834. return in_array(FALSE, $items);
  835. }
  836. /**
  837. * Validate a hex color value.
  838. *
  839. * @param $input
  840. * The string to validate.
  841. *
  842. * @return
  843. * TRUE if $input is a valid hex color value (e.g. 000 or 000000)
  844. */
  845. function _validate_hex_color($input, $long_only = FALSE) {
  846. if ($long_only == TRUE) {
  847. return preg_match('!^[a-f0-9]{6}$!i', $input);
  848. }
  849. else {
  850. return preg_match('!^[a-f0-9]{3}([a-f0-9]{3})?$!i', $input);
  851. }
  852. }
  853. /**
  854. * Filter values that are not strings from an array.
  855. *
  856. * @param $array
  857. * An array to filter.
  858. *
  859. * @return $output
  860. * The filtered array.
  861. */
  862. function _array_to_string_values($array) {
  863. $output = array();
  864. foreach ($array as $value) {
  865. if (is_string($value)) {
  866. $output[] = $value;
  867. }
  868. }
  869. return $output;
  870. }