mostly filebased Content Presentation System
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

735 lines
28KB

  1. <?php
  2. namespace Modules;
  3. class FilesInFolders {
  4. private $folder;
  5. public $content = array();
  6. public $extras = array();
  7. private $domains = array('default'=>'default');
  8. private $keyfiles = array();
  9. public $structs = array();
  10. private $EXT=array(
  11. 'txt'=>array( 'txt', 'text', 'md' ),
  12. 'pic'=>array( 'jpg', 'jpeg', 'png', 'svg' ),
  13. 'tpl'=>array( 'html', 'htm', 'tpl' ),
  14. 'audio'=>array('mp3','wav','ogg'),
  15. 'csv'=>array( 'csv' )
  16. );
  17. public $config = array();
  18. private $state=array();
  19. function __construct($folder,$conf=array()) {
  20. debug("about to construct FIF: $folder");
  21. $f3 = \Base::instance();
  22. if(is_dir($folder)) { // are we given a valid path?
  23. $this->folder = $folder;
  24. } else { // and if not?
  25. $this->folder = $f3->get('CONTENT')."./";
  26. }
  27. if(is_array($conf)) {
  28. foreach($conf as $key=>$value) {
  29. switch($key) {
  30. case 'content':
  31. if(is_array($value)) {
  32. foreach($value as $k=>$v) {
  33. $this->domains[$k] = $v;
  34. }
  35. }
  36. break;
  37. case 'keyfiles':
  38. if(is_array($value)) {
  39. foreach($value as $k=>$v) {
  40. $this->keyfiles[$k] = $v;
  41. }
  42. }
  43. break;
  44. }
  45. }
  46. }
  47. foreach($this->domains as $domain) {
  48. $this->content[$domain] = array();
  49. }
  50. foreach($this->keyfiles as $keys=>$d) {
  51. $this->extras[$keys] = "";
  52. }
  53. debug("constructed files in folder: $folder");
  54. }
  55. /////////////////////////////
  56. // read folder into struct //
  57. /////////////////////////////
  58. function prepare_files() {
  59. foreach ($this->domains as $k=>$v) {
  60. foreach($this->EXT as $cat=>$endings) {
  61. $this->structs[$k][$cat]=array();
  62. }
  63. }
  64. $ls = scandir($this->folder);
  65. foreach ($ls as $k=>$f) {
  66. if (!strncmp($f,'.',1)) continue; // ignore hidden files
  67. $ex=explode(".", $f);
  68. $ext=strtolower(end($ex));
  69. if (array_key_exists($ex[0],$this->domains)) {
  70. $domain_key=$ex[0];
  71. $sort_key=1;
  72. } elseif (array_key_exists($ex[0], $this->keyfiles)) {
  73. if(in_array($ext,$this->EXT[$this->keyfiles[$ex[0]]['type']])) {
  74. $this->extras[$ex[0]]=$this->folder.$f;
  75. continue;
  76. }
  77. $domain_key='default';
  78. $sort_key=0;
  79. } else {
  80. $domain_key='default';
  81. $sort_key=0;
  82. }
  83. foreach ($this->EXT as $cat=>$endings) {
  84. if (in_array($ext, $endings)) {
  85. $this->structs[$domain_key][$cat][$ex[$sort_key]] = $this->folder.$f;
  86. break;
  87. }
  88. }
  89. }
  90. foreach($this->keyfiles as $key=>$param) {
  91. if(!$this->extras[$key]) {
  92. $this->extras[$key] = self::search_up(
  93. $key,
  94. array($this->folder,$param['until']),
  95. $this->EXT[$param['type']]
  96. );
  97. }
  98. if($this->extras[$key]) {
  99. if ($param['type'] == 'txt') {
  100. $this->read_textfile($this->extras[$key]);
  101. }
  102. }
  103. }
  104. }
  105. ///////////////////////////////////////
  106. // prepare content as per the struct //
  107. ///////////////////////////////////////
  108. function fill_content() {
  109. $md = new \freaParsedown();
  110. $md->deactivate_ol();
  111. //var_dump($md->get_BlockTypes());
  112. foreach($this->domains as $domain_key=>$domain) {
  113. // don't act on hidden files
  114. if ($domain == 'hidden') { continue; }
  115. $this->state['current_domain'] = $domain_key;
  116. foreach($this->structs[$domain_key]['txt'] as $key=>$file) {
  117. $str = $this->read_textfile($file);
  118. $str = self::content_element_dispatcher($str);
  119. $str = $md->text($str);
  120. //$str = sprintf("%s", $str);
  121. $this->content[$domain][$key] = sprintf(
  122. "<div class=\"item %s %s\">%s</div>",
  123. $page,
  124. $key,
  125. $str
  126. );
  127. }
  128. foreach($this->structs[$domain_key]['tpl'] as $key=>$file) {
  129. $str = file_get_contents($file);
  130. $str = \Template::instance()->render($file);
  131. $str = self::linkify($str);
  132. $this->content[$domain][$key] = sprintf(
  133. "<div class=\"item %s %s\">%s</div>",
  134. $page,
  135. $key,
  136. $str
  137. );
  138. }
  139. if (false) { // TODO: make this configurable in main.cfg
  140. foreach($this->structs[$domain_key]['pic'] as $key=>$file) {
  141. $this->content[$domain][$key] = sprintf(
  142. "<img class=\"direct\" src=\"/$file\" />"
  143. );
  144. }
  145. }
  146. if (true) {
  147. foreach ($this->structs[$domain_key]['audio'] as $key=>$file) {
  148. $this->content[$domain][$key] = sprintf(
  149. '<a href="%s" class="audio">%s</a><br>',
  150. $file, $key
  151. );
  152. }
  153. }
  154. foreach($this->structs[$domain_key]['csv'] as $key=>$file) {
  155. $csv = new \Modules\Ography($file,TRUE);
  156. $str="<table>";
  157. foreach($csv->entries as $entry) {
  158. $tmp="";
  159. foreach($entry as $key=>$value) {
  160. $tmp .= sprintf("<td class=\"%s\">%s</td>", $key, $value);
  161. }
  162. $str .= sprintf("<tr>%s</tr>",$tmp);
  163. }
  164. $str.="</table>";
  165. $this->content[$domain][$key] = $str;
  166. }
  167. }
  168. }
  169. //////////////////////
  170. // read config data //
  171. //////////////////////
  172. function read_config($domain=false) {
  173. foreach ($this->domains as $source=>$destination) {
  174. if (is_string($domain)) {
  175. if ($source != $domain) { continue; }
  176. } elseif (is_array($domain)) {
  177. if (!in_array($source,$domain)) { continue; }
  178. }
  179. foreach ($this->structs[$source]['txt'] as $key=>$file) {
  180. $this->read_textfile($file);
  181. }
  182. }
  183. return $this->config;
  184. }
  185. ////////////////
  186. // recursions //
  187. ////////////////
  188. function search_up($key,$paths,$ext) {
  189. $return = "";
  190. if(count($paths) == 2) {
  191. $current = $paths[0];
  192. $last_try = $paths[1];
  193. $ls=scandir($current);
  194. foreach($ls as $f) {
  195. if(!strncmp($f,'.',1)) continue; // ignore hidden files
  196. $ex=explode(".", $f);
  197. if(in_array(strtolower(end($ex)),$ext)) {
  198. if($ex[0]==$key) {
  199. $return = $current.$f;
  200. break;
  201. }
  202. }
  203. }
  204. }
  205. if ($return) {
  206. return $return;
  207. } elseif($current == $last_try) {
  208. return false;
  209. } else {
  210. $p = explode('/',$current);
  211. array_pop($p);
  212. array_pop($p);
  213. return self::search_up($key,array(implode("/",$p)."/",$last_try),$ext);
  214. }
  215. }
  216. ///////////////////////
  217. // Utility functions //
  218. ///////////////////////
  219. function read_textfile($file) {
  220. debug("about to read file: $file");
  221. $str = file_get_contents($file);
  222. debug("read file: $file");
  223. $str = self::linkify($str);
  224. $str = self::strip_comments($str);
  225. $str = self::get_config_from_content($str);
  226. debug("processed file: $file");
  227. return $str;
  228. }
  229. function strip_comments($str) {
  230. $single_line_comments = "/(^;.*\R)/m";
  231. $str = preg_replace($single_line_comments,"",$str);
  232. $multi_line_comments = "/\/\*.*?\*\//s";
  233. $str = preg_replace($multi_line_comments,"",$str);
  234. return $str;
  235. }
  236. function linkify($string) {
  237. $pattern = "/\s@(\w+)[=]([\w,]+)\s/";
  238. $count = 0;
  239. $new = preg_replace_callback
  240. ($pattern,
  241. function($m){
  242. $f3 = \Base::instance();
  243. return $f3->get('SITE_URL')
  244. .$f3->alias($m[1],self::$keyword."=".$m[2])
  245. ;},
  246. $string);
  247. return $new;
  248. }
  249. function get_config_from_content($string) {
  250. $f3 = \Base::instance();
  251. $f = 0;
  252. $pattern = "/#\+(\w+):\s?(.*)/";
  253. $f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
  254. for ($i=0;$i<$f;$i++) {
  255. $string = str_replace($matches[0][$i],"",$string);
  256. $key = $matches[1][$i];
  257. $value = $matches[2][$i];
  258. if(strtolower($value) == "false") {
  259. $value = FALSE;
  260. }
  261. if(!strncmp(trim($value),'@',1)) {
  262. //var_dump($f3->get('DATA'));
  263. if (array_key_exists($key,$f3->get('DATA'))) {
  264. $DATA = $f3->get('DATA.'.$key);
  265. $conf = array($DATA['type'],$DATA['dir']);
  266. $relation = new \Modules\TOC($conf,$f3->get('CONTENT'),substr($value,1));
  267. $relation->dispatch();
  268. $this->config[$key]=array_shift($relation->entries);
  269. } else {
  270. $this->config[$key]=$value;
  271. }
  272. } else {
  273. $this->config[$key]=$value;
  274. }
  275. }
  276. $pattern = "/§>\s*(\w+):(.*?)\R[\011\040]*\R/s";
  277. $f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
  278. for ($i=0;$i<$f;$i++) {
  279. $string = str_replace($matches[0][$i],"",$string);
  280. $key = $matches[1][$i];
  281. $value = trim($matches[2][$i]);
  282. if(strtolower($value) == "false") {
  283. $value = FALSE;
  284. }
  285. if (!strncmp($value,'@',1)) {
  286. # var_dump();
  287. if (array_key_exists($key,$f3->get('DATA'))) {
  288. $entries = explode("@",$value);
  289. array_shift($entries); // first entry is always empty
  290. $DATA = $f3->get('DATA.'.$key);
  291. $conf = array($DATA['type'],$DATA['dir']);
  292. $relation = new \Modules\TOC($conf,$f3->get('CONTENT'),$entries);
  293. $relation->dispatch();
  294. if(/*count($entries) >*/ 1) {
  295. $this->config[$key]= new CMultiple($relation->entries);
  296. } else {
  297. $this->config[$key]=array_shift($relation->entries);
  298. }
  299. } else {
  300. $this->config[$key]=$value;
  301. }
  302. } else {
  303. $this->config[$key]=$value;
  304. }
  305. }
  306. return $string;
  307. }
  308. function content_element_dispatcher($string) {
  309. $f3 = \Base::instance();
  310. $md = new \freaParsedown();
  311. $md->deactivate_ol();
  312. $f0 = 0;
  313. // find occorances of {| keyword |}
  314. $pattern = "/\{\|(.+?)\|\}/s";
  315. $f = preg_match_all($pattern, $string,$matches,PREG_PATTERN_ORDER);
  316. for ($i=0;$i<$f;$i++) {
  317. $body = preg_split("/\R/",trim($matches[1][$i]));
  318. $request = explode(":", trim(array_shift($body)));
  319. $new="";
  320. switch($request[0]) {
  321. case 'test':
  322. $new="seems to work";
  323. break;
  324. case ';':
  325. $new="";
  326. break;
  327. case 'path':
  328. $new="/".$this->folder;
  329. break;
  330. case 'space':
  331. $new=sprintf("<div style=\"height:%s;\"></div>",
  332. $request[1]
  333. );
  334. break;
  335. case 'span':
  336. $new=sprintf("<span class=\"%s\">%s</span>",
  337. $request[1],
  338. $request[2] ? : implode("\n",$body)
  339. );
  340. break;
  341. case 'small-text':
  342. if(count($body)) {
  343. $new=sprintf("<div class=\"smalltext\">%s</div>",
  344. implode("\n",$body)
  345. );
  346. } else {
  347. $new=sprintf("<span class=\"smalltext\">%s</span>",
  348. $request[1]
  349. );
  350. }
  351. break;
  352. case 'TOC':
  353. // throw away TOC part of request, we don't need it
  354. array_shift($request);
  355. $toc = new \Modules\TOC($request,$this->folder,$body);
  356. $toc->dispatch();
  357. $new=sprintf("<div class=\"TOC %s\">%s</div>",
  358. array_shift($request),
  359. $toc);
  360. break;
  361. case 'box':
  362. array_shift($request); //get rid of identifier
  363. $type = array_shift($request);
  364. $pics = explode(":",array_shift($body));
  365. $pic = $pics[0];
  366. $pic_hover = count($pics) > 1 ? $pics[1] : $pic;
  367. if (count($body) >= 3) {
  368. $caption = ['normal'=>['cap1' => array_shift($body),
  369. 'cap2' => array_shift($body)],
  370. 'hover'=> ['cap1' => array_shift($body),
  371. 'cap2' => array_shift($body)]];
  372. $caption_html=[];
  373. foreach ($caption as $state => $set) {
  374. if(!$set['cap2']) {
  375. $caption_html[$state] = [
  376. sprintf('<span class="first">&nbsp;</span>'),
  377. sprintf('<span class="first">%s</span>',$set['cap1'])
  378. ];
  379. } else if (!$set['cap1'] && $set['cap2']) {
  380. $caption_html[$state] = [
  381. sprintf('<span class="second">%s</span>',$set['cap2'])
  382. ];
  383. } else {
  384. $caption_html[$state] = [
  385. sprintf('<span class="first">%s</span>',$set['cap1']?:"&nbsp;"),
  386. sprintf('<span class="second">%s</span>',$set['cap2'])
  387. ];
  388. }
  389. }
  390. $has_caption = TRUE;
  391. } else {
  392. $has_caption = FALSE;
  393. }
  394. if (file_exists($this->folder.$pic)) {
  395. $pic = $this->folder.$pic;
  396. } else {
  397. $pic = $f3->get('RESOURCES')."img/default_img.png";
  398. }
  399. if (file_exists($this->folder.$pic_hover)) {
  400. $pic_hover = $this->folder.$pic_hover;
  401. } else {
  402. $pic_hover = $pic;
  403. }
  404. if(0) {
  405. $PIC = new \Image($pic);
  406. $orientation = $PIC->width() > $PIC->height()
  407. ? 'landscape'
  408. : 'portrait'
  409. ;
  410. unset($PIC);
  411. } else {
  412. list($wwidth, $hheight) = getimagesize($pic);
  413. $orientation = $wwidth > $hheight ? 'landscape' : 'portrait';
  414. }
  415. $pic = new CachedImage($pic);
  416. $pic_hover = new CachedImage($pic_hover);
  417. $class="";
  418. $add="";
  419. switch($type) {
  420. case 'plain':
  421. $link=false;
  422. break;
  423. case 'download':
  424. $file = "/".$this->folder.implode(":",$request);
  425. $link='href="'.$file.'" download ';
  426. break;
  427. case 'lightbox':
  428. $body = implode("\n",$body);
  429. if (count($request) % 2) {
  430. $class = array_pop($request);
  431. }
  432. if (count($request) >= 2) {
  433. $body=str_replace([$request[0],$request[1]],["{|","|}"],$body);
  434. $body=$this->content_element_dispatcher($body);
  435. }
  436. $hash=md5($body);
  437. $add=sprintf("<div id=\"%s\" class=\"content_elment_box_body\">\n%s\n</div>",
  438. $hash,
  439. $md->text($body));
  440. $link='href="#" data-featherlight="#'.$hash.'" ';
  441. break;
  442. case 'internal':
  443. $dest=implode(":",$request);
  444. $data = [];
  445. $base = substr($dest,0,strpos($dest,'?') ? : strlen($dest));
  446. parse_str(parse_url($dest,PHP_URL_QUERY),$data);
  447. $fragment = parse_url($dest,PHP_URL_FRAGMENT);
  448. if ($f3->get('LANG') != $f3->get('default_lang')) {
  449. if (!array_key_exists('lang',$data)) {
  450. $data['lang'] = $f3->get('LANG');
  451. }
  452. }
  453. $new_dest = $base;
  454. if (count($data) > 0) {
  455. $new_dest .= "?" . http_build_query($data);
  456. }
  457. if ($fragment) {
  458. $new_dest .= "#" . $fragment;
  459. }
  460. //if ($f3->get('LANG') != $f3->get('default_lang')) {
  461. // $dest .= "?lang=".$f3->get('LANG');
  462. //}
  463. $link=sprintf('href="%s" ',$f3->get('SITE_URL')."/".$new_dest);
  464. break;
  465. case 'external':
  466. $dest=implode(":",$request);
  467. $target = $f3->get('external_links_open_in_new_window')
  468. ? 'target="_blank"'
  469. : ''
  470. ;
  471. $link=sprintf('href="%s" %s',$dest, $target);
  472. break;
  473. default:
  474. $link='href="#"';
  475. break;
  476. }
  477. $new=sprintf("<div class=\"brick %s\">\n<a class=\"content_element_box\" %s>\n<div class=\"content_element_box\">
  478. <div class=\"image\">
  479. <img class=\"standard\" src=\"%s\" /><img class=\"hover\" src=\"%s\" />
  480. </div>
  481. %s
  482. %s
  483. \n</div>\n</a>\n%s\n</div>",
  484. implode(" ",[$orientation,$class,$type]),
  485. $link,
  486. $pic->get_src(1600),
  487. $pic_hover->get_src(1600),
  488. ($has_caption ? "<div class=\"caption standard\">".implode("<br>",$caption_html['normal'])."</div>": " "),
  489. ($has_caption ? "<div class=\"caption hover\">".implode("<br>",$caption_html['hover'])."</div>" : " " ),
  490. $add
  491. );
  492. break;
  493. case 'brick':
  494. array_shift($request);
  495. $class = array_shift($request);
  496. $new = sprintf("<div class=\"content_element brick %s\">\n%s\n</div>",
  497. $class,
  498. $md->text(implode("\n",$body))
  499. );
  500. break;
  501. case 'calendar':
  502. array_shift($request);
  503. $conf = array('path', $this->folder);
  504. $v = $this->read_config();
  505. $cal = new \Modules\CCalendar($v,$conf);
  506. $new=sprintf("<div class=\"calendar\">\n%s\n</div>",
  507. $cal);
  508. break;
  509. case 'header':
  510. array_shift($request);
  511. $conf = array('path', $this->folder);
  512. $v = $this->read_config();
  513. switch (array_shift($request)) {
  514. case 'event':
  515. $el = new CEvent($v,$conf);
  516. $el->set_layout('archive');
  517. $new = $el;
  518. break;
  519. case 'concert':
  520. $el = new CConcert($v,$conf);
  521. $el->set_layout('header');
  522. $new = $el;
  523. break;
  524. }
  525. break;
  526. case 'image':
  527. $key=$request[1];
  528. $image="";
  529. // if(!$key) {
  530. // $new=self::warn("Content Module \"Image\" needs name of a file (without extension)");
  531. // break;
  532. //}
  533. foreach($this->structs as $domain=>$destination) {
  534. if(array_key_exists($key, $this->structs[$domain]['pic'])) {
  535. $image = $this->structs[$domain]['pic'][$key];
  536. unset($this->structs[$domain]['pic'][$key]);
  537. unset($this->content[$this->domains[$domain]][$key]);
  538. break;
  539. }
  540. }
  541. if ($image) {
  542. if( in_array($request[2],array('left','right','full'))) {
  543. $class = $request[2];
  544. } else {
  545. $class = 'full';
  546. }
  547. $img = new \Image($image);
  548. $ratio = ($img->width() >= $img->height())
  549. ? "landscape"
  550. : "portrait"
  551. ;
  552. $cached = new CachedImage($image);
  553. foreach ($body as $k=>$line) {
  554. if (strpos($line,"©") !== FALSE
  555. || strpos($line,"&copy;") !== FALSE) {
  556. $body[$k] = sprintf("<span class=\"copyright\">%s</span>",$line);
  557. }
  558. }
  559. $new=sprintf("<div class='content_element_image %s'>"
  560. ."<div class=\"media %s\"><a href=\"%s\" data-featherlight=\"image\"><img src=\"%s\" alt=\"user supplied image\" /></a></div>"
  561. ."<img src=\"%s\" style=\"display:none;\" alt=\"user supplied image\" />"
  562. ."<div class=\"caption\">%s</div>"
  563. ."</div>",
  564. $class,
  565. $ratio,
  566. $cached->get_src(1600),
  567. $cached->get_src(1600),
  568. $cached->get_src(1600),
  569. $md->text(implode("\n",$body))
  570. );
  571. } else {
  572. $new=sprintf("<div class='content_element_image %s'>\n"
  573. ."<div class=\"media %s\">\n"
  574. ."<div class=\"caption\">\n%s\n</div>\n"
  575. ."</div>\n",
  576. $class,
  577. $ratio,
  578. $md->text(implode("\n",$body))
  579. );
  580. }
  581. break;
  582. case 'devide':
  583. $contents = explode($request[1],implode("\n",$body));
  584. $c=count($contents);
  585. $str="";
  586. for ($iiii=0;$iiii<$c;$iiii++) {
  587. $str .= sprintf(" %f%%",100/$c);
  588. }
  589. $template = sprintf('grid-template-columns:%s;',
  590. $str);
  591. $counter=0;
  592. $new = sprintf('<div class="content_element_devided" style="%s">',$template);
  593. foreach($contents as $part) {
  594. $counter++;
  595. if (count($request) >= 4) {
  596. $part=str_replace([$request[2],$request[3]],["{|","|}"],$part);
  597. $part=$this->content_element_dispatcher($part);
  598. }
  599. $new .= sprintf("<div>\n%s\n</div>",
  600. $md->text($part)
  601. );
  602. }
  603. $new.="</div>";
  604. break;
  605. case 'page':
  606. array_shift($request);
  607. $target = array_shift($request);
  608. $class = array_shift($request);
  609. $folder = $this->folder.$target."/";
  610. $anchor_name=array_pop(explode("/",$target));
  611. $fff = new \Modules\FilesInFolders(
  612. $folder,
  613. array(
  614. 'content'=>array(
  615. 'secondary'=>'secondary',
  616. 'zzz'=>'hidden',
  617. 'unpublish'=>'hidden'
  618. ))
  619. );
  620. $fff->prepare_files();
  621. $fff->fill_content();
  622. $new = sprintf("<div class=\"content_element_page %s\"><a name=\"%s\"></a>\n%s\n</div>",
  623. $class,
  624. $anchor_name,
  625. implode("\n",$fff->content['default']));
  626. break;
  627. case 'youtube':
  628. $vid=array_shift($request);
  629. $pos= array_shift($request);
  630. if( in_array($pos,array('left','right','full'))) {
  631. $class = $pos;
  632. } else {
  633. $class = 'left';
  634. }
  635. $video=sprintf("<iframe width=\"700\" height=\"394\" class=\"ytvideo\" "
  636. ."src=\"https://www.youtube.com/embed/%s\"></iframe>",
  637. $vid);
  638. $thumbnail = sprintf("<div class=\"video-thumbnail\"><a href=\"%s\" data-featherlight=\"#%s\">"
  639. ."<img class=\"thumbnail\" src=\"https://img.youtube.com/vi/%s/mqdefault.jpg\" alt=\"video-preview\"/>"
  640. ."<img class=\"play-button\" src=\"%s\" alt=\"play-button\" />"
  641. ."</a></div><div class=\"lightbox\" id=\"%s\" >%s</div>",
  642. "https://www.youtube.com/watch?v=".$vid,
  643. $vid,
  644. $vid,
  645. "/rsc/img/play-button.png",
  646. $vid,
  647. $video
  648. );
  649. foreach ($body as $k=>$line) {
  650. if (strpos($line,"©") !== FALSE
  651. || strpos($line,"&copy;") !== FALSE) {
  652. $body[$k] = sprintf("<span class=\"copyright\">%s</span>",$line);
  653. }
  654. }
  655. $new=sprintf("<div class='video-container %s'>"
  656. ."<div class=\"media\">%s</div>"
  657. ."<div class=\"caption\">%s</div>"
  658. ."</div>",
  659. $class,
  660. $thumbnail,
  661. $md->text(implode("\n",$body)));
  662. break;
  663. default:
  664. $new=self::warn("Content Module \"".$request[0]."\" unknown");
  665. break;
  666. }
  667. $string = str_replace($matches[0][$i],$new,$string);
  668. }
  669. return $string;
  670. }
  671. function warn($message) {
  672. return sprintf("<div class=\"warning\">%s</div>",$message);
  673. }
  674. }