url = THEME_URL; $this->safename = PREVIEWING ? $_SESSION['theme'] : Config::current()->theme ; } /** * Function: pages_list * Returns a simple array of pages with @depth@ and @children@ attributes. * * Parameters: * $page_id - Page ID to use as the basis. * $exclude - Page ID to exclude from the list. */ 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")); 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); } return $this->caches["pages_list"][$cache_id] = $this->caches["pages"]["flat"]; } /** * Function: recurse_pages * Populates the page cache and gives each page @depth@ and @children@ attributes. * * Parameters: * $page - Page to start recursion at. */ private function recurse_pages($page): void { $page->depth = isset($page->depth) ? $page->depth : 1 ; $page->children = isset($this->caches["pages"]["children"][$page->id]); $this->caches["pages"]["flat"][] = $page; if (isset($this->caches["pages"]["children"][$page->id])) 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" => $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 tags: $tags = array(); foreach ($stylesheets as $stylesheet) $tags[] = ''; 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 substr_count($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[] = ''; } } 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 '; 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 substr_count($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[] = ''; } } return javascripts().implode("\n", $tags); } /** * Function: feeds * Outputs the feeds and other general purpose 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 tags: $tags = array(); foreach ($links as $link) { if (!isset($link["href"])) continue; fallback($link["rel"], "alternate"); fallback($link["type"]); fallback($link["title"]); $tag = '