LiaCat.php

<?php

class LiaCat extends \Lia\Compo {

    protected $projects = [];

    protected $useKittenLogo = false;
    protected $appName = "Liaison WiKitten implementation";
    protected $pathParts = [];

    public $projectsDir;
    public $baseUrl;
    public $reqUrl;

    public function __construct($package){
        parent::__construct($package);
        $options = (object)$package->setup['options'];
        
        $this->projectsDir = $options->projectsDir;
        $this->baseUrl = $options->baseUrl;

        if (!is_dir($this->projectsDir)){
            throw new \Exception("You must pass a valid directory path as 'projectsDir' to the wiki package setup.");
        }
    }

    protected $defaultPageData = array(
        'title' => false, // will use APP_NAME by default
        'description' => 'Wikitten is a small, fast, PHP wiki.',
        'tags' => array('wikitten', 'wiki'),
        'page' => ''
    );

    //need to make the base url function with the static files & project files

    public function onRequest_Setup($event, $url){
                // echo 'oh good';exit;
        //add each project's directory to the router
        //probably do it as a callable, since will not use the regular file-routing mechanisms
        //or add a new feature to liaison to fine-tune route handling
        //or implement an existing event that lets me filter content handlers
    }
    public function onEvents_Sort($event, $name, $args, $events){
        if ($name!='Request_Deliver')return $events;

        $others = [];
        foreach ($events as $callable){
            if (!is_array($callable))continue;
            if ($callable[0]!==$this)$others[] = $callable;
        }


        $baseUrl = $this->baseUrl;  
        $url = $args[0];
        if (substr($url,0,strlen($baseUrl))!=$baseUrl)return $others;
        $staticUrl = $baseUrl.'static/';
        if (substr($url,0,strlen($staticUrl))==$staticUrl)return $others;
        foreach ($events as $callable){
            if (!is_array($callable))continue;
            if ($callable[0]===$this)return [$callable];
        }
    }

    public function onRequest_Deliver($event,$url){
        if (($_GET['view_source']??false)=='true'){
            $source = $this->getFileContent($url);
            if (($_GET['raw']??false)=='true'){
                header(('Content-type: text/plain'));
                echo $source;
                exit;
            }
            header(('Content-type: text/html'));
            $extension = pathinfo($url,PATHINFO_EXTENSION);
            $source = htmlentities($source);
            $html = 
<<<HTML
<!DOCTYPE html>
<html>
    <head>
        <style>
            html,body, body > pre {
                padding:0;
                margin:0;
            }
            body > pre > code.hljs{
                margin:0;
                padding:16px;
            }
        </style>
        <link rel="stylesheet"
            href="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/styles/default.min.css">
        <script src="//cdn.jsdelivr.net/gh/highlightjs/cdn-release@10.1.2/build/highlight.min.js"></script>
        <script>hljs.initHighlightingOnLoad();</script>
    </head>
    <body>
        <pre><code class="language-{$extension}">{$source}</code></pre>
    </body>
</html>
HTML;
            echo $html;
            exit;
        }

        $url = str_replace(['%20'],' ', $url);
        $this->reqUrl = $url;
        $content = $this->getWikiContent($url);

        ob_start();
        $lia = $this->lia;
        // require($this->dir('theme').'/init.php');
        require($this->dir('theme').'/template.php');

        $pageOutput = ob_get_clean();
        // exit;
        // $this->lia->send('Theme_Display',$pageOutput,$url,[]);\
        $themeView = $this->lia->view('Site/Theme',['content'=>$pageOutput,'without_content_area'=>true]);
        if ($themeView==null)echo $pageOutput;
        else echo $themeView;

    }
    protected function config($name){
        return null;
    }
    protected function getFileContent($url){
        $url = str_replace(['%20'],' ', $url);
        if (substr($url,0,strlen($this->baseUrl))!==$this->baseUrl)return;
        $relUrl = substr($url,strlen($this->baseUrl));
        $file = str_replace(['///','//'],'/',$this->projectsDir.'/'.$relUrl);
        if (!file_exists($file))return "File not found...";

        $content = file_get_contents($file);

        return $content;

        // $ext = pathinfo($file,PATHINFO_EXTENSION);
        // $mdContent = "```{$ext}\n{$content}\n```";
        // // $converter = new League\CommonMark\CommonMarkConverter([
        // //     // 'html_input' => 'strip',
        // //     // 'allow_unsafe_links' => false,
        // // ]);
        // // $coolOutput = $converter->convertToHtml($mdContent);
        // return $coolOutput;
    }
    protected function getWikiContent($url){
        $url = str_replace(['%20'],' ', $url);
        if (substr($url,0,strlen($this->baseUrl))!==$this->baseUrl)return;
        $relUrl = substr($url,strlen($this->baseUrl));
        $file = str_replace(['///','//'],'/',$this->projectsDir.'/'.$relUrl);

        return $this->render($file);
    }
    public function addProject($projectDir){
        $this->projects[] = $projectDir;
    }

