Merge pull request #53 from @Mytho
[kohana-userguide.git] / classes / Kohana / Controller / Userguide.php
blob5b6d15098da761b01734b2a1ce749c0cacd7e345
1 <?php defined('SYSPATH') or die('No direct script access.');
2 /**
3 * Kohana user guide and api browser.
5 * @package Kohana/Userguide
6 * @category Controller
7 * @author Kohana Team
8 * @copyright (c) 2008-2013 Kohana Team
9 * @license http://kohanaframework.org/license
11 abstract class Kohana_Controller_Userguide extends Controller_Template {
13 public $template = 'userguide/template';
15 // Routes
16 protected $media;
17 protected $api;
18 protected $guide;
20 public function before()
22 parent::before();
24 if ($this->request->action() === 'media')
26 // Do not template media files
27 $this->auto_render = FALSE;
29 else
31 // Grab the necessary routes
32 $this->media = Route::get('docs/media');
33 $this->guide = Route::get('docs/guide');
35 // Set the base URL for links and images
36 Kodoc_Markdown::$base_url = URL::site($this->guide->uri()).'/';
37 Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/';
40 // Default show_comments to config value
41 $this->template->show_comments = Kohana::$config->load('userguide.show_comments');
44 // List all modules that have userguides
45 public function index()
47 $this->template->title = "Userguide";
48 $this->template->breadcrumb = array('User Guide');
49 $this->template->content = View::factory('userguide/index', array('modules' => $this->_modules()));
50 $this->template->menu = View::factory('userguide/menu', array('modules' => $this->_modules()));
52 // Don't show disqus on the index page
53 $this->template->show_comments = FALSE;
56 // Display an error if a page isn't found
57 public function error($message)
59 $this->response->status(404);
60 $this->template->title = "Userguide - Error";
61 $this->template->content = View::factory('userguide/error',array('message' => $message));
63 // Don't show disqus on error pages
64 $this->template->show_comments = FALSE;
66 // If we are in a module and that module has a menu, show that
67 if ($module = $this->request->param('module') AND $menu = $this->file($module.'/menu') AND Kohana::$config->load('userguide.modules.'.$module.'.enabled'))
69 // Namespace the markdown parser
70 Kodoc_Markdown::$base_url = URL::site($this->guide->uri()).'/'.$module.'/';
71 Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/'.$module.'/';
73 $this->template->menu = Kodoc_Markdown::markdown($this->_get_all_menu_markdown());
74 $this->template->breadcrumb = array(
75 $this->guide->uri() => 'User Guide',
76 $this->guide->uri(array('module' => $module)) => Kohana::$config->load('userguide.modules.'.$module.'.name'),
77 'Error'
80 // If we are in the api browser, show the menu and show the api browser in the breadcrumbs
81 elseif (Route::name($this->request->route()) == 'docs/api')
83 $this->template->menu = Kodoc::menu();
85 // Bind the breadcrumb
86 $this->template->breadcrumb = array(
87 $this->guide->uri(array('page' => NULL)) => 'User Guide',
88 $this->request->route()->uri() => 'API Browser',
89 'Error'
92 // Otherwise, show the userguide module menu on the side
93 else
95 $this->template->menu = View::factory('userguide/menu',array('modules' => $this->_modules()));
96 $this->template->breadcrumb = array($this->request->route()->uri() => 'User Guide','Error');
100 public function action_docs()
102 $module = $this->request->param('module');
103 $page = $this->request->param('page');
105 // Trim trailing slash
106 $page = rtrim($page, '/');
108 // If no module provided in the url, show the user guide index page, which lists the modules.
109 if ( ! $module)
111 return $this->index();
114 // If this module's userguide pages are disabled, show the error page
115 if ( ! Kohana::$config->load('userguide.modules.'.$module.'.enabled'))
117 return $this->error('That module doesn\'t exist, or has userguide pages disabled.');
120 // Prevent "guide/module" and "guide/module/index" from having duplicate content
121 if ($page == 'index')
123 return $this->error('Userguide page not found');
126 // If a module is set, but no page was provided in the url, show the index page
127 if ( ! $page)
129 $page = 'index';
132 // Find the markdown file for this page
133 $file = $this->file($module.'/'.$page);
135 // If it's not found, show the error page
136 if ( ! $file)
138 return $this->error('Userguide page not found');
141 // Namespace the markdown parser
142 Kodoc_Markdown::$base_url = URL::site($this->guide->uri()).'/'.$module.'/';
143 Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/'.$module.'/';
145 // Set the page title
146 $this->template->title = ($page == 'index')
147 ? Kohana::$config->load('userguide.modules.'.$module.'.name')
148 : $this->title($page);
150 // Parse the page contents into the template
151 Kodoc_Markdown::$show_toc = TRUE;
152 $this->template->content = Kodoc_Markdown::markdown(file_get_contents($file));
153 Kodoc_Markdown::$show_toc = FALSE;
155 // Attach this module's menu to the template
156 $this->template->menu = Kodoc_Markdown::markdown($this->_get_all_menu_markdown());
158 // Bind the breadcrumb
159 $this->template->bind('breadcrumb', $breadcrumb);
161 // Bind the copyright
162 $this->template->copyright = Kohana::$config->load('userguide.modules.'.$module.'.copyright');
164 // Add the breadcrumb trail
165 $breadcrumb = array();
166 $breadcrumb[$this->guide->uri()] = 'User Guide';
167 $breadcrumb[$this->guide->uri(array('module' => $module))] = Kohana::$config->load('userguide.modules.'.$module.'.name');
169 // TODO try and get parent category names (from menu). Regex magic or javascript dom stuff perhaps?
171 // Only add the current page title to breadcrumbs if it isn't the index, otherwise we get repeats.
172 if ($page != 'index')
174 $breadcrumb[] = $this->template->title;
178 public function action_api()
180 // Enable the missing class autoloader. If a class cannot be found a
181 // fake class will be created that extends Kodoc_Missing
182 spl_autoload_register(array('Kodoc_Missing', 'create_class'));
184 // Get the class from the request
185 $class = $this->request->param('class');
187 // If no class was passed to the url, display the API index page
188 if ( ! $class)
190 $this->template->title = 'Table of Contents';
192 $this->template->content = View::factory('userguide/api/toc')
193 ->set('classes', Kodoc::class_methods())
194 ->set('route', $this->request->route());
196 else
198 // Create the Kodoc_Class version of this class.
199 $_class = Kodoc_Class::factory($class);
201 // If the class requested and the actual class name are different
202 // (different case, orm vs ORM, auth vs Auth) redirect
203 if ($_class->class->name != $class)
205 $this->redirect($this->request->route()->uri(array('class'=>$_class->class->name)));
208 // If this classes immediate parent is Kodoc_Missing, then it should 404
209 if ($_class->class->getParentClass() AND $_class->class->getParentClass()->name == 'Kodoc_Missing')
210 return $this->error('That class was not found. Check your url and make sure that the module with that class is enabled.');
212 // If this classes package has been disabled via the config, 404
213 if ( ! Kodoc::show_class($_class))
214 return $this->error('That class is in package that is hidden. Check the <code>api_packages</code> config setting.');
216 // Everything is fine, display the class.
217 $this->template->title = $class;
219 $this->template->content = View::factory('userguide/api/class')
220 ->set('doc', $_class)
221 ->set('route', $this->request->route());
224 // Attach the menu to the template
225 $this->template->menu = Kodoc::menu();
227 // Bind the breadcrumb
228 $this->template->bind('breadcrumb', $breadcrumb);
230 // Add the breadcrumb
231 $breadcrumb = array();
232 $breadcrumb[$this->guide->uri(array('page' => NULL))] = 'User Guide';
233 $breadcrumb[$this->request->route()->uri()] = 'API Browser';
234 $breadcrumb[] = $this->template->title;
237 public function action_media()
239 // Get the file path from the request
240 $file = $this->request->param('file');
242 // Find the file extension
243 $ext = pathinfo($file, PATHINFO_EXTENSION);
245 // Remove the extension from the filename
246 $file = substr($file, 0, -(strlen($ext) + 1));
248 if ($file = Kohana::find_file('media/guide', $file, $ext))
250 // Check if the browser sent an "if-none-match: <etag>" header, and tell if the file hasn't changed
251 $this->check_cache(sha1($this->request->uri()).filemtime($file));
253 // Send the file content as the response
254 $this->response->body(file_get_contents($file));
256 // Set the proper headers to allow caching
257 $this->response->headers('content-type', File::mime_by_ext($ext));
258 $this->response->headers('last-modified', date('r', filemtime($file)));
260 else
262 // Return a 404 status
263 $this->response->status(404);
267 public function after()
269 if ($this->auto_render)
271 // Get the media route
272 $media = Route::get('docs/media');
274 // Add styles
275 $this->template->styles = array(
276 $media->uri(array('file' => 'css/print.css')) => 'print',
277 $media->uri(array('file' => 'css/screen.css')) => 'screen',
278 $media->uri(array('file' => 'css/kodoc.css')) => 'screen',
279 $media->uri(array('file' => 'css/shCore.css')) => 'screen',
280 $media->uri(array('file' => 'css/shThemeKodoc.css')) => 'screen',
283 // Add scripts
284 $this->template->scripts = array(
285 $media->uri(array('file' => 'js/jquery.min.js')),
286 $media->uri(array('file' => 'js/jquery.cookie.js')),
287 $media->uri(array('file' => 'js/kodoc.js')),
288 // Syntax Highlighter
289 $media->uri(array('file' => 'js/shCore.js')),
290 $media->uri(array('file' => 'js/shBrushPhp.js')),
293 // Add languages
294 $this->template->translations = Kohana::message('userguide', 'translations');
297 return parent::after();
301 * Locates the appropriate markdown file for a given guide page. Page URLS
302 * can be specified in one of three forms:
304 * * userguide/adding
305 * * userguide/adding.md
306 * * userguide/adding.markdown
308 * In every case, the userguide will search the cascading file system paths
309 * for the file guide/userguide/adding.md.
311 * @param string $page The relative URL of the guide page
312 * @return string
314 public function file($page)
317 // Strip optional .md or .markdown suffix from the passed filename
318 $info = pathinfo($page);
319 if (isset($info['extension'])
320 AND (($info['extension'] === 'md') OR ($info['extension'] === 'markdown')))
322 $page = $info['dirname'].DIRECTORY_SEPARATOR.$info['filename'];
324 return Kohana::find_file('guide', $page, 'md');
327 public function section($page)
329 $markdown = $this->_get_all_menu_markdown();
331 if (preg_match('~\*{2}(.+?)\*{2}[^*]+\[[^\]]+\]\('.preg_quote($page).'\)~mu', $markdown, $matches))
333 return $matches[1];
336 return $page;
339 public function title($page)
341 $markdown = $this->_get_all_menu_markdown();
343 if (preg_match('~\[([^\]]+)\]\('.preg_quote($page).'\)~mu', $markdown, $matches))
345 // Found a title for this link
346 return $matches[1];
349 return $page;
352 protected function _get_all_menu_markdown()
354 // Only do this once per request...
355 static $markdown = '';
357 if (empty($markdown))
359 // Get menu items
360 $file = $this->file($this->request->param('module').'/menu');
362 if ($file AND $text = file_get_contents($file))
364 // Add spans around non-link categories. This is a terrible hack.
365 $text = preg_replace('/^(\s*[\-\*\+]\s*)([^\[\]]+)$/m','$1<span>$2</span>',$text);
366 $markdown .= $text;
371 return $markdown;
374 // Get the list of modules from the config, and reverses it so it displays in the order the modules are added, but move Kohana to the top.
375 protected function _modules()
377 $modules = array_reverse(Kohana::$config->load('userguide.modules'));
379 if (isset($modules['kohana']))
381 $kohana = $modules['kohana'];
382 unset($modules['kohana']);
383 $modules = array_merge(array('kohana' => $kohana), $modules);
386 // Remove modules that have been disabled via config
387 foreach ($modules as $key => $value)
389 if ( ! Kohana::$config->load('userguide.modules.'.$key.'.enabled'))
391 unset($modules[$key]);
395 return $modules;
398 } // End Userguide