Userguide.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. <?php
  2. /**
  3. * Kohana user guide and api browser.
  4. *
  5. * @package Kohana/Userguide
  6. * @category Controller
  7. * @author Kohana Team
  8. * @copyright (c) Kohana Team
  9. * @license https://koseven.ga/LICENSE.md
  10. */
  11. abstract class Kohana_Controller_Userguide extends Controller_Template {
  12. public $template = 'userguide/template';
  13. // Routes
  14. protected $media;
  15. protected $api;
  16. protected $guide;
  17. public function before()
  18. {
  19. parent::before();
  20. if ($this->request->action() === 'media')
  21. {
  22. // Do not template media files
  23. $this->auto_render = FALSE;
  24. }
  25. else
  26. {
  27. // Grab the necessary routes
  28. $this->media = Route::get('docs/media');
  29. $this->guide = Route::get('docs/guide');
  30. // Set the base URL for links and images
  31. Kodoc_Markdown::$base_url = URL::site($this->guide->uri()).'/';
  32. Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/';
  33. }
  34. // Default show_comments to config value
  35. $this->template->show_comments = Kohana::$config->load('userguide.show_comments');
  36. }
  37. // List all modules that have userguides
  38. public function index()
  39. {
  40. $this->template->title = "Userguide";
  41. $this->template->breadcrumb = ['User Guide'];
  42. $this->template->content = View::factory('userguide/index', ['modules' => $this->_modules()]);
  43. $this->template->menu = View::factory('userguide/menu', ['modules' => $this->_modules()]);
  44. // Don't show disqus on the index page
  45. $this->template->show_comments = FALSE;
  46. }
  47. // Display an error if a page isn't found
  48. public function error($message)
  49. {
  50. $this->response->status(404);
  51. $this->template->title = "Userguide - Error";
  52. $this->template->content = View::factory('userguide/error',['message' => $message]);
  53. // Don't show disqus on error pages
  54. $this->template->show_comments = FALSE;
  55. // If we are in a module and that module has a menu, show that
  56. if ($module = $this->request->param('module') AND $menu = $this->file($module.'/menu') AND Kohana::$config->load('userguide.modules.'.$module.'.enabled'))
  57. {
  58. // Namespace the markdown parser
  59. Kodoc_Markdown::$base_url = URL::site($this->guide->uri()).'/'.$module.'/';
  60. Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/'.$module.'/';
  61. $this->template->menu = Kodoc_Markdown::markdown($this->_get_all_menu_markdown());
  62. $this->template->breadcrumb = [
  63. $this->guide->uri() => 'User Guide',
  64. $this->guide->uri(['module' => $module]) => Kohana::$config->load('userguide.modules.'.$module.'.name'),
  65. 'Error'
  66. ];
  67. }
  68. // If we are in the api browser, show the menu and show the api browser in the breadcrumbs
  69. elseif (Route::name($this->request->route()) == 'docs/api')
  70. {
  71. $this->template->menu = Kodoc::menu();
  72. // Bind the breadcrumb
  73. $this->template->breadcrumb = [
  74. $this->guide->uri(['page' => NULL]) => 'User Guide',
  75. $this->request->route()->uri() => 'API Browser',
  76. 'Error'
  77. ];
  78. }
  79. // Otherwise, show the userguide module menu on the side
  80. else
  81. {
  82. $this->template->menu = View::factory('userguide/menu',['modules' => $this->_modules()]);
  83. $this->template->breadcrumb = [$this->request->route()->uri() => 'User Guide','Error'];
  84. }
  85. }
  86. public function action_docs()
  87. {
  88. $module = $this->request->param('module');
  89. $page = $this->request->param('page');
  90. // Trim trailing slash
  91. $page = rtrim($page, '/');
  92. // If no module provided in the url, show the user guide index page, which lists the modules.
  93. if ( ! $module)
  94. {
  95. return $this->index();
  96. }
  97. // If this module's userguide pages are disabled, show the error page
  98. if ( ! Kohana::$config->load('userguide.modules.'.$module.'.enabled'))
  99. {
  100. return $this->error('That module doesn\'t exist, or has userguide pages disabled.');
  101. }
  102. // Prevent "guide/module" and "guide/module/index" from having duplicate content
  103. if ($page == 'index')
  104. {
  105. return $this->error('Userguide page not found');
  106. }
  107. // If a module is set, but no page was provided in the url, show the index page
  108. if ( ! $page)
  109. {
  110. $page = 'index';
  111. }
  112. // Find the markdown file for this page
  113. $file = $this->file($module.'/'.$page);
  114. // If it's not found, show the error page
  115. if ( ! $file)
  116. {
  117. return $this->error('Userguide page not found');
  118. }
  119. // Namespace the markdown parser
  120. Kodoc_Markdown::$base_url = URL::site($this->guide->uri()).'/'.$module.'/';
  121. Kodoc_Markdown::$image_url = URL::site($this->media->uri()).'/'.$module.'/';
  122. // Set the page title
  123. $this->template->title = ($page == 'index')
  124. ? Kohana::$config->load('userguide.modules.'.$module.'.name')
  125. : $this->title($page);
  126. // Parse the page contents into the template
  127. Kodoc_Markdown::$show_toc = TRUE;
  128. $this->template->content = Kodoc_Markdown::markdown(file_get_contents($file));
  129. Kodoc_Markdown::$show_toc = FALSE;
  130. // Attach this module's menu to the template
  131. $this->template->menu = Kodoc_Markdown::markdown($this->_get_all_menu_markdown());
  132. // Bind the breadcrumb
  133. $this->template->bind('breadcrumb', $breadcrumb);
  134. // Bind the copyright
  135. $this->template->copyright = Kohana::$config->load('userguide.modules.'.$module.'.copyright');
  136. // Add the breadcrumb trail
  137. $breadcrumb = [];
  138. $breadcrumb[$this->guide->uri()] = 'User Guide';
  139. $breadcrumb[$this->guide->uri(['module' => $module])] = Kohana::$config->load('userguide.modules.'.$module.'.name');
  140. // TODO try and get parent category names (from menu). Regex magic or javascript dom stuff perhaps?
  141. // Only add the current page title to breadcrumbs if it isn't the index, otherwise we get repeats.
  142. if ($page != 'index')
  143. {
  144. $breadcrumb[] = $this->template->title;
  145. }
  146. }
  147. public function action_api()
  148. {
  149. // Enable the missing class autoloader. If a class cannot be found a
  150. // fake class will be created that extends Kodoc_Missing
  151. spl_autoload_register(['Kodoc_Missing', 'create_class']);
  152. // Get the class from the request
  153. $class = $this->request->param('class');
  154. // If no class was passed to the url, display the API index page
  155. if ( ! $class)
  156. {
  157. $this->template->title = 'Table of Contents';
  158. $this->template->content = View::factory('userguide/api/toc')
  159. ->set('classes', Kodoc::class_methods())
  160. ->set('route', $this->request->route());
  161. }
  162. else
  163. {
  164. // Create the Kodoc_Class version of this class.
  165. $_class = Kodoc_Class::factory($class);
  166. // If the class requested and the actual class name are different
  167. // (different case, orm vs ORM, auth vs Auth) redirect
  168. if ($_class->class->name != $class)
  169. {
  170. $this->redirect($this->request->route()->uri(['class'=>$_class->class->name]));
  171. }
  172. // If this classes immediate parent is Kodoc_Missing, then it should 404
  173. if ($_class->class->getParentClass() AND $_class->class->getParentClass()->name == 'Kodoc_Missing')
  174. return $this->error('That class was not found. Check your url and make sure that the module with that class is enabled.');
  175. // If this classes package has been disabled via the config, 404
  176. if ( ! Kodoc::show_class($_class))
  177. return $this->error('That class is in package that is hidden. Check the <code>api_packages</code> config setting.');
  178. // Everything is fine, display the class.
  179. $this->template->title = $class;
  180. $this->template->content = View::factory('userguide/api/class')
  181. ->set('doc', $_class)
  182. ->set('route', $this->request->route());
  183. }
  184. // Attach the menu to the template
  185. $this->template->menu = Kodoc::menu();
  186. // Bind the breadcrumb
  187. $this->template->bind('breadcrumb', $breadcrumb);
  188. // Add the breadcrumb
  189. $breadcrumb = [];
  190. $breadcrumb[$this->guide->uri(['page' => NULL])] = 'User Guide';
  191. $breadcrumb[$this->request->route()->uri()] = 'API Browser';
  192. $breadcrumb[] = $this->template->title;
  193. }
  194. public function action_media()
  195. {
  196. // Get the file path from the request
  197. $file = $this->request->param('file');
  198. // Find the file extension
  199. $ext = pathinfo($file, PATHINFO_EXTENSION);
  200. // Remove the extension from the filename
  201. $file = substr($file, 0, -(strlen($ext) + 1));
  202. if ($file = Kohana::find_file('media/guide', $file, $ext))
  203. {
  204. // Check if the browser sent an "if-none-match: <etag>" header, and tell if the file hasn't changed
  205. $this->check_cache(sha1($this->request->uri()).filemtime($file));
  206. // Send the file content as the response
  207. $this->response->body(file_get_contents($file));
  208. // Set the proper headers to allow caching
  209. $this->response->headers('content-length', filesize($file));
  210. $this->response->headers('content-type', File::mime_by_ext($ext));
  211. $this->response->headers('last-modified', date('r', filemtime($file)));
  212. }
  213. else
  214. {
  215. // Return a 404 status
  216. $this->response->status(404);
  217. }
  218. }
  219. public function after()
  220. {
  221. if ($this->auto_render)
  222. {
  223. // Get the media route
  224. $media = Route::get('docs/media');
  225. // Add styles
  226. $this->template->styles = [
  227. $media->uri(['file' => 'css/print.css']) => 'print',
  228. $media->uri(['file' => 'css/screen.css']) => 'screen',
  229. $media->uri(['file' => 'css/kodoc.css']) => 'screen',
  230. $media->uri(['file' => 'css/shCore.css']) => 'screen',
  231. $media->uri(['file' => 'css/shThemeKodoc.css']) => 'screen',
  232. ];
  233. // Add scripts
  234. $this->template->scripts = [
  235. $media->uri(['file' => 'js/jquery.min.js']),
  236. $media->uri(['file' => 'js/jquery.cookie.js']),
  237. $media->uri(['file' => 'js/kodoc.js']),
  238. // Syntax Highlighter
  239. $media->uri(['file' => 'js/shCore.js']),
  240. $media->uri(['file' => 'js/shBrushPhp.js']),
  241. ];
  242. // Add languages
  243. $this->template->translations = Kohana::message('userguide', 'translations');
  244. }
  245. return parent::after();
  246. }
  247. /**
  248. * Locates the appropriate markdown file for a given guide page. Page URLS
  249. * can be specified in one of three forms:
  250. *
  251. * * userguide/adding
  252. * * userguide/adding.md
  253. * * userguide/adding.markdown
  254. *
  255. * In every case, the userguide will search the cascading file system paths
  256. * for the file guide/userguide/adding.md.
  257. *
  258. * @param string $page The relative URL of the guide page
  259. * @return string
  260. */
  261. public function file($page)
  262. {
  263. // Strip optional .md or .markdown suffix from the passed filename
  264. $info = pathinfo($page);
  265. if (isset($info['extension'])
  266. AND (($info['extension'] === 'md') OR ($info['extension'] === 'markdown')))
  267. {
  268. $page = $info['dirname'].DIRECTORY_SEPARATOR.$info['filename'];
  269. }
  270. return Kohana::find_file('guide', $page, 'md');
  271. }
  272. public function section($page)
  273. {
  274. $markdown = $this->_get_all_menu_markdown();
  275. if (preg_match('~\*{2}(.+?)\*{2}[^*]+\[[^\]]+\]\('.preg_quote($page).'\)~mu', $markdown, $matches))
  276. {
  277. return $matches[1];
  278. }
  279. return $page;
  280. }
  281. public function title($page)
  282. {
  283. $markdown = $this->_get_all_menu_markdown();
  284. if (preg_match('~\[([^\]]+)\]\('.preg_quote($page).'\)~mu', $markdown, $matches))
  285. {
  286. // Found a title for this link
  287. return $matches[1];
  288. }
  289. return $page;
  290. }
  291. protected function _get_all_menu_markdown()
  292. {
  293. // Only do this once per request...
  294. static $markdown = '';
  295. if (empty($markdown))
  296. {
  297. // Get menu items
  298. $file = $this->file($this->request->param('module').'/menu');
  299. if ($file AND $text = file_get_contents($file))
  300. {
  301. // Add spans around non-link categories. This is a terrible hack.
  302. $text = preg_replace('/^(\s*[\-\*\+]\s*)([^\[\]]+)$/m','$1<span>$2</span>',$text);
  303. $markdown .= $text;
  304. }
  305. }
  306. return $markdown;
  307. }
  308. // 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.
  309. protected function _modules()
  310. {
  311. $modules = array_reverse(Kohana::$config->load('userguide.modules'));
  312. if (isset($modules['kohana']))
  313. {
  314. $kohana = $modules['kohana'];
  315. unset($modules['kohana']);
  316. $modules = array_merge(['kohana' => $kohana], $modules);
  317. }
  318. // Remove modules that have been disabled via config
  319. foreach ($modules as $key => $value)
  320. {
  321. if ( ! Kohana::$config->load('userguide.modules.'.$key.'.enabled'))
  322. {
  323. unset($modules[$key]);
  324. }
  325. }
  326. return $modules;
  327. }
  328. } // End Userguide