PageRenderTime 71ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/mediafilter/class.php

https://github.com/KieranRBriggs/moodle-mod_hotpot
PHP | 892 lines | 653 code | 71 blank | 168 comment | 57 complexity | b73b08c64fa9fc2b64f44009768b0e45 MD5 | raw file
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * mod/hotpot/mediafilter/class.php
  18. *
  19. * @package mod-hotpot
  20. * @copyright 2010 Gordon Bateson <gordon.bateson@gmail.com>
  21. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  22. */
  23. defined('MOODLE_INTERNAL') || die();
  24. // get the standard Moodle mediaplugin filter
  25. require_once($CFG->dirroot.'/filter/mediaplugin/filter.php');
  26. /**
  27. * hotpot_mediafilter
  28. *
  29. * @copyright 2010 Gordon Bateson
  30. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  31. * @since Moodle 2.0
  32. */
  33. class hotpot_mediafilter {
  34. // media filetypes that this filter can handle
  35. // this initial list is of the file types that Moodle's standard mediaplugin can handle
  36. // media file types specified by individual media players will be added to this list
  37. public $media_filetypes = array(
  38. // any params allowed (flash audio/video, html5 audio/video)
  39. 'aac'=>'any', 'f4v'=>'any', 'flv'=>'any', 'm4a'=>'any', 'm4v'=>'any',
  40. 'mp4'=>'any', 'oga'=>'any', 'ogg'=>'any', 'ogv'=>'any', 'webm'=>'any',
  41. // only "d=WIDTHxHEIGHT" param allowed in moodle filter
  42. 'avi'=>'size', 'm4v'=>'size', 'm4a'=>'size', 'mov'=>'size',
  43. 'mp4'=>'size', 'mpeg'=>'size', 'mpg'=>'size', 'swf'=>'size', 'wmv'=>'size',
  44. // no params allowed in moodle filter
  45. 'mp3'=>'none', 'ra'=>'none', 'ram'=>'none', 'rm'=>'none', 'rv'=>'none'
  46. );
  47. public $param_names = 'movie|src|url';
  48. // wmp : url
  49. // quicktime : src
  50. // realplayer : src
  51. // flash : movie
  52. public $tagopen = '(?:(<)|(\\\\u003C))'; // left angle-bracket (uses two parenthese)
  53. public $tagchars = '(?(1)[^>]|(?(2).(?!\\\\u003E)))*?'; // string of chars inside the tag
  54. public $tagclose = '(?(1)>|(?(2)\\\\u003E))'; // right angle-bracket (to match the left one)
  55. public $tagreopen = '(?(1)<|(?(2)\\\\u003C))'; // another left angle-bracket (to match the first one)
  56. //$tagopen = '(?:(<)|(&lt;)|(&amp;#x003C;))';
  57. //$tagclose = '(?(2)>|(?(3)&gt;|(?(4)&amp;#x003E;)))';
  58. public $link_search = '';
  59. public $object_search = '';
  60. public $object_searches = array();
  61. public $js_inline = '';
  62. public $js_external = '';
  63. public $players = array();
  64. public $defaultplayer = 'moodle';
  65. public $moodle_flashvars = array('waitForPlay', 'autoPlay', 'buffer');
  66. // bgColour, btnColour, btnBorderColour,
  67. // iconColour, iconOverColour,
  68. // trackColour, handleColour, loaderColour,
  69. // waitForPlay, autoPlay, buffer
  70. // constructor function
  71. /**
  72. * __construct
  73. *
  74. * @param xxx $output (passed by reference)
  75. */
  76. function __construct($output) {
  77. global $CFG, $THEME;
  78. $this->players[$this->defaultplayer] = new hotpot_mediaplayer();
  79. $flashvars_paramnames = array();
  80. $querystring_paramname = array();
  81. $players = get_list_of_plugins('mod/hotpot/mediafilter/hotpot'); // sorted
  82. foreach ($players as $player) {
  83. $filepath = $CFG->dirroot.'/mod/hotpot/mediafilter/hotpot/'.$player.'/class.php';
  84. if (file_exists($filepath) && include_once($filepath)) {
  85. $playerclass = 'hotpot_mediaplayer_'.$player;
  86. $this->players[$player] = new $playerclass();
  87. // note the names urls in flashvars and querystring
  88. if ($name = $this->players[$player]->flashvars_paramname) {
  89. $flashvars_paramnames[$name] = true;
  90. }
  91. if ($name = $this->players[$player]->querystring_paramname) {
  92. $querystring_paramnames[$name] = true;
  93. }
  94. // add aliases to this player
  95. foreach ($this->players[$player]->aliases as $alias) {
  96. $this->players[$alias] =&$this->players[$player];
  97. }
  98. // add any new media file types
  99. foreach ($this->players[$player]->media_filetypes as $filetype) {
  100. if (! array_key_exists($filetype, $this->media_filetypes)) {
  101. $this->media_filetypes[$filetype] = '';
  102. }
  103. }
  104. }
  105. }
  106. $filetypes = implode('|', array_keys($this->media_filetypes));
  107. $filepath = '[^"'."'?]*".'\.('.$filetypes.')[^"'."']*";
  108. // detect backslash before double quotes and slashes within JavaScript
  109. $escape = '(?:\\\\)?';
  110. // search string to extract <a> tags
  111. $this->link_search = '/'.$this->tagopen.'a'.'\s+'.$this->tagchars.'href='.$escape.'"('.$filepath.')'.$escape.'"'.$this->tagchars.$this->tagclose.'.*?'.$this->tagreopen.$escape.'\/a'.$this->tagclose.'/is';
  112. // search string to extract <object> or <embed> tags
  113. $this->object_search = '/'.$this->tagopen.'(object|embed)'.'\s'.$this->tagchars.$this->tagclose.'(.*?)(?:'.$this->tagreopen.'(?:\\\\)?'.'\/\3'.$this->tagclose.')+/is';
  114. // search Flashvars with specific names
  115. // $flashvars_paramnames e.g. TheSound
  116. // e.g. param name="Flashvars" value="TheSound=abc.mp3"
  117. if ($flashvars_paramnames = implode('|', array_keys($flashvars_paramnames))) {
  118. $this->object_searches[] = '/'.$this->tagopen.'param'.'\s+'.$this->tagchars.'name='.$escape.'"FlashVars'.$escape.'"'.$this->tagchars.'value='.$escape.'"(?:'.$flashvars_paramnames.')=('.$filepath.')'.$escape.'"'.$this->tagchars.$this->tagclose.'/is';
  119. }
  120. // html tags and attributes to search for urls
  121. $tags = array(
  122. 'object'=>'data', 'embed'=>'src', 'a'=>'href'
  123. );
  124. // search for specific querystrings
  125. // e.g. param name="movie" value="player.swf?mp3=abc.mp3"
  126. // e.g. object data="player.swf?mp3=abc.mp3"
  127. // e.g. embed src="player.swf?mp3=abc.mp3"
  128. // e.g. a href="player.swf?mp3=abc.mp3"
  129. if ($querystring_paramnames = implode('|', array_keys($querystring_paramnames))) {
  130. $querystring_filepath = '[^"'."'?]*".'\?[^"'."']*(?:$querystring_paramnames)=($filepath)".'[^"'."']*";
  131. if ($this->param_names) {
  132. $this->object_searches[] = '/'.$this->tagopen.'param'.'\s+'.$this->tagchars.'name='.$escape.'"(?:'.$this->param_names.')'.$escape.'"'.$this->tagchars.'value='.$escape.'"'.$querystring_filepath.$escape.'"'.$this->tagchars.$this->tagclose.'/is';
  133. }
  134. foreach ($tags as $tag => $attribute) {
  135. $this->object_searches[] = '/'.$this->tagopen.$tag.'\s+'.$this->tagchars.$attribute.'='.$escape.'"'.$querystring_filepath.$escape.'"'.$this->tagchars.$this->tagclose.'.*?'.$this->tagreopen.$escape.'\/'.$tag.$this->tagclose.'/is';
  136. }
  137. }
  138. // search for full urls
  139. // e.g. param name="movie" value="abc.mp3"
  140. // e.g. object data="mp3=abc.mp3"
  141. // e.g. embed src="abc=abc.mp3"
  142. // e.g. a href="abc=abc.mp3"
  143. if ($this->param_names) {
  144. $this->object_searches[] = '/'.$this->tagopen.'param'.'\s+'.$this->tagchars.'name='.$escape.'"(?:'.$this->param_names.')'.$escape.'"'.$this->tagchars.'value='.$escape.'"('.$filepath.')'.$escape.'"'.$this->tagchars.$this->tagclose.'/is';
  145. }
  146. foreach ($tags as $tag => $attribute) {
  147. $this->object_searches[] = '/'.$this->tagopen.$tag.'\s+'.$this->tagchars.$attribute.'='.$escape.'"('.$filepath.')'.$escape.'"'.$this->tagchars.$this->tagclose.'.*?'.$this->tagreopen.$escape.'\/'.$tag.$this->tagclose.'/is';
  148. }
  149. // check player settings
  150. $names = array_keys($this->players);
  151. foreach ($names as $name) {
  152. // convert player url to absolute url
  153. $player = &$this->players[$name];
  154. if ($player->playerurl && ! preg_match('/^(?:https?:)?\/+/i', $player->playerurl)) {
  155. $player->playerurl = $CFG->wwwroot.'/mod/hotpot/mediafilter/hotpot/'.$player->playerurl;
  156. }
  157. // set basic flashvars settings
  158. $options = &$player->options;
  159. if (is_null($options['flashvars'])) {
  160. if (empty($THEME->filter_mediaplugin_colors)) {
  161. $options['flashvars'] = ''
  162. .'bgColour=000000&'
  163. .'btnColour=ffffff&'.'btnBorderColour=cccccc&'
  164. .'iconColour=000000&'.'iconOverColour=00cc00&'
  165. .'trackColour=cccccc&'.'handleColour=ffffff&'
  166. .'loaderColour=ffffff&'.'waitForPlay=yes'
  167. ;
  168. } else {
  169. // You can set this up in your theme/xxx/config.php
  170. $options['flashvars'] = $THEME->filter_mediaplugin_colors;
  171. }
  172. $options['flashvars'] = htmlspecialchars($options['flashvars']);
  173. }
  174. }
  175. }
  176. /**
  177. * fix
  178. *
  179. * @param xxx $text
  180. * @param xxx $output (passed by reference)
  181. */
  182. function fix($text, $output) {
  183. $this->fix_objects($text, $output);
  184. $this->fix_links($text, $output);
  185. $this->fix_specials($text, $output);
  186. }
  187. /**
  188. * fix_objects
  189. *
  190. * @param xxx $text
  191. * @param xxx $output (passed by reference)
  192. */
  193. function fix_objects($text, $output) {
  194. // Segments[0][0] = '<object classid=\"CLSID:6BF52A52-394A-11d3-B153-00C04F79FAA6\" width=\"100\" height=\"30\"><param name=\"url\" value=\"http://localhost/moodle/19/mysql/file.php/2/hennyjellema/frag.01.mp3\" /><param name=\"autostart\" value=\"false\" /><param name=\"showcontrols\" value=\"true\" /><\/object>';
  195. $callback = array($this, 'fix_object');
  196. $callback = partial($callback, $output);
  197. $output->$text = preg_replace_callback($this->object_search, $callback, $output->$text);
  198. }
  199. /**
  200. * fix_object
  201. *
  202. * @param xxx $output (passed by reference)
  203. * @param xxx $object
  204. * @param xxx $unicode
  205. * @param xxx $quote (optional, default="'")
  206. * @return xxx
  207. */
  208. function fix_object($output, $match) {
  209. $object = $match[0];
  210. $unicode = $match[2];
  211. $url = '';
  212. $filetype = '';
  213. foreach ($this->object_searches as $search) {
  214. if (preg_match($search, $object, $matches)) {
  215. $url = $matches[3];
  216. $filetype = $matches[4];
  217. break;
  218. }
  219. }
  220. if ($url=='') {
  221. return $object;
  222. }
  223. // strip inner tags (e.g. <embed>)
  224. $txt = preg_replace('/'.$this->tagopen.'.*?'.$this->tagclose.'/', '', $object);
  225. $txt = trim($txt);
  226. // if url has a query string, we assume the target url
  227. // is one of the values in the query string
  228. // $pos : 0=first value, 1=named value, 2=last value
  229. $pos = 1;
  230. switch ($pos) {
  231. case 0: $search = '/^[^?]*\?'.'[^=]+=([^&]*)'.'.*$/'; break;
  232. case 1: $search = '/^[^?]*\?'.'(?:file|src|thesound|mp3)+=([^&]*)'.'.*$/'; break;
  233. case 2: $search = '/^[^?]*\?'.'(?:[^=]+=[^&]*&(?:amp;))*'.'[^=]+=([^&]*)'.'$/'; break;
  234. }
  235. $url = preg_replace($search, '$1', $url, 1);
  236. // create new media player for this media file
  237. $player = $this->get_defaultplayer($url, $filetype);
  238. if ($player=='moodle' && isset($this->media_filetypes[$filetype])) {
  239. $allow = $this->media_filetypes[$filetype];
  240. $count = 0;
  241. if ($allow=='size') {
  242. $url = preg_replace('/^([^?#&]*).*?(d=\d{1,4}x\d{1,4}).*$/', '$1?$2', $url, -1, $count);
  243. }
  244. if ($allow=='none' || ($allow=='size' && $count==0)) {
  245. $url = preg_replace('/^([^?]*)[?#&].*$/', '$1', $url);
  246. }
  247. }
  248. $options = array('unicode' => $unicode, 'player' => $player);
  249. $link = '<a href="'.$url.'">'.$txt.'</a>';
  250. return $this->fix_link($output, $options, $link);
  251. }
  252. /**
  253. * fix_specials
  254. *
  255. * @param xxx $output (passed by reference)
  256. * @param xxx $text
  257. */
  258. function fix_specials($text, $output) {
  259. // search for [url player width height options]
  260. // url : the (relative or absolute) url of the media file
  261. // player : string of alpha chars (underscore and hyphen are also allowed)
  262. // "img" or "image" : insert an <img> tag for this url
  263. // "a" or "link" : insert a link to the url
  264. // "object" or "movie" : url is a stand-alone movie; insert <object> tags
  265. // "moodle" : insert a standard moodle media player to play the media file
  266. // otherwise the url is for a media file, so insert a player to play/display it
  267. // width : the required display width (e.g. 50 or 50px or 10em)
  268. // height : the required display height (e.g. 25 or 25px or 5em)
  269. // options : xx OR xx= OR xx=abc123 OR xx="a b c 1 2 3"
  270. // Note: only url is required; others values are optional
  271. $filetypes = implode('|', array_keys($this->media_filetypes));
  272. $search = ''
  273. .'/\[\s*'
  274. .'('.'[^ \]]*?'.'\.(?:'.$filetypes.')(?:\?[^ \]]*)?)' // 1: url (+ querystring)
  275. .'(\s+[a-z][0-9a-z._-]*)?' // 2: player
  276. .'(\s+\d+(?:\.\d+)?[a-z]*)?' // 3: width
  277. .'(\s+\d+(?:\.\d+)?[a-z]*)?' // 4: height
  278. .'((?:\s+[^ =\]]+(?:=(?:(?:\\\\?"[^"]*")|\w*))?)*)' // 5: options
  279. .'\s*\]'
  280. .'((?:\s*<br\s*\/?>)*)' // 6: trailing newlines
  281. .'/is'
  282. ;
  283. $callback = array($this, 'fix_special');
  284. $callback = partial($callback, $output);
  285. $output->$text = preg_replace_callback($search, $callback, $output->$text);
  286. }
  287. /**
  288. * fix_special
  289. *
  290. * @param xxx $match
  291. * @param xxx $output (passed by reference)
  292. * @return xxx
  293. */
  294. function fix_special($output, $match) {
  295. $url = trim($match[1]);
  296. $player = trim($match[2]);
  297. $width = trim($match[3]);
  298. $height = trim($match[4]);
  299. $options = trim($match[5]);
  300. $space = trim($match[6]);
  301. // convert $url to $absoluteurl
  302. $absoluteurl = $output->convert_url_relative($url);
  303. //$absoluteurl = $output->convert_url($url, '');
  304. // set height equal to width, if necessary
  305. if ($width && ! $height) {
  306. $height = $width;
  307. }
  308. //if ($player=='' && $this->image_filetypes && preg_match('/\.(?:'.$this->image_filetypes.')/i', $url)) {
  309. // $player = 'img';
  310. //}
  311. //if ($player=='img' || $player=='image') {
  312. // return '<img src="'.$absoluteurl.'" width="'.$width.'" height="'.$height.'" />';
  313. //}
  314. if ($player=='') {
  315. $player = $this->get_defaultplayer($url);
  316. }
  317. // $options_array will be passed to mediaplugin_filter
  318. $options_array = array();
  319. // add $player, $width and $height to $option_array
  320. if ($player=='movie' || $player=='object') {
  321. $options_array['movie'] = $absoluteurl;
  322. $options_array['skipmediaurl'] = true;
  323. } else if ($player=='center' || $player=='hide') {
  324. $options_array[$player] = true;
  325. $player = '';
  326. } else if ($player) {
  327. $options_array['player'] = $player;
  328. }
  329. if ($width) {
  330. $options_array['width'] = $width;
  331. }
  332. if ($height) {
  333. $options_array['height'] = $height;
  334. }
  335. // transfer $options to $option_array
  336. if (preg_match_all('/([^ =\]]+)(=((?:\\\\?"[^"]*")|\w*))?/s', $options, $matches)) {
  337. $i_max = count($matches[0]);
  338. for ($i=0; $i<$i_max; $i++) {
  339. $name = $matches[1][$i];
  340. if ($matches[2][$i]) {
  341. $options_array[$name] = trim($matches[3][$i], '"\\');
  342. } else {
  343. $options_array[$name] = true; // boolean switch
  344. }
  345. }
  346. }
  347. // remove trailing space if player is to be centered or hidden
  348. if (! empty($options_array['center']) || ! empty($options_array['hide'])) {
  349. $space = '';
  350. }
  351. $link = '<a href="'.$absoluteurl.'" target="_blank">'.$url.'</a>';
  352. return $this->fix_link($output, $options_array, $link).$space;
  353. }
  354. /**
  355. * fix_links
  356. *
  357. * @param xxx $output (passed by reference)
  358. * @param xxx $text
  359. */
  360. function fix_links($text, $output) {
  361. $callback = array($this, 'fix_link');
  362. $callback = partial($callback, $output, array());
  363. $output->$text = preg_replace_callback($this->link_search, $callback, $output->$text);
  364. }
  365. /**
  366. * fix_link
  367. *
  368. * @param xxx $match
  369. * @param xxx $output (passed by reference)
  370. * @param xxx $options (optional, default=array)
  371. * @return xxx
  372. */
  373. function fix_link($output, $options, $match) {
  374. global $CFG, $PAGE;
  375. static $load_flowplayer = 0;
  376. static $eolas_fix_applied = 0;
  377. if (is_string($match)) {
  378. $link = $match;
  379. $unicode = '';
  380. } else if (is_array($match)) {
  381. $link = $match[0];
  382. $unicode = $match[2];
  383. } else {
  384. debugging('Oops, $match is not an array or string !');
  385. $args = func_get_args();
  386. print_object(count($args));
  387. die;
  388. }
  389. if (array_key_exists('unicode', $options)) {
  390. $unicode = $options['unicode'];
  391. }
  392. // set player default, if necessary
  393. if (empty($options['player'])) {
  394. $options['player'] = $this->defaultplayer;
  395. }
  396. // hide player if required
  397. if (array_key_exists('hide', $options)) {
  398. if ($options['hide']) {
  399. $options['width'] = 1;
  400. $options['height'] = 1;
  401. if ($options['player']=='moodle') {
  402. $options['autoPlay'] = 'yes';
  403. $options['waitForPlay'] = 'no';
  404. }
  405. }
  406. unset($options['hide']);
  407. unset($options['center']);
  408. }
  409. // call filter to add media player
  410. if (empty($options['movie']) && $options['player']=='moodle') {
  411. $filter = new filter_mediaplugin($output->hotpot->context, array());
  412. $object = $filter->filter($link);
  413. if ($object==$link) {
  414. // do nothing
  415. } else if ($eolas_fix_applied==$output->hotpot->id) {
  416. // eolas_fix.js and ufo.js have already been added for this quiz
  417. } else {
  418. if ($eolas_fix_applied==0) {
  419. // 1st quiz - eolas_fix.js was added by filter/mediaplugin/filter.php
  420. } else {
  421. // 2nd (or later) quiz - e.g. we are being called by hotpot_cron()
  422. $PAGE->requires->js('/mod/hotpot/mediafilter/eolas_fix.js');
  423. //$object .= '<script defer="defer" src="'.$CFG->wwwroot.'/mod/hotpot/mediafilter/eolas_fix.js" type="text/javascript"></script>';
  424. }
  425. $PAGE->requires->js('/mod/hotpot/mediafilter/ufo.js', true);
  426. //$object .= '<script type="text/javascript" src="'.$CFG->wwwroot.'/mod/hotpot/mediafilter/ufo.js"></script>';
  427. $eolas_fix_applied = $output->hotpot->id;
  428. }
  429. $search = '/(flashvars:")([^"]*)(")/';
  430. $callback = array($this, 'fix_flashvars');
  431. $callback = partial($callback, $options);
  432. $object = preg_replace_callback($search, $callback, $object);
  433. // fix height and width (e.g. height="15", "height": 15)
  434. foreach (array('width', 'height') as $option) {
  435. if (array_key_exists($option, $options)) {
  436. $search = array('/(?<='.$option.':")\w+(?=")/i', '/(?<="'.$option.'": )\w+/i');
  437. $object = preg_replace($search, $options[$option], $object);
  438. }
  439. }
  440. } else {
  441. $object = $this->mediaplugin_filter($output->hotpot, $link, $options);
  442. }
  443. // center content if required
  444. if (array_key_exists('center', $options)) {
  445. if ($options['center']) {
  446. $object = '<div style="text-align:center;">'.$object.'</div>';
  447. }
  448. unset($options['center']);
  449. }
  450. // if required, remove the link contained in the object tag
  451. // Note: strcmp() returns true if strings are different
  452. $player = $options['player'];
  453. if ($this->players[$player]->removelink && strcmp($object, $link)) {
  454. $search = '/<a href="[^"]*"[^>]*>[^<]*<\/a>\s*/is';
  455. $object = preg_replace($search, '', $object);
  456. }
  457. // extract the external javascripts
  458. $search = '/\s*<script[^>]*src[^>]*>.*?<\/script>\s*/is';
  459. if (preg_match_all($search, $object, $scripts, PREG_OFFSET_CAPTURE)) {
  460. foreach (array_reverse($scripts[0]) as $script) {
  461. // $script: [0] = matched string, [1] = offset to start of string
  462. // remove the javascript from the player
  463. $object = substr_replace($object, "\n", $script[1], strlen($script[0]));
  464. // store this javascript so it can be run later
  465. $this->js_external = trim($script[0])."\n".$this->js_external;
  466. }
  467. }
  468. // extract the inline javascripts
  469. $search = '/\s*<script[^>]*>.*?<\/script>\s*/is';
  470. if (preg_match_all($search, $object, $scripts, PREG_OFFSET_CAPTURE)) {
  471. foreach (array_reverse($scripts[0]) as $script) {
  472. // $script: [0] = matched string, [1] = offset to start of string
  473. // remove the script from the player
  474. $object = substr_replace($object, "\n", $script[1], strlen($script[0]));
  475. // format the script (helps readability of the html source)
  476. $script[0] = $this->format_script($script[0]);
  477. //store this javascript so it can be run later
  478. $this->js_inline = trim($script[0])."\n".$this->js_inline;
  479. }
  480. if ($this->js_inline && $load_flowplayer==0) {
  481. $load_flowplayer = 1;
  482. $this->js_inline .= ''
  483. .'<script type="text/javascript">'."\n"
  484. ."//<![CDATA[\n"
  485. ."\t".'M.util.load_flowplayer();'."\n"
  486. ."//]]>\n"
  487. ."</script>\n"
  488. ;
  489. }
  490. }
  491. // remove white space between tags, standardize other white space to a single space
  492. $object = preg_replace('/(?<=>)\s+(?=<)/', '', $object);
  493. $object = preg_replace('/\s+/', ' ', $object);
  494. if ($unicode) {
  495. // encode angle brackets as javascript $unicode
  496. $object = str_replace('<', '\\u003C', $object);
  497. $object = str_replace('>', '\\u003E', $object);
  498. //$object = str_replace('&amp;', '&', $object);
  499. }
  500. return $object;
  501. }
  502. /**
  503. * get_defaultplayer
  504. *
  505. * @param xxx $url
  506. * @return xxx
  507. */
  508. function get_defaultplayer($url, $filetype='') {
  509. if ($filetype=='') {
  510. $filetype = pathinfo($url, PATHINFO_EXTENSION);
  511. }
  512. foreach ($this->players as $playername => $player) {
  513. if (in_array($filetype, $player->media_filetypes)) {
  514. return $playername;
  515. }
  516. }
  517. return $this->defaultplayer;
  518. }
  519. /**
  520. * fix_flashvars
  521. *
  522. * @param xxx $match
  523. * @param xxx $options (passed by reference)
  524. * @return xxx
  525. */
  526. function fix_flashvars($options, $match) {
  527. global $CFG;
  528. $before = $match[1];
  529. $flashvars = $match[2];
  530. $after = $match[3];
  531. // entities_to_utf8() is required undo the call to htmlentities(), see MDL-5223
  532. // this is necessary to allow waitForPlay and autoPlay to be effective on Firefox
  533. $flashvars = hotpot_textlib('entities_to_utf8', $flashvars);
  534. $vars = explode('&', $flashvars);
  535. foreach ($this->moodle_flashvars as $var) {
  536. if (array_key_exists($var, $options)) {
  537. $vars = preg_grep("/^$var=/", $vars, PREG_GREP_INVERT);
  538. $vars[] = "$var=".hotpot_textlib('utf8_to_entities', $options[$var]);
  539. }
  540. }
  541. return $before.implode('&', $vars).$after;
  542. }
  543. /**
  544. * format_script
  545. *
  546. * @param xxx $str
  547. * @param xxx $quote (optional, default="'")
  548. * @return xxx
  549. */
  550. function format_script($str) {
  551. // fix indents
  552. $str = preg_replace('/^ +/m', "\t", $str);
  553. // format FO (Flash Object) properties (one property per line)
  554. $search = '/var FO\s*=\s*\{\s*(.*?)\s*\}/is';
  555. // $1 : properties of the FO object
  556. if (preg_match_all($search, $str, $matches, PREG_OFFSET_CAPTURE)) {
  557. $search = '/\s*(\w+)\s*:\s*(".*?",?)/is';
  558. // $1 : the name of an FO object property
  559. // $2 : the value of an FO object property
  560. $replace = "\t".'$1 : $2'."\n\t";
  561. $i_max = count($matches[0]) - 1;
  562. for ($i=$i_max; $i>=0; $i--) {
  563. list($match, $start) = $matches[0][$i];
  564. $length = strlen($match);
  565. $properties = preg_replace($search, $replace, $matches[1][$i][0]);
  566. $str = substr_replace($str, 'var FO ={'."\n\t".$properties.'}', $start, $length);
  567. }
  568. } else {
  569. $str = preg_replace('/\s*(M.util.add_[^;]*;)\s*/', "\n\t".'$1'."\n", $str);
  570. }
  571. return $str;
  572. }
  573. /**
  574. * mediaplugin_filter
  575. *
  576. * @param xxx $courseid
  577. * @param xxx $text
  578. * @param xxx $options (optional, default=array)
  579. * @return xxx
  580. */
  581. function mediaplugin_filter($hotpot, $text, $options=array()) {
  582. // this function should be overloaded by the subclass
  583. return $text;
  584. }
  585. }
  586. /**
  587. * hotpot_mediaplayer
  588. *
  589. * @copyright 2010 Gordon Bateson
  590. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  591. * @since Moodle 2.0
  592. */
  593. class hotpot_mediaplayer {
  594. public $aliases = array();
  595. public $playerurl = '';
  596. public $flashvars = array();
  597. public $flashvars_paramname = '';
  598. public $querystring_paramname = '';
  599. public $options = array(
  600. 'width' => 0, 'height' => 0, 'build' => 40,
  601. 'quality' => 'high', 'majorversion' => '6', 'flashvars' => null
  602. );
  603. public $more_options = array();
  604. public $media_filetypes = array();
  605. public $spantext = '';
  606. public $removelink = true;
  607. /**
  608. * contructor for this class
  609. */
  610. function __construct() {
  611. $this->options = array_merge($this->options, $this->more_options);
  612. }
  613. /**
  614. * generate
  615. *
  616. * @param xxx $filetype
  617. * @param xxx $link
  618. * @param xxx $mediaurl
  619. * @param xxx $options
  620. * @return xxx
  621. */
  622. function generate($filetype, $link, $mediaurl, $options) {
  623. global $CFG;
  624. // cache language strings
  625. static $str;
  626. if (! isset($str->$filetype)) {
  627. $str->$filetype = $filetype.'audio'; // get_string($filetype.'audio', 'mediaplugin');
  628. }
  629. // $id must be unique to prevent it being stored in Moodle's text cache
  630. static $id_count = 0;
  631. $id = str_replace('hotpot_mediaplayer_', '', get_class($this)).'_'.time().sprintf('%02d', ($id_count++));
  632. // add movie id to $options, if necessary
  633. // this is required in order to allow Flash addCallback on IE
  634. // 2009/11/30 - it is not necessary for IE8, maybe not necessary at all
  635. //if (! isset($options['id'])) {
  636. // $options['id'] = 'ufo_'.$id;
  637. //}
  638. // add movie url to $options, if necessary
  639. if (! isset($options['movie'])) {
  640. $options['movie'] = $this->playerurl;
  641. if ($this->querystring_paramname) {
  642. $options['movie'] .= '?'.$this->querystring_paramname.'='.$mediaurl;
  643. }
  644. }
  645. // do we need to make sure the mediaurl is added to flashvars?
  646. if ($this->flashvars_paramname && empty($options['skipmediaurl'])) {
  647. $find_mediaurl = true;
  648. } else {
  649. $find_mediaurl = false;
  650. }
  651. // get list of option names to be cleaned
  652. $search = '/^player|playerurl|querystring_paramname|flashvars_paramname|skipmediaurl$/i';
  653. $names = preg_grep($search, array_keys($options), PREG_GREP_INVERT);
  654. // clean the options
  655. foreach ($names as $name) {
  656. switch ($name) {
  657. case 'id':
  658. // allow a-z A-Z 0-9 and underscore (could use PARAM_SAFEDIR, but that allows hyphen too)
  659. $options[$name] = preg_replace('/\W/', '', $options[$name]);
  660. break;
  661. case 'movie':
  662. // clean_param() will reject url if it contains spaces
  663. $options[$name] = str_replace(' ', '%20', $options[$name]);
  664. $options[$name] = clean_param($options[$name], PARAM_URL);
  665. break;
  666. case 'flashvars':
  667. // split flashvars into an array
  668. $flashvars = str_replace('&amp;', '&', $options[$name]);
  669. $flashvars = explode('&', $flashvars);
  670. // loop through $flashvars, cleaning as we go
  671. $options[$name] = array();
  672. $found_mediaurl = false;
  673. foreach ($flashvars as $flashvar) {
  674. if (trim($flashvar)=='') {
  675. continue;
  676. }
  677. list($n, $v) = explode('=', $flashvar, 2);
  678. $n = clean_param($n, PARAM_ALPHANUM);
  679. if ($n==$this->flashvars_paramname) {
  680. $found_mediaurl = true;
  681. $options[$name][$n] = clean_param($v, PARAM_URL);
  682. } else if (array_key_exists($n, $this->flashvars)) {
  683. $options[$name][$n] = clean_param($v, $this->flashvars[$n]);
  684. } else {
  685. // $flashvar not defined for this media player so ignore it
  686. }
  687. }
  688. // add media url to flashvars, if necessary
  689. if ($find_mediaurl && ! $found_mediaurl) {
  690. $n = $this->flashvars_paramname;
  691. $options[$name][$n] = clean_param($mediaurl, PARAM_URL);
  692. }
  693. // add flashvars values passed via $options
  694. foreach ($this->flashvars as $n => $type) {
  695. if (isset($options[$n])) {
  696. $options[$name][$n] = clean_param($options[$n], $type);
  697. unset($options[$n]);
  698. }
  699. }
  700. // rebuild $flashvars
  701. $flashvars = array();
  702. foreach ($options[$name] as $n => $v) {
  703. $flashvars[] = "$n=".$v; // urlencode($v);
  704. }
  705. // join $namevalues back together
  706. $options[$name] = implode('&', $flashvars);
  707. unset($flashvars);
  708. break;
  709. default:
  710. $quote = '';
  711. if (isset($options[$name])) {
  712. $value = $options[$name];
  713. if (preg_match('/^(\\\\*["'."']".')?(.*)'.'$1'.'$/', $value, $matches)) {
  714. $quote = $matches[1];
  715. $value = $matches[2];
  716. }
  717. $options[$name] = $quote.clean_param($value, PARAM_ALPHANUM).$quote;
  718. }
  719. } // end switch $name
  720. } // end foreach $names
  721. // re-order options ("movie" first, "flashvars" last)
  722. $names = array_merge(
  723. array('id'), array('movie'),
  724. preg_grep('/^id|movie|flashvars$/i', $names, PREG_GREP_INVERT),
  725. array('flashvars')
  726. );
  727. $args = array();
  728. $properties = array();
  729. foreach ($names as $name) {
  730. if (empty($options[$name])) {
  731. continue;
  732. }
  733. $args[$name] = $options[$name];
  734. $properties[] = $name.':"'.$this->obfuscate_js(addslashes_js($options[$name])).'"';
  735. }
  736. $properties = implode(',', $properties);
  737. if (strlen($this->spantext)) {
  738. $spantext = $this->spantext;
  739. } else {
  740. $size = '';
  741. if (isset($options['width'])) {
  742. $size .= ' width="'.$options['width'].'"';
  743. }
  744. if (isset($options['height'])) {
  745. $size .= ' height="'.$options['height'].'"';
  746. }
  747. $spantext = '<img src="'.$CFG->wwwroot.'/pix/spacer.gif"'.$size.' alt="'.$str->$filetype.'" />';
  748. }
  749. return $link
  750. .'<span class="mediaplugin mediaplugin_'.$filetype.'" id="'.$id.'">'.$spantext.'</span>'."\n"
  751. .'<script type="text/javascript">'."\n"
  752. .'//<![CDATA['."\n"
  753. .' var FO = { '.$properties.' };'."\n"
  754. .' UFO.create(FO, "'.$this->obfuscate_js($id).'");'."\n"
  755. .' UFO.main("'.$this->obfuscate_js($id).'");'."\n"
  756. .'//]]>'."\n"
  757. .'</script>'
  758. ;
  759. }
  760. /**
  761. * obfuscate_js
  762. *
  763. * @param xxx $str
  764. * @return xxx
  765. */
  766. function obfuscate_js($str) {
  767. global $CFG;
  768. if (empty($CFG->hotpot_enableobfuscate)) {
  769. return $str;
  770. }
  771. $obfuscated = '';
  772. $strlen = strlen($str);
  773. for ($i=0; $i<$strlen; $i++) {
  774. if ($i==0 || mt_rand(0,2)) {
  775. $obfuscated .= '\\u'.sprintf('%04X', ord($str{$i}));
  776. } else {
  777. $obfuscated .= $str{$i};
  778. }
  779. }
  780. return $obfuscated;
  781. }
  782. }