KXStudio Website https://kx.studio/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

768 lines
32KB

  1. <?php
  2. /**
  3. * Class: Main Controller
  4. * The logic behind the Chyrp install.
  5. */
  6. class MainController {
  7. # Array: $urls
  8. # An array of clean URL => dirty URL translations.
  9. public $urls = array('|/id/([0-9]+)/|' => '/?action=view&id=$1',
  10. '|/page/(([^/]+)/)+|' => '/?action=page&url=$2',
  11. '|/search/|' => '/?action=search',
  12. '|/search/([^/]+)/|' => '/?action=search&query=$1',
  13. '|/archive/([0-9]{4})/([0-9]{2})/|'
  14. => '/?action=archive&year=$1&month=$2',
  15. '|/archive/([0-9]{4})/([0-9]{2})/([0-9]{2})/|'
  16. => '/?action=archive&year=$1&month=$2&day=$3',
  17. '|/([^/]+)/feed/([^/]+)/|' => '/?action=$1&feed&title=$2',
  18. '|/([^/]+)/feed/|' => '/?action=$1&feed');
  19. # Boolean: $displayed
  20. # Has anything been displayed?
  21. public $displayed = false;
  22. # Array: $context
  23. # Context for displaying pages.
  24. public $context = array();
  25. # Boolean: $feed
  26. # Is the visitor requesting a feed?
  27. public $feed = false;
  28. /**
  29. * Function: __construct
  30. * Loads the Twig parser into <Theme>, and sets up the theme l10n domain.
  31. */
  32. private function __construct() {
  33. $this->feed = (isset($_GET['feed']) or (isset($_GET['action']) and $_GET['action'] == "feed"));
  34. $this->post_limit = Config::current()->posts_per_page;
  35. $cache = (is_writable(INCLUDES_DIR."/caches") and
  36. !DEBUG and
  37. !PREVIEWING and
  38. !defined('CACHE_TWIG') or CACHE_TWIG);
  39. if (defined('THEME_DIR'))
  40. $this->twig = new Twig_Loader(THEME_DIR,
  41. $cache ?
  42. INCLUDES_DIR."/caches" :
  43. null) ;
  44. }
  45. /**
  46. * Function: parse
  47. * Determines the action.
  48. */
  49. public function parse($route) {
  50. $config = Config::current();
  51. if (empty($route->arg[0]) and !isset($config->routes["/"])) # If they're just at /, don't bother with all this.
  52. return $route->action = "index";
  53. # Protect non-responder functions.
  54. if (in_array($route->arg[0], array("__construct", "parse", "post_from_url", "display", "current")))
  55. show_404();
  56. # Feed
  57. if (preg_match("/\/feed\/?$/", $route->request)) {
  58. $this->feed = true;
  59. $this->post_limit = $config->feed_items;
  60. if ($route->arg[0] == "feed") # Don't set $route->action to "feed" (bottom of this function).
  61. return $route->action = "index";
  62. }
  63. # Feed with a title parameter
  64. if (preg_match("/\/feed\/([^\/]+)\/?$/", $route->request, $title)) {
  65. $this->feed = true;
  66. $this->post_limit = $config->feed_items;
  67. $_GET['title'] = $title[1];
  68. if ($route->arg[0] == "feed") # Don't set $route->action to "feed" (bottom of this function).
  69. return $route->action = "index";
  70. }
  71. # Paginator
  72. if (preg_match_all("/\/((([^_\/]+)_)?page)\/([0-9]+)/", $route->request, $page_matches)) {
  73. foreach ($page_matches[1] as $key => $page_var)
  74. $_GET[$page_var] = (int) $page_matches[4][$key];
  75. if ($route->arg[0] == $page_matches[1][0]) # Don't fool ourselves into thinking we're viewing a page.
  76. return $route->action = (isset($config->routes["/"])) ? $config->routes["/"] : "index" ;
  77. }
  78. # Viewing a post by its ID
  79. if ($route->arg[0] == "id") {
  80. $_GET['id'] = $route->arg[1];
  81. return $route->action = "id";
  82. }
  83. # Archive
  84. if ($route->arg[0] == "archive") {
  85. # Make sure they're numeric; there might be a /page/ in there.
  86. if (isset($route->arg[1]) and is_numeric($route->arg[1]))
  87. $_GET['year'] = $route->arg[1];
  88. if (isset($route->arg[2]) and is_numeric($route->arg[2]))
  89. $_GET['month'] = $route->arg[2];
  90. if (isset($route->arg[3]) and is_numeric($route->arg[3]))
  91. $_GET['day'] = $route->arg[3];
  92. return $route->action = "archive";
  93. }
  94. # Searching
  95. if ($route->arg[0] == "search") {
  96. if (isset($route->arg[1]))
  97. $_GET['query'] = $route->arg[1];
  98. return $route->action = "search";
  99. }
  100. # Custom pages added by Modules, Feathers, Themes, etc.
  101. foreach ($config->routes as $path => $action) {
  102. if (is_numeric($action))
  103. $action = $route->arg[0];
  104. preg_match_all("/\(([^\)]+)\)/", $path, $matches);
  105. if ($path != "/")
  106. $path = trim($path, "/");
  107. $escape = preg_quote($path, "/");
  108. $to_regexp = preg_replace("/\\\\\(([^\)]+)\\\\\)/", "([^\/]+)", $escape);
  109. if ($path == "/")
  110. $to_regexp = "\$";
  111. if (preg_match("/^\/{$to_regexp}/", $route->request, $url_matches)) {
  112. array_shift($url_matches);
  113. if (isset($matches[1]))
  114. foreach ($matches[1] as $index => $parameter)
  115. $_GET[$parameter] = urldecode($url_matches[$index]);
  116. $params = explode(";", $action);
  117. $action = $params[0];
  118. array_shift($params);
  119. foreach ($params as $param) {
  120. $split = explode("=", $param);
  121. $_GET[$split[0]] = oneof(@$split[1], "");
  122. }
  123. $route->action = $action;
  124. }
  125. }
  126. # Are we viewing a post?
  127. $this->post_from_url($route, $route->request);
  128. # Try viewing a page.
  129. $route->try["page"] = array($route->arg);
  130. }
  131. /**
  132. * Function: post_from_url
  133. * Check to see if we're viewing a post, and if it is, handle it.
  134. *
  135. * This can also be used for grabbing a Post from a given URL.
  136. *
  137. * Parameters:
  138. * $route - The route to respond to.
  139. * $url - If this argument is passed, it will attempt to grab a post from a given URL.
  140. * If a post is found by that URL, it will be returned.
  141. * $return_post - Return a Post?
  142. */
  143. public function post_from_url($route, $request, $return_post = false) {
  144. $config = Config::current();
  145. $post_url = $config->post_url;
  146. foreach (explode("/", $post_url) as $path)
  147. foreach (preg_split("/\(([^\)]+)\)/", $path) as $leftover) {
  148. $request = preg_replace("/".preg_quote($leftover)."/", "", $request, 1);
  149. $post_url = preg_replace("/".preg_quote($leftover)."/", "", $post_url, 1);
  150. }
  151. $args = array_map("urldecode", explode("/", trim($request, "/")));
  152. $post_url = $this->key_regexp(rtrim($post_url, "/"));
  153. $post_url_attrs = array();
  154. preg_match_all("/\(([^\/]+)\)/", $config->post_url, $parameters);
  155. if (preg_match("/".$post_url."/", rtrim($request, "/"), $matches)) {
  156. array_shift($matches);
  157. foreach ($parameters[0] as $index => $parameter)
  158. if ($parameter[0] == "(") {
  159. if ($parameter == "(id)") {
  160. $post_url_attrs = array("id" => $args[$index]);
  161. break;
  162. } else
  163. $post_url_attrs[rtrim(ltrim($parameter, "("), ")")] = $args[$index];
  164. }
  165. if ($return_post)
  166. return Post::from_url($post_url_attrs);
  167. else
  168. $route->try["view"] = array($post_url_attrs, $args);
  169. }
  170. }
  171. /**
  172. * Function: key_regexp
  173. * Converts the values in $config->post_url to regular expressions.
  174. *
  175. * Parameters:
  176. * $key - Input URL with the keys from <Post.$url_attrs>.
  177. *
  178. * Returns:
  179. * $key values replaced with their regular expressions from <Routes->$code>.
  180. */
  181. private function key_regexp($key) {
  182. Trigger::current()->filter(Post::$url_attrs, "url_code");
  183. return str_replace(array_keys(Post::$url_attrs), array_values(Post::$url_attrs), str_replace("/", "\\/", $key));
  184. }
  185. /**
  186. * Function: index
  187. * Grabs the posts for the main page.
  188. */
  189. public function index() {
  190. $this->display("pages/index",
  191. array("posts" => new Paginator(Post::find(array("placeholders" => true)),
  192. $this->post_limit)));
  193. }
  194. /**
  195. * Function: archive
  196. * Grabs the posts for the Archive page when viewing a year or a month.
  197. */
  198. public function archive() {
  199. fallback($_GET['year']);
  200. fallback($_GET['month']);
  201. fallback($_GET['day']);
  202. if (isset($_GET['year']) and isset($_GET['month']) and isset($_GET['day']))
  203. $posts = new Paginator(Post::find(array("placeholders" => true,
  204. "where" => array("YEAR(created_at)" => $_GET['year'],
  205. "MONTH(created_at)" => $_GET['month'],
  206. "DAY(created_at)" => $_GET['day']))),
  207. $this->post_limit);
  208. elseif (isset($_GET['year']) and isset($_GET['month']))
  209. $posts = new Paginator(Post::find(array("placeholders" => true,
  210. "where" => array("YEAR(created_at)" => $_GET['year'],
  211. "MONTH(created_at)" => $_GET['month']))),
  212. $this->post_limit);
  213. $sql = SQL::current();
  214. if (empty($_GET['year']) or empty($_GET['month'])) {
  215. if (!empty($_GET['year']))
  216. $timestamps = $sql->select("posts",
  217. array("DISTINCT YEAR(created_at) AS year",
  218. "MONTH(created_at) AS month",
  219. "created_at AS created_at",
  220. "id"),
  221. array("YEAR(created_at)" => $_GET['year']),
  222. array("created_at DESC", "id DESC"),
  223. array(),
  224. null,
  225. null,
  226. array("YEAR(created_at)", "MONTH(created_at)", "created_at", "id"));
  227. else
  228. $timestamps = $sql->select("posts",
  229. array("DISTINCT YEAR(created_at) AS year",
  230. "MONTH(created_at) AS month",
  231. "created_at AS created_at",
  232. "id"),
  233. null,
  234. array("created_at DESC", "id DESC"),
  235. array(),
  236. null,
  237. null,
  238. array("YEAR(created_at)", "MONTH(created_at)", "created_at", "id"));
  239. $archives = array();
  240. $archive_hierarchy = array();
  241. while ($time = $timestamps->fetchObject()) {
  242. $year = mktime(0, 0, 0, 1, 0, $time->year);
  243. $month = mktime(0, 0, 0, $time->month + 1, 0, $time->year);
  244. $posts = Post::find(array("where" => array("YEAR(created_at)" => when("Y", $time->created_at),
  245. "MONTH(created_at)" => when("m", $time->created_at))));
  246. $archives[$month] = array("posts" => $posts,
  247. "year" => $time->year,
  248. "month" => strftime("%B", $month),
  249. "timestamp" => $month,
  250. "url" => url("archive/".when("Y/m/", $time->created_at)));
  251. $archive_hierarchy[$year][$month] = $posts;
  252. }
  253. $this->display("pages/archive",
  254. array("archives" => $archives,
  255. "archive_hierarchy" => $archive_hierarchy),
  256. __("Archive"));
  257. } else {
  258. if (!is_numeric($_GET['year']) or !is_numeric($_GET['month']))
  259. error(__("Error"), __("Please enter a valid year and month."));
  260. $timestamp = mktime(0, 0, 0, $_GET['month'], oneof(@$_GET['day'], 1), $_GET['year']);
  261. $depth = isset($_GET['day']) ? "day" : (isset($_GET['month']) ? "month" : (isset($_GET['year']) ? "year" : ""));
  262. $this->display("pages/archive",
  263. array("posts" => $posts,
  264. "archive" => array("year" => $_GET['year'],
  265. "month" => strftime("%B", $timestamp),
  266. "day" => strftime("%d", $timestamp),
  267. "timestamp" => $timestamp,
  268. "depth" => $depth)),
  269. _f("Archive of %s", array(strftime("%B %Y", $timestamp))));
  270. }
  271. }
  272. /**
  273. * Function: search
  274. * Grabs the posts for a search query.
  275. */
  276. public function search() {
  277. fallback($_GET['query'], "");
  278. $config = Config::current();
  279. if ($config->clean_urls and
  280. substr_count($_SERVER['REQUEST_URI'], "?") and
  281. !substr_count($_SERVER['REQUEST_URI'], "%2F")) # Searches with / and clean URLs = server 404
  282. redirect("search/".urlencode($_GET['query'])."/");
  283. if (empty($_GET['query']))
  284. return Flash::warning(__("Please enter a search term."));
  285. list($where, $params) = keywords($_GET['query'], "post_attributes.value LIKE :query OR url LIKE :query", "posts");
  286. $results = Post::find(array("placeholders" => true,
  287. "where" => $where,
  288. "params" => $params));
  289. $ids = array();
  290. foreach ($results[0] as $result)
  291. $ids[] = $result["id"];
  292. if (!empty($ids))
  293. $posts = new Paginator(Post::find(array("placeholders" => true,
  294. "where" => array("id" => $ids))),
  295. $this->post_limit);
  296. else
  297. $posts = new Paginator(array());
  298. $this->display(array("pages/search", "pages/index"),
  299. array("posts" => $posts,
  300. "search" => $_GET['query']),
  301. fix(_f("Search results for \"%s\"", array($_GET['query']))));
  302. }
  303. /**
  304. * Function: drafts
  305. * Grabs the posts for viewing the Drafts lists.
  306. */
  307. public function drafts() {
  308. $visitor = Visitor::current();
  309. if (!$visitor->group->can("view_own_draft", "view_draft"))
  310. show_403(__("Access Denied"), __("You do not have sufficient privileges to view drafts."));
  311. $posts = new Paginator(Post::find(array("placeholders" => true,
  312. "where" => array("status" => "draft",
  313. "user_id" => $visitor->id))),
  314. $this->post_limit);
  315. $this->display(array("pages/drafts", "pages/index"),
  316. array("posts" => $posts),
  317. __("Drafts"));
  318. }
  319. /**
  320. * Function: view
  321. * Views a post.
  322. */
  323. public function view($attrs = null, $args = array()) {
  324. if (isset($attrs))
  325. $post = Post::from_url($attrs, array("drafts" => true));
  326. else
  327. $post = new Post(array("url" => @$_GET['url']), array("drafts" => true));
  328. if ($post->no_results)
  329. return false;
  330. if ((oneof(@$attrs["url"], @$attrs["clean"]) == "feed") and # do some checking to see if they're trying
  331. (count(explode("/", trim($post_url, "/"))) > count($args) or # to view the post or the post's feed.
  332. end($args) != "feed"))
  333. $this->feed = false;
  334. if (!$post->theme_exists())
  335. error(__("Error"), __("The feather theme file for this post does not exist. The post cannot be displayed."));
  336. if ($post->status == "draft")
  337. Flash::message(__("This post is a draft."));
  338. if ($post->groups() and !substr_count($post->status, "{".Visitor::current()->group->id."}"))
  339. Flash::message(_f("This post is only visible by the following groups: %s.", $post->groups()));
  340. $this->display(array("pages/view", "pages/index"),
  341. array("post" => $post, "posts" => array($post)),
  342. $post->title());
  343. }
  344. /**
  345. * Function: page
  346. * Handles page viewing.
  347. */
  348. public function page($urls = null) {
  349. if (isset($urls)) { # Viewing with clean URLs, e.g. /parent/child/child-of-child/
  350. $valids = Page::find(array("where" => array("url" => $urls)));
  351. if (count($valids) == count($urls)) { # Make sure all page slugs are valid.
  352. foreach ($valids as $page)
  353. if ($page->url == end($urls)) # Loop until we reach the last one.
  354. break;
  355. } else
  356. return false; # A "link in the chain" is broken
  357. } else
  358. $page = new Page(array("url" => $_GET['url']));
  359. if ($page->no_results)
  360. return false; # Page not found; the 404 handling is handled externally.
  361. $this->display(array("pages/page", "pages/".$page->url), array("page" => $page), $page->title);
  362. }
  363. /**
  364. * Function: rss
  365. * Redirects to /feed (backwards compatibility).
  366. */
  367. public function rss() {
  368. header("HTTP/1.1 301 Moved Permanently");
  369. redirect(oneof(@Config::current()->feed_url, url("feed")));
  370. }
  371. /**
  372. * Function: id
  373. * Views a post by its static ID.
  374. */
  375. public function id() {
  376. $post = new Post($_GET['id']);
  377. redirect($post->url());
  378. }
  379. /**
  380. * Function: toggle_admin
  381. * Toggles the Admin control panel (if available).
  382. */
  383. public function toggle_admin() {
  384. if (!isset($_SESSION['hide_admin']))
  385. $_SESSION['hide_admin'] = true;
  386. else
  387. unset($_SESSION['hide_admin']);
  388. redirect("/");
  389. }
  390. /**
  391. * Function: register
  392. * Process registration. If registration is disabled or if the user is already logged in, it will error.
  393. */
  394. public function register() {
  395. $config = Config::current();
  396. if (!$config->can_register)
  397. error(__("Registration Disabled"), __("I'm sorry, but this site is not allowing registration."));
  398. if (logged_in())
  399. error(__("Error"), __("You're already logged in."));
  400. if (!empty($_POST)) {
  401. $route = Route::current();
  402. if (empty($_POST['login']))
  403. Flash::warning(__("Please enter a username for your account."));
  404. elseif (count(User::find(array("where" => array("login" => $_POST['login'])))))
  405. Flash::warning(__("That username is already in use."));
  406. if (empty($_POST['password1']) and empty($_POST['password2']))
  407. Flash::warning(__("Password cannot be blank."));
  408. elseif ($_POST['password1'] != $_POST['password2'])
  409. Flash::warning(__("Passwords do not match."));
  410. if (empty($_POST['email']))
  411. Flash::warning(__("E-mail address cannot be blank."));
  412. elseif (!preg_match("/^[_A-z0-9-]+((\.|\+)[_A-z0-9-]+)*@[A-z0-9-]+(\.[A-z0-9-]+)*(\.[A-z]{2,4})$/", $_POST['email']))
  413. Flash::warning(__("Invalid e-mail address."));
  414. if (!Flash::exists("warning")) {
  415. $user = User::add($_POST['login'], $_POST['password1'], $_POST['email']);
  416. Trigger::current()->call("user_registered", $user);
  417. $_SESSION['user_id'] = $user->id;
  418. Flash::notice(__("Registration successful."), "/");
  419. }
  420. }
  421. $this->display("forms/user/register", array(), __("Register"));
  422. }
  423. /**
  424. * Function: login
  425. * Process logging in. If the username and password are incorrect or if the user is already logged in, it will error.
  426. */
  427. public function login() {
  428. if (logged_in())
  429. error(__("Error"), __("You're already logged in."));
  430. if (!empty($_POST)) {
  431. fallback($_POST['login']);
  432. fallback($_POST['password']);
  433. $trigger = Trigger::current();
  434. if ($trigger->exists("authenticate"))
  435. return $trigger->call("authenticate");
  436. if (!User::authenticate($_POST['login'], $_POST['password']))
  437. if (!count(User::find(array("where" => array("login" => $_POST['login'])))))
  438. Flash::warning(__("There is no user with that login name."));
  439. else
  440. Flash::warning(__("Password incorrect."));
  441. if (!Flash::exists("warning")) {
  442. $user = new User(array("login" => $_POST['login']));
  443. $_SESSION['user_id'] = $user->id;
  444. $redirect = $_SESSION['redirect_to'];
  445. unset($_SESSION['redirect_to']);
  446. Flash::notice(__("Logged in."), oneof($redirect, "/"));
  447. }
  448. }
  449. $this->display("forms/user/login", array(), __("Log In"));
  450. }
  451. /**
  452. * Function: logout
  453. * Logs the current user out. If they are not logged in, it will error.
  454. */
  455. public function logout() {
  456. if (!logged_in())
  457. error(__("Error"), __("You aren't logged in."));
  458. session_destroy();
  459. session();
  460. Flash::notice(__("Logged out."), "/");
  461. }
  462. /**
  463. * Function: controls
  464. * Updates the current user when the form is submitted. Shows an error if they aren't logged in.
  465. */
  466. public function controls() {
  467. if (!logged_in())
  468. error(__("Error"), __("You must be logged in to access this area."));
  469. if (!empty($_POST)) {
  470. $visitor = Visitor::current();
  471. $password = (!empty($_POST['new_password1']) and $_POST['new_password1'] == $_POST['new_password2']) ?
  472. User::hashPassword($_POST['new_password1']) :
  473. $visitor->password ;
  474. $visitor->update($visitor->login,
  475. $password,
  476. $_POST['email'],
  477. $_POST['full_name'],
  478. $_POST['website'],
  479. $visitor->group->id);
  480. Flash::notice(__("Your profile has been updated."), "/");
  481. }
  482. $this->display("forms/user/controls", array(), __("Controls"));
  483. }
  484. /**
  485. * Function: lost_password
  486. * Handles e-mailing lost passwords to a user's email address.
  487. */
  488. public function lost_password() {
  489. if (!empty($_POST)) {
  490. $user = new User(array("login" => $_POST['login']));
  491. if ($user->no_results) {
  492. Flash::warning(__("Invalid user specified."));
  493. return $this->display("forms/user/lost_password", array(), __("Lost Password"));
  494. }
  495. $new_password = random(16);
  496. $user->update($user->login,
  497. User::hashPassword($new_password),
  498. $user->email,
  499. $user->full_name,
  500. $user->website,
  501. $user->group_id);
  502. $sent = email($user->email,
  503. __("Lost Password Request"),
  504. _f("%s,\n\nWe have received a request for a new password for your account at %s.\n\nPlease log in with the following password, and feel free to change it once you've successfully logged in:\n\t%s",
  505. array($user->login, Config::current()->name, $new_password)));
  506. if ($sent)
  507. Flash::notice(_f("An e-mail has been sent to your e-mail address that contains a new password. Once you have logged in, you can change it at <a href=\"%s\">User Controls</a>.",
  508. array(url("controls"))));
  509. else {
  510. # Set their password back to what it was originally.
  511. $user->update($user->login,
  512. $user->password,
  513. $user->email,
  514. $user->full_name,
  515. $user->website,
  516. $user->group_id);
  517. Flash::warning(__("E-Mail could not be sent. Password change cancelled."));
  518. }
  519. }
  520. $this->display("forms/user/lost_password", array(), __("Lost Password"));
  521. }
  522. /**
  523. * Function: feed
  524. * Grabs posts for the feed.
  525. */
  526. public function feed($posts = null) {
  527. fallback($posts, Post::find(array("limit" => Config::current()->feed_items)));
  528. header("Content-Type: application/atom+xml; charset=UTF-8");
  529. if (!is_array($posts))
  530. $posts = $posts->paginated;
  531. $latest_timestamp = 0;
  532. foreach ($posts as $post)
  533. if (strtotime($post->created_at) > $latest_timestamp)
  534. $latest_timestamp = strtotime($post->created_at);
  535. require INCLUDES_DIR."/feed.php";
  536. }
  537. /**
  538. * Function: display
  539. * Display the page.
  540. *
  541. * If "posts" is in the context and the visitor requested a feed, they will be served.
  542. *
  543. * Parameters:
  544. * $file - The theme file to display.
  545. * $context - The context for the file.
  546. * $title - The title for the page.
  547. */
  548. public function display($file, $context = array(), $title = "") {
  549. if (is_array($file))
  550. for ($i = 0; $i < count($file); $i++) {
  551. $check = ($file[$i][0] == "/" or preg_match("/[a-zA-Z]:\\\/", $file[$i])) ?
  552. $file[$i] :
  553. THEME_DIR."/".$file[$i] ;
  554. if (file_exists($check.".twig") or ($i + 1) == count($file))
  555. return $this->display($file[$i], $context, $title);
  556. }
  557. $this->displayed = true;
  558. $route = Route::current();
  559. $trigger = Trigger::current();
  560. # Serve feeds.
  561. if ($this->feed) {
  562. if ($trigger->exists($route->action."_feed"))
  563. return $trigger->call($route->action."_feed", $context);
  564. if (isset($context["posts"]))
  565. return $this->feed($context["posts"]);
  566. }
  567. $this->context = array_merge($context, $this->context);
  568. $visitor = Visitor::current();
  569. $config = Config::current();
  570. $theme = Theme::current();
  571. $theme->title = $title;
  572. $this->context["theme"] = $theme;
  573. $this->context["flash"] = Flash::current();
  574. $this->context["trigger"] = $trigger;
  575. $this->context["modules"] = Modules::$instances;
  576. $this->context["feathers"] = Feathers::$instances;
  577. $this->context["title"] = $title;
  578. $this->context["site"] = $config;
  579. $this->context["visitor"] = $visitor;
  580. $this->context["route"] = Route::current();
  581. $this->context["hide_admin"] = isset($_COOKIE["hide_admin"]);
  582. $this->context["version"] = CHYRP_VERSION;
  583. $this->context["now"] = time();
  584. $this->context["debug"] = DEBUG;
  585. $this->context["POST"] = $_POST;
  586. $this->context["GET"] = $_GET;
  587. $this->context["sql_queries"] =& SQL::current()->queries;
  588. $this->context["visitor"]->logged_in = logged_in();
  589. $this->context["enabled_modules"] = array();
  590. foreach ($config->enabled_modules as $module)
  591. $this->context["enabled_modules"][$module] = true;
  592. $context["enabled_feathers"] = array();
  593. foreach ($config->enabled_feathers as $feather)
  594. $this->context["enabled_feathers"][$feather] = true;
  595. $this->context["sql_debug"] =& SQL::current()->debug;
  596. $trigger->filter($this->context, array("main_context", "main_context_".str_replace("/", "_", $file)));
  597. $file = ($file[0] == "/" or preg_match("/[a-zA-Z]:\\\/", $file)) ? $file : THEME_DIR."/".$file ;
  598. if (!file_exists($file.".twig"))
  599. error(__("Template Missing"), _f("Couldn't load template: <code>%s</code>", array($file.".twig")));
  600. try {
  601. return $this->twig->getTemplate($file.".twig")->display($this->context);
  602. } catch (Exception $e) {
  603. $prettify = preg_replace("/([^:]+): (.+)/", "\\1: <code>\\2</code>", $e->getMessage());
  604. $trace = debug_backtrace();
  605. $twig = array("file" => $e->filename, "line" => $e->lineno);
  606. array_unshift($trace, $twig);
  607. error(__("Error"), $prettify, $trace);
  608. }
  609. }
  610. /**
  611. * Function: resort
  612. * Queue a failpage in the event that none of the routes are successful.
  613. */
  614. public function resort($file, $context, $title = null) {
  615. $this->fallback = array($file, $context, $title);
  616. return false;
  617. }
  618. /**
  619. * Function: current
  620. * Returns a singleton reference to the current class.
  621. */
  622. public static function & current() {
  623. static $instance = null;
  624. return $instance = (empty($instance)) ? new self() : $instance ;
  625. }
  626. }