Billy Barrow пре 3 година
родитељ
комит
2037eb573e
11 измењених фајлова са 415 додато и 32 уклоњено
  1. 3 3
      .htaccess
  2. 7 1
      404.php
  3. 1 1
      Parsedown.php
  4. 6 0
      README.md
  5. 3 1
      config.php
  6. 42 6
      content_template.php
  7. 89 12
      index.php
  8. 99 6
      index_template.php
  9. 163 0
      ppix.php
  10. 1 1
      ppub.php
  11. 1 1
      rss_template.php

+ 3 - 3
.htaccess

@@ -1,5 +1,5 @@
 RewriteEngine on
 RewriteRule vanilla.css - [L]
-RewriteRule feed.rss  index.php    [L,QSA]
-RewriteRule ^([A-Za-z.]+)/?$  index.php?ppub=$1    [L,QSA]
-RewriteRule ^([A-Za-z.]+)/([A-Za-z./]+)/?$  index.php?ppub=$1&asset=$2    [L,QSA]
+RewriteRule index.php - [L]
+RewriteRule ^(.+?(?=/))/(.*)/?$  index.php?ppub=$1&asset=$2    [L,QSA]
+RewriteRule ^(.+?(?=$|\/))$  index.php?ppub=$1    [L,QSA]

+ 7 - 1
404.php

@@ -1 +1,7 @@
-<h1>404 Not Found</h1>
+<h1>404 Not Found</h1>
+<pre>
+Request details:
+    ppub: <?php echo($_GET["ppub"]);?>
+
+    asset: <?php echo($_GET["asset"]);?>
+</pre>

+ 1 - 1
Parsedown.php