    public function cleanHtml($dirty){
        return htmlspecialchars($dirty, ENT_QUOTES, 'UTF-8');
    }
    public function tree($array, $parent, $parts = array(), $step = 0) {
        // return 'this is where tree items print';
        // return;
        if (!count($array)) {
            return '';
        }

        $tid = ($step == 0) ? 'id="tree"' : '';
        $t = '<ul class="unstyled" '.$tid.'>';

        // var_dump('parent',$parent);
        // exit;
        $reqUrl = $this->reqUrl;
        // var_dump($parent,$reqUrl);
        foreach ($array as $key => $item) {
            if (is_array($item)) {

            $open = false;
            // var_dump($key);
            $itemUrl = $parent.'/'.$key;
            if (substr($reqUrl,0,strlen($itemUrl))===$itemUrl){
                // var_dump($reqUrl);
                // var_dump($itemUrl);
                $open = true;
            } 


                $ourlpen = $step !== false && (isset($parts[$step]) && $key == $parts[$step]);

                $t .= '<li class="directory'. ($open ? ' open' : '') .'">';
                    $t .= '<a href="#" data-role="directory"><i class="far fa-folder'. ($open ? '-open' : '') .'"></i>&nbsp; ' . $key . '</a>';
                    $t .= $this->tree($item, "$parent/$key", $parts, $open ? $step + 1 : false);
                $t .=  '</li>';
            } else {
                $selected = (isset($parts[$step]) && $item == $parts[$step]);
                $t .= '<li class="file'. ($selected ? ' active' : '') .'"><a href="'. $parent .'/'. $item . '">'.$item.'</a></li>';
            }
        }

        $t .= '</ul>';

        return $t;
    }
    public function getTree($dir =null)
    {   
        $dir = $dir ?? $this->projectsDir;
        // echo 'tree';exit;

        // return 'tree items';exit;
        $return = array('directories' => array(), 'files' => array());

        $items = scandir($dir);
        foreach ($items as $item) {
            if ($item=='.'||$item=='..')continue;
            // if (preg_match($this->_ignore, $item)) {
            //     if ($this->_force_unignore === false || !preg_match($this->_force_unignore, $item)) {
            //         continue;
            //     }
            // }

            $path = $dir.'/'.$item;
            if (is_dir($path)) {
                $return['directories'][$item] = $this->getTree($path);
                continue;
            }

            $return['files'][$item] = $item;
        }

        uksort($return['directories'], "strnatcasecmp");
        uksort($return['files'], "strnatcasecmp");

        return $return['directories'] + $return['files'];
    }

