path = $path; $this->type = $type; $this->id = $id; $this->title = $title; $this->category = $category; $this->url = $url; $this->date = $date; $this->source_filename = $source_filename; $this->target_filename = $target_filename; } } function garden_make_process_items($output_directory, $content_paths) { $output_items = []; foreach ($content_paths as $item) { $id = garden_slug($item->filename); $target_extension = $item->extension; $ignore_file = false; $type = GardenItemType::Raw; switch ($item->extension) { case 'php': $ignore_file = true; break; case 'md': $type = GardenItemType::Article; $target_extension = 'html'; break; case 'png': case 'jpg': case 'jpeg': case 'gif': $type = GardenItemType::Image; break; } if ($ignore_file == true) { continue; } $target_basename = $item->filename; if ($target_extension != '') { $target_basename .= '.' . $target_extension; } $category_components = explode(DIRECTORY_SEPARATOR, $item->path); $category = count($category_components) >= 2 ? $category_components[1] : ""; $url = GARDEN_SITE_BASE_URL . garden_url($item->path, $target_basename); $target_path = garden_path($output_directory, garden_url($item->path, $target_basename)); $date = filemtime($item->full_path); $output_items[] = new GardenItem($item, $type, $id, $item->filename, $category, $url, $date, $item->full_path, $target_path); } return $output_items; } /////////////////////////////////////////////////////////////////////////////// // Paths /////////////////////////////////////////////////////////////////////////////// class GardenContentPath { public $full_path; public $root; public $path; public $basename; public $filename; public $extension; public function __construct($full_path, $root, $path, $path_parts) { $this->full_path = $full_path; $this->root = $root; $this->path = $path; $this->basename = $path_parts['basename']; $this->filename = $path_parts['filename']; $this->extension = $path_parts['extension']; } } function garden_find_content_files($content_dir) { $content_dir = realpath($content_dir); $scan_paths = ['']; $output_paths = []; while (count($scan_paths) > 0) { $scan_path = array_shift($scan_paths); $path_contents = scandir(garden_path($content_dir, $scan_path)); foreach ($path_contents as $item) { if (str_starts_with($item, '.')) { continue; } $full_path = garden_path($content_dir, $scan_path, $item); if (is_dir($full_path)) { $scan_paths[] = garden_path($scan_path, $item); continue; } $path_parts = pathinfo($full_path); $output_paths[] = new GardenContentPath($full_path, $content_dir, $scan_path, $path_parts); } } return $output_paths; } /////////////////////////////////////////////////////////////////////////////// // Directories /////////////////////////////////////////////////////////////////////////////// function garden_make_directories($content_items) { foreach ($content_items as $item) { $path_parts = pathinfo($item->target_filename); $directory = $path_parts['dirname']; if (is_dir($directory ) == false) { mkdir($directory , 0777, true); // FIXME, permissions... } } return $content_items; } function garden_move_raw($content_items) { foreach ($content_items as $item) { if ($item->type != GardenItemType::Raw && $item->type != GardenItemType::Image) { // FIXME, do we need to copy images? continue; } $success = copy($item->source_filename, $item->target_filename); if ($success != true) { error('Failed to copy file: filename="', $item->source_filename, '"'); } } return $content_items; } /////////////////////////////////////////////////////////////////////////////// // Template /////////////////////////////////////////////////////////////////////////////// function garden_template_render($name, $variables = null) { global $garden_template_base, $garden_template_content; $base_template = null; $output = ''; while ($name != null) { $path = garden_path(GARDEN_TEMPLATE_DIR, $name . '.php'); $base_template = null; $base_template_variables = null; $garden_template_base_previous = $garden_template_base; $garden_template_base = function($name, $base_variables) use (&$base_template, &$base_template_variables) { $base_template = $name; $base_template_variables = $base_variables; }; $garden_template_content_previous = $garden_template_content; $garden_template_content = function() use ($output) { return $output; }; if ($variables != null) { extract($variables); } ob_start(); include($path); $output = ob_get_contents(); ob_end_clean(); $garden_template_base = $garden_template_base_previous; $garden_template_content = $garden_template_content_previous; $name = $base_template; $variables = $base_template_variables; } return $output; } /////////////////////////////////////////////////////////////////////////////// // Images /////////////////////////////////////////////////////////////////////////////// class GardenImage { public $content_item; public $width; public $height; public $url; public $target_filename; public function __construct($content_item, $width, $height, $url, $target_filename) { $this->content_item = $content_item; $this->width = $width; $this->height = $height; $this->url = $url; $this->target_filename = $target_filename; } } function garden_make_images($post_process_items) { foreach ($post_process_items as $item) { $image = new Imagick($item->content_item->source_filename); $image->thumbnailImage($item->width, $item->height); $image->writeImage($item->target_filename); } } /////////////////////////////////////////////////////////////////////////////// // Template helpers /////////////////////////////////////////////////////////////////////////////// function garden_template_base($name, $variables = null) { global $garden_template_base; $garden_template_base($name, $variables); } function garden_template_content() { global $garden_template_content; return $garden_template_content(); } function garden_site_url(...$url_segments) { return GARDEN_SITE_BASE_URL . garden_url(...$url_segments); } /////////////////////////////////////////////////////////////////////////////// // HTML /////////////////////////////////////////////////////////////////////////////// require_once(garden_path(__DIR__, 'third_party', 'parsedown', 'Parsedown.php')); class GardenExtendedParsedown extends Parsedown { public $post_process_items; private $output_directory; private $content_items; public function __construct($output_directory, $content_items) { $this->post_process_items = []; $this->output_directory = $output_directory; $this->content_items = $content_items; $this->InlineTypes['!'][] = 'Youtube'; $this->InlineTypes['!'][] = 'Image'; $this->InlineTypes['['][] = 'WikiLinks'; $this->inlineMarkerList .= '!'; $this->inlineMarkerList .= '['; } protected function inlineImage($excerpt) { if (preg_match('/^!\[\[([^\|\]]+)(\|([0-9]+)x([0-9]+))?\]\]/', $excerpt['text'], $matches)) { $image_name = $matches[1]; $image_width = count($matches) == 5 ? $matches[3] : null; $image_height = count($matches) == 5 ? $matches[4] : null; if ($image_name == null) { return; } $image_content_item = null; foreach ($this->content_items as $content_item) { if ($content_item->path->basename == $image_name) { $image_content_item = $content_item; break; } } if ($image_content_item == null) { return; } $target_url = $image_content_item->url; if ($image_width != null && $image_height != null) { $original_path = $image_content_item->path; $target_basename = $original_path->filename . '_' . $image_width . 'x' . $image_height . '.png'; $target_url = GARDEN_SITE_BASE_URL . garden_url($original_path->path, $target_basename); $target_path = garden_path($this->output_directory, garden_url($original_path->path, $target_basename)); $post_process_item = new GardenImage($image_content_item, $image_width, $image_height, $target_url, $target_path); $this->post_process_items[] = $post_process_item; } return array( 'extent' => strlen($matches[0]), 'element' => array( 'name' => 'img', 'text' => '', 'attributes' => array( 'src' => $target_url, ), ), ); } } protected function inlineYoutube($excerpt) { if (preg_match('/^!\[\[yt:([^\]]+)\]\]/', $excerpt['text'], $matches)) { $video_id = $matches[1]; if ($video_id == null) { return; } return array( 'extent' => strlen($matches[0]), 'element' => array( 'name' => 'iframe', 'text' => '', 'attributes' => array( 'class' => "video", 'type' => "text/html", //'width' => "640", //'height' => "360", 'src' => "https://www.youtube.com/embed/" . $video_id, 'frameborder' => "0", 'loading' => "lazy", 'referrerpolicy' => "no-referrer", 'sandbox' => "allow-same-origin allow-scripts", ), ), ); } } protected function inlineWikiLinks($excerpt) { if (preg_match('/^\[\[(.+)\]\]/', $excerpt['text'], $matches)) { $target_title = $matches[1]; if ($target_title == null) { return; } $target_url = null; foreach ($this->content_items as $content_item) { if ($content_item->title == $target_title) { $target_url = $content_item->url; break; } } if ($target_url == null) { return; } return array( 'extent' => strlen($matches[0]), 'element' => array( 'name' => 'a', 'text' => $target_title, 'attributes' => array( 'href' => $target_url, ), ), ); } } } function garden_generate_html($output_directory, $content_items) { $output_items = []; $categorised_items = []; foreach ($content_items as $item) { if ($item->type != GardenItemType::Article) { continue; } if (isset($categorised_items[$item->category]) == false) { $categorised_items[$item->category] = []; } $categorised_items[$item->category][] = $item; } foreach ($content_items as $item) { if ($item->type != GardenItemType::Article) { continue; } $markdown_data = garden_read_file($item->source_filename); $parsedown = new GardenExtendedParsedown($output_directory, $content_items); $markdown_html = $parsedown->text($markdown_data); if (count($parsedown->post_process_items) > 0) { array_push($output_items, ...$parsedown->post_process_items); } $html_data = garden_template_render('article', [ 'article' => $item, 'article_content' => $markdown_html, 'categorised_items' => $categorised_items ]); garden_write_file($item->target_filename, $html_data); } return $output_items; } /////////////////////////////////////////////////////////////////////////////// // Main /////////////////////////////////////////////////////////////////////////////// function garden() { $content_files = garden_find_content_files(GARDEN_CONTENT_DIR); array_push($content_files, ...garden_find_content_files(GARDEN_TEMPLATE_DIR)); $process_items = garden_make_process_items(GARDEN_OUTPUT_DIR, $content_files); garden_make_directories($process_items); garden_move_raw($process_items); $post_process_items = garden_generate_html(GARDEN_OUTPUT_DIR, $process_items); garden_make_images($post_process_items); } /////////////////////////////////////////////////////////////////////////////// // Main code! /////////////////////////////////////////////////////////////////////////////// assert($argc >= 2); // First parameter needs to be the configuration php $config_file = $argv[1]; output_debug("Will use configuration: file='", $config_file, "'"); require_once($config_file); garden();