<?php
namespace Lia\App\MdBlog;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\CommonMark\Node\Block\FencedCode;
use League\CommonMark\Extension\CommonMark\Node\Block\IndentedCode;
use League\CommonMark\MarkdownConverter;
use Spatie\CommonMarkHighlighter\FencedCodeRenderer;
use Spatie\CommonMarkHighlighter\IndentedCodeRenderer;
/**
* Add caching of html
*/
class Main extends \Lia\Addon {
public $name = 'blog';
public $fqn = 'tlf:mdblog.blog';
public $dir;
public function __construct($package=null){
parent::__construct($package);
$this->dir = &$this->props['dir'];
// $this->prefixes['on']('test is yes',null,null);
// exit;
$this->scan('on', $this);
}
public function init(){
if (!isset($this->dir))throw new \Exception("You must set mdblog.blog.dir");
$this->scan('route');
}
public function onServerStart(){
$this->init();
}
/**
*
* @todo test seo title
* @todo add: seoDescription
*/
public function routeBlogs($route,$response=null){
if ($route===false)return $this->get_routes();
$url = $route->url();
$url = substr($url,strlen($this->lia->base_url), -1);
$response->content = $this->get_blog_html($url);
preg_match('/<h1>([^<]+)<\/h1>/', $response->content, $matches);
if (isset($matches[1])){
$this->seoTitle($matches[1]);
}
}
public function get_routes(){
$urls = [];
$blogs = $this->get_all_blogs();
foreach ($blogs as $category=>$list){
foreach ($list as $blog){
$urls[] = $blog['url'];
}
}
return $urls;
}
/**
* convert markdown to html
*/
public function md_to_html(string $markdown){
$environment = new Environment();
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addRenderer(FencedCode::class, new FencedCodeRenderer());
$environment->addRenderer(IndentedCode::class, new IndentedCodeRenderer());
$markdownConverter = new MarkdownConverter($environment);
return $markdownConverter->convertToHtml($markdown);
}
/**
* @param $name the `category/slug` of the post
*/
public function get_blog_html($name){
$file = $this->dir .'/' . $name.'.md';
$cache_file = __DIR__.'/cache/'.$name.'.html';
if (!is_file($cache_file) || filemtime($file) > filemtime($cache_file)){
$content = file_get_contents($file);
$html = $this->md_to_html($content).'';
$dir = dirname($cache_file);
if (!is_dir($dir))mkdir($dir, 0754, true);
file_put_contents($cache_file, $html);
return $html;
}
return file_get_contents($cache_file);
}
/**
*
*
* @param $rel the relative path inside $dir
* @param $dir the base dir to search in
* @return array of relative paths that do NOT include `$dir` in the string
*/
public function get_all_dirs($rel, $dir){
$dirs = [];
foreach (scandir($dir.'/'.$rel) as $f){
if ($f == '.' || $f == '..')continue;
if (!is_dir($dir.'/'.$rel.'/'.$f))continue;
$dirs[] = $rel.$f.'/';
$sub = $this->get_all_dirs($rel.$f.'/', $dir);
foreach ($sub as $s){$dirs[] = $s;}
}
return $dirs;
}
public function get_all_blogs(array $categories=[], ?callable $sorter=null){
if (!isset($this->dir))throw new \Exception("You need to set mdblog.blog.dir");
$blogs = [];
foreach ($this->get_all_dirs('', $this->dir) as $rel){
$category = substr($rel,0,-1);
if ($categories!==[]&&!in_array($category, $categories)){
continue;
}
$entries = $this->get_blog_entries($category);
if (count($entries)==0)continue;
$blogs[$category] = $entries;
}
if (is_callable($sorter)){
$blogs = $sorter($blogs);
}
return $blogs;
}
/**
* @param array $categories different categories to show or empty array for all
* @param $sorter a callable that gets an array of blogs & returns an array of blogs. @see(Lia\Addon\MdBlog\Test\Main::testGridSorter())
*/
public function get_blog_grid(array $categories=[], ?callable $sorter=null){
$blogs = $this->get_all_blogs($categories, $sorter);
$content = $this->view('Blog/CardGrid', ['blogs'=>$blogs, 'blogger'=>$this]);
return $content;
}
public function get_blog_card($category){
$blogs = $this->get_blog_entries($category);
$content = $this->view('Blog/Card', ['blogList'=>$blogs, 'blogger'=>$this, 'category'=>$category]);
return $content;
}
public function get_blog_entries($category){
$dir = $this->dir.'/'.$category;
$entries = [];
foreach (scandir($dir) as $file){
if (substr($file,-3)!='.md')continue;
$path = $dir.'/'.$file;
$name = substr($file,0,-3);
if (substr($name,-6)=='.draft')continue;
$entries[] = [
'title'=>$this->get_title($category.'/'.$name, $path),
'url'=>$this->lia->base_url.'/'.$category.'/'.$name.'/',
'path'=>$path,
];
}
return $entries;
}
/**
* get the title of the given post. $path is used for loading, $name is used for caching?
* @param $name the name of the blog post, used for caching
* @param $path the path of the blog post, used for loading the title (not-from-cache)
*/
public function get_title($name, $path){
$title = $this->cache_get('mdblog.title:'.$name,false);
if ($title!=false)return $title;
$fh = fopen($path, 'r');
$i=0;
while (($line = fgets($fh))&&$i++<5){
if (substr($line,0,2)=='# '){
$title = trim(substr($line,2));
$this->cache('mdblog.title:'.$name, $title);
return $title;
break;
}
}
fclose($fh);
return 'no-title';
}
/**
* convert a slug into a readable title
*/
public function titleFormat($name){
if (trim($name)=='')$name = 'No Category';
// $name =
$name = implode(' ', explode('-',$name));
$name = ucwords($name);
return $name;
}
}