468 lines
14 KiB
PHP
468 lines
14 KiB
PHP
<?php
|
|
/**
|
|
* Class: Theme
|
|
* Various helper functions for blog themes.
|
|
*/
|
|
class Theme {
|
|
# String: $safename
|
|
# The theme's non-camelized name.
|
|
public $safename = "";
|
|
|
|
# String: $url
|
|
# The theme's absolute URL.
|
|
public $url = "";
|
|
|
|
# String: $title
|
|
# The title for the current page.
|
|
public $title = "";
|
|
|
|
# Array: $caches
|
|
# Query caches for methods.
|
|
private $caches = array();
|
|
|
|
/**
|
|
* Function: __construct
|
|
* Populates useful attributes.
|
|
*/
|
|
private function __construct() {
|
|
$this->url = THEME_URL;
|
|
|
|
$this->safename = PREVIEWING ?
|
|
$_SESSION['theme'] :
|
|
Config::current()->theme ;
|
|
}
|
|
|
|
/**
|
|
* Function: pages_list
|
|
* Returns an array of pages with @depth@ and @children@ attributes.
|
|
*
|
|
* Parameters:
|
|
* $page_id - Page ID to start from, or zero to return all pages.
|
|
* $exclude - Page ID/s to exclude, integer or array of integers.
|
|
*/
|
|
public function pages_list($page_id = 0, $exclude = null): array {
|
|
$cache_id = serialize(array($page_id, $exclude));
|
|
|
|
if (
|
|
isset($this->caches["pages_list"][$cache_id])
|
|
) {
|
|
return $this->caches["pages_list"][$cache_id];
|
|
}
|
|
|
|
$this->caches["pages"]["flat"] = array();
|
|
$this->caches["pages"]["children"] = array();
|
|
|
|
$where = array("id not" => $exclude);
|
|
|
|
if (MAIN)
|
|
$where["show_in_list"] = true;
|
|
|
|
$pages = Page::find(
|
|
array(
|
|
"where" => $where,
|
|
"order" => "list_order ASC, title ASC"
|
|
)
|
|
);
|
|
|
|
if (empty($pages))
|
|
return $this->caches["pages_list"][$cache_id] = array();
|
|
|
|
foreach ($pages as $page) {
|
|
if ($page->parent_id != 0)
|
|
$this->caches["pages"]["children"][$page->parent_id][] = $page;
|
|
}
|
|
|
|
foreach ($pages as $page) {
|
|
if (
|
|
($page_id == 0 and $page->parent_id == 0) or
|
|
($page->id == $page_id)
|
|
)
|
|
$this->recurse_pages($page);
|
|
}
|
|
|
|
$list = $this->caches["pages"]["flat"];
|
|
return $this->caches["pages_list"][$cache_id] = $list;
|
|
}
|
|
|
|
/**
|
|
* Function: recurse_pages
|
|
* Populates the page cache and gives each page the attributes
|
|
* of @depth@ (integer, 1 or greater) and @children@ (boolean).
|
|
*
|
|
* Parameters:
|
|
* $page - Page to start recursion at.
|
|
*/
|
|
private function recurse_pages($page): void {
|
|
if (!isset($page->depth))
|
|
$page->depth = 1;
|
|
|
|
$page->children = isset(
|
|
$this->caches["pages"]["children"][$page->id]
|
|
);
|
|
|
|
$this->caches["pages"]["flat"][] = $page;
|
|
|
|
if ($page->children) {
|
|
foreach (
|
|
$this->caches["pages"]["children"][$page->id] as $child
|
|
) {
|
|
$child->depth = $page->depth + 1;
|
|
$this->recurse_pages($child);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Function: archive_list
|
|
* Generates an array listing each month with entries in the archives.
|
|
*
|
|
* Parameters:
|
|
* $limit - Maximum number of months to list.
|
|
*/
|
|
public function archives_list($limit = 12): array {
|
|
if (
|
|
isset($this->caches["archives_list"][$limit])
|
|
) {
|
|
return $this->caches["archives_list"][$limit];
|
|
}
|
|
|
|
$main = MainController::current();
|
|
$sql = SQL::current();
|
|
$feathers = Post::feathers();
|
|
$statuses = Post::statuses();
|
|
|
|
$results = $sql->select(
|
|
tables:"posts",
|
|
fields:array("created_at"),
|
|
conds:array($feathers, $statuses),
|
|
order:"created_at DESC"
|
|
)->fetchAll();
|
|
|
|
$nums = array();
|
|
|
|
foreach ($results as $result) {
|
|
$created_at = strtotime($result["created_at"]);
|
|
$this_month = strtotime(
|
|
"midnight first day of this month",
|
|
$created_at
|
|
);
|
|
|
|
if (!isset($nums[$this_month])) {
|
|
if (count($nums) == $limit)
|
|
break;
|
|
|
|
$nums[$this_month] = 0;
|
|
}
|
|
|
|
$nums[$this_month]++;
|
|
}
|
|
|
|
$list = array();
|
|
|
|
foreach ($nums as $when => $count) {
|
|
$list[] = array(
|
|
"when" => $when,
|
|
"url" => url("archive/".when("Y/m/", $when), $main),
|
|
"count" => $count
|
|
);
|
|
}
|
|
|
|
return $this->caches["archives_list"][$limit] = $list;
|
|
}
|
|
|
|
/**
|
|
* Function: recent_posts
|
|
* Generates an array of recent posts.
|
|
*
|
|
* Parameters:
|
|
* $limit - Maximum number of recent posts to list.
|
|
*/
|
|
public function recent_posts($limit = 5): array {
|
|
if (
|
|
isset($this->caches["recent_posts"][$limit])
|
|
) {
|
|
return $this->caches["recent_posts"][$limit];
|
|
}
|
|
|
|
$results = Post::find(
|
|
array(
|
|
"placeholders" => true,
|
|
"where" => array("status" => "public"),
|
|
"order" => "created_at DESC, id DESC"
|
|
)
|
|
);
|
|
|
|
$posts = array();
|
|
|
|
for ($i = 0; $i < $limit; $i++) {
|
|
if (isset($results[0][$i]))
|
|
$posts[] = new Post(
|
|
null,
|
|
array("read_from" => $results[0][$i])
|
|
);
|
|
}
|
|
|
|
return $this->caches["recent_posts"][$limit] = $posts;
|
|
}
|
|
|
|
/**
|
|
* Function: related_posts
|
|
* Ask modules to contribute to a list of related posts.
|
|
*
|
|
* Parameters:
|
|
* $post - The post to use as the basis.
|
|
* $limit - Maximum number of related posts to list.
|
|
*/
|
|
public function related_posts($post, $limit = 5): array {
|
|
if ($post->no_results)
|
|
return array();
|
|
|
|
if (
|
|
isset($this->caches["related_posts"][$post->id][$limit])
|
|
) {
|
|
return $this->caches["related_posts"][$post->id][$limit];
|
|
}
|
|
|
|
$ids = array();
|
|
|
|
Trigger::current()->filter($ids, "related_posts", $post, $limit);
|
|
|
|
if (empty($ids))
|
|
return array();
|
|
|
|
$results = Post::find(
|
|
array(
|
|
"placeholders" => true,
|
|
"where" => array("id" => array_unique($ids)),
|
|
"order" => "created_at DESC, id DESC")
|
|
);
|
|
|
|
$posts = array();
|
|
|
|
for ($i = 0; $i < $limit; $i++)
|
|
if (isset($results[0][$i]))
|
|
$posts[] = new Post(
|
|
null,
|
|
array("read_from" => $results[0][$i])
|
|
);
|
|
|
|
return $this->caches["related_posts"][$post->id][$limit] = $posts;
|
|
}
|
|
|
|
/**
|
|
* Function: file_exists
|
|
* Returns whether the specified Twig template file exists or not.
|
|
*
|
|
* Parameters:
|
|
* $name - The filename.
|
|
*/
|
|
public function file_exists($name): bool {
|
|
return file_exists(THEME_DIR.DIR.$name.".twig");
|
|
}
|
|
|
|
/**
|
|
* Function: stylesheets
|
|
* Outputs the stylesheet tags.
|
|
*/
|
|
public function stylesheets(): string {
|
|
$config = Config::current();
|
|
|
|
$stylesheets = array();
|
|
|
|
# Ask extensions to provide additional stylesheets.
|
|
Trigger::current()->filter($stylesheets, "stylesheets");
|
|
|
|
# Generate <link> tags:
|
|
$tags = array();
|
|
|
|
foreach ($stylesheets as $stylesheet)
|
|
$tags[] = '<link rel="stylesheet" href="'.
|
|
fix($stylesheet, true).
|
|
'" type="text/css" media="all">';
|
|
|
|
if (
|
|
is_dir(THEME_DIR.DIR."stylesheets") or
|
|
is_dir(THEME_DIR.DIR."css")
|
|
) {
|
|
foreach (
|
|
array_merge(
|
|
(array) glob(THEME_DIR.DIR."stylesheets".DIR."*.css"),
|
|
(array) glob(THEME_DIR.DIR."css".DIR."*.css")
|
|
) as $filepath
|
|
) {
|
|
$filename = basename($filepath);
|
|
|
|
if (empty($filename) or str_ends_with($filename, ".inc.css"))
|
|
continue;
|
|
|
|
$qdir = preg_quote(DIR, "/");
|
|
$path = preg_replace(
|
|
"/(.+)".$qdir."themes".$qdir."(.+)/",
|
|
"$2",
|
|
$filepath
|
|
);
|
|
$href = $config->chyrp_url.
|
|
"/themes/".
|
|
str_replace(DIR, "/", $path);
|
|
|
|
$tags[] = '<link rel="stylesheet" href="'.
|
|
fix($href, true).
|
|
'" type="text/css" media="all">';
|
|
}
|
|
}
|
|
|
|
return implode("\n", $tags);
|
|
}
|
|
|
|
/**
|
|
* Function: javascripts
|
|
* Outputs the JavaScript tags.
|
|
*/
|
|
public function javascripts(): string {
|
|
$config = Config::current();
|
|
$route = Route::current();
|
|
|
|
$scripts = array();
|
|
|
|
# Ask extensions to provide additional scripts.
|
|
Trigger::current()->filter($scripts, "scripts");
|
|
|
|
# Generate <script> tags:
|
|
$tags = array();
|
|
|
|
foreach ($scripts as $script)
|
|
$tags[] = '<script src="'.
|
|
fix($script, true).
|
|
'"></script>';
|
|
|
|
if (
|
|
is_dir(THEME_DIR.DIR."javascripts") or
|
|
is_dir(THEME_DIR.DIR."js")
|
|
) {
|
|
foreach (
|
|
array_merge(
|
|
(array) glob(THEME_DIR.DIR."javascripts".DIR."*.js"),
|
|
(array) glob(THEME_DIR.DIR."js".DIR."*.js")
|
|
) as $filepath
|
|
) {
|
|
$filename = basename($filepath);
|
|
|
|
if (empty($filename) or str_ends_with($filename, ".inc.js"))
|
|
continue;
|
|
|
|
$qdir = preg_quote(DIR, "/");
|
|
$path = preg_replace(
|
|
"/(.+)".$qdir."themes".$qdir."(.+)/",
|
|
"$2",
|
|
$filepath
|
|
);
|
|
|
|
$href = $config->chyrp_url.
|
|
"/themes/".
|
|
str_replace(DIR, "/", $path);
|
|
|
|
$tags[] = '<script src="'.
|
|
fix($href, true).
|
|
'"></script>';
|
|
}
|
|
}
|
|
|
|
return javascripts().implode("\n", $tags);
|
|
}
|
|
|
|
/**
|
|
* Function: feeds
|
|
* Outputs the feeds and other general purpose <link> tags.
|
|
*/
|
|
public function feeds(): string {
|
|
$config = Config::current();
|
|
$route = Route::current();
|
|
$main = MainController::current();
|
|
|
|
# Generate the main feed that appears everywhere.
|
|
$links = array(
|
|
array(
|
|
"href" => url("feed", $main),
|
|
"type" => BlogFeed::type(),
|
|
"title" => $config->name
|
|
)
|
|
);
|
|
|
|
# Generate a feed for this route action if it seems appropriate.
|
|
if (
|
|
$route->action != "index" and
|
|
!$main->feed and
|
|
!empty($main->context["posts"])
|
|
) {
|
|
# Rewind to page 1 (most recent) if the posts are paginated.
|
|
$page_url = ($main->context["posts"] instanceof Paginator) ?
|
|
$main->context["posts"]->prev_page_url(1) :
|
|
self_url() ;
|
|
|
|
$feed_url = ($config->clean_urls) ?
|
|
rtrim($page_url, "/")."/feed/"
|
|
:
|
|
$page_url.(
|
|
substr_count($page_url, "?") ?
|
|
"&feed" :
|
|
"?feed"
|
|
)
|
|
;
|
|
|
|
$links[] = array(
|
|
"href" => $feed_url,
|
|
"type" => BlogFeed::type(),
|
|
"title" => $this->title
|
|
);
|
|
}
|
|
|
|
# Ask extensions to provide additional links.
|
|
Trigger::current()->filter($links, "links");
|
|
|
|
# Generate <link> tags:
|
|
$tags = array();
|
|
|
|
foreach ($links as $link) {
|
|
if (!isset($link["href"]))
|
|
continue;
|
|
|
|
fallback($link["rel"], "alternate");
|
|
fallback($link["type"]);
|
|
fallback($link["title"]);
|
|
|
|
$tag = '<link rel="'.fix($link["rel"], true).
|
|
'" href="'.fix($link["href"], true).'"';
|
|
|
|
if (!empty($link["type"]))
|
|
$tag.= ' type="'.fix($link["type"], true).'"';
|
|
|
|
if (!empty($link["title"]))
|
|
$tag.= ' title="'.fix($link["title"], true).'"';
|
|
|
|
$tag.= '>';
|
|
|
|
$tags[] = $tag;
|
|
}
|
|
|
|
return implode("\n", $tags);
|
|
}
|
|
|
|
/**
|
|
* Function: load_time
|
|
* Returns the total elapsed time for this page load.
|
|
*/
|
|
public function load_time(): string {
|
|
return timer_stop();
|
|
}
|
|
|
|
/**
|
|
* Function: current
|
|
* Returns a singleton reference to the current class.
|
|
*/
|
|
public static function & current(): self {
|
|
static $instance = null;
|
|
$instance = (empty($instance)) ? new self() : $instance ;
|
|
return $instance;
|
|
}
|
|
}
|