mostly filebased Content Presentation System
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

595 lines
22KB

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