@@ -573,7 +573,7 @@ class Parsedown
 
         $Block = array(
             'element' => array(
-                'name' => 'h' . ($level),
+                'name' => 'h' . ($level + 1),
                 'handler' => array(
                     'function' => 'lineElements',
                     'argument' => $text,

+ 6 - 0
README.md

@@ -0,0 +1,6 @@
+# php-ppub
+*Portable PUBlications for PHP*
+
+This is a simple blog engine using PPUB and PPIX formats that started with [PyPPUB](https://github.com/Tilo15/PyPPUB). It is minimalistic but configurable. It makes use of [Vanilla CSS](https://vanillacss.com/) and generates some very simple and standards compliant HTML.
+
+It can show an index based on a raw folder of ppub files, or it can be configured to use a PPIX index file to enable tags and searching.

+ 3 - 1
config.php

@@ -1,10 +1,12 @@
 <?php
 
 define("SITE_URL", "http://localhost:8080");
-define("SITE_NAME", "Weblog");
+define("SITE_NAME", "Billy's Blog Site");
 define("SITE_TAGLINE", "A log of the web");
 define("SITE_LANGUAGE", "en-nz");
 define("PUBLICATION_DIR", "ppubs");
 define("PUBLICATION_NAME", "Post");
+define("DATE_FORMAT", "l d F Y, H:i");
+define("USE_PPIX", true);
 
 ?>

+ 42 - 6
content_template.php

@@ -1,20 +1,27 @@
 <?php
 
-function content_start($ppub) {
+function content_start($ppub, $path) {
     $metadata = $ppub->metadata;
     ?>
 
 <!DOCTYPE html>
-<html>
+<html lang="<?php echo($metadata["language"] ?? SITE_LANGUAGE);?>">
     <head>
         <meta charset="utf-8">
         <title><?php echo(htmlentities($metadata["title"]));?> - <?php echo(SITE_NAME);?></title>
         <meta name="description" content="<?php echo(htmlentities($metadata["description"]));?>">
         <meta name="author" content="<?php echo(htmlentities($metadata["author"]));?>">
-        <link rel="stylesheet" href="/vanilla.css">
+        <link rel="stylesheet" href="<?php echo(SITE_URL);?>/vanilla.css">
         <link rel="alternate" type="application/x-ppub" title="<?php echo(htmlentities($metadata["title"]));?> (as PPUB)" href="?download=true" />
     </head>
     <body>
+    <header>
+        <h1>
+            <a style="color: var(--text-color); text-decoration: none; display: inline-block;" href="<?php echo(SITE_URL);?>"><?php echo(SITE_NAME);?></a>
+            <!-- <small style="font-weight: light; margin: 0px 5px 0px 5px;" aria-label="<?php echo(PUBLICATION_NAME);?> title:">/</small> -->
+            <!-- <small><a style="color: var(--text-color); text-decoration: none; display: inline-block;" href="<?php echo(SITE_URL);?>/<?php echo($path);?>"><?php echo(htmlentities($metadata["title"]));?></a></small> -->
+        </h1>
+    </header>
     <?php
 }
 
@@ -24,10 +31,39 @@ function content_html($content) {
 
 function content_end($ppub) {
     ?>
-    <footer role="contentinfo">
+    <footer>
         <hr>
-        <p><strong><?php echo(htmlentities($ppub->metadata["title"]));?></strong><br/>Post authored by <?php echo(htmlentities($ppub->metadata["author"]));?>.<br/><a href="/">Return to <?php echo(PUBLICATION_NAME);?> Index</a> | <a href="/feed.rss">Subscribe to <?php echo(SITE_NAME);?> RSS</a> | <a href="?download=true">Download <?php echo(PUBLICATION_NAME);?> PPUB</a>
-        <br/><small>Powered by <a href="https://github.com/Tilo15/php-ppub">php-ppub</a>, styled with <a href="https://vanillacss.com/">Vanilla CSS</a>.</small></p>
+        <p><strong><?php echo(htmlentities($ppub->metadata["title"]));?></strong>
+        <?php if($ppub->metadata["author"] != null) {
+            preg_match("/^([^<]*(?= *<|$))<*([^>]*)>*/", $ppub->metadata["author"], $author);
+         ?>
+        <br/><?php echo(PUBLICATION_NAME);?> authored by <?php
+            if(isset($author[2]) && $author[2] != '') {
+                echo("<a href=\"mailto:".$author[2]."\">");
+                echo(htmlentities(trim($author[1])));
+                echo("</a>");
+            } else {
+                echo(htmlentities($ppub->metadata["author"]));
+            }
+        ?>.
+        <?php } if ($ppub->metadata["tags"] != null and USE_PPIX) { ?>
+        <br/>Tagged with: 
+        <?php
+            foreach(explode(" ", $ppub->metadata["tags"]) as $tag) {
+                ?>
+                <a href="<?php echo(SITE_URL);?>/?tag=<?php echo(urlencode($tag));?>"><?php echo(htmlentities($tag));?></a>
+                <?php
+            }
+        ?>
+        <?php } if ($ppub->metadata["date"] != null) { ?>
+        <br/>Last updated on <?php echo(htmlentities((new DateTime($ppub->metadata["date"]))->format(DATE_FORMAT)));?>.
+        <br/><?php } if ($ppub->metadata["copyright"] != null) { ?>
+        <?php echo($ppub->metadata["copyright"]);?>
+        <?php } if ($ppub->metadata["licence"] != null) { ?>
+        <a href="<?php echo($ppub->metadata["licence"]);?>">See Licence</a>
+        <?php } ?></p>
+        <p><a href="<?php echo(SITE_URL);?>/">Return to <?php echo(PUBLICATION_NAME);?> Index</a> | <a href="<?php echo(SITE_URL);?>/feed.rss">Subscribe to <?php echo(SITE_NAME);?> RSS</a> | <a href="?download=true">Download <?php echo(PUBLICATION_NAME);?> PPUB</a>
+        <br/><small>Powered by <a href="https://github.com/Tilo15/php-ppub">php-ppub</a> and <a href="https://parsedown.org">Parsedown</a>, styled with <a href="https://vanillacss.com/">Vanilla CSS</a>.</small></p>
     </footer>
     </body>
 </html>

+ 89 - 12
index.php

@@ -2,6 +2,71 @@
 include("config.php");
 include("ppub.php");
 
+define("INDEX_TYPE_MAIN", 0);
+define("INDEX_TYPE_TAG", 1);
+define("INDEX_TYPE_SEARCH", 2);
+
+function get_ppub_file_list() {
+    if(USE_PPIX) {
+        include_once("ppix.php");
+        $ppix = new Ppix(fopen(PUBLICATION_DIR . "/lib.ppix", 'rb'));
+        if(isset($_GET["q"])) {
+            $ids = $ppix->do_search(strtolower(str_replace("/", "", $_GET["q"])));
+            $list = array();
+            for ($i=0; $i < count($ids); $i++) { 
+                $list[$i] = $ppix->get_publication_by_id($ids[$i]);
+            }
+            return $list;
+
+        } else if(isset($_GET["tag"])) {
+            $tag = str_replace("/", "", $_GET["tag"]);
+            $tags = $ppix->get_tags();
+            $col = $tags[$tag];
+            if($col === null) {
+                return array();
+            }
+            $ids = $ppix->get_collection_by_id($col);
+            $list = array();
+            for ($i=0; $i < count($ids); $i++) { 
+                $list[$i] = $ppix->get_publication_by_id($ids[$i]);
+            }
+            return $list;
+
+        } else {
+            $count = $ppix->get_publication_count();
+            $list = array();
+            for ($i=0; $i < $count; $i++) { 
+                $list[$i] = $ppix->get_publication_by_id($i);
+            }
+            return $list;
+        }
+    }
+    else {
+        $dir = opendir(PUBLICATION_DIR . "/");
+        $list = array();
+        while($file = readdir($dir)){
+            if ($file != '.' and $file != '..' and $file != "lib.ppix"){
+                $ctime = filectime(PUBLICATION_DIR . "/" . $file) . ',' . $file;
+                $list[$ctime] = $file;
+            }
+        }
+        closedir($dir);
+        krsort($list);
+        return $list;
+    }
+}
+
+function get_tag_list() {
+    if(USE_PPIX) {
+        include_once("ppix.php");
+        $ppix = new Ppix(fopen(PUBLICATION_DIR . "/lib.ppix", 'rb'));
+        return array_keys($ppix->get_tags());
+    }
+    else {
+        return array();
+    }
+}
+
 $file = $_GET["ppub"];
 $asset = urldecode($_GET["asset"]);
 
@@ -20,18 +85,26 @@ if($file == "" or $file == "/" or $file == "feed.rss") {
         header("content-type: text/html");
         include("index_template.php");
     }
-    index_start();
-    
-    $dir = opendir(PUBLICATION_DIR . "/");
-    $list = array();
-    while($file = readdir($dir)){
-        if ($file != '.' and $file != '..'){
-            $ctime = filectime(PUBLICATION_DIR . "/" . $file) . ',' . $file;
-            $list[$ctime] = $file;
-        }
+
+    $index_type = INDEX_TYPE_MAIN;
+    $index_arg = null;
+
+    if(isset($_GET["q"])) {
+        $index_type = INDEX_TYPE_SEARCH;
+        $index_arg = str_replace("/", "", $_GET["q"]);
+    }
+
+    if(isset($_GET["tag"])) {
+        $index_type = INDEX_TYPE_TAG;
+        $index_arg = str_replace("/", "", $_GET["tag"]);
+    }
+
+    if($index_type == INDEX_TYPE_MAIN) {
+        $index_arg = get_tag_list();
     }
-    closedir($dir);
-    krsort($list);
+    
+    index_start($index_type, $index_arg);
+    $list = get_ppub_file_list();
     
     foreach ($list as $file) {
         $ppub = new Ppub();
@@ -39,6 +112,10 @@ if($file == "" or $file == "/" or $file == "feed.rss") {
         index_listing($ppub, $file);
     }
 
+    if(count($list) == 0) {
+        index_no_content($index_type, $index_arg);
+    }
+
     index_end();
     exit();
 }
@@ -77,7 +154,7 @@ if(strpos($accepts, "text/html") !== false && $asset->mimetype == "text/markdown
     include("content_template.php");
     include("Parsedown.php");
     $pd = new Parsedown();
-    content_start($ppub);
+    content_start($ppub, $file_name);
     content_html($pd->text($ppub->read_asset($asset)));
     content_end($ppub);
 }

+ 99 - 6
index_template.php

@@ -1,24 +1,112 @@
 <?php
 
-function index_start() {
+function index_start($index_type, $arg) {
 
     ?>
 
 <!DOCTYPE html>
-<html>
+<html lang="<?php echo(SITE_LANGUAGE);?>">
     <head>
         <meta charset="utf-8">
         <title><?php echo(PUBLICATION_NAME)?> Index - <?php echo(SITE_NAME);?></title>
-        <link rel="stylesheet" href="/vanilla.css">
-        <link rel="alternate" type="application/rss+xml" title="<?php echo(SITE_NAME);?> RSS Feed" href="/feed.rss" />
+        <link rel="stylesheet" href="<?php echo(SITE_URL);?>/vanilla.css">
+        <link rel="alternate" type="application/rss+xml" title="<?php echo(SITE_NAME);?> RSS Feed" href="<?php echo(SITE_URL);?>/feed.rss" />
+        <style type="text/css">
+            aside {
+                float: right;
+                border: 1px solid var(--text-color);
+                padding: 16px;
+                max-width: 100vw;
+                width: 316px;
+                margin: 0px 0px 15px 0px;
+
+            }
+            ul.tags {
+                list-style: none;
+                padding-left: 0px;
+                margin: 0px;
+            }
+            ul.tags li {
+                display: inline;
+            }
+            details summary {
+                cursor: pointer;
+            }
+            @media screen and (max-width: 75ch) {
+                aside {
+                    float: none;
+                    width: 100%;
+                }
+            }
+        </style>
     </head>
     <body>
-        <h1><?php echo(SITE_NAME);?></h1>
-        <h2><?php echo(PUBLICATION_NAME)?> Index</h2>
+
+    <header>
+        <h1><a style="color: var(--text-color); text-decoration: none;" href="<?php echo(SITE_URL);?>"><?php echo(SITE_NAME);?></a></h1>
+    </header>
+
+    <?php
+    if(USE_PPIX and $index_type == INDEX_TYPE_MAIN) {
+        ?>
+        <aside>
+            <form action="./" >
+                <label for="q">Search this site:</label><br>
+                <input style="width: 100%;" type="text" id="q" name="q" placeholder="Search...">
+            </form> 
+            <?php
+            if(count($arg) > 0) { ?>
+            <details>
+                <summary>Browse by tags</summary>
+                <ul class="tags">
+                <?php
+                foreach ($arg as $tag) {
+                    ?>
+                    <li><a href="?tag=<?php echo(urlencode($tag));?>"><?php echo(htmlentities($tag));?></a></li>
+                    <?php
+                }
+                ?>
+                </ul>
+            </details>
+            <?php } ?>
+        </aside>
+        <?php
+    }
+
+    if($index_type == INDEX_TYPE_MAIN) {
+        ?>
+         <h2><?php echo(PUBLICATION_NAME)?> Index</h2>
+        <?php
+    }
+
+    if($index_type == INDEX_TYPE_TAG) {
+        ?>
+        <h2>Tagged with <em><?php echo(htmlentities($arg));?></em></h2>
+       <?php
+    }
+
+    if($index_type == INDEX_TYPE_SEARCH) {
+        ?>
+        <form action="./">
+            <label for="q">Search query:</label><br>
+            <input style="width: 100%;" type="text" id="q" name="q" value="<?php echo(htmlentities($arg));?>">
+        </form> 
+        <!-- <hr/> -->
+        <!-- <h2>Search Results</h2> -->
+       <?php
+    }
+
+    ?>
         <dl>
     <?php
 }
 
+function index_no_content($index_type, $arg) {
+    if($index_type == INDEX_TYPE_SEARCH) {
+        echo("<p>Nothing found for search query &quot;" . htmlentities($arg) . "&quot.</p>");
+    }
+}
+
 function index_listing($ppub, $url) {
     ?>
             <dt><a href="<?php echo($url);?>"><?php echo(htmlentities($ppub->metadata["title"]));?></a></dt>
@@ -29,6 +117,11 @@ function index_listing($ppub, $url) {
 function index_end() {
     ?>
     </dl>
+    <footer>
+        <hr>
+        <p><strong><?php echo($_SERVER['SERVER_NAME'])?></strong> | <a href="<?php echo(SITE_URL);?>/feed.rss">Subscribe to <?php echo(SITE_NAME);?> RSS</a>
+        <br/><small>Powered by <a href="https://github.com/Tilo15/php-ppub">php-ppub</a> and <a href="https://parsedown.org">Parsedown</a>, styled with <a href="https://vanillacss.com/">Vanilla CSS</a>.</small></p>
+    </footer>
     </body>
 </html>
     <?php

+ 163 - 0
ppix.php

@@ -0,0 +1,163 @@
+<?php
+
+class Ppix {
+
+    private $handle;
+
+    private $locations;
+
+    public function __construct($handle) {
+        $this->handle = $handle;
+        fseek($handle, 0);
+        if(fread($handle, 5) != "PPIX\x00") {
+            throw new Exception("File did not start with PPIX magic number", 1);
+        }
+
+        $this->locations = unpack("Vpub/Vcol/Vtag/Vtre", fread($handle, 16));
+    }
+
+    public function get_publication_count() {
+        fseek($this->handle, $this->locations["pub"]);
+        return unpack("V", fread($this->handle, 4))[1];
+    }
+
+    public function get_publication_by_id($id) {
+        $location = $this->locations["pub"] + 4 + ($id * 6);
+        fseek($this->handle, $location);
+        $string_info = unpack("Vloc/vlen", fread($this->handle, 6));
+        fseek($this->handle, $string_info["loc"]);
+        return fread($this->handle, $string_info["len"]);
+    }
+
+    public function get_collection_by_id($id) {
+        $location = $this->locations["col"] + ($id * 6);
+        fseek($this->handle, $location);
+        $collection_info = unpack("Vloc/vcount", fread($this->handle, 6));
+        fseek($this->handle, $collection_info["loc"]);
+        $values = array();
+        for ($i=0; $i < $collection_info["count"]; $i++) { 
+            $values[$i] = unpack("V", fread($this->handle, 4))[1];
+        }
+        return $values;
+    }
+
+    public function get_tags_count() {
+        fseek($this->handle, $this->locations["tag"]);
+        return unpack("v", fread($this->handle, 2))[1];
+    }
+
+    public function get_tags() {
+        $count = $this->get_tags_count();
+        $tags = array();
+        for ($i=0; $i < $count; $i++) { 
+            $tag_data = unpack("Cstrlen/Vcolid", fread($this->handle, 5));
+            $tag = fread($this->handle, $tag_data["strlen"]);
+            $tags[$tag] = $tag_data["colid"];
+        }
+        return $tags;
+    }
+
+    public function find_word_matches($word) {
+        $binarr = $this->string_to_bin_arr($word);
+        $node = $this->read_tree_node($this->locations["tre"]);
+
+        foreach ($binarr as $bit) {
+            if(!$bit and $node["zero"] != 0) {
+                $node = $this->read_tree_node($node["zero"]);
+            }
+            else if($bit and $node["one"] != 0) {
+                $node = $this->read_tree_node($node["one"]);
+            }
+            else {
+                return null;
+            }
+        }
+
+        if($node["has"] == 255) {
+            return $node["col"];
+        }
+        return null;
+    }
+
+    private function find_partial_matches($bin_word) {
+        $binarr = $this->string_to_bin_arr($word);
+        $node = $this->read_tree_node($this->locations["tre"]);
+        $built_key = array();
+
+        foreach ($binarr as $bit) {
+            if(!$bit and $node["zero"] != 0) {
+                array_push($built_key, $bit);
+                $node = $this->read_tree_node($node["zero"]);
+            }
+            else if($bit and $node["one"] != 0) {
+                array_push($built_key, $bit);
+                $node = $this->read_tree_node($node["one"]);
+            }
+        }
+
+        if($node["has"] == 255) {
+            return $node["col"];
+        }
+        return null;
+    }
+
+    private function get_subkeys($key, $node) {
+        $subkeys = array();
+        if($node["has"] == 255) {
+            array_push($subkeys, $key);
+        }
+        if($node["one"] != 0) {
+            $nkey = array_merge($key);
+            array_push($nkey, true);
+            array_merge($subkeys, $this->get_subkeys($nkey, $node));
+        }
+        if($node["zero"] != 0) {
+            $nkey = array_merge($key);
+            array_push($nkey, false);
+            array_merge($subkeys, $this->get_subkeys($nkey, $node));
+        }
+        return $subkeys;
+    }
+
+    private function read_tree_node($location) {
+        fseek($this->handle, $location);
+        $data = unpack("Vzero/Chas/Vcol/Vone", fread($this->handle, 13));
+        return $data;
+    }
+
+    private function string_to_bin_arr($string) {
+        $data = array();
+        $refbits = array(1,2,4,8,16,32,64,128);
+        for ($i=0; $i < strlen($string) * 8; $i++) { 
+            $char = ord($string[intdiv($i,8)]);
+            $ref = $refbits[$i%8];
+            $data[$i] = ($char & $ref) == $ref;
+        }
+        return $data;
+    }
+
+    public function do_search($query) {
+        $words = explode(" ", $query);
+        $results = null;
+        foreach($words as $word) {
+            $col = $this->find_word_matches($word);
+            if($col == null){
+                return array();
+            }
+            $col = $this->get_collection_by_id($col);
+            if($results == null){
+                $results = $col;
+            }
+            else {
+                $results = array_intersect($results, $col);
+            }
+            if(count($results) == 0) {
+                return $results;
+            }            
+        }
+        return array_values($results);
+    }
+}
+
+
+?>

+ 1 - 1
ppub.php

@@ -28,7 +28,7 @@ class Ppub {
     public function read_file($file_path) {
         $handle = fopen($file_path, "rb");
         if(fread($handle, 5) != "ppub\n"){
-            throw new Exception("File did not start with magic number", 1);
+            throw new Exception("File did not start with PPUB magic number", 1);
         }
 
         $head_size_string = "";

+ 1 - 1
rss_template.php

@@ -1,6 +1,6 @@
 <?php
 
-function index_start() {
+function index_start($type, $arg) {
 ?>
 <?xml version='1.0' encoding='UTF-8'?>
 <rss version='2.0'>