controller.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005
  1. <?PHP
  2. //////////////////////////////////////////////////////////////////////////
  3. // + $Id$
  4. // +------------------------------------------------------------------+ //
  5. // + Cake <https://developers.nextco.com/cake/> + //
  6. // + Copyright: (c) 2005, Cake Authors/Developers + //
  7. // + Author(s): Michal Tatarynowicz aka Pies <tatarynowicz@gmail.com> + //
  8. // + Larry E. Masters aka PhpNut <nut@phpnut.com> + //
  9. // + Kamil Dzielinski aka Brego <brego.dk@gmail.com> + //
  10. // +------------------------------------------------------------------+ //
  11. // + Licensed under The MIT License + //
  12. // + Redistributions of files must retain the above copyright notice. + //
  13. // + See: http://www.opensource.org/licenses/mit-license.php + //
  14. //////////////////////////////////////////////////////////////////////////
  15. /**
  16. * Purpose: Controller
  17. * Application controller (controllers are where you put all the actual code) based on RoR (www.rubyonrails.com)
  18. * Provides basic functionality, such as rendering views (aka displaying templates).
  19. * Automatically selects model name from on singularized object class name
  20. * and creates the model object if proper class exists.
  21. *
  22. * @filesource
  23. * @author Cake Authors/Developers
  24. * @copyright Copyright (c) 2005, Cake Authors/Developers
  25. * @link https://developers.nextco.com/cake/wiki/Authors Authors/Developers
  26. * @package cake
  27. * @subpackage cake.libs
  28. * @since Cake v 0.2.9
  29. * @version $Revision$
  30. * @modifiedby $LastChangedBy$
  31. * @lastmodified $Date$
  32. * @license http://www.opensource.org/licenses/mit-license.php The MIT License
  33. *
  34. */
  35. /**
  36. * Enter description here...
  37. */
  38. uses('model', 'template', 'inflector', 'folder');
  39. /**
  40. * Enter description here...
  41. *
  42. *
  43. * @package cake
  44. * @subpackage cake.libs
  45. * @since Cake v 0.2.9
  46. *
  47. */
  48. class Controller extends Template {
  49. /**
  50. * Name of the controller.
  51. *
  52. * @var unknown_type
  53. * @access public
  54. */
  55. var $name = null;
  56. /**
  57. * Enter description here...
  58. *
  59. * @var unknown_type
  60. * @access public
  61. */
  62. var $parent = null;
  63. /**
  64. * Enter description here...
  65. *
  66. * @var unknown_type
  67. * @access public
  68. */
  69. var $action = null;
  70. /**
  71. * This Controller's Model.
  72. *
  73. * @var unknown_type
  74. * @access public
  75. */
  76. var $use_model = null;
  77. /**
  78. * Enter description here...
  79. *
  80. * @var unknown_type
  81. * @access private
  82. */
  83. var $uses = false;
  84. /**
  85. * Enter description here...
  86. *
  87. * @var unknown_type
  88. * @access private
  89. */
  90. var $_crumbs = array();
  91. /**
  92. * Constructor.
  93. *
  94. */
  95. function __construct ($params=null)
  96. {
  97. $this->params = $params;
  98. $r = null;
  99. if (!preg_match('/(.*)Controller/i', get_class($this), $r))
  100. die("Controller::__construct() : Can't get or parse my own class name, exiting.");
  101. $this->name = strtolower($r[1]);
  102. $this->viewpath = Inflector::underscore($r[1]);
  103. $model_class = Inflector::singularize($this->name);
  104. if (class_exists($model_class) && $this->db && ($this->uses === false))
  105. {
  106. $this->$model_class = new $model_class ();
  107. }
  108. elseif ($this->uses)
  109. {
  110. if (!$DB)
  111. die("Controller::__construct() : ".$this->name." controller needs database access, exiting.");
  112. $uses = is_array($this->uses)? $this->uses: array($this->uses);
  113. foreach ($uses as $model_name)
  114. {
  115. $model_class = ucfirst(strtolower($model_name));
  116. if (class_exists($model_class))
  117. {
  118. $this->$model_name = new $model_class (false);
  119. }
  120. else
  121. {
  122. die("Controller::__construct() : ".ucfirst($this->name)." requires missing model {$model_class}, exiting.");
  123. }
  124. }
  125. }
  126. parent::__construct();
  127. }
  128. /**
  129. * Redirects to given $url, after turning off $this->autoRender.
  130. *
  131. * @param unknown_type $url
  132. */
  133. function redirect ($url) {
  134. $this->autoRender = false;
  135. header ('Location: '.$this->base.$url);
  136. }
  137. /**
  138. * Enter description here...
  139. *
  140. * @param unknown_type $action
  141. */
  142. function setAction ($action) {
  143. $this->action = $action;
  144. $args = func_get_args();
  145. call_user_func_array(array(&$this, $action), $args);
  146. }
  147. /**
  148. * Returns an URL for a combination of controller and action.
  149. *
  150. * @param string $url
  151. * @return string Full constructed URL as a string.
  152. */
  153. function urlFor ($url=null) {
  154. if (empty($url)) {
  155. $out = $this->base.'/'.strtolower($this->params['controller']).'/'.strtolower($this->params['action']);
  156. }
  157. elseif ($url[0] == '/') {
  158. $out = $this->base . $url;
  159. }
  160. else {
  161. $out = $this->base . '/' . strtolower($this->params['controller']) . '/' . $url;
  162. }
  163. return ereg_replace('&([^a])', '&amp;\1', $out);
  164. }
  165. /**
  166. * Returns a space-separated string with items of the $options array.
  167. *
  168. * @param array $options Array of HTML options.
  169. * @param string $insert_before
  170. * @param unknown_type $insert_after
  171. * @return string
  172. */
  173. function parseHtmlOptions ($options, $insert_before=' ', $insert_after=null) {
  174. if (is_array($options)) {
  175. $out = array();
  176. foreach ($options as $k=>$v) {
  177. $out[] = "{$k}=\"{$v}\"";
  178. }
  179. $out = join(' ', $out);
  180. return $out? $insert_before.$out.$insert_after: null;
  181. }
  182. else {
  183. return $options? $insert_before.$options.$insert_after: null;
  184. }
  185. }
  186. /**
  187. * Returns an HTML link to $url for given $title, optionally using $html_options and $confirm_message (for "flash").
  188. *
  189. * @param string $title The content of the A tag.
  190. * @param string $url
  191. * @param array $html_options Array of HTML options.
  192. * @param string $confirm_message Message to be shown in "flash".
  193. * @return string
  194. */
  195. function linkTo ($title, $url, $html_options=null, $confirm_message=false) {
  196. $confirm_message? $html_options['onClick'] = "return confirm('{$confirm_message}')": null;
  197. return sprintf(TAG_LINK, $this->UrlFor($url), $this->parseHtmlOptions($html_options), $title);
  198. }
  199. /**
  200. * Returns an external HTML link to $url for given $title, optionally using $html_options.
  201. *
  202. * @param string $title
  203. * @param string $url
  204. * @param array $html_options
  205. * @return string
  206. */
  207. function linkOut ($title, $url=null, $html_options=null) {
  208. $url = $url? $url: $title;
  209. return sprintf(TAG_LINK, $url, $this->parseHtmlOptions($html_options), $title);
  210. }
  211. /**
  212. * Returns an HTML FORM element.
  213. *
  214. * @param string $target URL for the FORM's ACTION attribute.
  215. * @param string $type FORM type (POST/GET).
  216. * @param array $html_options
  217. * @return string An formatted opening FORM tag.
  218. */
  219. function formTag ($target=null, $type='post', $html_options=null) {
  220. $html_options['action'] = $this->UrlFor($target);
  221. $html_options['method'] = $type=='get'? 'get': 'post';
  222. $type == 'file'? $html_options['enctype'] = 'multipart/form-data': null;
  223. return sprintf(TAG_FORM, $this->parseHtmlOptions($html_options, ''));
  224. }
  225. /**
  226. * Returns a generic HTML tag (no content).
  227. *
  228. * Examples:
  229. * * <i>tag("br") => <br /></i>
  230. * * <i>tag("input", array("type" => "text")) => <input type="text" /></i>
  231. *
  232. * @param string $name Name of HTML element
  233. * @param array $options HTML options
  234. * @param bool $open Is the tag open or closed? (defaults to closed "/>")
  235. * @return string The formatted HTML tag
  236. */
  237. function tag($name, $options=null, $open=false) {
  238. $tag = "<$name ". $this->parseHtmlOptions($options);
  239. $tag .= $open? ">" : " />";
  240. return $tag;
  241. }
  242. /**
  243. * Returns a generic HTML tag with content.
  244. *
  245. * Examples:
  246. * * <i>content_tag("p", "Hello world!") => <p>Hello world!</p></i>
  247. * * <i>content_tag("div", content_tag("p", "Hello world!"), array("class" => "strong")) => </i>
  248. * <i><div class="strong"><p>Hello world!</p></div></i>
  249. *
  250. * @param string $name Name of HTML element
  251. * @param array $options HTML options
  252. * @param bool $open Is the tag open or closed? (defaults to closed "/>")
  253. * @return string The formatted HTML tag
  254. */
  255. function contentTag($name, $content, $options=null) {
  256. return "<$name ". $this->parseHtmlOptions($options). ">$content</$name>";
  257. }
  258. /**
  259. * Returns a formatted SUBMIT button for HTML FORMs.
  260. *
  261. * @param string $caption Text on SUBMIT button
  262. * @param array $html_options HTML options
  263. * @return string The formatted SUBMIT button
  264. */
  265. function submitTag ($caption='Submit', $html_options=null) {
  266. $html_options['value'] = $caption;
  267. return sprintf(TAG_SUBMIT, $this->parseHtmlOptions($html_options, '', ' '));
  268. }
  269. /**
  270. * Returns a formatted INPUT tag for HTML FORMs.
  271. *
  272. * @param string $tag_name Name attribute for INPUT element
  273. * @param int $size Size attribute for INPUT element
  274. * @param array $html_options
  275. * @return string The formatted INPUT element
  276. */
  277. function inputTag ($tag_name, $size=20, $html_options=null) {
  278. $html_options['size'] = $size;
  279. $html_options['value'] = isset($html_options['value'])? $html_options['value']: $this->tagValue($tag_name);
  280. $this->tagIsInvalid($tag_name)? $html_options['class'] = 'form_error': null;
  281. return sprintf(TAG_INPUT, $tag_name, $this->parseHtmlOptions($html_options, '', ' '));
  282. }
  283. /**
  284. * Returns an INPUT element with type="password".
  285. *
  286. * @param string $tag_name
  287. * @param int $size
  288. * @param array $html_options
  289. * @return string
  290. */
  291. function passwordTag ($tag_name, $size=20, $html_options=null) {
  292. $html_options['size'] = $size;
  293. empty($html_options['value'])? $html_options['value'] = $this->tagValue($tag_name): null;
  294. return sprintf(TAG_PASSWORD, $tag_name, $this->parseHtmlOptions($html_options, '', ' '));
  295. }
  296. /**
  297. * Returns an INPUT element with type="hidden".
  298. *
  299. * @param string $tag_name
  300. * @param string $value
  301. * @param array $html_options
  302. * @return string
  303. */
  304. function hiddenTag ($tag_name, $value=null, $html_options=null) {
  305. $html_options['value'] = $value? $value: $this->tagValue($tag_name);
  306. return sprintf(TAG_HIDDEN, $tag_name, $this->parseHtmlOptions($html_options, '', ' '));
  307. }
  308. /**
  309. * Returns an INPUT element with type="file".
  310. *
  311. * @param string $tag_name
  312. * @param array $html_options
  313. * @return string
  314. */
  315. function fileTag ($tag_name, $html_options=null) {
  316. return sprintf(TAG_FILE, $tag_name, $this->parseHtmlOptions($html_options, '', ' '));
  317. }
  318. /**
  319. * Returns a TEXTAREA element.
  320. *
  321. * @param string $tag_name
  322. * @param int $cols
  323. * @param int $rows
  324. * @param array $html_options
  325. * @return string
  326. */
  327. function areaTag ($tag_name, $cols=60, $rows=10, $html_options=null) {
  328. $value = empty($html_options['value'])? $this->tagValue($tag_name): empty($html_options['value']);
  329. $html_options['cols'] = $cols;
  330. $html_options['rows'] = $rows;
  331. return sprintf(TAG_AREA, $tag_name, $this->parseHtmlOptions($html_options, ' '), $value);
  332. }
  333. /**
  334. * Returns an INPUT element with type="checkbox". Checkedness is to be passed as string "checked" with the key "checked" in the $html_options array.
  335. *
  336. * @param string $tag_name
  337. * @param string $title
  338. * @param array $html_options
  339. * @return string
  340. */
  341. function checkboxTag ($tag_name, $title=null, $html_options=null) {
  342. $this->tagValue($tag_name)? $html_options['checked'] = 'checked': null;
  343. $title = $title? $title: ucfirst($tag_name);
  344. return sprintf(TAG_CHECKBOX, $tag_name, $tag_name, $tag_name, $this->parseHtmlOptions($html_options, '', ' '), $title);
  345. }
  346. /**
  347. * Returns a set of radio buttons.
  348. *
  349. * @param string $tag_name
  350. * @param array $options Array of options to select from
  351. * @param string $inbetween String to separate options. See PHP's implode() function
  352. * @param array $html_options
  353. * @return string
  354. */
  355. function radioTags ($tag_name, $options, $inbetween=null, $html_options=null) {
  356. $value = isset($html_options['value'])? $html_options['value']: $this->tagValue($tag_name);
  357. $out = array();
  358. foreach ($options as $opt_value=>$opt_title) {
  359. $options_here = array('value' => $opt_value);
  360. $opt_value==$value? $options_here['checked'] = 'checked': null;
  361. $parsed_options = $this->parseHtmlOptions(array_merge($html_options, $options_here), '', ' ');
  362. $individual_tag_name = "{$tag_name}_{$opt_value}";
  363. $out[] = sprintf(TAG_RADIOS, $individual_tag_name, $tag_name, $individual_tag_name, $parsed_options, $opt_title);
  364. }
  365. $out = join($inbetween, $out);
  366. return $out? $out: null;
  367. }
  368. /**
  369. * Returns a SELECT element,
  370. *
  371. * @param string $tag_name Name attribute of the SELECT
  372. * @param array $option_elements Array of the OPTION elements (as 'value'=>'Text' pairs) to be used in the SELECT element
  373. * @param array $select_attr Array of HTML options for the opening SELECT element
  374. * @param array $option_attr Array of HTML options for the enclosed OPTION elements
  375. * @return string Formatted SELECT element
  376. */
  377. function selectTag ($tag_name, $option_elements, $selected=null, $select_attr=null, $option_attr=null)
  378. {
  379. if (!is_array($option_elements) || !count($option_elements))
  380. return null;
  381. $select[] = sprintf(TAG_SELECT_START, $tag_name, $this->parseHtmlOptions($select_attr));
  382. $select[] = sprintf(TAG_SELECT_EMPTY, $this->parseHtmlOptions($option_attr));
  383. foreach ($option_elements as $name=>$title)
  384. {
  385. $options_here = $option_attr;
  386. if ($selected == $name)
  387. $options_here['selected'] = 'selected';
  388. $select[] = sprintf(TAG_SELECT_OPTION, $name, $this->parseHtmlOptions($options_here), $title);
  389. }
  390. $select[] = sprintf(TAG_SELECT_END);
  391. return implode("\n", $select);
  392. }
  393. /**
  394. * Returns a formatted IMG element.
  395. *
  396. * @param string $path Path to the image file
  397. * @param string $alt ALT attribute for the IMG tag
  398. * @param array $html_options
  399. * @return string Formatted IMG tag
  400. */
  401. function imageTag ($path, $alt=null, $html_options=null) {
  402. $url = "{$this->base}/images/{$path}";
  403. return sprintf(TAG_IMAGE, $url, $alt, $this->parseHtmlOptions($html_options, '', ' '));
  404. }
  405. /**
  406. * Returns a LINK element for CSS stylesheets.
  407. *
  408. * @param string $path Path to CSS file
  409. * @param string $rel Rel attribute. Defaults to "stylesheet".
  410. * @param array $html_options
  411. * @return string Formatted LINK element.
  412. */
  413. function cssTag ($path, $rel='stylesheet', $html_options=null) {
  414. $url = "{$this->base}/css/{$path}.css";
  415. return sprintf(TAG_CSS, $rel, $url, $this->parseHtmlOptions($html_options, '', ' '));
  416. }
  417. /**
  418. * Returns a charset meta-tag
  419. *
  420. * @param string $charset
  421. * @return string
  422. */
  423. function charsetTag ($charset) {
  424. return sprintf(TAG_CHARSET, $charset);
  425. }
  426. /**
  427. * Returns a JavaScript script tag.
  428. *
  429. * @param string $script The JavaScript to be wrapped in SCRIPT tags.
  430. * @return string The full SCRIPT element, with the JavaScript inside it.
  431. */
  432. function javascriptTag ($script) {
  433. return sprintf(TAG_JAVASCRIPT, $script);
  434. }
  435. /**
  436. * Returns a JavaScript include tag
  437. *
  438. * @param string $url URL to JavaScript file.
  439. * @return string
  440. */
  441. function javascriptIncludeTag ($url) {
  442. return sprintf(TAG_JAVASCRIPT_INCLUDE, $this->base.$url);
  443. }
  444. /**
  445. * Returns a row of formatted and named TABLE headers.
  446. *
  447. * @param array $names
  448. * @param array $tr_options
  449. * @param array $th_options
  450. * @return string
  451. */
  452. function tableHeaders ($names, $tr_options=null, $th_options=null)
  453. {
  454. $out = array();
  455. foreach ($names as $arg)
  456. $out[] = sprintf(TAG_TABLE_HEADER, $this->parseHtmlOptions($th_options), $arg);
  457. return sprintf(TAG_TABLE_HEADERS, $this->parseHtmlOptions($tr_options), join(' ', $out));
  458. }
  459. /**
  460. * Returns a formatted string of table rows (TR's with TD's in them).
  461. *
  462. * @param array $data Array of table data
  463. * @param array $tr_options HTML options for TR elements
  464. * @param array $td_options HTML options for TD elements
  465. * @return string
  466. */
  467. function tableCells ($data, $odd_tr_options=null, $even_tr_options=null) {
  468. if (empty($data[0]) || !is_array($data[0]))
  469. $data = array($data);
  470. $count=0;
  471. foreach ($data as $line) {
  472. $count++;
  473. $cells_out = array();
  474. foreach ($line as $cell)
  475. $cells_out[] = sprintf(TAG_TABLE_CELL, null, $cell);
  476. $options = $this->parseHtmlOptions($count%2? $odd_tr_options: $even_tr_options);
  477. $out[] = sprintf(TAG_TABLE_ROW, $options, join(' ', $cells_out));
  478. }
  479. return join("\n", $out);
  480. }
  481. /**
  482. * Generates a nested <UL> (unordered list) tree from an array
  483. *
  484. * @param array $data
  485. * @param array $htmlOptions
  486. * @param string $bodyKey
  487. * @param childrenKey $bodyKey
  488. * @return string
  489. */
  490. function guiListTree ($data, $htmlOptions=null, $bodyKey='body', $childrenKey='children') {
  491. $out = "<ul".$this->parseHtmlOptions($htmlOptions).">\n";
  492. foreach ($data as $item) {
  493. $out .= "<li>{$item[$bodyKey]}</li>\n";
  494. if (isset($item[$childrenKey]) && is_array($item[$childrenKey]) && count($item[$childrenKey])) {
  495. $out .= $this->guiListTree($item[$childrenKey], $htmlOptions, $bodyKey, $childrenKey);
  496. }
  497. }
  498. $out .= "</ul>\n";
  499. return $out;
  500. }
  501. /**
  502. * Returns value of $tag_name. False is the tag does not exist.
  503. *
  504. * @param string $tag_name
  505. * @return unknown Value of the named tag.
  506. */
  507. function tagValue ($tag_name) {
  508. return isset($this->params['data'][$tag_name])? $this->params['data'][$tag_name]: false;
  509. }
  510. /**
  511. * Returns number of errors in a submitted FORM.
  512. *
  513. * @return int Number of errors
  514. */
  515. function validate () {
  516. $args = func_get_args();
  517. $errors = call_user_func_array(array(&$this, 'validateErrors'), $args);
  518. return count($errors);
  519. }
  520. /**
  521. * Validates a FORM according to the rules set up in the Model.
  522. *
  523. * @return int Number of errors
  524. */
  525. function validateErrors () {
  526. $objects = func_get_args();
  527. if (!count($objects)) return false;
  528. $errors = array();
  529. foreach ($objects as $object) {
  530. $errors = array_merge($errors, $object->invalidFields($object->data));
  531. }
  532. return $this->validationErrors = (count($errors)? $errors: false);
  533. }
  534. /**
  535. * Returns a formatted error message for given FORM field, NULL if no errors.
  536. *
  537. * @param string $field
  538. * @param string $text
  539. * @return string If there are errors this method returns an error message, else NULL.
  540. */
  541. function tagErrorMsg ($field, $text)
  542. {
  543. $error = $this->tagIsInvalid($field);
  544. if (0 == $error)
  545. {
  546. return sprintf(SHORT_ERROR_MESSAGE, is_array($text)? (empty($text[$error-1])? 'Error in field': $text[$error-1]): $text);
  547. }
  548. else
  549. {
  550. return null;
  551. }
  552. }
  553. /**
  554. * Returns false if given FORM field has no errors. Otherwise it returns the constant set in the array Model->validationErrors.
  555. *
  556. * @param unknown_type $field
  557. * @return unknown
  558. */
  559. function tagIsInvalid ($field) {
  560. return empty($this->validationErrors[$field])? 0: $this->validationErrors[$field];
  561. }
  562. /**
  563. * Adds $name and $link to the breadcrumbs array.
  564. *
  565. * @param string $name Text for link
  566. * @param string $link URL for link
  567. */
  568. function addCrumb ($name, $link) {
  569. $this->_crumbs[] = array ($name, $link);
  570. }
  571. /**
  572. * Returns the breadcrumb trail as a sequence of &raquo;-separated links.
  573. *
  574. * @return string Formatted &raquo;-separated list of breadcrumb links. Returns NULL if $this->_crumbs is empty.
  575. */
  576. function getCrumbs () {
  577. if (count($this->_crumbs)) {
  578. $out = array("<a href=\"{$this->base}\">START</a>");
  579. foreach ($this->_crumbs as $crumb) {
  580. $out[] = "<a href=\"{$this->base}{$crumb[1]}\">{$crumb[0]}</a>";
  581. }
  582. return join(' &raquo; ', $out);
  583. }
  584. else
  585. return null;
  586. }
  587. /**
  588. * Displays an error page to the user. Uses layouts/error.html to render the page.
  589. *
  590. * @param int $code Error code (for instance: 404)
  591. * @param string $name Name of the error (for instance: Not Found)
  592. * @param string $message Error message
  593. */
  594. function error ($code, $name, $message) {
  595. header ("HTTP/1.0 {$code} {$name}");
  596. print ($this->_render(VIEWS.'layouts/error.thtml', array('code'=>$code,'name'=>$name,'message'=>$message)));
  597. }
  598. /**
  599. * Returns link to javascript function
  600. *
  601. * Returns a link that'll trigger a javascript function using the
  602. * onclick handler and return false after the fact.
  603. *
  604. * Examples:
  605. * <code>
  606. * linkToFunction("Greeting", "alert('Hello world!')");
  607. * linkToFunction(imageTag("delete"), "if confirm('Really?'){ do_delete(); }");
  608. * </code>
  609. *
  610. * @param string $title title of link
  611. * @param string $func javascript function to be called on click
  612. * @param array $html_options html options for link
  613. * @return string html code for link to javascript function
  614. */
  615. function linkToFunction ($title, $func, $html_options=null) {
  616. $html_options['onClick'] = "$func; return false;";
  617. return $this->linkTo($title, '#', $html_options);
  618. }
  619. /**
  620. * Returns link to remote action
  621. *
  622. * Returns a link to a remote action defined by <i>options[url]</i>
  623. * (using the urlFor format) that's called in the background using
  624. * XMLHttpRequest. The result of that request can then be inserted into a
  625. * DOM object whose id can be specified with <i>options[update]</i>.
  626. * Usually, the result would be a partial prepared by the controller with
  627. * either renderPartial or renderPartialCollection.
  628. *
  629. * Examples:
  630. * <code>
  631. * linkToRemote("Delete this post",
  632. * array("update" => "posts", "url" => "delete/{$postid->id}"));
  633. * linkToRemote(imageTag("refresh"),
  634. * array("update" => "emails", "url" => "list_emails" ));
  635. * </code>
  636. *
  637. * By default, these remote requests are processed asynchronous during
  638. * which various callbacks can be triggered (for progress indicators and
  639. * the likes).
  640. *
  641. * Example:
  642. * <code>
  643. * linkToRemote (word,
  644. * array("url" => "undo", "n" => word_counter),
  645. * array("complete" => "undoRequestCompleted(request)"));
  646. * </code>
  647. *
  648. * The callbacks that may be specified are:
  649. *
  650. * - <i>loading</i>:: Called when the remote document is being
  651. * loaded with data by the browser.
  652. * - <i>loaded</i>:: Called when the browser has finished loading
  653. * the remote document.
  654. * - <i>interactive</i>:: Called when the user can interact with the
  655. * remote document, even though it has not
  656. * finished loading.
  657. * - <i>complete</i>:: Called when the XMLHttpRequest is complete.
  658. *
  659. * If you for some reason or another need synchronous processing (that'll
  660. * block the browser while the request is happening), you can specify
  661. * <i>options[type] = synchronous</i>.
  662. *
  663. * You can customize further browser side call logic by passing
  664. * in Javascript code snippets via some optional parameters. In
  665. * their order of use these are:
  666. *
  667. * - <i>confirm</i>:: Adds confirmation dialog.
  668. * -<i>condition</i>:: Perform remote request conditionally
  669. * by this expression. Use this to
  670. * describe browser-side conditions when
  671. * request should not be initiated.
  672. * - <i>before</i>:: Called before request is initiated.
  673. * - <i>after</i>:: Called immediately after request was
  674. * initiated and before <i>loading</i>.
  675. *
  676. * @param string $title title of link
  677. * @param array $options options for javascript function
  678. * @param array $html_options options for link
  679. * @return string html code for link to remote action
  680. */
  681. function linkToRemote ($title, $options=null, $html_options=null) {
  682. return $this->linkToFunction($title, $this->remoteFunction($options), $html_options);
  683. }
  684. /**
  685. * Creates javascript function for remote AJAX call
  686. *
  687. * This function creates the javascript needed to make a remote call
  688. * it is primarily used as a helper for linkToRemote.
  689. *
  690. * @see linkToRemote() for docs on options parameter.
  691. *
  692. * @param array $options options for javascript
  693. * @return string html code for link to remote action
  694. */
  695. function remoteFunction ($options=null)
  696. {
  697. $javascript_options = $this->__optionsForAjax($options);
  698. $func = isset($options['update'])
  699. ? "new Ajax.Updater('{$options['update']}', "
  700. : "new Ajax.Request(";
  701. $func .= "'" . $this->urlFor($options['url']) . "'";
  702. $func .= ", $javascript_options)";
  703. if (isset($options['before']))
  704. $func = "{$options['before']}; $func";
  705. if (isset($options['after']))
  706. $func = "$func; {$options['before']};";
  707. if (isset($options['condition']))
  708. $func = "if ({$options['condition']}) { $func; }";
  709. if (isset($options['confirm']))
  710. $func = "if (confirm('" . $this->escapeJavascript($options['confirm']) . "')) { $func; }";
  711. return $func;
  712. }
  713. /**
  714. * Escape carrier returns and single and double quotes for Javascript segments.
  715. *
  716. * @param string $javascript string that might have javascript elements
  717. * @return string escaped string
  718. */
  719. function escapeJavascript ($javascript) {
  720. $javascript = str_replace(array("\r\n","\n","\r"),'\n', $javascript);
  721. $javascript = str_replace(array('"', "'"), array('\"', "\\'"), $javascript);
  722. return $javascript;
  723. }
  724. /**
  725. * Periodically call remote url via AJAX.
  726. *
  727. * Periodically calls the specified url (<i>options[url]</i>) every <i>options[frequency]</i> seconds (default is 10).
  728. * Usually used to update a specified div (<i>options[update]</i>) with the results of the remote call.
  729. * The options for specifying the target with url and defining callbacks is the same as linkToRemote.
  730. *
  731. * @param array $options callback options
  732. * @return string javascript code
  733. */
  734. function periodicallyCallRemote ($options=null) {
  735. $frequency = (isset($options['frequency']))? $options['frequency'] : 10;
  736. $code = "new PeriodicalExecuter(function() {" . $this->remote_function($options) . "}, $frequency)";
  737. return $this->javascriptTag($code);
  738. }
  739. /**
  740. * Returns form tag that will submit using Ajax.
  741. *
  742. * Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular
  743. * reloading POST arrangement. Even though it's using Javascript to serialize the form elements, the form submission
  744. * will work just like a regular submission as viewed by the receiving side (all elements available in params).
  745. * The options for specifying the target with :url and defining callbacks is the same as link_to_remote.
  746. *
  747. * @param array $options callback options
  748. * @return string javascript code
  749. */
  750. function formRemoteTag ($options=null) {
  751. $options['form'] = true;
  752. $options['html']['onsubmit']=$this->remoteFunction($options) . "; return false;";
  753. return $this->tag("form", $options['html'], true);
  754. }
  755. /**
  756. * Returns a button input tag that will submit using Ajax
  757. *
  758. * Returns a button input tag that will submit form using XMLHttpRequest in the background instead of regular
  759. * reloading POST arrangement. <i>options</i> argument is the same as in <i>form_remote_tag</i>
  760. *
  761. * @param string $name input button name
  762. * @param string $value input button value
  763. * @param array $options callback options
  764. * @return string ajaxed input button
  765. */
  766. function submitToRemote ($name, $value, $options = null) {
  767. $options['with'] = 'Form.serialize(this.form)';
  768. $options['html']['type'] = 'button';
  769. $options['html']['onclick'] = $this->remoteFunction($options)."; return false;";
  770. $options['html']['name'] = $name;
  771. $options['html']['value'] = $value;
  772. return $this->tag("input", $options['html'], false);
  773. }
  774. /**
  775. * Includes the Prototype Javascript library (and anything else) inside a single script tag
  776. *
  777. * Note: The recommended approach is to copy the contents of
  778. * lib/javascripts/ into your application's
  779. * public/javascripts/ directory, and use @see javascriptIncludeTag() to
  780. * create remote script links.
  781. * @return string script with all javascript in /javascripts folder
  782. */
  783. function defineJavascriptFunctions () {
  784. $dir = VENDORS."/javascript";
  785. $folder = new Folder($dir);
  786. $files = $folder->find('.*\.js');
  787. $javascript = '';
  788. foreach($files as $file) {
  789. if (substr($file, -3)=='.js') {
  790. $javascript .= file_get_contents("$dir/$file") . "\n\n";
  791. }
  792. }
  793. return $this->javascriptTag($javascript);
  794. }
  795. /**
  796. * Observe field and call ajax on change.
  797. *
  798. * Observes the field with the DOM ID specified by <i>field_id</i> and makes
  799. * an Ajax when its contents have changed.
  800. *
  801. * Required +options+ are:
  802. * - <i>frequency</i>:: The frequency (in seconds) at which changes to
  803. * this field will be detected.
  804. * - <i>url</i>:: @see urlFor() -style options for the action to call
  805. * when the field has changed.
  806. *
  807. * Additional options are:
  808. * - <i>update</i>:: Specifies the DOM ID of the element whose
  809. * innerHTML should be updated with the
  810. * XMLHttpRequest response text.
  811. * - <i>with</i>:: A Javascript expression specifying the
  812. * parameters for the XMLHttpRequest. This defaults
  813. * to Form.Element.serialize('$field_id'), which can be
  814. * accessed from params['form']['field_id'].
  815. *
  816. * Additionally, you may specify any of the options documented in
  817. * @see linkToRemote().
  818. *
  819. * @param string $field_id DOM ID of field to observe
  820. * @param array $options ajax options
  821. * @return string ajax script
  822. */
  823. function observeField ($field_id, $options = null) {
  824. if (!isset($options['with']))
  825. $options['with'] = "Form.Element.serialize('$field_id')";
  826. return $this->__buildObserver('Form.Element.Observer', $field_id, $options);
  827. }
  828. /**
  829. * Observe entire form and call ajax on change.
  830. *
  831. * Like @see observeField(), but operates on an entire form identified by the
  832. * DOM ID <b>form_id</b>. <b>options</b> are the same as <b>observe_field</b>, except
  833. * the default value of the <i>with</i> option evaluates to the
  834. * serialized (request string) value of the form.
  835. *
  836. * @param string $field_id DOM ID of field to observe
  837. * @param array $options ajax options
  838. * @return string ajax script
  839. */
  840. function observeForm ($field_id, $options = null) {
  841. //i think this is a rails bug... should be set
  842. if (!isset($options['with']))
  843. $options['with'] = 'Form.serialize(this.form)';
  844. return $this->__buildObserver('Form.Observer', $field_id, $options);
  845. }
  846. /**
  847. * Javascript helper function (private).
  848. *
  849. */
  850. function __optionsForAjax ($options) {
  851. $js_options = $this->__buildCallbacks($options);
  852. $js_options['asynchronous'] = 'true';
  853. if (isset($options['type'])) {
  854. if ($options['type'] == 'synchronous')
  855. $js_options['asynchronous'] = 'false';
  856. }
  857. if (isset($options['method']))
  858. $js_options['method'] = $this->__methodOptionToString($options['method']);
  859. if (isset($options['position']))
  860. $js_options['insertion'] = "Insertion." . Inflector::camelize($options['position']);
  861. if (isset($options['form'])) {
  862. $js_options['parameters'] = 'Form.serialize(this)';
  863. } elseif (isset($options['with'])) {
  864. $js_options['parameters'] = $options['with'];
  865. }
  866. $out = array();
  867. foreach ($js_options as $k => $v) {
  868. $out[] = "$k:$v";
  869. }
  870. $out = join(', ', $out);
  871. $out = '{' . $out . '}';
  872. return $out;
  873. }
  874. function __methodOptionToString ($method) {
  875. return (is_string($method) && !$method[0]=="'")? $method : "'$method'";
  876. }
  877. function __buildObserver ($klass, $name, $options=null) {
  878. if(!isset($options['with']) && isset($options['update'])) {
  879. $options['with'] = 'value';
  880. }
  881. $callback = $this->remoteFunction($options);
  882. $javascript = "new $klass('$name', ";
  883. $javascript .= "{$options['frequency']}, function(element, value) {";
  884. $javascript .= "$callback})";
  885. return $this->javascriptTag($javascript);
  886. }
  887. function __buildCallbacks($options) {
  888. $actions= array('uninitialized', 'loading', 'loaded', 'interactive', 'complete');
  889. $callbacks=array();
  890. foreach($actions as $callback) {
  891. if(isset($options[$callback])) {
  892. $name = 'on' . ucfirst($callback);
  893. $code = $options[$callback];
  894. $callbacks[$name] = "function(request){".$code."}";
  895. }
  896. }
  897. return $callbacks;
  898. }
  899. }
  900. ?>