PageRenderTime 76ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/library/core/class.controller.php

https://github.com/wufoo/Garden
PHP | 1532 lines | 774 code | 191 blank | 567 comment | 235 complexity | 37f982ecc61afc1ff4fc53f903ffdce7 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, BSD-3-Clause, MIT

Large files files are truncated, but you can click here to view the full file

  1. <?php if (!defined('APPLICATION')) exit();
  2. /*
  3. Copyright 2008, 2009 Vanilla Forums Inc.
  4. This file is part of Garden.
  5. Garden is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
  6. Garden is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  7. You should have received a copy of the GNU General Public License along with Garden. If not, see <http://www.gnu.org/licenses/>.
  8. Contact Vanilla Forums Inc. at support [at] vanillaforums [dot] com
  9. */
  10. /**
  11. * A base class that all controllers can inherit for common controller
  12. * properties and methods.
  13. *
  14. * @author Mark O'Sullivan
  15. * @copyright 2003 Mark O'Sullivan
  16. * @license http://www.opensource.org/licenses/gpl-2.0.php GPL
  17. * @package Garden
  18. * @version @@GARDEN-VERSION@@
  19. * @namespace Garden.Core
  20. */
  21. /**
  22. * @method void Render() Render the controller's view.
  23. * @param string $View
  24. * @param string $ControllerName
  25. * @param string $ApplicationFolder
  26. * @param string $AssetName The name of the asset container that the content should be rendered in.
  27. */
  28. class Gdn_Controller extends Gdn_Pluggable {
  29. /**
  30. * The name of the application that this controller can be found in
  31. * (ie. Vanilla, Garden, etc).
  32. *
  33. * @var string
  34. */
  35. public $Application;
  36. /**
  37. * The name of the application folder that this controller can be found in
  38. * (ie. vanilla, dashboard, etc).
  39. *
  40. * @var string
  41. */
  42. public $ApplicationFolder;
  43. /**
  44. * An associative array that contains content to be inserted into the
  45. * master view. All assets are placed in this array before being passed to
  46. * the master view. If an asset's key is not called by the master view,
  47. * that asset will not be rendered.
  48. *
  49. * @var array
  50. */
  51. public $Assets;
  52. protected $_CanonicalUrl;
  53. /**
  54. * The controllers subfolder that this controller is placed in (if present).
  55. * This is defined by the dispatcher.
  56. *
  57. * @var string
  58. */
  59. public $ControllerFolder;
  60. /**
  61. * The name of the controller that holds the view (used by $this->FetchView
  62. * when retrieving the view). Default value is $this->ClassName.
  63. *
  64. * @var string
  65. */
  66. public $ControllerName;
  67. /**
  68. * A CSS class to apply to the body tag of the page. Note: you can only
  69. * assume that the master view will use this property (ie. a custom theme
  70. * may not decide to implement this property).
  71. *
  72. * @var string
  73. */
  74. public $CssClass;
  75. /**
  76. * The data that a controller method has built up from models and other calcualtions.
  77. *
  78. * @var array The data from method calls.
  79. */
  80. public $Data = array();
  81. /**
  82. * The Head module that this controller should use to add CSS files.
  83. *
  84. * @var object
  85. */
  86. public $Head;
  87. /**
  88. * The name of the master view that has been requested. Typically this is
  89. * part of the master view's file name. ie. $this->MasterView.'.master.tpl'
  90. *
  91. * @var string
  92. */
  93. public $MasterView;
  94. /**
  95. * A Menu module for rendering the main menu on each page.
  96. *
  97. * @var object
  98. */
  99. public $Menu;
  100. /**
  101. * If specified, this string will be used to identify the sort collection
  102. * in conf/modules.php to use when organizing modules within page assets.
  103. * $Configuration['Modules']['ModuleSortContainer']['AssetName'] = array('Module1', 'Module2');
  104. *
  105. * @var string
  106. */
  107. public $ModuleSortContainer;
  108. /**
  109. * The method that was requested before the dispatcher did any re-routing.
  110. *
  111. * @var string
  112. */
  113. public $OriginalRequestMethod;
  114. /**
  115. * The url to redirect the user to by ajax'd forms after the form is
  116. * successfully saved.
  117. *
  118. * @var string
  119. */
  120. public $RedirectUrl;
  121. /**
  122. * This is typically an array of arguments passed after the controller
  123. * name and controller method in the query string. Additional arguments are
  124. * parsed out by the @@Dispatcher and sent to $this->RequestArgs as an
  125. * array. If there are no additional arguments specified, this value will
  126. * remain FALSE.
  127. * ie. http://localhost/index.php?/controller_name/controller_method/arg1/arg2/arg3
  128. * translates to: array('arg1', 'arg2', 'arg3');
  129. *
  130. * @var mixed
  131. */
  132. public $RequestArgs;
  133. /**
  134. * The method that has been requested. The request method is defined by the
  135. * @@Dispatcher as the second parameter passed in the query string. In the
  136. * following example it would be "controller_method" and it relates
  137. * directly to the method that will be called in the controller. This value
  138. * is also used as $this->View unless $this->View has already been
  139. * hard-coded to be something else.
  140. * ie. http://localhost/index.php?/controller_name/controller_method/
  141. *
  142. * @var string
  143. */
  144. public $RequestMethod;
  145. /**
  146. * Reference to the Request object that spawned this controller
  147. *
  148. * @var Gdn_Request
  149. */
  150. public $Request;
  151. /**
  152. * The requested url to this controller.
  153. *
  154. * @var string
  155. */
  156. public $SelfUrl;
  157. /**
  158. * The message to be displayed on the screen by ajax'd forms after the form
  159. * is successfully saved.
  160. *
  161. * @var string
  162. */
  163. public $StatusMessage;
  164. /**
  165. * Defined by the dispatcher: SYNDICATION_RSS, SYNDICATION_ATOM, or
  166. * SYNDICATION_NONE (default).
  167. *
  168. * @var string
  169. */
  170. public $SyndicationMethod;
  171. /**
  172. * The name of the folder containing the views to be used by this
  173. * controller. This value is retrieved from the $Configuration array when
  174. * this class is instantiated. Any controller can then override the property
  175. * before render if there is a need.
  176. *
  177. * @var string
  178. */
  179. public $Theme;
  180. /**
  181. * Specific options on the currently selected theme.
  182. * @var array
  183. */
  184. public $ThemeOptions;
  185. /**
  186. * The name of the view that has been requested. Typically this is part of
  187. * the view's file name. ie. $this->View.'.php'
  188. *
  189. * @var string
  190. */
  191. public $View;
  192. /**
  193. * An array of CSS file names to search for in theme folders & include in
  194. * the page.
  195. *
  196. * @var array
  197. */
  198. protected $_CssFiles;
  199. /**
  200. * An array of JS file names to search for in app folders & include in
  201. * the page.
  202. *
  203. * @var array
  204. */
  205. protected $_JsFiles;
  206. /**
  207. * A collection of definitions that will be written to the screen in a
  208. * hidden unordered list so that JavaScript has access to them (ie. for
  209. * language translations, web root, etc).
  210. *
  211. * @var array
  212. */
  213. protected $_Definitions;
  214. /**
  215. * An enumerator indicating how the response should be delivered to the
  216. * output buffer. Options are:
  217. * DELIVERY_METHOD_XHTML: page contents are delivered as normal.
  218. * DELIVERY_METHOD_JSON: page contents and extra information delivered as JSON.
  219. * The default value is DELIVERY_METHOD_XHTML.
  220. *
  221. * @var string
  222. */
  223. protected $_DeliveryMethod;
  224. /**
  225. * An enumerator indicating what should be delivered to the screen. Options
  226. * are:
  227. * DELIVERY_TYPE_ALL: The master view and everything in the requested asset.
  228. * DELIVERY_TYPE_ASSET: Everything in the requested asset.
  229. * DELIVERY_TYPE_VIEW: Only the requested view.
  230. * DELIVERY_TYPE_BOOL: Deliver only the success status (or error) of the request
  231. * DELIVERY_TYPE_NONE: Deliver nothing
  232. * The default value is DELIVERY_TYPE_ALL.
  233. *
  234. * @var string
  235. */
  236. protected $_DeliveryType;
  237. /**
  238. * An associative array of header values to be sent to the browser before
  239. * the page is rendered.
  240. *
  241. * @var array
  242. */
  243. protected $_Headers;
  244. /**
  245. * If JSON is going to be delivered to the client (see the render method),
  246. * this property will hold the values being sent.
  247. *
  248. * @var array
  249. */
  250. protected $_Json;
  251. /**
  252. * A collection of view locations that have already been found. Used to
  253. * prevent re-finding views.
  254. *
  255. * @var array
  256. */
  257. protected $_ViewLocations;
  258. /**
  259. * Undocumented method.
  260. *
  261. * @todo Method __construct() needs a description.
  262. */
  263. public function __construct() {
  264. $this->Application = '';
  265. $this->ApplicationFolder = '';
  266. $this->Assets = array();
  267. $this->ControllerFolder = '';
  268. $this->CssClass = '';
  269. $this->Head = Gdn::Factory('Dummy');
  270. $this->MasterView = '';
  271. $this->ModuleSortContainer = '';
  272. $this->OriginalRequestMethod = '';
  273. $this->RedirectUrl = '';
  274. $this->RequestMethod = '';
  275. $this->RequestArgs = FALSE;
  276. $this->Request = FALSE;
  277. $this->SelfUrl = '';
  278. $this->StatusMessage = '';
  279. $this->SyndicationMethod = SYNDICATION_NONE;
  280. $this->Theme = Theme();
  281. $this->ThemeOptions = Gdn::Config('Garden.ThemeOptions', array());
  282. $this->View = '';
  283. $this->_CssFiles = array();
  284. $this->_JsFiles = array();
  285. $this->_Definitions = array();
  286. $this->_DeliveryMethod = DELIVERY_METHOD_XHTML;
  287. $this->_DeliveryType = DELIVERY_TYPE_ALL;
  288. $this->_Json = array();
  289. $this->_Headers = array(
  290. 'Expires' => 'Mon, 26 Jul 1997 05:00:00 GMT', // Make sure the client always checks at the server before using it's cached copy.
  291. 'X-Garden-Version' => APPLICATION.' '.APPLICATION_VERSION,
  292. 'Content-Type' => Gdn::Config('Garden.ContentType', '').'; charset='.Gdn::Config('Garden.Charset', ''), // PROPERLY ENCODE THE CONTENT
  293. 'Last-Modified' => gmdate('D, d M Y H:i:s') . ' GMT' // PREVENT PAGE CACHING: always modified (this can be overridden by specific controllers)
  294. // $Dispatcher->Header('Cache-Control', 'no-cache, must-revalidate'); // PREVENT PAGE CACHING: HTTP/1.1
  295. // $Dispatcher->Header('Pragma', 'no-cache'); // PREVENT PAGE CACHING: HTTP/1.0
  296. );
  297. parent::__construct();
  298. $this->ControllerName = strtolower($this->ClassName);
  299. }
  300. /**
  301. * Adds as asset (string) to the $this->Assets collection. The assets will
  302. * later be added to the view if their $AssetName is called by
  303. * $this->RenderAsset($AssetName) within the view.
  304. *
  305. * @param string $AssetContainer The name of the asset container to add $Asset to.
  306. * @param mixed $Asset The asset to be rendered in the view. This can be one of:
  307. * - <b>string</b>: The string will be rendered.
  308. * - </b>Gdn_IModule</b>: Gdn_IModule::Render() will be called.
  309. * @param string $AssetName The name of the asset being added. This can be
  310. * used later to sort assets before rendering.
  311. */
  312. public function AddAsset($AssetContainer, $Asset, $AssetName = '') {
  313. if (is_object($AssetName)) {
  314. return FALSE;
  315. } else if ($AssetName == '') {
  316. $this->Assets[$AssetContainer][] = $Asset;
  317. } else {
  318. if (isset($this->Assets[$AssetContainer][$AssetName]))
  319. $this->Assets[$AssetContainer][$AssetName] .= $Asset;
  320. else
  321. $this->Assets[$AssetContainer][$AssetName] = $Asset;
  322. }
  323. }
  324. /**
  325. * Adds a CSS file to search for in the theme folder(s).
  326. *
  327. * @param string $FileName The CSS file to search for.
  328. * @param string $AppFolder The application folder that should contain the CSS file. Default is to
  329. * use the application folder that this controller belongs to.
  330. * - If you specify plugins/PluginName as $AppFolder then you can contain a CSS file in a plugin's design folder.
  331. */
  332. public function AddCssFile($FileName, $AppFolder = '') {
  333. $this->_CssFiles[] = array('FileName' => $FileName, 'AppFolder' => $AppFolder);
  334. }
  335. /**
  336. * Undocumented method.
  337. *
  338. * @param string $Term
  339. * @param string $Definition
  340. * @todo Method AddDefinition(), $Term and $Definition need descriptions.
  341. */
  342. public function AddDefinition($Term, $Definition = NULL) {
  343. if(!is_null($Definition)) {
  344. // Make sure the term is a valid id.
  345. if (!preg_match('/[a-z][0-9a-z_\-]*/i', $Term))
  346. throw new Exception('Definition term must start with a letter or an underscore and consist of alphanumeric characters.');
  347. $this->_Definitions[$Term] = $Definition;
  348. }
  349. return ArrayValue($Term, $this->_Definitions);
  350. }
  351. /**
  352. * Adds a JS file to search for in the application or global js folder(s).
  353. *
  354. * @param string $FileName The CSS file to search for.
  355. * @param string $AppFolder The application folder that should contain the JS file. Default is to
  356. * use the application folder that this controller belongs to.
  357. */
  358. public function AddJsFile($FileName, $AppFolder = '') {
  359. $this->_JsFiles[] = array('FileName' => $FileName, 'AppFolder' => $AppFolder);
  360. }
  361. /**
  362. * Adds the specified module to the specified asset target. If no asset
  363. * target is defined, it will use the asset target defined by the module's
  364. * AssetTarget method.
  365. *
  366. * @param mixed $Module A module or the name of a module to add to the page.
  367. * @param string $AssetTarget
  368. * @todo $AssetTarget need the correct variable type and description.
  369. */
  370. public function AddModule($Module, $AssetTarget = '') {
  371. $this->FireEvent('BeforeAddModule');
  372. if (!is_object($Module)) {
  373. if (property_exists($this, $Module) && is_object($this->$Module)) {
  374. $Module = $this->$Module;
  375. } else {
  376. $ModuleClassExists = class_exists($Module);
  377. if ($ModuleClassExists) {
  378. // Make sure that the class implements Gdn_IModule
  379. $ReflectionClass = new ReflectionClass($Module);
  380. if ($ReflectionClass->implementsInterface("Gdn_IModule"))
  381. $Module = new $Module($this);
  382. }
  383. }
  384. }
  385. if (is_object($Module)) {
  386. $AssetTarget = ($AssetTarget == '' ? $Module->AssetTarget() : $AssetTarget);
  387. // echo '<div>adding: '.$Module->Name().' ('.(property_exists($Module, 'HtmlId') ? $Module->HtmlId : '').') to '.$AssetTarget.' <textarea>'.$Module->ToString().'</textarea></div>';
  388. $this->AddAsset($AssetTarget, $Module->ToString(), $Module->Name());
  389. }
  390. $this->FireEvent('AfterAddModule');
  391. }
  392. public function CanonicalUrl($Value = NULL) {
  393. if ($Value === NULL) {
  394. if ($this->_CanonicalUrl) {
  395. return $this->_CanonicalUrl;
  396. } else {
  397. $Parts = array(strtolower($this->ApplicationFolder));
  398. if (substr_compare($this->ControllerName, 'controller', -10, 10, TRUE) == 0)
  399. $Parts[] = substr(strtolower($this->ControllerName), 0, -10);
  400. else
  401. $Parts[] = strtolower($this->ControllerName);
  402. if (strcasecmp($this->RequestMethod, 'index') != 0)
  403. $Parts[] = strtolower($this->RequestMethod);
  404. // The default canonical url is the fully-qualified url.
  405. if (is_array($this->RequestArgs))
  406. $Parts = array_merge($Parts, $this->RequestArgs);
  407. elseif (is_string($this->RequestArgs))
  408. $Parts = trim($this->RequestArgs, '/');
  409. $Path = implode('/', $Parts);
  410. $Result = Url($Path, TRUE);
  411. return $Result;
  412. }
  413. } else {
  414. $this->_CanonicalUrl = $Value;
  415. return $Value;
  416. }
  417. }
  418. public function ClearCssFiles() {
  419. $this->_CssFiles = array();
  420. }
  421. /**
  422. * Clear all js files from the collection.
  423. */
  424. public function ClearJsFiles() {
  425. $this->_JsFiles = array();
  426. }
  427. public function CssFiles() {
  428. return $this->_CssFiles;
  429. }
  430. /** Get a value out of the controller's data array.
  431. *
  432. * @param string $Path The path to the data.
  433. * @param mixed $Default The default value if the data array doesn't contain the path.
  434. * @return mixed
  435. * @see GetValueR()
  436. */
  437. public function Data($Path, $Default = '' ) {
  438. $Result = GetValueR($Path, $this->Data, $Default);
  439. return $Result;
  440. }
  441. /**
  442. * Undocumented method.
  443. *
  444. * @todo Method DefinitionList() needs a description.
  445. */
  446. public function DefinitionList() {
  447. $Session = Gdn::Session();
  448. if (!array_key_exists('TransportError', $this->_Definitions))
  449. $this->_Definitions['TransportError'] = T('Transport error: %s', 'A fatal error occurred while processing the request.<br />The server returned the following response: %s');
  450. if (!array_key_exists('TransientKey', $this->_Definitions))
  451. $this->_Definitions['TransientKey'] = $Session->TransientKey();
  452. if (!array_key_exists('WebRoot', $this->_Definitions))
  453. $this->_Definitions['WebRoot'] = CombinePaths(array(Gdn::Request()->Domain(), Gdn::Request()->WebRoot()), '/');
  454. if (!array_key_exists('UrlFormat', $this->_Definitions))
  455. $this->_Definitions['UrlFormat'] = Url('{Path}');
  456. if (!array_key_exists('ConfirmHeading', $this->_Definitions))
  457. $this->_Definitions['ConfirmHeading'] = T('Confirm');
  458. if (!array_key_exists('ConfirmText', $this->_Definitions))
  459. $this->_Definitions['ConfirmText'] = T('Are you sure you want to do that?');
  460. if (!array_key_exists('Okay', $this->_Definitions))
  461. $this->_Definitions['Okay'] = T('Okay');
  462. if (!array_key_exists('Cancel', $this->_Definitions))
  463. $this->_Definitions['Cancel'] = T('Cancel');
  464. if (!array_key_exists('Search', $this->_Definitions))
  465. $this->_Definitions['Search'] = T('Search');
  466. $Return = '<!-- Various definitions for Javascript //-->
  467. <div id="Definitions" style="display: none;">
  468. ';
  469. foreach ($this->_Definitions as $Term => $Definition) {
  470. $Return .= '<input type="hidden" id="'.$Term.'" value="'.Gdn_Format::Form($Definition).'" />'."\n";
  471. }
  472. return $Return .'</div>';
  473. }
  474. /**
  475. * Returns the requested delivery type of the controller if $Default is not
  476. * provided. Sets and returns the delivery type otherwise.
  477. *
  478. * @param string $Default One of the DELIVERY_TYPE_* constants.
  479. */
  480. public function DeliveryType($Default = '') {
  481. if ($Default)
  482. $this->_DeliveryType = $Default;
  483. return $this->_DeliveryType;
  484. }
  485. /**
  486. * Returns the requested delivery method of the controller if $Default is not
  487. * provided. Sets and returns the delivery method otherwise.
  488. *
  489. * @param string $Default One of the DELIVERY_METHOD_* constants.
  490. */
  491. public function DeliveryMethod($Default = '') {
  492. if ($Default != '')
  493. $this->_DeliveryMethod = $Default;
  494. return $this->_DeliveryMethod;
  495. }
  496. /**
  497. * Fetches the contents of a view into a string and returns it. Returns
  498. * false on failure.
  499. *
  500. * @param string $View The name of the view to fetch. If not specified, it will use the value
  501. * of $this->View. If $this->View is not specified, it will use the value
  502. * of $this->RequestMethod (which is defined by the dispatcher class).
  503. * @param string $ControllerName The name of the controller that owns the view if it is not $this.
  504. * @param string $ApplicationFolder The name of the application folder that contains the requested controller
  505. * if it is not $this->ApplicationFolder.
  506. */
  507. public function FetchView($View = '', $ControllerName = FALSE, $ApplicationFolder = FALSE) {
  508. $ViewPath = $this->FetchViewLocation($View, $ControllerName, $ApplicationFolder);
  509. // Check to see if there is a handler for this particular extension.
  510. $ViewHandler = Gdn::Factory('ViewHandler' . strtolower(strrchr($ViewPath, '.')));
  511. $ViewContents = '';
  512. ob_start();
  513. if(is_null($ViewHandler)) {
  514. // Parse the view and place it into the asset container if it was found.
  515. include($ViewPath);
  516. } else {
  517. // Use the view handler to parse the view.
  518. $ViewHandler->Render($ViewPath, $this);
  519. }
  520. $ViewContents = ob_get_clean();
  521. return $ViewContents;
  522. }
  523. /**
  524. * Fetches the location of a view into a string and returns it. Returns
  525. * false on failure.
  526. *
  527. * @param string $View The name of the view to fetch. If not specified, it will use the value
  528. * of $this->View. If $this->View is not specified, it will use the value
  529. * of $this->RequestMethod (which is defined by the dispatcher class).
  530. * @param string $ControllerName The name of the controller that owns the view if it is not $this.
  531. * - If the controller name is FALSE then the name of the current controller will be used.
  532. * - If the controller name is an empty string then the view will be looked for in the base views folder.
  533. * @param string $ApplicationFolder The name of the application folder that contains the requested controller if it is not $this->ApplicationFolder.
  534. */
  535. public function FetchViewLocation($View = '', $ControllerName = FALSE, $ApplicationFolder = FALSE, $ThrowError = TRUE) {
  536. // Accept an explicitly defined view, or look to the method that was called on this controller
  537. if ($View == '')
  538. $View = $this->View;
  539. if ($View == '')
  540. $View = $this->RequestMethod;
  541. if ($ControllerName === FALSE)
  542. $ControllerName = $this->ControllerName;
  543. // Munge the controller folder onto the controller name if it is present.
  544. if ($this->ControllerFolder != '')
  545. $ControllerName = $this->ControllerFolder . DS . $ControllerName;
  546. if (StringEndsWith($ControllerName, 'controller', TRUE))
  547. $ControllerName = substr($ControllerName, 0, -10);
  548. if (strtolower(substr($ControllerName, 0, 4)) == 'gdn_')
  549. $ControllerName = substr($ControllerName, 4);
  550. if (!$ApplicationFolder)
  551. $ApplicationFolder = $this->ApplicationFolder;
  552. //$ApplicationFolder = strtolower($ApplicationFolder);
  553. $ControllerName = strtolower($ControllerName);
  554. if(strpos($View, DS) === FALSE) // keep explicit paths as they are.
  555. $View = strtolower($View);
  556. // If this is a syndication request, append the method to the view
  557. if ($this->SyndicationMethod == SYNDICATION_ATOM)
  558. $View .= '_atom';
  559. else if ($this->SyndicationMethod == SYNDICATION_RSS)
  560. $View .= '_rss';
  561. $LocationName = ConcatSep('/', strtolower($ApplicationFolder), $ControllerName, $View);
  562. $ViewPath = ArrayValue($LocationName, $this->_ViewLocations, FALSE);
  563. if ($ViewPath === FALSE) {
  564. // Define the search paths differently depending on whether or not we are in a plugin or application.
  565. $ApplicationFolder = trim($ApplicationFolder, '/');
  566. if (StringBeginsWith($ApplicationFolder, 'plugins/')) {
  567. $BasePath = PATH_PLUGINS;
  568. $ApplicationFolder = trim(strstr($ApplicationFolder, '/'), '/');
  569. } else {
  570. $BasePath = PATH_APPLICATIONS;
  571. $ApplicationFolder = strtolower($ApplicationFolder);
  572. }
  573. $SubPaths = array();
  574. // Define the subpath for the view.
  575. // The $ControllerName used to default to '' instead of FALSE.
  576. // This extra search is added for backwards-compatibility.
  577. if (strlen($ControllerName) > 0)
  578. $SubPaths[] = "views/$ControllerName/$View";
  579. else {
  580. $SubPaths[] = "views/$View";
  581. $SubPaths[] = "views/{$this->ControllerName}/$View";
  582. }
  583. // Views come from one of four places:
  584. $ViewPaths = array();
  585. foreach ($SubPaths as $SubPath) {
  586. // 1. An explicitly defined path to a view
  587. if (strpos($View, DS) !== FALSE)
  588. $ViewPaths[] = $View;
  589. if ($this->Theme) {
  590. // 2. Application-specific theme view. eg. /path/to/application/themes/theme_name/app_name/views/controller_name/
  591. $ViewPaths[] = PATH_THEMES."/{$this->Theme}/$ApplicationFolder/$SubPath.*";
  592. // $ViewPaths[] = CombinePaths(array(PATH_THEMES, $this->Theme, $ApplicationFolder, 'views', $ControllerName, $View . '.*'));
  593. // 3. Garden-wide theme view. eg. /path/to/application/themes/theme_name/views/controller_name/
  594. $ViewPaths[] = PATH_THEMES."/{$this->Theme}/$SubPath.*";
  595. //$ViewPaths[] = CombinePaths(array(PATH_THEMES, $this->Theme, 'views', $ControllerName, $View . '.*'));
  596. }
  597. // 4. Application/plugin default. eg. /path/to/application/app_name/views/controller_name/
  598. $ViewPaths[] = "$BasePath/$ApplicationFolder/$SubPath.*";
  599. //$ViewPaths[] = CombinePaths(array(PATH_APPLICATIONS, $ApplicationFolder, 'views', $ControllerName, $View . '.*'));
  600. }
  601. // Find the first file that matches the path.
  602. $ViewPath = FALSE;
  603. foreach($ViewPaths as $Glob) {
  604. $Paths = SafeGlob($Glob);
  605. if(is_array($Paths) && count($Paths) > 0) {
  606. $ViewPath = $Paths[0];
  607. break;
  608. }
  609. }
  610. //$ViewPath = Gdn_FileSystem::Exists($ViewPaths);
  611. $this->_ViewLocations[$LocationName] = $ViewPath;
  612. }
  613. // echo '<div>['.$LocationName.'] RETURNS ['.$ViewPath.']</div>';
  614. if ($ViewPath === FALSE && $ThrowError)
  615. trigger_error(ErrorMessage("Could not find a '$View' view for the '$ControllerName' controller in the '$ApplicationFolder' application.", $this->ClassName, 'FetchViewLocation'), E_USER_ERROR);
  616. return $ViewPath;
  617. }
  618. /**
  619. * Cleanup any remaining resources for this controller.
  620. */
  621. public function Finalize() {
  622. $Database = Gdn::Database();
  623. $Database->CloseConnection();
  624. }
  625. /**
  626. * Undocumented method.
  627. *
  628. * @param string $AssetName
  629. * @todo Method GetAsset() and $AssetName needs descriptions.
  630. */
  631. public function GetAsset($AssetName) {
  632. if(!array_key_exists($AssetName, $this->Assets))
  633. return '';
  634. if(!is_array($this->Assets[$AssetName]))
  635. return $this->Assets[$AssetName];
  636. // Include the module sort
  637. $Modules = Gdn::Config('Modules', array());
  638. if($this->ModuleSortContainer === FALSE)
  639. $ModuleSort = FALSE; // no sort wanted
  640. elseif(array_key_exists($this->ModuleSortContainer, $Modules) && array_key_exists($AssetName, $Modules[$this->ModuleSortContainer]))
  641. $ModuleSort = $Modules[$this->ModuleSortContainer][$AssetName]; // explicit sort
  642. elseif(array_key_exists($this->Application, $Modules) && array_key_exists($AssetName, $Modules[$this->Application]))
  643. $ModuleSort = $Modules[$this->Application][$AssetName]; // application default sort
  644. $ThisAssets = $this->Assets[$AssetName];
  645. $Assets = array();
  646. if(isset($ModuleSort) && is_array($ModuleSort)) {
  647. // There is a specified sort so sort by it.
  648. foreach($ModuleSort as $Name) {
  649. if(array_key_exists($Name, $ThisAssets)) {
  650. $Assets[] = $ThisAssets[$Name];
  651. unset($ThisAssets[$Name]);
  652. }
  653. }
  654. }
  655. // Pick up any leftover assets
  656. foreach($ThisAssets as $Name => $Asset) {
  657. $Assets[] = $Asset;
  658. }
  659. if(count($Assets) == 0) {
  660. return '';
  661. } elseif(count($Assets) == 1) {
  662. return $Assets[0];
  663. } else {
  664. $Result = new Gdn_ModuleCollection();
  665. $Result->Items = $Assets;
  666. return $Result;
  667. }
  668. }
  669. /**
  670. * Undocumented method.
  671. *
  672. * @todo Method GetImports() needs a description.
  673. */
  674. public function GetImports() {
  675. if(!isset($this->Uses) || !is_array($this->Uses))
  676. return;
  677. // Load any classes in the uses array and make them properties of this class
  678. foreach ($this->Uses as $Class) {
  679. if(strlen($Class) >= 4 && substr_compare($Class, 'Gdn_', 0, 4) == 0) {
  680. $Property = substr($Class, 4);
  681. } else {
  682. $Property = $Class;
  683. }
  684. // Find the class and instantiate an instance..
  685. if(Gdn::FactoryExists($Property)) {
  686. $this->$Property = Gdn::Factory($Property);
  687. } if(Gdn::FactoryExists($Class)) {
  688. // Instantiate from the factory.
  689. $this->$Property = Gdn::Factory($Class);
  690. } elseif(class_exists($Class)) {
  691. // Instantiate as an object.
  692. $ReflectionClass = new ReflectionClass($Class);
  693. // Is this class a singleton?
  694. if ($ReflectionClass->implementsInterface("ISingleton")) {
  695. eval('$this->'.$Property.' = '.$Class.'::GetInstance();');
  696. } else {
  697. $this->$Property = new $Class();
  698. }
  699. } else {
  700. trigger_error(ErrorMessage('The "'.$Class.'" class could not be found.', $this->ClassName, '__construct'), E_USER_ERROR);
  701. }
  702. }
  703. }
  704. public function GetJson() {
  705. return $this->_Json;
  706. }
  707. /**
  708. * The initialize method is called by the dispatcher after the constructor
  709. * has completed, objects have been passed along, assets have been
  710. * retrieved, and before the requested method fires. Use it in any extended
  711. * controller to do things like loading script and CSS into the head.
  712. */
  713. public function Initialize() {
  714. if (is_object($this->Menu))
  715. $this->Menu->Sort = Gdn::Config('Garden.Menu.Sort');
  716. }
  717. public function JsFiles() {
  718. return $this->_JsFiles;
  719. }
  720. /**
  721. * If JSON is going to be sent to the client, this method allows you to add
  722. * extra values to the JSON array.
  723. *
  724. * @param string $Key The name of the array key to add.
  725. * @param mixed $Value The value to be added. If null, then it won't be set.
  726. * @return mixed The value at the key.
  727. */
  728. public function Json($Key, $Value = NULL) {
  729. if(!is_null($Value)) {
  730. $this->_Json[$Key] = $Value;
  731. }
  732. return ArrayValue($Key, $this->_Json, NULL);
  733. }
  734. public function JsonTarget($Target, $Data, $Type = 'Html') {
  735. $Item = array('Target' => $Target, 'Data' => $Data, 'Type' => $Type);
  736. if(!array_key_exists('Targets', $this->_Json))
  737. $this->_Json['Targets'] = array($Item);
  738. else
  739. $this->_Json['Targets'][] = $Item;
  740. }
  741. protected $_PageName = NULL;
  742. /** Gets or sets the name of the page for the controller.
  743. * The page name is meant to be a friendly name suitable to be consumed by developers.
  744. *
  745. * @param string|NULL $Value A new value to set.
  746. */
  747. public function PageName($Value = NULL) {
  748. if ($Value !== NULL) {
  749. $this->_PageName = $Value;
  750. return $Value;
  751. }
  752. if ($this->_PageName === NULL) {
  753. if ($this->ControllerName)
  754. $Name = $this->ControllerName;
  755. else
  756. $Name = get_class($this);
  757. $Name = strtolower($Name);
  758. if (StringEndsWith($Name, 'controller', FALSE))
  759. $Name = substr($Name, 0, -strlen('controller'));
  760. return $Name;
  761. } else {
  762. return $this->_PageName;
  763. }
  764. }
  765. /**
  766. * Checks that the user has the specified permissions. If the user does not, they are redirected to the DefaultPermission route.
  767. * @param mixed $Permission A permission or array of permission names required to access this resource.
  768. * @param bool $FullMatch If $Permission is an array, $FullMatch indicates if all permissions specified are required. If false, the user only needs one of the specified permissions.
  769. * @param string $JunctionTable The name of the junction table for a junction permission.
  770. * @param in $JunctionID The ID of the junction permission.
  771. */
  772. public function Permission($Permission, $FullMatch = TRUE, $JunctionTable = '', $JunctionID = '') {
  773. $Session = Gdn::Session();
  774. // TODO: Make this work with different delivery types.
  775. if (!$Session->CheckPermission($Permission, $FullMatch, $JunctionTable, $JunctionID)) {
  776. if (!$Session->IsValid() && $this->DeliveryType() == DELIVERY_TYPE_ALL) {
  777. Redirect(Gdn::Authenticator()->SignInUrl($this->SelfUrl));
  778. } else {
  779. Gdn::Dispatcher()->Dispatch('DefaultPermission');
  780. exit();
  781. }
  782. }
  783. }
  784. /**
  785. * Removes a CSS file from the collection.
  786. *
  787. * @param string $FileName The CSS file to search for.
  788. */
  789. public function RemoveCssFile($FileName) {
  790. foreach ($this->_CssFiles as $Key => $FileInfo) {
  791. if ($FileInfo['FileName'] == $FileName) {
  792. unset($this->_CssFiles[$Key]);
  793. return;
  794. }
  795. }
  796. }
  797. /**
  798. * Removes a JS file from the collection.
  799. *
  800. * @param string $FileName The JS file to search for.
  801. */
  802. public function RemoveJsFile($FileName) {
  803. foreach ($this->_JsFiles as $Key => $FileInfo) {
  804. if ($FileInfo['FileName'] == $FileName) {
  805. unset($this->_JsFiles[$Key]);
  806. return;
  807. }
  808. }
  809. }
  810. /**
  811. * Defines & retrieves the view and master view. Renders all content within
  812. * them to the screen.
  813. *
  814. * @param string $View
  815. * @param string $ControllerName
  816. * @param string $ApplicationFolder
  817. * @param string $AssetName The name of the asset container that the content should be rendered in.
  818. * @todo $View, $ControllerName, and $ApplicationFolder need correct variable types and descriptions.
  819. */
  820. public function xRender($View = '', $ControllerName = FALSE, $ApplicationFolder = FALSE, $AssetName = 'Content') {
  821. if ($this->_DeliveryType == DELIVERY_TYPE_NONE)
  822. return;
  823. // If there were uncontrolled errors above the json data, wipe them out
  824. // before fetching it (otherwise the json will not be properly parsed
  825. // by javascript).
  826. if ($this->_DeliveryMethod == DELIVERY_METHOD_JSON)
  827. ob_clean();
  828. // Send headers to the browser
  829. $this->SendHeaders();
  830. // Make sure to clear out the content asset collection if this is a syndication request
  831. if ($this->SyndicationMethod !== SYNDICATION_NONE)
  832. $this->Assets['Content'] = '';
  833. // Define the view
  834. if (!in_array($this->_DeliveryType, array(DELIVERY_TYPE_BOOL, DELIVERY_TYPE_DATA))) {
  835. $View = $this->FetchView($View, $ControllerName, $ApplicationFolder);
  836. // Add the view to the asset container if necessary
  837. if ($this->_DeliveryType != DELIVERY_TYPE_VIEW)
  838. $this->AddAsset($AssetName, $View, 'Content');
  839. }
  840. // Redefine the view as the entire asset contents if necessary
  841. if ($this->_DeliveryType == DELIVERY_TYPE_ASSET) {
  842. $View = $this->GetAsset($AssetName);
  843. } else if ($this->_DeliveryType == DELIVERY_TYPE_BOOL) {
  844. // Or as a boolean if necessary
  845. $View = TRUE;
  846. if (property_exists($this, 'Form') && is_object($this->Form))
  847. $View = $this->Form->ErrorCount() > 0 ? FALSE : TRUE;
  848. }
  849. if ($this->_DeliveryType == DELIVERY_TYPE_MESSAGE && $this->Form) {
  850. $View = $this->Form->Errors();
  851. }
  852. if ($this->_DeliveryType == DELIVERY_TYPE_DATA) {
  853. $this->RenderData();
  854. }
  855. if ($this->_DeliveryMethod == DELIVERY_METHOD_JSON) {
  856. // Format the view as JSON with some extra information about the
  857. // success status of the form so that jQuery knows what to do
  858. // with the result.
  859. $FormSaved = (property_exists($this, 'Form') && $this->Form->ErrorCount() == 0) ? TRUE : FALSE;
  860. $this->SetJson('FormSaved', $FormSaved);
  861. $this->SetJson('DeliveryType', $this->_DeliveryType);
  862. $this->SetJson('Data', base64_encode(($View instanceof Gdn_IModule) ? $View->ToString() : $View));
  863. $this->SetJson('StatusMessage', $this->StatusMessage);
  864. $this->SetJson('RedirectUrl', $this->RedirectUrl);
  865. // Make sure the database connection is closed before exiting.
  866. $Database = Gdn::Database();
  867. $Database->CloseConnection();
  868. if (!check_utf8($this->_Json['Data']))
  869. $this->_Json['Data'] = utf8_encode($this->_Json['Data']);
  870. //$Result = json_encode($this->_Json);
  871. $this->_Json['Data'] = json_encode($this->_Json);
  872. exit($this->_Json['Data']);
  873. } else {
  874. if ($this->StatusMessage != '' && $this->SyndicationMethod === SYNDICATION_NONE)
  875. $this->AddAsset($AssetName, '<div class="Messages Information"><ul><li>'.$this->StatusMessage.'</li></ul></div>');
  876. if ($this->RedirectUrl != '' && $this->SyndicationMethod === SYNDICATION_NONE)
  877. $this->AddDefinition('RedirectUrl', $this->RedirectUrl);
  878. // Render
  879. if ($this->_DeliveryType == DELIVERY_TYPE_BOOL) {
  880. echo $View ? 'TRUE' : 'FALSE';
  881. } else if ($this->_DeliveryType == DELIVERY_TYPE_ALL) {
  882. // Add definitions to the page
  883. if ($this->SyndicationMethod === SYNDICATION_NONE)
  884. $this->AddAsset('Foot', $this->DefinitionList());
  885. // Render
  886. $this->RenderMaster();
  887. } else {
  888. if($View instanceof Gdn_IModule) {
  889. $View->Render();
  890. } else {
  891. echo $View;
  892. }
  893. }
  894. }
  895. }
  896. /**
  897. * Undocumented method.
  898. *
  899. * @param string $AltAppFolder
  900. * @param string $AltController
  901. * @param string $AltMethod
  902. * @todo Method RenderAlternate() and $AltAppFolder, $AltController and $AltMethod needs descriptions.
  903. */
  904. public function RenderAlternate($AltAppFolder, $AltController, $AltMethod) {
  905. $this->AddAsset('Content', $this->FetchView($AltMethod, $AltController, $AltAppFolder));
  906. $this->RenderMaster();
  907. return;
  908. }
  909. /**
  910. * Searches $this->Assets for a key with $AssetName and renders all items
  911. * within that array element to the screen. Note that any element in
  912. * $this->Assets can contain an array of elements itself. This way numerous
  913. * assets can be rendered one after another in one place.
  914. *
  915. * @param string $AssetName The name of the asset to be rendered (the key related to the asset in
  916. * the $this->Assets associative array).
  917. */
  918. public function RenderAsset($AssetName) {
  919. $Asset = $this->GetAsset($AssetName);
  920. $this->EventArguments['AssetName'] = $AssetName;
  921. $this->FireEvent('BeforeRenderAsset');
  922. //$LengthBefore = ob_get_length();
  923. if(is_string($Asset)) {
  924. echo $Asset;
  925. } else {
  926. $Asset->AssetName = $AssetName;
  927. $Asset->Render();
  928. }
  929. $this->FireEvent('AfterRenderAsset');
  930. }
  931. // Render the data array.
  932. public function RenderData($Data = NULL) {
  933. if ($Data === NULL) {
  934. $Data = array();
  935. // Remove standard and "protected" data from the top level.
  936. foreach ($this->Data as $Key => $Value) {
  937. if (in_array($Key, array('Title')))
  938. continue;
  939. if (isset($Key[0]) && $Key[0] == '_')
  940. continue; // protected
  941. $Data[$Key] = $Value;
  942. }
  943. }
  944. // Massage the data for better rendering.
  945. foreach ($Data as $Key => $Value) {
  946. if (is_a($Value, 'Gdn_DataSet')) {
  947. $Data[$Key] = $Value->ResultArray();
  948. }
  949. }
  950. $this->Finalize();
  951. // Check for a special view.
  952. $ViewLocation = $this->FetchViewLocation(($this->View ? $this->View : $this->RequestMethod).'_'.strtolower($this->DeliveryMethod()), FALSE, FALSE, FALSE);
  953. if (file_exists($ViewLocation)) {
  954. include $ViewLocation;
  955. return;
  956. }
  957. switch ($this->DeliveryMethod()) {
  958. case DELIVERY_METHOD_XML:
  959. header('Content-Type: text/xml', TRUE);
  960. echo '<?xml version="1.0" encoding="utf-8"?>'."\n";
  961. $this->_RenderXml($Data);
  962. exit();
  963. break;
  964. case DELIVERY_METHOD_JSON:
  965. default:
  966. if ($Callback = $this->Request->GetValueFrom(Gdn_Request::INPUT_GET, 'callback', FALSE)) {
  967. // This is a jsonp request.
  968. exit($Callback.'('.json_encode($Data).');');
  969. } else {
  970. // This is a regular json request.
  971. exit(json_encode($Data));
  972. }
  973. break;
  974. }
  975. }
  976. /**
  977. * A simple default method for rendering xml.
  978. *
  979. * @param mixed $Data The data to render. This is usually $this->Data.
  980. * @param string $Node The name of the root node.
  981. * @param string $Indent The indent before the data for layout that is easier to read.
  982. */
  983. protected function _RenderXml($Data, $Node = 'Data', $Indent = '') {
  984. // Handle numeric arrays.
  985. if (is_numeric($Node))
  986. $Node = 'Item';
  987. echo "$Indent<$Node>";
  988. if (is_scalar($Data)) {
  989. echo htmlspecialchars($Data);
  990. } else {
  991. $Data = (array)$Data;
  992. foreach ($Data as $Key => $Value) {
  993. echo "\n";
  994. $this->_RenderXml($Value, $Key, $Indent.' ');
  995. }
  996. echo "\n";
  997. }
  998. echo "</$Node>";
  999. }
  1000. /**
  1001. * Render an exception as the sole output.
  1002. *
  1003. * @param Exception $Ex The exception to render.
  1004. */
  1005. public function RenderException($Ex) {
  1006. if ($this->DeliveryMethod() == DELIVERY_METHOD_XHTML) {
  1007. try {
  1008. switch ($Ex->getCode()) {
  1009. case 401:
  1010. Gdn::Dispatcher()->Dispatch('DefaultPermission');
  1011. break;
  1012. case 404:
  1013. Gdn::Dispatcher()->Dispatch('Default404');
  1014. break;
  1015. default:
  1016. Gdn_ExceptionHandler($Ex);
  1017. }
  1018. } catch(Exception $Ex2) {
  1019. Gdn_ExceptionHandler($Ex);
  1020. }
  1021. return;
  1022. }
  1023. $this->Finalize();
  1024. $this->SendHeaders();
  1025. $Code = $Ex->getCode();
  1026. if (defined('DEBUG'))
  1027. $Message = $Ex->getMessage()."\n\n".$Ex->getTraceAsString();
  1028. else
  1029. $Message = $Ex->getMessage();
  1030. if ($Code >= 100 && $Code <= 505)
  1031. header("HTTP/1.0 $Code", TRUE, $Code);
  1032. else
  1033. header('HTTP/1.0 500', TRUE, 500);
  1034. $Data = array('Code' => $Code, 'Exception' => $Message);
  1035. switch ($this->DeliveryMethod()) {
  1036. case DELIVERY_METHOD_JSON:
  1037. if ($Callback = $this->Request->GetValueFrom(Gdn_Request::INPUT_GET, 'callback', FALSE)) {
  1038. // This is a jsonp request.
  1039. exit($Callback.'('.json_encode($Data).');');
  1040. } else {
  1041. // This is a regular json request.
  1042. exit(json_encode($Data));
  1043. }
  1044. break;
  1045. // case DELIVERY_METHOD_XHTML:
  1046. // Gdn_ExceptionHandler($Ex);
  1047. // break;
  1048. case DELIVERY_METHOD_XML:
  1049. header('Content-Type: text/xml', TRUE);
  1050. array_map('htmlspecialchars', $Data);
  1051. exit("<Exception><Code>{$Data['Code']}</Code><Message>{$Data['Exception']}</Message></Exception>");
  1052. break;
  1053. }
  1054. }
  1055. /**
  1056. * Undocumented method.
  1057. *
  1058. * @todo Method RenderMaster() needs a description.
  1059. */
  1060. public function RenderMaster() {
  1061. // Build the master view if necessary
  1062. if ($this->_DeliveryType = DELIVERY_TYPE_ALL) {
  1063. // Define some default master views unless one was explicitly defined
  1064. if ($this->MasterView == '') {
  1065. // If this is a syndication request, use the appropriate master view
  1066. if ($this->SyndicationMethod == SYNDICATION_ATOM)
  1067. $this->MasterView = 'atom';
  1068. else if ($this->SyndicationMethod == SYNDICATION_RSS)
  1069. $this->MasterView = 'rss';
  1070. else
  1071. $this->MasterView = 'default'; // Otherwise go with the default
  1072. }
  1073. // Only get css & ui components if this is NOT a syndication request
  1074. if ($this->SyndicationMethod == SYNDICATION_NONE && is_object($this->Head)) {
  1075. if (ArrayHasValue($this->_CssFiles, 'style.css'))
  1076. $this->AddCssFile('custom.css');
  1077. if (ArrayHasValue($this->_CssFiles, 'admin.css'))
  1078. $this->AddCssFile('customadmin.css');
  1079. $this->EventArguments['CssFiles'] = &$this->_CssFiles;
  1080. $this->FireEvent('BeforeAddCss');
  1081. // And now search for/add all css files
  1082. foreach ($this->_CssFiles as $CssInfo) {
  1083. $CssFile = $CssInfo['FileName'];
  1084. if(strpos($CssFile, '/') !== FALSE) {
  1085. // A direct path to the file was given.
  1086. $CssPaths = array(CombinePaths(array(PATH_ROOT, str_replace('/', DS, $CssFile))));
  1087. } else {
  1088. $CssGlob = preg_replace('/(.*)(\.css)/', '\1*\2', $CssFile);
  1089. $AppFolder = $CssInfo['AppFolder'];
  1090. if ($AppFolder == '')
  1091. $AppFolder = $this->ApplicationFolder;
  1092. // CSS comes from one of four places:
  1093. $CssPaths = array();
  1094. if ($this->Theme) {
  1095. // 1. Application-specific css. eg. root/themes/theme_name/app_name/design/
  1096. // $CssPaths[] = PATH_THEMES . DS . $this->Theme . DS . $AppFolder . DS . 'design' . DS . $CssGlob;
  1097. // 2. Theme-wide theme view. eg. root/themes/theme_name/design/
  1098. // a) Check to see if a customized version of the css is there.
  1099. if ($this->ThemeOptions) {
  1100. $Filenames = GetValueR('Styles.Value', $this->ThemeOptions);
  1101. if (is_string($Filenames) && $Filenames != '%s')
  1102. $CssPaths[] = PATH_THEMES.DS.$this->Theme.DS.'design'.DS.ChangeBasename($CssFile, $Filenames);
  1103. }
  1104. // b) Use the default filename.
  1105. $CssPaths[] = PATH_THEMES . DS . $this->Theme . DS . 'design' . DS . $CssFile;
  1106. }
  1107. // 3. Application or plugin.
  1108. if (StringBeginsWith($AppFolder, 'plugins/')) {
  1109. // The css is coming from a plugin.
  1110. $AppFolder = substr($AppFolder, strlen('plugins/'));
  1111. $CssPaths[] = PATH_PLUGINS . "/$AppFolder/design/$CssFile";
  1112. } else {
  1113. // Application default. eg. root/applications/app_name/design/
  1114. $CssPaths[] = PATH_APPLICATIONS . DS . $AppFolder . DS . 'design' . DS . $CssFile;
  1115. }
  1116. // 4. Garden default. eg. root/applications/dashboard/design/
  1117. $CssPaths[] = PATH_APPLICATIONS . DS . 'dashboard' . DS . 'design' . DS . $CssFile;
  1118. }
  1119. // Find the first file that matches the path.
  1120. $CssPath = FALSE;
  1121. foreach($CssPaths as $Glob) {
  1122. $Paths = SafeGlob($Glob);
  1123. if(is_array($Paths) && count($Paths) > 0) {
  1124. $CssPath = $Paths[0];
  1125. break;
  1126. }
  1127. }
  1128. // Check to see if there is a CSS cacher.
  1129. $CssCacher = Gdn::Factory('CssCacher');
  1130. if(!is_null($CssCacher)) {
  1131. $CssPath = $CssCacher->Get($CssPath, $AppFolder);
  1132. }
  1133. if ($CssPath !== FALSE) {
  1134. $CssPath = substr($CssPath, strlen(PATH_ROOT));
  1135. $CssPath = str_replace(DS, '/', $CssPath);
  1136. $this->Head->AddCss($CssPath, 'screen');
  1137. }
  1138. }
  1139. // Add a custom js file.
  1140. if (ArrayHasValue($this->_CssFiles, 'style.css'))
  1141. $this->AddJsFile('custom.js'); // only to non-admin pages.
  1142. // And now search for/add all JS files
  1143. foreach ($this->_JsFiles as $JsInfo) {
  1144. $JsFile = $JsInfo['FileName'];
  1145. if (strpos($JsFile, '//') !== FALSE) {
  1146. // This is a link to an external file.
  1147. $this->Head->AddScript($JsFile);
  1148. continue;
  1149. } if (strpos($JsFile, '/') !== FALSE) {
  1150. // A direct path to the file was given.
  1151. $JsPaths = array(CombinePaths(array(PATH_ROOT, str_replace('/', DS, $JsFile)), DS));
  1152. } else {
  1153. $AppFolder = $JsInfo['AppFolder'];
  1154. if ($AppFolder == '')
  1155. $AppFolder = $this->ApplicationFolder;
  1156. // JS can come from a theme, an any of the application folder, or it can come from the global js folder:
  1157. $JsPaths = array();
  1158. if ($this->Theme) {
  1159. // 1. Application-specific js. eg. root/themes/theme_name/app_name/design/
  1160. $JsPaths[] = PATH_THEMES . DS . $this->Theme . DS . $AppFolder . DS . 'js' . DS . $JsFile;
  1161. // 2. Garden-wide theme view. eg. root/themes/theme_name/design/
  1162. $JsPaths[] = PATH_THEMES . DS . $this->Theme . DS . 'js' . DS . $JsFile;
  1163. }
  1164. // 3. The application or plugin folder.
  1165. if (StringBeginsWith(trim($AppFolder, '/'), 'plugins/'))
  1166. $JsPaths[] = PATH_PLUGINS.strstr($AppFolder, '/')."/js/$JsFile";
  1167. else
  1168. $JsPaths[] = PATH_APPLICATIONS."/$AppFolder/js/$JsFile";
  1169. // 4. Global JS folder. eg. root/js/
  1170. $JsPaths[] = PATH_ROOT . DS . 'js' . DS . $JsFile;
  1171. // 5. Global JS library folder. eg. root/js/library/

Large files files are truncated, but you can click here to view the full file