327 lines
14 KiB
PHP
327 lines
14 KiB
PHP
<?php
|
|
|
|
$fdir = dirname(__FILE__);
|
|
require_once($fdir . '/PHPMarkdownExtra/markdown.php');
|
|
require_once($fdir . '/PHPSmartyPants/smartypants.php');
|
|
require_once($fdir . '/Updater.php');
|
|
require_once($fdir . '/Template.php');
|
|
require_once($fdir . '/Utils.php');
|
|
|
|
class Post
|
|
{
|
|
public static $blog_title = 'Untitled Blog';
|
|
public static $blog_url = 'http://no-idea.com/';
|
|
public static $blog_description = 'About my blog.';
|
|
|
|
public $source_filename = '';
|
|
public $title = '';
|
|
public $is_draft = false;
|
|
public $publish_now = false;
|
|
public $timestamp = 0;
|
|
public $slug = '';
|
|
public $type = '';
|
|
public $headers = array();
|
|
public $tags = array();
|
|
public $body = '';
|
|
|
|
public $year;
|
|
public $month;
|
|
public $day;
|
|
public $offset_in_day;
|
|
|
|
public $is_first_post_on_this_date = false; // Used for index-page rendering
|
|
|
|
public static function from_files(array $filenames)
|
|
{
|
|
$posts = array();
|
|
$last_date = false;
|
|
foreach ($filenames as $f) {
|
|
if (! file_exists($f)) continue;
|
|
$post = new Post($f);
|
|
$date = $post->year . $post->month . $post->day;
|
|
if ($date != $last_date) {
|
|
$post->is_first_post_on_this_date = true;
|
|
$last_date = $date;
|
|
}
|
|
$posts[] = $post;
|
|
}
|
|
return $posts;
|
|
}
|
|
|
|
public static function next_sequence_number_for_day($filename_prefix)
|
|
{
|
|
$max = 0;
|
|
foreach (glob($filename_prefix . '*' . Updater::$post_extension) as $filename) {
|
|
$n = intval(substr(substring_after($filename, $filename_prefix), 0, 2));
|
|
if ($n > $max) $max = $n;
|
|
}
|
|
return str_pad($max + 1, 2, '0', STR_PAD_LEFT);
|
|
}
|
|
|
|
public function __construct($source_filename, $is_draft = -1 /* auto */)
|
|
{
|
|
$this->source_filename = $source_filename;
|
|
$this->is_draft = ($is_draft === -1 ? (false !== strpos($source_filename, 'drafts/') || false !== strpos($source_filename, 'pages/')) : $is_draft);
|
|
$this->timestamp = filemtime($source_filename);
|
|
|
|
$segments = preg_split( '/\R\R/', trim(file_get_contents($source_filename)), 2);
|
|
if (! isset($segments[1])) $segments[1] = '';
|
|
|
|
if (count($segments) > 1) {
|
|
// Read headers for Tag, Type values
|
|
$headers = explode("\n", $segments[0]);
|
|
$has_title_yet = false;
|
|
foreach ($headers as $header) {
|
|
if (isset($header[0]) && $header[0] == '=') {
|
|
$has_title_yet = true;
|
|
continue;
|
|
}
|
|
|
|
if (! $has_title_yet) {
|
|
$has_title_yet = true;
|
|
$this->title = $header;
|
|
continue;
|
|
}
|
|
|
|
if ($this->is_draft && strtolower($header) == 'publish-now') {
|
|
$this->publish_now = true;
|
|
continue;
|
|
}
|
|
|
|
$fields = explode(':', $header, 2);
|
|
if (count($fields) < 2) continue;
|
|
$fname = strtolower($fields[0]);
|
|
$fields[1] = trim($fields[1]);
|
|
if ($fname == 'tags') {
|
|
$this->tags = self::parse_tag_str($fields[1]);
|
|
} else if ($fname == 'type') {
|
|
$this->type = str_replace('|', ' ', $fields[1]);
|
|
} else if ($fname == 'published') {
|
|
$this->timestamp = strtotime($fields[1]);
|
|
} else {
|
|
$this->headers[$fname] = $fields[1];
|
|
}
|
|
|
|
if (isset($this->headers['link'])) $this->type = 'link';
|
|
}
|
|
array_shift($segments);
|
|
}
|
|
|
|
$this->body = isset($segments[0]) ? $segments[0] : '';
|
|
|
|
$filename = basename($source_filename);
|
|
$filename_datestr = substr($filename, 0, 8);
|
|
if (is_numeric($filename_datestr)) {
|
|
$this->year = intval(substr($filename_datestr, 0, 4));
|
|
$this->month = intval(substr($filename_datestr, 4, 2));
|
|
$this->day = intval(substr($filename_datestr, 6, 2));
|
|
$this->offset_in_day = intval(substr($filename, 9, 2));
|
|
$this->slug = ltrim(substr($filename, 11, -(strlen(Updater::$post_extension))), '-');
|
|
} else {
|
|
$this->year = intval(idate('Y', $this->timestamp));
|
|
$this->month = intval(idate('m', $this->timestamp));
|
|
$this->day = intval(idate('d', $this->timestamp));
|
|
$posts_in_ym = Updater::posts_in_year_month($this->year, $this->month);
|
|
$this->offset_in_day = isset($posts_in_ym[$this->day]) ? count($posts_in_ym[$this->day]) + 1 : 1;
|
|
$this->slug = substring_before($filename, '.');
|
|
}
|
|
|
|
if (! $this->is_draft && $source_filename != $this->expected_source_filename()) {
|
|
error_log("Expected filename mismatch:\nfilename: $source_filename\nexpected: " . $this->expected_source_filename());
|
|
}
|
|
}
|
|
|
|
public function expected_source_filename($for_new_file = false)
|
|
{
|
|
$padded_month = str_pad($this->month, 2, '0', STR_PAD_LEFT);
|
|
$padded_day = str_pad($this->day, 2, '0', STR_PAD_LEFT);
|
|
|
|
$prefix =
|
|
Updater::$source_path . '/posts/' .
|
|
$this->year . '/' . $padded_month . '/' .
|
|
$this->year . $padded_month . $padded_day . '-'
|
|
;
|
|
|
|
if ($for_new_file) $padded_offset = self::next_sequence_number_for_day($prefix);
|
|
else $padded_offset = str_pad($this->offset_in_day, 2, '0', STR_PAD_LEFT);
|
|
|
|
if ($for_new_file) {
|
|
$slug = trim(preg_replace('/[^a-z0-9-]+/ms', '-', strtolower($this->slug)), '-');
|
|
if (! $slug) $slug = 'post';
|
|
} else {
|
|
$slug = $this->slug;
|
|
}
|
|
|
|
return $prefix . $padded_offset . '-' . strtolower($slug) . Updater::$post_extension;
|
|
}
|
|
|
|
public function normalized_source()
|
|
{
|
|
$out_headers = $this->headers;
|
|
|
|
if ($this->is_draft) unset($out_headers['published']);
|
|
else $out_headers['published'] = date('Y-m-d h:i:sa', $this->timestamp);
|
|
$source = $this->title . "\n" . str_repeat('=', max(10, min(40, strlen($this->title)))) . "\n";
|
|
if ($this->type) $out_headers['type'] = $this->type;
|
|
if ($this->tags) $out_headers['tags'] = implode(', ', $this->tags);
|
|
foreach ($out_headers as $k => $v) $source .= ucfirst($k) . ': ' . $v . "\n";
|
|
$source .= "\n" . $this->body;
|
|
return $source;
|
|
}
|
|
|
|
public function array_for_template()
|
|
{
|
|
$tags = array();
|
|
foreach ($this->tags as $tag) { $tags[$tag] = array('post-tag' => $tag); }
|
|
|
|
// Convert relative image references to absolute so index pages work
|
|
$base_uri = '/' . $this->year . '/' . str_pad($this->month, 2, '0', STR_PAD_LEFT) . '/' . str_pad($this->day, 2, '0', STR_PAD_LEFT);
|
|
|
|
return array_merge(
|
|
$this->headers,
|
|
array(
|
|
'post-title' => html_entity_decode(SmartyPants($this->title), ENT_QUOTES, 'UTF-8'),
|
|
'post-slug' => $this->slug,
|
|
'post-timestamp' => $this->timestamp,
|
|
'post-rss-date' => date('D, d M Y H:i:s T', $this->timestamp),
|
|
'post-body' => SmartyPants(Markdown($this->body)),
|
|
'post-tags' => $tags,
|
|
'post-type' => $this->type,
|
|
'post-permalink' => $base_uri . '/' . $this->slug,
|
|
'post-permalink-or-link' => isset($this->headers['link']) && $this->headers['link'] ? $this->headers['link'] : $base_uri . '/' . $this->slug,
|
|
'post-absolute-permalink' => rtrim(self::$blog_url, '/') . $base_uri . '/' . $this->slug,
|
|
'post-absolute-permalink-or-link' => rtrim(self::$blog_url, '/') . (isset($this->headers['link']) && $this->headers['link'] ? $this->headers['link'] : $base_uri . '/' . $this->slug),
|
|
|
|
'post-is-first-on-date' => $this->is_first_post_on_this_date ? 'yes' : '',
|
|
)
|
|
);
|
|
}
|
|
|
|
public function write_page()
|
|
{
|
|
$t = new Template(Updater::$page_template);
|
|
$t->content = array(
|
|
'page-title' => html_entity_decode(SmartyPants($this->title), ENT_QUOTES, 'UTF-8'),
|
|
'page-body' => SmartyPants(Markdown($this->body)),
|
|
'blog-title' => html_entity_decode(SmartyPants(self::$blog_title), ENT_QUOTES, 'UTF-8'),
|
|
'blog-url' => self::$blog_url,
|
|
'blog-description' => html_entity_decode(SmartyPants(self::$blog_description), ENT_QUOTES, 'UTF-8'),
|
|
'page-type' => 'page',
|
|
'archives' => array(),
|
|
'previous_page_url' => false,
|
|
'next_page_url' => false,
|
|
);
|
|
$output_html = $t->outputHTML();
|
|
|
|
if (! file_exists(Updater::$dest_path)) mkdir_as_parent_owner(Updater::$dest_path, 0755, true);
|
|
file_put_contents_as_dir_owner(Updater::$dest_path . '/' . $this->slug, $output_html);
|
|
}
|
|
|
|
public function write_permalink_page($draft = false)
|
|
{
|
|
$post_data = $this->array_for_template();
|
|
$post_data['post-is-first-on-date'] = true;
|
|
|
|
$t = new Template(Updater::$permalink_template);
|
|
$t->content = array_merge(
|
|
$post_data,
|
|
array(
|
|
'page-title' => html_entity_decode(SmartyPants(self::$blog_title), ENT_QUOTES, 'UTF-8'),
|
|
'blog-title' => html_entity_decode(SmartyPants(self::$blog_title), ENT_QUOTES, 'UTF-8'),
|
|
'blog-url' => self::$blog_url,
|
|
'blog-description' => html_entity_decode(SmartyPants(self::$blog_description), ENT_QUOTES, 'UTF-8'),
|
|
'page-type' => 'post',
|
|
'posts' => array($post_data),
|
|
'post' => $post_data,
|
|
'archives' => array(),
|
|
'previous_page_url' => false,
|
|
'next_page_url' => false,
|
|
)
|
|
);
|
|
$output_html = $t->outputHTML();
|
|
|
|
if (! $draft || Updater::$write_public_drafts) {
|
|
$output_www_path = $draft ? '/drafts' : dirname($post_data['post-permalink']);
|
|
$output_path = Updater::$dest_path . $output_www_path;
|
|
if (! file_exists($output_path)) mkdir_as_parent_owner($output_path, 0755, true);
|
|
file_put_contents_as_dir_owner(Updater::$dest_path . ($draft ? '/drafts/' . $this->slug : $post_data['post-permalink']), $output_html);
|
|
}
|
|
|
|
if ($draft) file_put_contents_as_dir_owner(Updater::$source_path . '/drafts/_previews/' . $this->slug . '.html', $output_html);
|
|
}
|
|
|
|
public static function write_index($dest_path, $title, $type, array $posts, $template = 'main.html', $archive_array = false, $seq_count = 0)
|
|
{
|
|
$posts_data = array();
|
|
foreach ($posts as $p) $posts_data[] = $p->array_for_template();
|
|
|
|
$t = new Template($template);
|
|
$t->content = array(
|
|
'page-title' => html_entity_decode(SmartyPants($title), ENT_QUOTES, 'UTF-8'),
|
|
'blog-title' => html_entity_decode(SmartyPants(self::$blog_title), ENT_QUOTES, 'UTF-8'),
|
|
'blog-url' => self::$blog_url,
|
|
'blog-description' => html_entity_decode(SmartyPants(self::$blog_description), ENT_QUOTES, 'UTF-8'),
|
|
'page-type' => $type,
|
|
'posts' => $posts_data,
|
|
'previous_page_url' => false,
|
|
'next_page_url' => $seq_count > 1 ? substring_after(substring_before($dest_path, '.', true), Updater::$dest_path) . '-2' : false,
|
|
'archives' => $archive_array ? $archive_array : array(),
|
|
);
|
|
$output_html = $t->outputHTML();
|
|
|
|
$output_path = dirname($dest_path);
|
|
if (! file_exists($output_path)) mkdir_as_parent_owner($output_path, 0755, true);
|
|
file_put_contents_as_dir_owner($dest_path, $output_html);
|
|
}
|
|
|
|
public function write_index_sequence($dest_path, $title, $type, array $posts, $template = 'main.html', $archive_array = false, $posts_per_page = 20)
|
|
{
|
|
$sequence = 0;
|
|
$new_dest_path = $dest_path;
|
|
// $dest_uri = substring_after(substring_before($dest_path, '.', true), Updater::$dest_path);
|
|
$dest_uri = substring_after(substring_after($dest_path, '/', true), Updater::$dest_path);
|
|
$total_sequences = ceil(count($posts) / $posts_per_page);
|
|
while ($posts) {
|
|
$sequence++;
|
|
$seq_array = array_splice($posts, 0, $posts_per_page);
|
|
if ($sequence == 1) continue; // skip writing the redundant "-1" page
|
|
|
|
$new_dest_path = $dest_path . '-' . $sequence . '.html';
|
|
|
|
$posts_data = array();
|
|
foreach ($seq_array as $p) $posts_data[] = $p->array_for_template();
|
|
|
|
$t = new Template($template);
|
|
$t->content = array(
|
|
'page-title' => html_entity_decode(SmartyPants($title), ENT_QUOTES, 'UTF-8'),
|
|
'blog-title' => html_entity_decode(SmartyPants(self::$blog_title), ENT_QUOTES, 'UTF-8'),
|
|
'blog-url' => self::$blog_url,
|
|
'blog-description' => html_entity_decode(SmartyPants(self::$blog_description), ENT_QUOTES, 'UTF-8'),
|
|
'page-type' => $type,
|
|
'posts' => $posts_data,
|
|
'previous_page_url' => $sequence != 1 ? ($sequence == 2 ? $dest_uri : $dest_uri . '-' . ($sequence - 1)) : false,
|
|
'next_page_url' => $sequence < $total_sequences ? $dest_uri . '-' . ($sequence + 1) : false,
|
|
'archives' => $archive_array ? $archive_array : array(),
|
|
);
|
|
$output_html = $t->outputHTML();
|
|
|
|
$output_path = dirname($new_dest_path);
|
|
if (! file_exists($output_path)) mkdir_as_parent_owner($output_path, 0755, true);
|
|
file_put_contents_as_dir_owner($new_dest_path, $output_html);
|
|
}
|
|
|
|
return $total_sequences;
|
|
}
|
|
|
|
public static function parse_tag_str($tag_str)
|
|
{
|
|
// Tags are comma-separated, and any spaces between multiple words in a tag will be converted to underscores for URLs
|
|
if (! strlen($tag_str)) return array();
|
|
$tags = array_map('trim', explode(',', strtolower($tag_str)));
|
|
$tags = str_replace(' ', '_', $tags);
|
|
$tags = array_unique(array_filter($tags, 'strlen'));
|
|
sort($tags);
|
|
return $tags;
|
|
}
|
|
}
|