    protected function pathIsSafe($fullPath){
        if (substr($fullPath,0,strlen($this->projectsDir))==$this->projectsDir){
            return true;
        }
        return false;
    }
    protected function render($absPath)
    {
        // var_dump($absPath);
        // exit;
        $relPath = substr($absPath,strlen($this->projectsDir));
        if (substr($relPath,0,1)=='/')$relPath = substr($relPath,1);
        $breadcrumbs = explode('/', $relPath);
        // array_shift($breadcrumbs);
        $this->pathParts = $breadcrumbs;
        if (!$this->pathIsSafe($absPath)) {
            $this->notFound();
        }

        // var_dump($absPath);
        // Handle directories by showing a neat listing of its
        // contents
        if (is_dir($absPath)) {

            if (file_exists($indexFile=$absPath.'/index.md')
                ||file_exists($indexFile=$absPath.'/index.html')) {
                return $this->render($indexFile);
            }

            // Get a printable version of the actual folder name:
            $dir_name = htmlspecialchars(end($breadcrumbs), ENT_QUOTES, 'UTF-8');

            // Pass this to the render view, cleverly disguised as just
            // another page, so we can make use of the tree, breadcrumb,
            // etc.
            $page_data = $this->defaultPageData;
            $page_data['title'] = 'Listing: ' . $dir_name;

            // var_dump($absPath);exit;
            $files = scandir($absPath);
            $list = "<h2>I'm an empty folder... Very hungry!</h2>\n";
            if (2 < count($files)) {
                $list = '';
                $dirs = [];
                $nondirs = [];
                foreach ($files as $file){
                    if ($file=='.'||$file=='..')continue;
                    $targetUrl = str_replace(['///','//'],'/',$this->reqUrl.'/'.$file);
                    $relUrl = substr($targetUrl,strlen($this->baseUrl));
                    $targetPath = $this->projectsDir.'/'.$relUrl;
                    if (is_dir($targetPath))$dirs[$file] = $targetUrl;
                    else $nondirs[$file] = $targetUrl;
                }
                if (count($dirs)>0){
                    $list .= "<h2>Lovely subfolders</h2>\n";
                    $list .="<ul>\n";
                    foreach ($dirs as $name=>$targetUrl) {
                        $list .= "<li><a href=\"{$targetUrl}\">${name}</a></li>\n";
                    }
                    $list .= "</ul>\n";
                }

                if (count($nondirs)>0){
                    $list .= "<h2>Beautiful files!</h2>\n";
                    $list .="<ul>\n";
                    foreach ($nondirs as $name=>$targetUrl) {
                        $list .= "<li><a href=\"{$targetUrl}\">${name}</a></li>\n";
                    }
                    $list .= "</ul>\n";
                }
            }
            // echo $list;
            // exit;
            return $this->lia->view('Wiki/render', array(
                'breadcrumbs' => $breadcrumbs,
                'page' => $page_data,
                'html' => $list,
                'is_dir' => true,
                'compo'=>$this
            ));
        }

        // $finfo = finfo_open(FILEINFO_MIME);
        // $mime_type = trim(finfo_file($finfo, $path));
        // if (substr($mime_type, 0, strlen('text/plain')) != 'text/plain'
        //     && substr($mime_type, 0, strlen('inode/x-empty')) != 'inode/x-empty'
        // ) {
        //     echo 'zabeebebee';exit;
        //     // not an ASCII file, send it directly to the browser
        //     $file = fopen($path, 'rb');

        //     header("Content-Type: $mime_type");
        //     header("Content-Length: " . filesize($path));

        //     fpassthru($file);
        //     exit();
        // }

        $source = file_get_contents($absPath);
        $extension = pathinfo($absPath, PATHINFO_EXTENSION);
        $renderer = true;
        $page_data = $this->defaultPageData;

        // Extract the JSON header, if the feature is enabled:
        if ($this->usePageMetaData??false) {
            list($source, $meta_data) = $this->_extractJsonFrontMatter($source);
            $page_data = array_merge($page_data, $meta_data);
        }

        // We need to know the source file in case editing is enabled:
        // $page_data['file'] = $page;

        $html = false;
        if ($renderer===true){


            // $rawcontent = $liaison->package('liaison_base')->compo('RawContent')
            $mimetype = \Lia\Content\RawContent::extensionMimeType($extension);
            $mimeparts = explode('/',$mimetype);
            if ($mimeparts[0]=='image'){
                $blob = 'data:'.$mimetype.';base64,'.base64_encode($source);
                $html = '<img src="'.$blob.'" />';
                // var_dump($path);
                // var_dump($page);
                // exit;
            } else if ($extension=='md'){
                $cm = new \League\CommonMark\CommonMarkConverter([
                    'html_input' => 'strip',
                    'allow_unsafe_links' => false,
                ]);
                $source = $source;
                $html = $cm->convertToHtml($source);
            } else {
                $source = htmlentities($source);
                $basename = basename($absPath);
                $html = 
                <<<HTML
                    <h2>{$basename}</h2>
                    <pre><code class="language-{$extension}">{$source}</code></pre>
                HTML;
            }

            
            // echo $html;exit;
            // $source = '';
            $renderer = true;
            // $html = \Wikitten\MarkdownExtra::defaultTransform('```json'."\n".$source."\n```");
        }
        // if ($extension=='html') {
        //     $html = $source;
        // }

        // if ($renderer && $renderer == 'Markdown') {
        //     $html = \Wikitten\MarkdownExtra::defaultTransform($source);
        // }

        if (empty(trim($html))) {
            $html = "<h1>This page is empty</h1>\n";
            $source = $breadcrumbs[0];
        }

        return  $this->lia->view('Wiki/render', array(
            'html' => $html,
            'source' => $source,
            'extension' => $extension,
            'breadcrumbs' => $breadcrumbs,
            'page' => $page_data,
            'is_dir' => false,
            'use_pastebin' => false,
            'compo'=>$this,
            'reqUrl'=>$this->reqUrl
        ));
    }


    static public function packageDir(){
        return dirname(__DIR__);
    }
}