From 75cce3f107236e09659a2e01be7b455fb753b172 Mon Sep 17 00:00:00 2001 From: phpnut Date: Sat, 2 Jul 2005 22:49:04 +0000 Subject: [PATCH] Merging changes from Kamil's sandbox. Beginning refactoring to bring current trunk to a usable release. git-svn-id: https://svn.cakephp.org/repo/trunk/cake@301 3807eeeb-6ff5-0310-8944-8be069107fe0 --- libs/controller.php | 213 ++++++-- libs/template.php | 1187 ------------------------------------------- libs/view.php | 479 ++++++++++++++++- 3 files changed, 658 insertions(+), 1221 deletions(-) diff --git a/libs/controller.php b/libs/controller.php index d58bccb01..4663b2ba4 100644 --- a/libs/controller.php +++ b/libs/controller.php @@ -37,7 +37,7 @@ /** * Enter description here... */ -uses('model', 'template', 'inflector', 'folder'); +uses('model', 'inflector', 'folder', 'view'); /** * Enter description here... @@ -47,7 +47,7 @@ uses('model', 'template', 'inflector', 'folder'); * @since Cake v 0.2.9 * */ -class Controller extends Template +class Controller extends Object { /** * Name of the controller. @@ -96,6 +96,66 @@ class Controller extends Template */ var $helpers = array('html'); + var $viewPath; + + /** + * Variables for the view + * + * @var array + * @access private + */ + var $_viewVars = array(); + + /** + * Enter description here... + * + * @var boolean + * @access private + */ + var $pageTitle = false; + + /** + * An array of model objects. + * + * @var array Array of model objects. + * @access public + */ + var $models = array(); + + + /** + * Enter description here... + * + * @var unknown_type + * @access public + */ + var $base = null; + + /** + * Enter description here... + * + * @var string + * @access public + */ + var $layout = 'default'; + + /** + * Enter description here... + * + * @var boolean + * @access public + */ + var $autoRender = true; + + /** + * Enter description here... + * + * @var boolean + * @access public + */ + var $autoLayout = true; + + /** * Constructor. * @@ -113,7 +173,7 @@ class Controller extends Template } $this->name = strtolower($r[1]); - $this->viewpath = Inflector::underscore($r[1]); + $this->viewPath = Inflector::underscore($r[1]); $model_class = Inflector::singularize($this->name); @@ -122,7 +182,7 @@ class Controller extends Template if (class_exists($model_class) && ($this->uses === false)) { - $this->$model_class = new $model_class(); + $this->models[$model_class] = new $model_class(); } elseif ($this->uses) { @@ -139,7 +199,7 @@ class Controller extends Template if (class_exists($model_class)) { - $this->$model_name = new $model_class (false); + $this->models[$model_name] = new $model_class(false); } else { @@ -149,6 +209,97 @@ class Controller extends Template } } + /** + * Redirects to given $url, after turning off $this->autoRender. + * + * @param unknown_type $url + */ + function redirect ($url) + { + $this->autoRender = false; + header ('Location: '.$this->base.$url); + } + + /** + * Saves a variable to use inside a template. + * + * @param mixed $one A string or an array of data. + * @param string $two Value in case $one is a string (which then works as the key), otherwise unused. + * @return unknown + */ + function set($one, $two=null) + { + return $this->_setArray(is_array($one)? $one: array($one=>$two)); + } + + /** + * Enter description here... + * + * @param unknown_type $action + */ + function setAction ($action) + { + $this->action = $action; + + $args = func_get_args(); + call_user_func_array(array(&$this, $action), $args); + } + + /** + * Returns number of errors in a submitted FORM. + * + * @return int Number of errors + */ + function validate () + { + $args = func_get_args(); + $errors = call_user_func_array(array(&$this, 'validateErrors'), $args); + + return count($errors); + } + + /** + * Validates a FORM according to the rules set up in the Model. + * + * @return int Number of errors + */ + function validateErrors () + { + $objects = func_get_args(); + if (!count($objects)) return false; + + $errors = array(); + foreach ($objects as $object) + { + $errors = array_merge($errors, $object->invalidFields($object->data)); + } + + return $this->validationErrors = (count($errors)? $errors: false); + } + + function render($action=null, $layout=null, $file=null) + { + $v = new View(); + $v->_viewVars = $this->_viewVars; + $v->action = $this->action; + $v->autoLayout = $this->autoLayout; + $v->autoRender = $this->autoRender; + $v->base = $this->base; + $v->helpers = $this->helpers; + $v->here = $this->here; + $v->layout = $this->layout; + $v->models = $this->models; + $v->name = $this->name; + $v->pageTitle = $this->pageTitle; + $v->parent = $this->parent; + $v->viewPath = $this->viewPath; + + $v->params = $this->params; + $v->data = $this->data; + //$this->view = $v; + return $v->render($action, $layout, $file); + } + function missingController() { //We are simulating action call below, this is not a filename! @@ -167,41 +318,43 @@ class Controller extends Template $this->render('../errors/missingView'); } + // /** + // * Displays an error page to the user. Uses layouts/error.html to render the page. + // * + // * @param int $code Error code (for instance: 404) + // * @param string $name Name of the error (for instance: Not Found) + // * @param string $message Error message + // */ + // function error ($code, $name, $message) + // { + // header ("HTTP/1.0 {$code} {$name}"); + // print ($this->_render(VIEWS.'layouts/error.thtml', array('code'=>$code,'name'=>$name,'message'=>$message))); + // } + /** - * Redirects to given $url, after turning off $this->autoRender. + * Sets data for this view. Will set title if the key "title" is in given $data array. * - * @param unknown_type $url + * @param array $data Array of */ - function redirect ($url) + function _setArray($data) { - $this->autoRender = false; - header ('Location: '.$this->base.$url); + foreach ($data as $name => $value) + { + if ($name == 'title') + $this->_setTitle($value); + else + $this->_viewVars[$name] = $value; + } } /** - * Enter description here... + * Set the title element of the page. * - * @param unknown_type $action + * @param string $pageTitle Text for the title */ - function setAction ($action) + function _setTitle($pageTitle) { - $this->action = $action; - - $args = func_get_args(); - call_user_func_array(array(&$this, $action), $args); - } - - /** - * Displays an error page to the user. Uses layouts/error.html to render the page. - * - * @param int $code Error code (for instance: 404) - * @param string $name Name of the error (for instance: Not Found) - * @param string $message Error message - */ - function error ($code, $name, $message) - { - header ("HTTP/1.0 {$code} {$name}"); - print ($this->_render(VIEWS.'layouts/error.thtml', array('code'=>$code,'name'=>$name,'message'=>$message))); + $this->pageTitle = $pageTitle; } } diff --git a/libs/template.php b/libs/template.php index d4bc23e91..e81dd9547 100644 --- a/libs/template.php +++ b/libs/template.php @@ -90,28 +90,6 @@ class Template extends Object */ var $pageTitle = false; - /** - * Choose the layout to be used when rendering. - * - * @param string $layout - */ - function setLayout($layout) - { - $this->layout = $layout; - } - - /** - * Saves a variable to use inside a template. - * - * @param mixed $one A string or an array of data. - * @param string $two Value in case $one is a string (which then works as the key), otherwise unused. - * @return unknown - */ - function set($one, $two=null) - { - return $this->_setArray(is_array($one)? $one: array($one=>$two)); - } - /** * Set the title element of the page. * @@ -122,1171 +100,6 @@ class Template extends Object $this->pageTitle = $pageTitle; } - /** - * Sets data for this view. Will set title if the key "title" is in given $data array. - * - * @param array $data Array of - */ - function _setArray($data) - { - foreach ($data as $name => $value) - { - if ($name == 'title') - $this->setTitle($value); - else - $this->_viewVars[$name] = $value; - } - } - - /** - * Displays a flash message. A flash message is feedback to the user that displays after editing actions, among other things. - * - * @param string $message Text to display to the user - * @param string $url URL fragment - * @param int $time Display time, in seconds - */ - function flash($message, $url, $time=1) - { - $this->autoRender = false; - $this->autoLayout = false; - - $this->set('url', $this->base.$url); - $this->set('message', $message); - $this->set('time', $time); - - $this->render(null,false,VIEWS.'layouts'.DS.'flash.thtml'); - } - - /** - * Render view for given action and layout. If $file is given, that is used - * for a view filename (e.g. customFunkyView.thtml). - * - * @param string $action Name of action to render for - * @param string $layout - * @param string $file Custom filename for view - */ - function render($action=null, $layout=null, $file=null) - { - if (isset($this->hasRendered) && $this->hasRendered) - { - return true; - } - else - { - $this->hasRendered = false; - } - - $this->autoRender = false; - - if (!$action) $action = $this->action; - if ($layout) $this->setLayout($layout); - - //$isFatal = isset($this->isFatal) ? $this->isFatal : false; - - $viewFn = $file? $file: $this->_getViewFn($action); - - if (!is_file($viewFn)) - { - if (strtolower(get_class($this)) == 'template') - { - return array('action' => $action, 'layout' => $layout, 'viewFn' => $viewFn); - } - - // check to see if the missing view is due to a custom missingAction - if (strpos($action, 'missingAction') !== false) - { - $errorAction = 'missingAction'; - } - else - { - $errorAction = 'missingView'; - } - - // check for controller-level view handler - foreach(array($this->name, 'errors') as $view_dir) - { - $missingViewFn = VIEWS.$view_dir.DS.Inflector::underscore($errorAction).'.thtml'; - $missingViewExists = is_file($missingViewFn); - if ($missingViewExists) - { - break; - } - } - - if (strpos($action, 'missingView') === false) - { - $controller = $this; - $controller->missingView = $viewFn; - $controller->action = $action; - call_user_func_array(array(&$controller, 'missingView'), empty($params['pass'])? null: $params['pass']); - $isFatal = isset($this->isFatal) ? $this->isFatal : false; - if (!$isFatal) - { - $viewFn = $missingViewFn; - } - } - else - { - $missingViewExists = false; - } - - if (!$missingViewExists || $isFatal) - { - // app/view/errors/missing_view.thtml view is missing! - if (DEBUG) - { - trigger_error(sprintf(ERROR_NO_VIEW, $action, $viewFn), E_USER_ERROR); - } - else - { - $this->error('404', 'Not found', sprintf(ERROR_404, '', "missing view \"{$action}\"")); - } - - die(); - } - } - - if ($viewFn && !$this->hasRendered) - { - $out = $this->_render($viewFn, $this->_viewVars, 0); - if ($out !== false) - { - if ($this->layout && $this->autoLayout) - { - $out = $this->renderLayout($out); - } - - print $out; - $this->hasRendered = true; - } - else - { - $out = $this->_render($viewFn, $this->_viewVars, false); - trigger_error(sprintf(ERROR_IN_VIEW, $viewFn, $out), E_USER_ERROR); - } - - return true; - } - } - - /** - * Renders a layout. Returns output from _render(). Returns false on error. - * - * @param string $content_for_layout Content to render in a view - * @return string Rendered output - */ - function renderLayout($content_for_layout) - { - $layout_fn = $this->_getLayoutFn(); - - $data_for_layout = array_merge($this->_viewVars, array( - 'title_for_layout'=>$this->pageTitle !== false? $this->pageTitle: Inflector::humanize($this->viewpath), - 'content_for_layout'=>$content_for_layout)); - - if (is_file($layout_fn)) { - $out = $this->_render($layout_fn, $data_for_layout); - - if ($out === false) { - $out = $this->_render($layout_fn, $data_for_layout, false); - trigger_error(sprintf(ERROR_IN_LAYOUT, $layout_fn, $out), E_USER_ERROR); - return false; - } - else { - return $out; - } - } - else { - trigger_error(sprintf(ERROR_NO_LAYOUT, $this->layout, $layout_fn), E_USER_ERROR); - return false; - } - } - - /** - * Renders a piece of PHP with provided parameters and returns HTML, XML, or any other string. - * - * @param string $name Name of template file - * @param array $params Array of data for rendered view - * @return string Rendered output - */ - function renderElement($name, $params=array()) - { - $fn = ELEMENTS.$name.'.thtml'; - - if (!file_exists($fn)) - return "(Error rendering {$name})"; - - return $this->_render($fn, array_merge($this->_viewVars, $params)); - } - - /** - * Returns layout filename for this template as a string. - * - * @return string Filename for layout file (.thtml). - */ - function _getLayoutFn() - { - return VIEWS."layouts".DS."{$this->layout}.thtml"; - } - - /** - * Returns filename of given action's template file (.thtml) as a string. CamelCased action names will be under_scored! This means that you can have LongActionNames that refer to long_action_names.thtml views. - * - * @param string $action Controller action to find template filename for - * @return string Template filename - */ - function _getViewFn($action) - { - $action = Inflector::underscore($action); - $viewFn = VIEWS.$this->viewpath.DS."{$action}.thtml"; - $viewPath = explode(DS, $viewFn); - - $i = array_search('..', $viewPath); - - unset($viewPath[$i-1]); - unset($viewPath[$i]); - - return '/'.implode('/', $viewPath); - } - - /** - * Renders and returns output for given view filename with its - * array of data. - * - * @param string $___viewFn Filename of the view - * @param array $___data_for_view Data to include in rendered view - * @param boolean $___play_safe If set to false, the include() of the $__viewFn is done without suppressing output of errors - * @return string Rendered output - */ - function _render($___viewFn, $___data_for_view, $___play_safe = true) - { - /** - * Fetching helpers - */ - if ($this->helpers !== false) - { - foreach ($this->helpers as $helper) - { - $helperFn = LIBS.'helpers'.DS.Inflector::underscore($helper).'.php'; - $helperCn = ucfirst($helper).'Helper'; - if (is_file($helperFn)) - { - require_once $helperFn; - if(class_exists($helperCn)===true); - { - ${$helper} = new $helperCn; - ${$helper}->base = $this->base; - ${$helper}->here = $this->here; - ${$helper}->params = $this->params; - ${$helper}->action = $this->action; - ${$helper}->data = $this->data; - } - } - } - } - - extract($___data_for_view, EXTR_SKIP); # load all view variables - /** - * Local template variables. - */ - $BASE = $this->base; - $params = &$this->params; - $page_title = $this->pageTitle; - - /** - * Start caching output (eval outputs directly so we need to cache). - */ - ob_start(); - - /** - * Include the template. - */ - $___play_safe? @include($___viewFn): include($___viewFn); - - $out = ob_get_clean(); - - return $out; - } - - - /////////////////////////////////////////////////////////////////////////// - - /** - * Returns an URL for a combination of controller and action. - * - * @param string $url - * @return string Full constructed URL as a string. - */ - function urlFor($url=null) - { - if (empty($url)) - { - return $this->here; - } - elseif ($url[0] == '/') - { - $out = $this->base . $url; - } - else - { - $out = $this->base . '/' . strtolower($this->params['controller']) . '/' . $url; - } - - return ereg_replace('&([^a])', '&\1', $out); - } - - /** - * Returns a space-separated string with items of the $options array. - * - * @param array $options Array of HTML options. - * @param string $insert_before - * @param unknown_type $insert_after - * @return string - */ - function parseHtmlOptions($options, $exclude=null, $insert_before=' ', $insert_after=null) - { - if (!is_array($exclude)) $exclude = array(); - - if (is_array($options)) - { - $out = array(); - foreach ($options as $k=>$v) - { - if (!in_array($k, $exclude)) - { - $out[] = "{$k}=\"{$v}\""; - } - } - $out = join(' ', $out); - return $out? $insert_before.$out.$insert_after: null; - } - else - { - return $options? $insert_before.$options.$insert_after: null; - } - } - - /** - * Returns an HTML link to $url for given $title, optionally using $html_options and $confirm_message (for "flash"). - * - * @param string $title The content of the A tag. - * @param string $url - * @param array $html_options Array of HTML options. - * @param string $confirm_message Message to be shown in "flash". - * @return string - */ - function linkTo($title, $url, $html_options=null, $confirm_message=false) - { - $confirm_message? $html_options['onClick'] = "return confirm('{$confirm_message}')": null; - return sprintf(TAG_LINK, $this->UrlFor($url), $this->parseHtmlOptions($html_options), $title); - } - - /** - * Returns an external HTML link to $url for given $title, optionally using $html_options. - * The ereg_replace is to replace the '&' in the URL into & for XHTML purity. - * - * @param string $title - * @param string $url - * @param array $html_options - * @return string - */ - function linkOut($title, $url=null, $html_options=null) - { - $url = $url? $url: $title; - return sprintf(TAG_LINK, ereg_replace('&([^a])', '&\1', $url), $this->parseHtmlOptions($html_options), $title); - } - - /** - * Returns an HTML FORM element. - * - * @param string $target URL for the FORM's ACTION attribute. - * @param string $type FORM type (POST/GET). - * @param array $html_options - * @return string An formatted opening FORM tag. - */ - function formTag($target=null, $type='post', $html_options=null) - { - $html_options['action'] = $this->UrlFor($target); - $html_options['method'] = $type=='get'? 'get': 'post'; - $type == 'file'? $html_options['enctype'] = 'multipart/form-data': null; - - return sprintf(TAG_FORM, $this->parseHtmlOptions($html_options, null, '')); - } - - /** - * Returns a generic HTML tag (no content). - * - * Examples: - * * tag("br") =>
- * * tag("input", array("type" => "text")) => - * - * @param string $name Name of HTML element - * @param array $options HTML options - * @param bool $open Is the tag open or closed? (defaults to closed "/>") - * @return string The formatted HTML tag - */ - function tag($name, $options=null, $open=false) - { - $tag = "<$name ". $this->parseHtmlOptions($options); - $tag .= $open? ">" : " />"; - return $tag; - } - - /** - * Returns a generic HTML tag with content. - * - * Examples: - * * content_tag("p", "Hello world!") =>

Hello world!

- * * content_tag("div", content_tag("p", "Hello world!"), array("class" => "strong")) => - *

Hello world!

- * - * @param string $name Name of HTML element - * @param array $options HTML options - * @param bool $open Is the tag open or closed? (defaults to closed "/>") - * @return string The formatted HTML tag - */ - function contentTag($name, $content, $options=null) - { - return "<$name ". $this->parseHtmlOptions($options). ">$content"; - } - - /** - * Returns a formatted SUBMIT button for HTML FORMs. - * - * @param string $caption Text on SUBMIT button - * @param array $html_options HTML options - * @return string The formatted SUBMIT button - */ - function submitTag($caption='Submit', $html_options=null) - { - $html_options['value'] = $caption; - return sprintf(TAG_SUBMIT, $this->parseHtmlOptions($html_options, null, '', ' ')); - } - - /** - * Returns a formatted INPUT tag for HTML FORMs. - * - * @param string $tag_name Name attribute for INPUT element - * @param int $size Size attribute for INPUT element - * @param array $html_options - * @return string The formatted INPUT element - */ - function inputTag($tag_name, $size=20, $html_options=null) - { - $html_options['size'] = $size; - $html_options['value'] = isset($html_options['value'])? $html_options['value']: $this->tagValue($tag_name); - $this->tagIsInvalid($tag_name)? $html_options['class'] = 'form_error': null; - return sprintf(TAG_INPUT, $tag_name, $this->parseHtmlOptions($html_options, null, '', ' ')); - } - - /** - * Returns an INPUT element with type="password". - * - * @param string $tag_name - * @param int $size - * @param array $html_options - * @return string - */ - function passwordTag($tag_name, $size=20, $html_options=null) - { - $html_options['size'] = $size; - empty($html_options['value'])? $html_options['value'] = $this->tagValue($tag_name): null; - return sprintf(TAG_PASSWORD, $tag_name, $this->parseHtmlOptions($html_options, null, '', ' ')); - } - - /** - * Returns an INPUT element with type="hidden". - * - * @param string $tag_name - * @param string $value - * @param array $html_options - * @return string - */ - function hiddenTag($tag_name, $value=null, $html_options=null) - { - $html_options['value'] = $value? $value: $this->tagValue($tag_name); - return sprintf(TAG_HIDDEN, $tag_name, $this->parseHtmlOptions($html_options, null, '', ' ')); - } - - /** - * Returns an INPUT element with type="file". - * - * @param string $tag_name - * @param array $html_options - * @return string - */ - function fileTag($tag_name, $html_options=null) - { - return sprintf(TAG_FILE, $tag_name, $this->parseHtmlOptions($html_options, null, '', ' ')); - } - - /** - * Returns a TEXTAREA element. - * - * @param string $tag_name - * @param int $cols - * @param int $rows - * @param array $html_options - * @return string - */ - function areaTag($tag_name, $cols=60, $rows=10, $html_options=null) - { - $value = empty($html_options['value'])? $this->tagValue($tag_name): empty($html_options['value']); - $html_options['cols'] = $cols; - $html_options['rows'] = $rows; - return sprintf(TAG_AREA, $tag_name, $this->parseHtmlOptions($html_options, null, ' '), $value); - } - - /** - * Returns an INPUT element with type="checkbox". Checkedness is to be passed as string "checked" with the key "checked" in the $html_options array. - * - * @param string $tag_name - * @param string $title - * @param array $html_options - * @return string - */ - function checkboxTag($tag_name, $title=null, $html_options=null) - { - $this->tagValue($tag_name)? $html_options['checked'] = 'checked': null; - $title = $title? $title: ucfirst($tag_name); - return sprintf(TAG_CHECKBOX, $tag_name, $tag_name, $tag_name, $this->parseHtmlOptions($html_options, null, '', ' '), $title); - } - - /** - * Returns a set of radio buttons. - * - * @param string $tag_name - * @param array $options Array of options to select from - * @param string $inbetween String to separate options. See PHP's implode() function - * @param array $html_options - * @return string - */ - function radioTags($tag_name, $options, $inbetween=null, $html_options=null) - { - $value = isset($html_options['value'])? $html_options['value']: $this->tagValue($tag_name); - $out = array(); - foreach ($options as $opt_value=>$opt_title) - { - $options_here = array('value' => $opt_value); - $opt_value==$value? $options_here['checked'] = 'checked': null; - $parsed_options = $this->parseHtmlOptions(array_merge($html_options, $options_here), null, '', ' '); - $individual_tag_name = "{$tag_name}_{$opt_value}"; - $out[] = sprintf(TAG_RADIOS, $individual_tag_name, $tag_name, $individual_tag_name, $parsed_options, $opt_title); - } - - $out = join($inbetween, $out); - return $out? $out: null; - } - - /** - * Returns a SELECT element, - * - * @param string $tag_name Name attribute of the SELECT - * @param array $option_elements Array of the OPTION elements (as 'value'=>'Text' pairs) to be used in the SELECT element - * @param array $select_attr Array of HTML options for the opening SELECT element - * @param array $option_attr Array of HTML options for the enclosed OPTION elements - * @return string Formatted SELECT element - */ - function selectTag($tag_name, $option_elements, $selected=null, $select_attr=null, $option_attr=null) - { - if (!is_array($option_elements) || !count($option_elements)) - return null; - - $select[] = sprintf(TAG_SELECT_START, $tag_name, $this->parseHtmlOptions($select_attr)); - $select[] = sprintf(TAG_SELECT_EMPTY, $this->parseHtmlOptions($option_attr)); - - foreach ($option_elements as $name=>$title) - { - $options_here = $option_attr; - - if ($selected == $name) - $options_here['selected'] = 'selected'; - - $select[] = sprintf(TAG_SELECT_OPTION, $name, $this->parseHtmlOptions($options_here), $title); - } - - $select[] = sprintf(TAG_SELECT_END); - - return implode("\n", $select); - } - - /** - * Returns a formatted IMG element. - * - * @param string $path Path to the image file - * @param string $alt ALT attribute for the IMG tag - * @param array $html_options - * @return string Formatted IMG tag - */ - function imageTag($path, $alt=null, $html_options=null) - { - $url = $this->base.IMAGES_URL.$path; - return sprintf(TAG_IMAGE, $url, $alt, $this->parseHtmlOptions($html_options, null, '', ' ')); - } - - /** - * Returns a mailto: link. - * - * @param string $title Title of the link, or the e-mail address (if the same) - * @param string $email E-mail address if different from title - * @param array $options - * @return string Formatted A tag - */ - function linkEmail($title, $email=null, $options=null) - { - // if no $email, then title contains the email. - if (empty($email)) $email = $title; - - $match = array(); - - // does the address contain extra attributes? - preg_match('!^(.*)(\?.*)$!', $email, $match); - - // plaintext - if (empty($options['encode']) || !empty($match[2])) - { - return sprintf(TAG_MAILTO, $email, $this->parseHtmlOptions($options), $title); - } - // encoded to avoid spiders - else - { - $email_encoded = null; - for ($ii=0; $ii < strlen($email); $ii++) - { - if(preg_match('!\w!',$email[$ii])) - { - $email_encoded .= '%' . bin2hex($email[$ii]); - } - else - { - $email_encoded .= $email[$ii]; - } - } - - $title_encoded = null; - for ($ii=0; $ii < strlen($title); $ii++) - { - $title_encoded .= preg_match('/^[A-Za-z0-9]$/', $title[$ii])? '&#x' . bin2hex($title[$ii]).';': $title[$ii]; - } - - return sprintf(TAG_MAILTO, $email_encoded, $this->parseHtmlOptions($options, array('encode')), $title_encoded); - } - } - - /** - * Returns a LINK element for CSS stylesheets. - * - * @param string $path Path to CSS file - * @param string $rel Rel attribute. Defaults to "stylesheet". - * @param array $html_options - * @return string Formatted LINK element. - */ - function cssTag($path, $rel='stylesheet', $html_options=null) - { - $url = "{$this->base}/".(COMPRESS_CSS? 'c': '')."css/{$path}.css"; - return sprintf(TAG_CSS, $rel, $url, $this->parseHtmlOptions($html_options, null, '', ' ')); - } - - /** - * Returns a charset meta-tag - * - * @param string $charset - * @return string - */ - function charsetTag($charset) - { - return sprintf(TAG_CHARSET, $charset); - } - - /** - * Returns a JavaScript script tag. - * - * @param string $script The JavaScript to be wrapped in SCRIPT tags. - * @return string The full SCRIPT element, with the JavaScript inside it. - */ - function javascriptTag($script) - { - return sprintf(TAG_JAVASCRIPT, $script); - } - - /** - * Returns a JavaScript include tag - * - * @param string $url URL to JavaScript file. - * @return string - */ - function javascriptIncludeTag($url) - { - return sprintf(TAG_JAVASCRIPT_INCLUDE, $this->base.$url); - } - - /** - * Returns a row of formatted and named TABLE headers. - * - * @param array $names - * @param array $tr_options - * @param array $th_options - * @return string - */ - function tableHeaders($names, $tr_options=null, $th_options=null) - { - $out = array(); - foreach ($names as $arg) - { - $out[] = sprintf(TAG_TABLE_HEADER, $this->parseHtmlOptions($th_options), $arg); - } - - return sprintf(TAG_TABLE_HEADERS, $this->parseHtmlOptions($tr_options), join(' ', $out)); - } - - /** - * Returns a formatted string of table rows (TR's with TD's in them). - * - * @param array $data Array of table data - * @param array $tr_options HTML options for TR elements - * @param array $td_options HTML options for TD elements - * @return string - */ - function tableCells($data, $odd_tr_options=null, $even_tr_options=null) - { - if (empty($data[0]) || !is_array($data[0])) - { - $data = array($data); - } - - $count=0; - foreach ($data as $line) - { - $count++; - $cells_out = array(); - foreach ($line as $cell) - { - $cells_out[] = sprintf(TAG_TABLE_CELL, null, $cell); - } - - $options = $this->parseHtmlOptions($count%2? $odd_tr_options: $even_tr_options); - $out[] = sprintf(TAG_TABLE_ROW, $options, join(' ', $cells_out)); - } - - return join("\n", $out); - } - - /** - * Generates a nested \n"; - - return $out; - } - - /** - * Adds $name and $link to the breadcrumbs array. - * - * @param string $name Text for link - * @param string $link URL for link - */ - function addCrumb($name, $link) - { - $this->_crumbs[] = array ($name, $link); - } - - /** - * Returns the breadcrumb trail as a sequence of »-separated links. - * - * @param string $separator Text to separate crumbs. - * @return string Formatted -separated list of breadcrumb links. Returns NULL if $this->_crumbs is empty. - */ - function getCrumbs($separator = '»') - { - - if (count($this->_crumbs)) - { - - $out = array("base}\">START"); - foreach ($this->_crumbs as $crumb) - { - $out[] = "base}{$crumb[1]}\">{$crumb[0]}"; - } - - return join($separator, $out); - } - else - { - return null; - } - } - - - /////////////////////////////////////////////////////////////////////////// - - - /** - * Returns link to javascript function - * - * Returns a link that'll trigger a javascript function using the - * onclick handler and return false after the fact. - * - * Examples: - * - * linkToFunction("Greeting", "alert('Hello world!')"); - * linkToFunction(imageTag("delete"), "if confirm('Really?'){ do_delete(); }"); - * - * - * @param string $title title of link - * @param string $func javascript function to be called on click - * @param array $html_options html options for link - * @return string html code for link to javascript function - */ - function linkToFunction($title, $func, $html_options=null) - { - $html_options['onClick'] = "$func; return false;"; - return $this->linkTo($title, '#', $html_options); - } - - /** - * Returns link to remote action - * - * Returns a link to a remote action defined by options[url] - * (using the urlFor format) that's called in the background using - * XMLHttpRequest. The result of that request can then be inserted into a - * DOM object whose id can be specified with options[update]. - * Usually, the result would be a partial prepared by the controller with - * either renderPartial or renderPartialCollection. - * - * Examples: - * - * linkToRemote("Delete this post", - * array("update" => "posts", "url" => "delete/{$postid->id}")); - * linkToRemote(imageTag("refresh"), - * array("update" => "emails", "url" => "list_emails" )); - * - * - * By default, these remote requests are processed asynchronous during - * which various callbacks can be triggered (for progress indicators and - * the likes). - * - * Example: - * - * linkToRemote (word, - * array("url" => "undo", "n" => word_counter), - * array("complete" => "undoRequestCompleted(request)")); - * - * - * The callbacks that may be specified are: - * - * - loading:: Called when the remote document is being - * loaded with data by the browser. - * - loaded:: Called when the browser has finished loading - * the remote document. - * - interactive:: Called when the user can interact with the - * remote document, even though it has not - * finished loading. - * - complete:: Called when the XMLHttpRequest is complete. - * - * If you for some reason or another need synchronous processing (that'll - * block the browser while the request is happening), you can specify - * options[type] = synchronous. - * - * You can customize further browser side call logic by passing - * in Javascript code snippets via some optional parameters. In - * their order of use these are: - * - * - confirm:: Adds confirmation dialog. - * -condition:: Perform remote request conditionally - * by this expression. Use this to - * describe browser-side conditions when - * request should not be initiated. - * - before:: Called before request is initiated. - * - after:: Called immediately after request was - * initiated and before loading. - * - * @param string $title title of link - * @param array $options options for javascript function - * @param array $html_options options for link - * @return string html code for link to remote action - */ - function linkToRemote($title, $options=null, $html_options=null) - { - return $this->linkToFunction($title, $this->remoteFunction($options), $html_options); - } - - /** - * Creates javascript function for remote AJAX call - * - * This function creates the javascript needed to make a remote call - * it is primarily used as a helper for linkToRemote. - * - * @see linkToRemote() for docs on options parameter. - * - * @param array $options options for javascript - * @return string html code for link to remote action - */ - function remoteFunction($options=null) - { - $javascript_options = $this->__optionsForAjax($options); - $func = isset($options['update']) - ? "new Ajax.Updater('{$options['update']}', " - : "new Ajax.Request("; - - $func .= "'" . $this->urlFor($options['url']) . "'"; - $func .= ", $javascript_options)"; - - if (isset($options['before'])) - { - $func = "{$options['before']}; $func"; - } - if (isset($options['after'])) - { - $func = "$func; {$options['before']};"; - } - if (isset($options['condition'])) - { - $func = "if ({$options['condition']}) { $func; }"; - } - if (isset($options['confirm'])) - { - $func = "if (confirm('" . $this->escapeJavascript($options['confirm']) . "')) { $func; }"; - } - - return $func; - } - - /** - * Escape carrier returns and single and double quotes for Javascript segments. - * - * @param string $javascript string that might have javascript elements - * @return string escaped string - */ - function escapeJavascript($javascript) - { - $javascript = str_replace(array("\r\n","\n","\r"),'\n', $javascript); - $javascript = str_replace(array('"', "'"), array('\"', "\\'"), $javascript); - return $javascript; - } - - /** - * Periodically call remote url via AJAX. - * - * Periodically calls the specified url (options[url]) every options[frequency] seconds (default is 10). - * Usually used to update a specified div (options[update]) with the results of the remote call. - * The options for specifying the target with url and defining callbacks is the same as linkToRemote. - * - * @param array $options callback options - * @return string javascript code - */ - function periodicallyCallRemote($options=null) - { - $frequency = (isset($options['frequency']))? $options['frequency'] : 10; - $code = "new PeriodicalExecuter(function() {" . $this->remote_function($options) . "}, $frequency)"; - return $this->javascriptTag($code); - } - - /** - * Returns form tag that will submit using Ajax. - * - * Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular - * reloading POST arrangement. Even though it's using Javascript to serialize the form elements, the form submission - * will work just like a regular submission as viewed by the receiving side (all elements available in params). - * The options for specifying the target with :url and defining callbacks is the same as link_to_remote. - * - * @param array $options callback options - * @return string javascript code - */ - function formRemoteTag($options=null) - { - $options['form'] = true; - $options['html']['onsubmit']=$this->remoteFunction($options) . "; return false;"; - return $this->tag("form", $options['html'], true); - } - - /** - * Returns a button input tag that will submit using Ajax - * - * Returns a button input tag that will submit form using XMLHttpRequest in the background instead of regular - * reloading POST arrangement. options argument is the same as in form_remote_tag - * - * @param string $name input button name - * @param string $value input button value - * @param array $options callback options - * @return string ajaxed input button - */ - function submitToRemote($name, $value, $options = null) - { - $options['with'] = 'Form.serialize(this.form)'; - $options['html']['type'] = 'button'; - $options['html']['onclick'] = $this->remoteFunction($options)."; return false;"; - $options['html']['name'] = $name; - $options['html']['value'] = $value; - return $this->tag("input", $options['html'], false); - } - - - /** - * Includes the Prototype Javascript library (in /vendors/javascript/prototype.js). - * - * @return string Javascript include tag for prototype library. - */ - function defineJavascriptFunctions() - { - return $this->javascriptIncludeTag(DS.'js'.DS.'vendors.php?file=prototype.js'); - } - - /** - * Observe field and call ajax on change. - * - * Observes the field with the DOM ID specified by field_id and makes - * an Ajax when its contents have changed. - * - * Required +options+ are: - * - frequency:: The frequency (in seconds) at which changes to - * this field will be detected. - * - url:: @see urlFor() -style options for the action to call - * when the field has changed. - * - * Additional options are: - * - update:: Specifies the DOM ID of the element whose - * innerHTML should be updated with the - * XMLHttpRequest response text. - * - with:: A Javascript expression specifying the - * parameters for the XMLHttpRequest. This defaults - * to Form.Element.serialize('$field_id'), which can be - * accessed from params['form']['field_id']. - * - * Additionally, you may specify any of the options documented in - * @see linkToRemote(). - * - * @param string $field_id DOM ID of field to observe - * @param array $options ajax options - * @return string ajax script - */ - function observeField($field_id, $options = null) - { - if (!isset($options['with'])) - { - $options['with'] = "Form.Element.serialize('$field_id')"; - } - return $this->__buildObserver('Form.Element.Observer', $field_id, $options); - } - - /** - * Observe entire form and call ajax on change. - * - * Like @see observeField(), but operates on an entire form identified by the - * DOM ID form_id. options are the same as observe_field, except - * the default value of the with option evaluates to the - * serialized (request string) value of the form. - * - * @param string $field_id DOM ID of field to observe - * @param array $options ajax options - * @return string ajax script - */ - function observeForm($field_id, $options = null) - { - //i think this is a rails bug... should be set - if (!isset($options['with'])) - { - $options['with'] = 'Form.serialize(this.form)'; - } - - return $this->__buildObserver('Form.Observer', $field_id, $options); - } - - - /** - * Javascript helper function (private). - * - */ - function __optionsForAjax($options) - { - $js_options = $this->__buildCallbacks($options); - $js_options['asynchronous'] = 'true'; - if (isset($options['type'])) - { - if ($options['type'] == 'synchronous') - { - $js_options['asynchronous'] = 'false'; - } - } - if (isset($options['method'])) - { - $js_options['method'] = $this->__methodOptionToString($options['method']); - } - if (isset($options['position'])) - { - $js_options['insertion'] = "Insertion." . Inflector::camelize($options['position']); - } - - if (isset($options['form'])) - { - $js_options['parameters'] = 'Form.serialize(this)'; - } - elseif (isset($options['with'])) - { - $js_options['parameters'] = $options['with']; - } - - $out = array(); - foreach ($js_options as $k => $v) - { - $out[] = "$k:$v"; - } - $out = join(', ', $out); - $out = '{' . $out . '}'; - return $out; - } - - - function __methodOptionToString($method) - { - return (is_string($method) && !$method[0]=="'")? $method : "'$method'"; - } - - function __buildObserver($klass, $name, $options=null) - { - if(!isset($options['with']) && isset($options['update'])) - { - $options['with'] = 'value'; - } - $callback = $this->remoteFunction($options); - $javascript = "new $klass('$name', "; - $javascript .= "{$options['frequency']}, function(element, value) {"; - $javascript .= "$callback})"; - return $this->javascriptTag($javascript); - } - - function __buildCallbacks($options) - { - $actions= array('uninitialized', 'loading', 'loaded', 'interactive', 'complete'); - $callbacks=array(); - foreach($actions as $callback) - { - if(isset($options[$callback])) - { - $name = 'on' . ucfirst($callback); - $code = $options[$callback]; - $callbacks[$name] = "function(request){".$code."}"; - } - } - return $callbacks; - } } diff --git a/libs/view.php b/libs/view.php index f0393b455..52ecb60ad 100644 --- a/libs/view.php +++ b/libs/view.php @@ -1,4 +1,5 @@ - + // @@ -13,8 +14,8 @@ ////////////////////////////////////////////////////////////////////////// /** - * Purpose: Dispatcher - * Dispatches the request, creating aproppriate models and controllers. + * Purpose: View + * * * @filesource * @author Cake Authors/Developers @@ -22,13 +23,483 @@ * @link https://developers.nextco.com/cake/wiki/Authors Authors/Developers * @package cake * @subpackage cake.libs - * @since Cake v 0.2.9 + * @since Cake v 0.9.1 * @version $Revision$ * @modifiedby $LastChangedBy$ * @lastmodified $Date$ * @license http://www.opensource.org/licenses/mit-license.php The MIT License */ +uses('object'); +/** + * + * + * @package cake + * @subpackage cake.libs + * @since Cake v 0.9.1 + */ +class View extends Object +{ + /** + * Name of the controller. + * + * @var unknown_type + * @access public + */ + var $name = null; + + /** + * Stores the current URL (for links etc.) + * + * @var string Current URL + */ + var $here = null; + + /** + * Enter description here... + * + * @var unknown_type + * @access public + */ + var $parent = null; + + /** + * Action to be performed. + * + * @var string + * @access public + */ + var $action = null; + + /** + * An array of names of models the particular controller wants to use. + * + * @var mixed A single name as a string or a list of names as an array. + * @access protected + */ + var $uses = false; + + /** + * An array of names of built-in helpers to include. + * + * @var mixed A single name as a string or a list of names as an array. + * @access protected + */ + var $helpers = array('html'); + + var $viewPath; + + /** + * Variables for the view + * + * @var array + * @access private + */ + var $_viewVars = array(); + + /** + * Enter description here... + * + * @var boolean + * @access private + */ + var $pageTitle = false; + + /** + * An array of model objects. + * + * @var array Array of model objects. + * @access public + */ + var $models = array(); + + + /** + * Enter description here... + * + * @var unknown_type + * @access public + */ + var $base = null; + + /** + * Enter description here... + * + * @var string + * @access public + */ + var $layout = 'default'; + + /** + * Enter description here... + * + * @var boolean + * @access public + */ + var $autoRender = true; + + /** + * Enter description here... + * + * @var boolean + * @access public + */ + var $autoLayout = true; + + + + + + var $params; + var $hasRendered = null; + + var $modelsLoaded = false; + + function getInstance() + { + static $instance; + if (!isset($instance)) + { + $instance = array(new View()); + } + return $instance[0]; + } + + /** + * Displays a flash message. A flash message is feedback to the user that displays after editing actions, among other things. + * + * @param string $message Text to display to the user + * @param string $url URL fragment + * @param int $time Display time, in seconds + */ + function flash($message, $url, $time=1) + { + $this->autoRender = false; + $this->autoLayout = false; + + $this->set('url', $this->base.$url); + $this->set('message', $message); + $this->set('time', $time); + + $this->render(null,false,VIEWS.'layouts'.DS.'flash.thtml'); + } + + /** + * Render view for given action and layout. If $file is given, that is used + * for a view filename (e.g. customFunkyView.thtml). + * + * @param string $action Name of action to render for + * @param string $layout + * @param string $file Custom filename for view + */ + function render($action=null, $layout=null, $file=null) + { + if ($this->modelsLoaded!==true) + { + foreach ($this->models as $modelName => $model) + { + $this->$modelName = $model; + } + } + + if (isset($this->hasRendered) && $this->hasRendered) + { + return true; + } + else + { + $this->hasRendered = false; + } + + $this->autoRender = false; + + if (!$action) + { + $action = $this->action; + } + if ($layout) + { + $this->setLayout($layout); + } + + $viewFileName = $file? $file: $this->_getViewFileName($action); + + if (!is_file($viewFileName)) + { + if (strtolower(get_class($this)) == 'template') + { + return array('action' => $action, 'layout' => $layout, 'viewFn' => $viewFileName); + } + + // check to see if the missing view is due to a custom missingAction + if (strpos($action, 'missingAction') !== false) + { + $errorAction = 'missingAction'; + } + else + { + $errorAction = 'missingView'; + } + + // check for controller-level view handler + foreach(array($this->name, 'errors') as $viewDir) + { + $missingViewFileName = VIEWS.$viewDir.DS.Inflector::underscore($errorAction).'.thtml'; + $missingViewExists = is_file($missingViewFileName); + if ($missingViewExists) + { + break; + } + } + + if (strpos($action, 'missingView') === false) + { + $controller = $this; + $controller->missingView = $viewFileName; + $controller->action = $action; + call_user_func_array(array(&$controller, 'missingView'), empty($params['pass'])? null: $params['pass']); + $isFatal = isset($this->isFatal) ? $this->isFatal : false; + if (!$isFatal) + { + $viewFileName = $missingViewFileName; + } + } + else + { + $missingViewExists = false; + } + + if (!$missingViewExists || $isFatal) + { + // app/view/errors/missing_view.thtml view is missing! + if (DEBUG) + { + trigger_error(sprintf(ERROR_NO_VIEW, $action, $viewFileName), E_USER_ERROR); + } + else + { + $this->error('404', 'Not found', sprintf(ERROR_404, '', "missing view \"{$action}\"")); + } + + die(); + } + } + + if ($viewFileName && !$this->hasRendered) + { + $out = $this->_render($viewFileName, $this->_viewVars, 0); + if ($out !== false) + { + if ($this->layout && $this->autoLayout) + { + $out = $this->renderLayout($out); + } + + print $out; + $this->hasRendered = true; + } + else + { + $out = $this->_render($viewFileName, $this->_viewVars, false); + trigger_error(sprintf(ERROR_IN_VIEW, $viewFileName, $out), E_USER_ERROR); + } + + return true; + } + } + + /** + * Renders a piece of PHP with provided parameters and returns HTML, XML, or any other string. + * + * @param string $name Name of template file + * @param array $params Array of data for rendered view + * @return string Rendered output + */ + function renderElement($name, $params=array()) + { + $fn = ELEMENTS.$name.'.thtml'; + + if (!file_exists($fn)) + { + return "(Error rendering {$name})"; + } + return $this->_render($fn, array_merge($this->_viewVars, $params)); + } + + /** + * Renders a layout. Returns output from _render(). Returns false on error. + * + * @param string $content_for_layout Content to render in a view + * @return string Rendered output + */ + function renderLayout($content_for_layout) + { + $layout_fn = $this->_getLayoutFileName(); + + $data_for_layout = array_merge($this->_viewVars, array( + 'title_for_layout'=>$this->pageTitle !== false? $this->pageTitle: Inflector::humanize($this->viewPath), + 'content_for_layout'=>$content_for_layout)); + + if (is_file($layout_fn)) + { + $out = $this->_render($layout_fn, $data_for_layout); + + if ($out === false) + { + $out = $this->_render($layout_fn, $data_for_layout, false); + trigger_error(sprintf(ERROR_IN_LAYOUT, $layout_fn, $out), E_USER_ERROR); + return false; + } + else + { + return $out; + } + } + else + { + trigger_error(sprintf(ERROR_NO_LAYOUT, $this->layout, $layout_fn), E_USER_ERROR); + return false; + } + } + + /** + * Choose the layout to be used when rendering. + * + * @param string $layout + */ + function setLayout($layout) + { + $this->layout = $layout; + } + + /** + * Displays an error page to the user. Uses layouts/error.html to render the page. + * + * @param int $code Error code (for instance: 404) + * @param string $name Name of the error (for instance: Not Found) + * @param string $message Error message + */ + function error ($code, $name, $message) + { + header ("HTTP/1.0 {$code} {$name}"); + print ($this->_render(VIEWS.'layouts/error.thtml', array('code'=>$code,'name'=>$name,'message'=>$message))); + } + + + function missingController() + { + //We are simulating action call below, this is not a filename! + $this->render('../errors/missingController'); + } + + function missingAction() + { + //We are simulating action call below, this is not a filename! + $this->render('../errors/missingAction'); + } + + function missingView() + { + //We are simulating action call below, this is not a filename! + $this->render('../errors/missingView'); + } + + + /************************************************************************** + * Private methods. + *************************************************************************/ + + + /** + * Returns filename of given action's template file (.thtml) as a string. CamelCased action names will be under_scored! This means that you can have LongActionNames that refer to long_action_names.thtml views. + * + * @param string $action Controller action to find template filename for + * @return string Template filename + */ + function _getViewFileName($action) + { + $action = Inflector::underscore($action); + $viewFileName = VIEWS.$this->viewPath.DS."{$action}.thtml"; + $viewPath = explode(DS, $viewFileName); + + $i = array_search('..', $viewPath); + + unset($viewPath[$i-1]); + unset($viewPath[$i]); + + return '/'.implode('/', $viewPath); + } + + /** + * Returns layout filename for this template as a string. + * + * @return string Filename for layout file (.thtml). + */ + function _getLayoutFileName() + { + return VIEWS."layouts".DS."{$this->layout}.thtml"; + } + + /** + * Renders and returns output for given view filename with its + * array of data. + * + * @param string $___viewFn Filename of the view + * @param array $___data_for_view Data to include in rendered view + * @param boolean $___play_safe If set to false, the include() of the $__viewFn is done without suppressing output of errors + * @return string Rendered output + */ + function _render($___viewFn, $___data_for_view, $___play_safe = true) + { + /** + * Fetching helpers + */ + if ($this->helpers !== false) + { + foreach ($this->helpers as $helper) + { + $helperFn = LIBS.'helpers'.DS.Inflector::underscore($helper).'.php'; + $helperCn = ucfirst($helper).'Helper'; + if (is_file($helperFn)) + { + require_once $helperFn; + if(class_exists($helperCn)===true); + { + ${$helper} = new $helperCn; + ${$helper}->base = $this->base; + ${$helper}->here = $this->here; + ${$helper}->params = $this->params; + ${$helper}->action = $this->action; + ${$helper}->data = $this->data; + } + } + } + } + + extract($___data_for_view, EXTR_SKIP); # load all view variables + /** + * Local template variables. + */ + $BASE = $this->base; + $params = &$this->params; + $page_title = $this->pageTitle; + + /** + * Start caching output (eval outputs directly so we need to cache). + */ + ob_start(); + + /** + * Include the template. + */ + $___play_safe? @include($___viewFn): include($___viewFn); + + $out = ob_get_clean(); + + return $out; + } + +} ?> \ No newline at end of file