4 _____ _ _____ _ _ _____ _
5 / ____| | | / ____(_) | / ____| | |
6 | | __ __ _ _ __ __| | ___ _ __ | (___ _| |_ ___ | | __ ___ _ __ ___ _ __ __ _| |_ ___ _ __
7 | | |_ |/ _` | '__/ _` |/ _ \ '_ \ \___ \| | __/ _ \ | | |_ |/ _ \ '_ \ / _ \ '__/ _` | __/ _ \| '__|
8 | |__| | (_| | | | (_| | __/ | | | ____) | | || __/ | |__| | __/ | | | __/ | | (_| | || (_) | |
9 \_____|\__,_|_| \__,_|\___|_| |_| |_____/|_|\__\___| \_____|\___|_| |_|\___|_| \__,_|\__\___/|_|
13 function output_debug(...$str_segments) {
14 $str = join('', $str_segments);
18 function output_error(...$str_segments) {
19 $str = join('', $str_segments);
23 ///////////////////////////////////////////////////////////////////////////////
25 ///////////////////////////////////////////////////////////////////////////////
27 function garden_path(...$path_segments) {
29 foreach ($path_segments as $path_segment) {
30 $inner_segments = explode(DIRECTORY_SEPARATOR, $path_segment);
31 foreach ($inner_segments as $inner_segment) {
32 if ($inner_segment != '') {
33 $segments[] = $inner_segment;
37 array_unshift($segments, '');
38 return join(DIRECTORY_SEPARATOR, $segments);
41 ///////////////////////////////////////////////////////////////////////////////
43 ///////////////////////////////////////////////////////////////////////////////
45 function garden_slug($str) {
46 $str = preg_replace('/\s+/', '-', $str);
47 $str = preg_replace('/\-+/', '-', $str);
48 $str = preg_replace('/[^\w\s\-\.]+/', '', $str);
49 $str = strtolower($str);
53 function garden_url(...$url_segments) {
55 foreach ($url_segments as $url_segment) {
56 $inner_segments = explode(DIRECTORY_SEPARATOR, $url_segment);
57 foreach ($inner_segments as $inner_segment) {
58 if ($inner_segment != '') {
59 $segments[] = garden_slug($inner_segment);
63 array_unshift($segments, '');
64 return join(DIRECTORY_SEPARATOR, $segments);
67 ///////////////////////////////////////////////////////////////////////////////
69 ///////////////////////////////////////////////////////////////////////////////
71 function garden_read_file($filename) {
72 return file_get_contents($filename);
75 function garden_write_file($filename, $content) {
76 $utf8_bom = "\xEF\xBB\xBF";
77 file_put_contents($filename, $utf8_bom . $content);
80 ///////////////////////////////////////////////////////////////////////////////
82 ///////////////////////////////////////////////////////////////////////////////
98 public $source_filename;
99 public $target_filename;
101 public function __construct($path, $type, $id, $title, $category, $url, $date, $source_filename, $target_filename) {
105 $this->title = $title;
106 $this->category = $category;
109 $this->source_filename = $source_filename;
110 $this->target_filename = $target_filename;
114 function garden_make_process_items($output_directory, $content_paths) {
117 foreach ($content_paths as $item) {
118 $id = garden_slug($item->filename);
120 $target_extension = $item->extension;
122 $ignore_file = false;
123 $type = GardenItemType::Raw;
124 switch ($item->extension) {
130 $type = GardenItemType::Article;
131 $target_extension = 'html';
138 $type = GardenItemType::Image;
142 if ($ignore_file == true) {
146 $target_basename = $item->filename;
147 if ($target_extension != '') {
148 $target_basename .= '.' . $target_extension;
151 $category_components = explode(DIRECTORY_SEPARATOR, $item->path);
152 $category = count($category_components) >= 2 ? $category_components[1] : "";
154 $url = GARDEN_SITE_BASE_URL . garden_url($item->path, $target_basename);
155 $target_path = garden_path($output_directory, garden_url($item->path, $target_basename));
157 $date = filemtime($item->full_path);
159 $output_items[] = new GardenItem($item, $type, $id, $item->filename, $category, $url, $date, $item->full_path, $target_path);
162 return $output_items;
165 ///////////////////////////////////////////////////////////////////////////////
167 ///////////////////////////////////////////////////////////////////////////////
169 class GardenContentPath {
177 public function __construct($full_path, $root, $path, $path_parts) {
178 $this->full_path = $full_path;
181 $this->basename = $path_parts['basename'];
182 $this->filename = $path_parts['filename'];
183 $this->extension = $path_parts['extension'];
187 function garden_find_content_files($content_dir) {
188 $content_dir = realpath($content_dir);
193 while (count($scan_paths) > 0) {
194 $scan_path = array_shift($scan_paths);
195 $path_contents = scandir(garden_path($content_dir, $scan_path));
196 foreach ($path_contents as $item) {
197 if (str_starts_with($item, '.')) {
201 $full_path = garden_path($content_dir, $scan_path, $item);
202 if (is_dir($full_path)) {
203 $scan_paths[] = garden_path($scan_path, $item);
207 $path_parts = pathinfo($full_path);
208 $output_paths[] = new GardenContentPath($full_path, $content_dir, $scan_path, $path_parts);
212 return $output_paths;
215 ///////////////////////////////////////////////////////////////////////////////
217 ///////////////////////////////////////////////////////////////////////////////
219 function garden_make_directories($content_items) {
220 foreach ($content_items as $item) {
221 $path_parts = pathinfo($item->target_filename);
223 $directory = $path_parts['dirname'];
224 if (is_dir($directory ) == false) {
225 mkdir($directory , 0777, true); // FIXME, permissions...
229 return $content_items;
232 function garden_move_raw($content_items) {
233 foreach ($content_items as $item) {
234 if ($item->type != GardenItemType::Raw && $item->type != GardenItemType::Image) { // FIXME, do we need to copy images?
238 $success = copy($item->source_filename, $item->target_filename);
239 if ($success != true) {
240 error('Failed to copy file: filename="', $item->source_filename, '"');
244 return $content_items;
247 ///////////////////////////////////////////////////////////////////////////////
249 ///////////////////////////////////////////////////////////////////////////////
251 function garden_template_render($name, $variables = null) {
252 global $garden_template_base, $garden_template_content;
254 $base_template = null;
257 while ($name != null) {
258 $path = garden_path(GARDEN_TEMPLATE_DIR, $name . '.php');
260 $base_template = null;
261 $base_template_variables = null;
263 $garden_template_base_previous = $garden_template_base;
264 $garden_template_base = function($name, $base_variables) use (&$base_template, &$base_template_variables) {
265 $base_template = $name;
266 $base_template_variables = $base_variables;
269 $garden_template_content_previous = $garden_template_content;
270 $garden_template_content = function() use ($output) {
274 if ($variables != null) {
280 $output = ob_get_contents();
283 $garden_template_base = $garden_template_base_previous;
284 $garden_template_content = $garden_template_content_previous;
286 $name = $base_template;
287 $variables = $base_template_variables;
293 ///////////////////////////////////////////////////////////////////////////////
295 ///////////////////////////////////////////////////////////////////////////////
298 public $content_item;
302 public $target_filename;
304 public function __construct($content_item, $width, $height, $url, $target_filename) {
305 $this->content_item = $content_item;
306 $this->width = $width;
307 $this->height = $height;
309 $this->target_filename = $target_filename;
313 function garden_make_images($post_process_items) {
314 foreach ($post_process_items as $item) {
315 $image = new Imagick($item->content_item->source_filename);
316 $image->thumbnailImage($item->width, $item->height);
317 $image->writeImage($item->target_filename);
321 ///////////////////////////////////////////////////////////////////////////////
323 ///////////////////////////////////////////////////////////////////////////////
325 function garden_template_base($name, $variables = null) {
326 global $garden_template_base;
327 $garden_template_base($name, $variables);
330 function garden_template_content() {
331 global $garden_template_content;
332 return $garden_template_content();
335 function garden_site_url(...$url_segments) {
336 return GARDEN_SITE_BASE_URL . garden_url(...$url_segments);
339 ///////////////////////////////////////////////////////////////////////////////
341 ///////////////////////////////////////////////////////////////////////////////
343 require_once(garden_path(__DIR__, 'third_party', 'parsedown', 'Parsedown.php'));
345 class GardenExtendedParsedown extends Parsedown {
346 public $post_process_items;
347 private $output_directory;
348 private $content_items;
350 public function __construct($output_directory, $content_items) {
351 $this->post_process_items = [];
352 $this->output_directory = $output_directory;
353 $this->content_items = $content_items;
354 $this->InlineTypes['!'][] = 'Youtube';
355 $this->InlineTypes['!'][] = 'Image';
356 $this->InlineTypes['['][] = 'WikiLinks';
357 $this->inlineMarkerList .= '!';
358 $this->inlineMarkerList .= '[';
361 protected function inlineImage($excerpt) {
362 if (preg_match('/^!\[\[([^\|\]]+)(\|([0-9]+)x([0-9]+))?\]\]/', $excerpt['text'], $matches)) {
363 $image_name = $matches[1];
364 $image_width = count($matches) == 5 ? $matches[3] : null;
365 $image_height = count($matches) == 5 ? $matches[4] : null;
367 if ($image_name == null) {
371 $image_content_item = null;
372 foreach ($this->content_items as $content_item) {
373 if ($content_item->path->basename == $image_name) {
374 $image_content_item = $content_item;
379 if ($image_content_item == null) {
383 $target_url = $image_content_item->url;
384 if ($image_width != null && $image_height != null) {
385 $original_path = $image_content_item->path;
387 $target_basename = $original_path->filename . '_' . $image_width . 'x' . $image_height . '.png';
389 $target_url = GARDEN_SITE_BASE_URL . garden_url($original_path->path, $target_basename);
390 $target_path = garden_path($this->output_directory, garden_url($original_path->path, $target_basename));
392 $post_process_item = new GardenImage($image_content_item, $image_width, $image_height, $target_url, $target_path);
393 $this->post_process_items[] = $post_process_item;
397 'extent' => strlen($matches[0]),
401 'attributes' => array(
402 'src' => $target_url,
409 protected function inlineYoutube($excerpt) {
410 if (preg_match('/^!\[\[yt:([^\]]+)\]\]/', $excerpt['text'], $matches)) {
411 $video_id = $matches[1];
413 if ($video_id == null) {
418 'extent' => strlen($matches[0]),
422 'attributes' => array(
424 'type' => "text/html",
427 'src' => "https://www.youtube.com/embed/" . $video_id,
428 'frameborder' => "0",
430 'referrerpolicy' => "no-referrer",
431 'sandbox' => "allow-same-origin allow-scripts",
438 protected function inlineWikiLinks($excerpt) {
439 if (preg_match('/^\[\[(.+)\]\]/', $excerpt['text'], $matches)) {
440 $target_title = $matches[1];
442 if ($target_title == null) {
447 foreach ($this->content_items as $content_item) {
448 if ($content_item->title == $target_title) {
449 $target_url = $content_item->url;
454 if ($target_url == null) {
459 'extent' => strlen($matches[0]),
462 'text' => $target_title,
463 'attributes' => array(
464 'href' => $target_url,
472 function garden_generate_html($output_directory, $content_items) {
475 $categorised_items = [];
476 foreach ($content_items as $item) {
477 if ($item->type != GardenItemType::Article) {
481 if (isset($categorised_items[$item->category]) == false) {
482 $categorised_items[$item->category] = [];
484 $categorised_items[$item->category][] = $item;
487 foreach ($content_items as $item) {
488 if ($item->type != GardenItemType::Article) {
492 $markdown_data = garden_read_file($item->source_filename);
494 $parsedown = new GardenExtendedParsedown($output_directory, $content_items);
495 $markdown_html = $parsedown->text($markdown_data);
497 if (count($parsedown->post_process_items) > 0) {
498 array_push($output_items, ...$parsedown->post_process_items);
501 $html_data = garden_template_render('article', [ 'article' => $item, 'article_content' => $markdown_html, 'categorised_items' => $categorised_items ]);
503 garden_write_file($item->target_filename, $html_data);
506 return $output_items;
509 ///////////////////////////////////////////////////////////////////////////////
511 ///////////////////////////////////////////////////////////////////////////////
514 $content_files = garden_find_content_files(GARDEN_CONTENT_DIR);
515 array_push($content_files, ...garden_find_content_files(GARDEN_TEMPLATE_DIR));
516 $process_items = garden_make_process_items(GARDEN_OUTPUT_DIR, $content_files);
517 garden_make_directories($process_items);
518 garden_move_raw($process_items);
519 $post_process_items = garden_generate_html(GARDEN_OUTPUT_DIR, $process_items);
520 garden_make_images($post_process_items);
523 ///////////////////////////////////////////////////////////////////////////////
525 ///////////////////////////////////////////////////////////////////////////////
529 // First parameter needs to be the configuration php
530 $config_file = $argv[1];
531 output_debug("Will use configuration: file='", $config_file, "'");
532 require_once($config_file);