From 6dfd5d507d9863f987b30b0a5ab4268aea2ed875 Mon Sep 17 00:00:00 2001
From: Ludovic Pouzenc Stack Trace
+
+getTrace() as $i => $stack): ?>
+
+
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Elements/sql_dump.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Elements/sql_dump.ctp
new file mode 100644
index 0000000..c8e634d
--- /dev/null
+++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Elements/sql_dump.ctp
@@ -0,0 +1,74 @@
+getLog();
+ endforeach;
+endif;
+
+if ($noLogs || isset($_forced_from_dbo_)):
+ foreach ($logs as $source => $logInfo):
+ $text = $logInfo['count'] > 1 ? 'queries' : 'query';
+ printf(
+ '',
+ preg_replace('/[^A-Za-z0-9_]/', '_', uniqid(time(), true))
+ );
+ printf('
+ Encountered unexpected $logs cannot generate SQL log
+
+
+ $i) :
+ $i += array('error' => '');
+ if (!empty($i['params']) && is_array($i['params'])) {
+ $bindParam = $bindType = null;
+ if (preg_match('/.+ :.+/', $i['query'])) {
+ $bindType = true;
+ }
+ foreach ($i['params'] as $bindKey => $bindVal) {
+ if ($bindType === true) {
+ $bindParam .= h($bindKey) ." => " . h($bindVal) . ", ";
+ } else {
+ $bindParam .= h($bindVal) . ", ";
+ }
+ }
+ $i['query'] .= " , params[ " . rtrim($bindParam, ', ') . " ]";
+ }
+ echo "Nr Query Error Affected Num. rows Took (ms) \n";
+ endforeach;
+ ?>
+ " . ($k + 1) . " " . h($i['query']) . " {$i['error']} {$i['affected']} {$i['numRows']} {$i['took']}
+ :
+ getMessage()); ?>
+
+
+ :
+ getFile()); ?>
+
+
+ :
+ getLine()); ?>
+
+ : + +
diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_action.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_action.ctp new file mode 100644 index 0000000..614933e --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_action.ctp @@ -0,0 +1,42 @@ + ++ : + ' . $action . '', '' . $controller . ''); ?> +
++ : + ' . $controller . '::', '' . $action . '()', APP_DIR . DS . 'Controller' . DS . $controller . '.php'); ?> +
++<?php +class extends AppController { + + + public function () { + + } + +} ++
+ : + +
+element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_behavior.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_behavior.ctp new file mode 100644 index 0000000..3bb8722 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_behavior.ctp @@ -0,0 +1,40 @@ + + ++ : + ' . $pluginDot . $class . ''); ?> +
++ : + ' . $class . '', (empty($plugin) ? APP_DIR . DS : CakePlugin::path($plugin)) . 'Model' . DS . 'Behavior' . DS . $class . '.php'); ?> +
++<?php +class extends ModelBehavior { + +} ++
+ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_component.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_component.ctp new file mode 100644 index 0000000..d0e6d23 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_component.ctp @@ -0,0 +1,40 @@ + + ++ : + ' . $pluginDot . $class . ''); ?> +
++ : + ' . $class . '', (empty($plugin) ? APP_DIR : CakePlugin::path($plugin)) . DS . 'Controller' . DS . 'Component' . DS . $class . '.php'); ?> +
++<?php +class extends Component { + +} ++
+ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_connection.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_connection.ctp new file mode 100644 index 0000000..b29fb9f --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_connection.ctp @@ -0,0 +1,36 @@ + + ++ : + +
+ ++ : + +
+ ++ : + +
+ +element('exception_stack_trace'); diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_controller.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_controller.ctp new file mode 100644 index 0000000..b4acb8e --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_controller.ctp @@ -0,0 +1,40 @@ + + ++ : + ' . $pluginDot . $class . ''); ?> +
++ : + ' . $class . '', (empty($plugin) ? APP_DIR . DS : CakePlugin::path($plugin)) . 'Controller' . DS . $class . '.php'); ?> +
++<?php +class AppController { + +} ++
+ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_database.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_database.ctp new file mode 100644 index 0000000..a8c892e --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_database.ctp @@ -0,0 +1,33 @@ + + ++ : + +
++ : + +
++ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_datasource.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_datasource.ctp new file mode 100644 index 0000000..d3df930 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_datasource.ctp @@ -0,0 +1,30 @@ + + ++ : + ' . $pluginDot . $class . ''); ?> +
++ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_datasource_config.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_datasource_config.ctp new file mode 100644 index 0000000..3bc6ff7 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_datasource_config.ctp @@ -0,0 +1,29 @@ + + ++ : + ' . $config . ''); ?> +
++ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_helper.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_helper.ctp new file mode 100644 index 0000000..62d7585 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_helper.ctp @@ -0,0 +1,40 @@ + + ++ : + ' . $pluginDot . $class . ''); ?> +
++ : + ' . $class . '', (empty($plugin) ? APP_DIR . DS : CakePlugin::path($plugin)) . 'View' . DS . 'Helper' . DS . $class . '.php'); ?> +
++<?php +class extends AppHelper { + +} ++
+ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_layout.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_layout.ctp new file mode 100644 index 0000000..f623b61 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_layout.ctp @@ -0,0 +1,33 @@ + + ++ : + ' . $file . ''); ?> +
++ : + ' . $file . ''); ?> +
++ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_plugin.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_plugin.ctp new file mode 100644 index 0000000..9d3f7ce --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_plugin.ctp @@ -0,0 +1,45 @@ + + ++ : + ' . $plugin . ''); ?> +
++ : + +
++<?php +CakePlugin::load(''); + ++
+ : + +
++CakePlugin::loadAll(); ++
+ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_table.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_table.ctp new file mode 100644 index 0000000..6e65871 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_table.ctp @@ -0,0 +1,29 @@ + + ++ : + ' . $table . '', '' . $class . '', '' . $ds . ''); ?> +
++ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_view.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_view.ctp new file mode 100644 index 0000000..c3b5920 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/missing_view.ctp @@ -0,0 +1,33 @@ + + ++ : + ' . Inflector::camelize($this->request->controller) . 'Controller::', '' . $this->request->action . '()'); ?> +
++ : + +
++ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/pdo_error.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/pdo_error.ctp new file mode 100644 index 0000000..9aecee6 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/pdo_error.ctp @@ -0,0 +1,38 @@ + + ++ : + getMessage()); ?> +
+queryString)) : ?> ++ : + queryString; ?> +
+ +params)) : ?> + : + params); ?> + ++ : + +
+element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/private_action.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/private_action.ctp new file mode 100644 index 0000000..74264a2 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/private_action.ctp @@ -0,0 +1,29 @@ + + ++ : + ' . $controller . '::', '' . $action . '()'); ?> +
++ : + +
+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/scaffold_error.ctp b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/scaffold_error.ctp new file mode 100644 index 0000000..492c561 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Errors/scaffold_error.ctp @@ -0,0 +1,36 @@ + + ++ : + +
++ : + +
++<?php +function _scaffoldError() {+ +element('exception_stack_trace'); ?> diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Helper.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Helper.php new file mode 100644 index 0000000..f6b17ac --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Helper.php @@ -0,0 +1,914 @@ + array('type' => 'string', 'length' => 100), + * primaryKey and validates array('field_name') + * + * @var array + */ + public $fieldset = array(); + +/** + * Holds tag templates. + * + * @var array + */ + public $tags = array(); + +/** + * Holds the content to be cleaned. + * + * @var mixed + */ + protected $_tainted = null; + +/** + * Holds the cleaned content. + * + * @var mixed + */ + protected $_cleaned = null; + +/** + * The View instance this helper is attached to + * + * @var View + */ + protected $_View; + +/** + * A list of strings that should be treated as suffixes, or + * sub inputs for a parent input. This is used for date/time + * inputs primarily. + * + * @var array + */ + protected $_fieldSuffixes = array( + 'year', 'month', 'day', 'hour', 'min', 'second', 'meridian' + ); + +/** + * The name of the current model entities are in scope of. + * + * @see Helper::setEntity() + * @var string + */ + protected $_modelScope; + +/** + * The name of the current model association entities are in scope of. + * + * @see Helper::setEntity() + * @var string + */ + protected $_association; + +/** + * The dot separated list of elements the current field entity is for. + * + * @see Helper::setEntity() + * @var string + */ + protected $_entityPath; + +/** + * Minimized attributes + * + * @var array + */ + protected $_minimizedAttributes = array( + 'compact', 'checked', 'declare', 'readonly', 'disabled', 'selected', + 'defer', 'ismap', 'nohref', 'noshade', 'nowrap', 'multiple', 'noresize', + 'autoplay', 'controls', 'loop', 'muted' + ); + +/** + * Format to attribute + * + * @var string + */ + protected $_attributeFormat = '%s="%s"'; + +/** + * Format to attribute + * + * @var string + */ + protected $_minimizedAttributeFormat = '%s="%s"'; + +/** + * Default Constructor + * + * @param View $View The View this helper is being attached to. + * @param array $settings Configuration settings for the helper. + */ + public function __construct(View $View, $settings = array()) { + $this->_View = $View; + $this->request = $View->request; + if (!empty($this->helpers)) { + $this->_helperMap = ObjectCollection::normalizeObjectArray($this->helpers); + } + } + +/** + * Provide non fatal errors on missing method calls. + * + * @param string $method Method to invoke + * @param array $params Array of params for the method. + * @return void + */ + public function __call($method, $params) { + trigger_error(__d('cake_dev', 'Method %1$s::%2$s does not exist', get_class($this), $method), E_USER_WARNING); + } + +/** + * Lazy loads helpers. Provides access to deprecated request properties as well. + * + * @param string $name Name of the property being accessed. + * @return mixed Helper or property found at $name + */ + public function __get($name) { + if (isset($this->_helperMap[$name]) && !isset($this->{$name})) { + $settings = array_merge((array)$this->_helperMap[$name]['settings'], array('enabled' => false)); + $this->{$name} = $this->_View->loadHelper($this->_helperMap[$name]['class'], $settings); + } + if (isset($this->{$name})) { + return $this->{$name}; + } + switch ($name) { + case 'base': + case 'here': + case 'webroot': + case 'data': + return $this->request->{$name}; + case 'action': + return isset($this->request->params['action']) ? $this->request->params['action'] : ''; + case 'params': + return $this->request; + } + } + +/** + * Provides backwards compatibility access for setting values to the request object. + * + * @param string $name Name of the property being accessed. + * @param mixed $value + * @return mixed Return the $value + */ + public function __set($name, $value) { + switch ($name) { + case 'base': + case 'here': + case 'webroot': + case 'data': + return $this->request->{$name} = $value; + case 'action': + return $this->request->params['action'] = $value; + } + return $this->{$name} = $value; + } + +/** + * Finds URL for specified action. + * + * Returns a URL pointing at the provided parameters. + * + * @param string|array $url Either a relative string url like `/products/view/23` or + * an array of url parameters. Using an array for urls will allow you to leverage + * the reverse routing features of CakePHP. + * @param boolean $full If true, the full base URL will be prepended to the result + * @return string Full translated URL with base path. + * @link http://book.cakephp.org/2.0/en/views/helpers.html + */ + public function url($url = null, $full = false) { + return h(Router::url($url, $full)); + } + +/** + * Checks if a file exists when theme is used, if no file is found default location is returned + * + * @param string $file The file to create a webroot path to. + * @return string Web accessible path to file. + */ + public function webroot($file) { + $asset = explode('?', $file); + $asset[1] = isset($asset[1]) ? '?' . $asset[1] : null; + $webPath = "{$this->request->webroot}" . $asset[0]; + $file = $asset[0]; + + if (!empty($this->theme)) { + $file = trim($file, '/'); + $theme = $this->theme . '/'; + + if (DS === '\\') { + $file = str_replace('/', '\\', $file); + } + + if (file_exists(Configure::read('App.www_root') . 'theme' . DS . $this->theme . DS . $file)) { + $webPath = "{$this->request->webroot}theme/" . $theme . $asset[0]; + } else { + $themePath = App::themePath($this->theme); + $path = $themePath . 'webroot' . DS . $file; + if (file_exists($path)) { + $webPath = "{$this->request->webroot}theme/" . $theme . $asset[0]; + } + } + } + if (strpos($webPath, '//') !== false) { + return str_replace('//', '/', $webPath . $asset[1]); + } + return $webPath . $asset[1]; + } + +/** + * Generate url for given asset file. Depending on options passed provides full url with domain name. + * Also calls Helper::assetTimestamp() to add timestamp to local files + * + * @param string|array Path string or url array + * @param array $options Options array. Possible keys: + * `fullBase` Return full url with domain name + * `pathPrefix` Path prefix for relative urls + * `ext` Asset extension to append + * `plugin` False value will prevent parsing path as a plugin + * @return string Generated url + */ + public function assetUrl($path, $options = array()) { + if (is_array($path)) { + $path = $this->url($path, !empty($options['fullBase'])); + } elseif (strpos($path, '://') === false) { + if (!array_key_exists('plugin', $options) || $options['plugin'] !== false) { + list($plugin, $path) = $this->_View->pluginSplit($path, false); + } + if (!empty($options['pathPrefix']) && $path[0] !== '/') { + $path = $options['pathPrefix'] . $path; + } + if ( + !empty($options['ext']) && + strpos($path, '?') === false && + substr($path, -strlen($options['ext'])) !== $options['ext'] + ) { + $path .= $options['ext']; + } + if (isset($plugin)) { + $path = Inflector::underscore($plugin) . '/' . $path; + } + $path = h($this->assetTimestamp($this->webroot($path))); + + if (!empty($options['fullBase'])) { + $base = $this->url('/', true); + $len = strlen($this->request->webroot); + if ($len) { + $base = substr($base, 0, -$len); + } + $path = $base . $path; + } + } + + return $path; + } + +/** + * Adds a timestamp to a file based resource based on the value of `Asset.timestamp` in + * Configure. If Asset.timestamp is true and debug > 0, or Asset.timestamp == 'force' + * a timestamp will be added. + * + * @param string $path The file path to timestamp, the path must be inside WWW_ROOT + * @return string Path with a timestamp added, or not. + */ + public function assetTimestamp($path) { + $stamp = Configure::read('Asset.timestamp'); + $timestampEnabled = $stamp === 'force' || ($stamp === true && Configure::read('debug') > 0); + if ($timestampEnabled && strpos($path, '?') === false) { + $filepath = preg_replace('/^' . preg_quote($this->request->webroot, '/') . '/', '', $path); + $webrootPath = WWW_ROOT . str_replace('/', DS, $filepath); + if (file_exists($webrootPath)) { + return $path . '?' . @filemtime($webrootPath); + } + $segments = explode('/', ltrim($filepath, '/')); + if ($segments[0] === 'theme') { + $theme = $segments[1]; + unset($segments[0], $segments[1]); + $themePath = App::themePath($theme) . 'webroot' . DS . implode(DS, $segments); + return $path . '?' . @filemtime($themePath); + } else { + $plugin = Inflector::camelize($segments[0]); + if (CakePlugin::loaded($plugin)) { + unset($segments[0]); + $pluginPath = CakePlugin::path($plugin) . 'webroot' . DS . implode(DS, $segments); + return $path . '?' . @filemtime($pluginPath); + } + } + } + return $path; + } + +/** + * Used to remove harmful tags from content. Removes a number of well known XSS attacks + * from content. However, is not guaranteed to remove all possibilities. Escaping + * content is the best way to prevent all possible attacks. + * + * @param string|array $output Either an array of strings to clean or a single string to clean. + * @return string|array cleaned content for output + */ + public function clean($output) { + $this->_reset(); + if (empty($output)) { + return null; + } + if (is_array($output)) { + foreach ($output as $key => $value) { + $return[$key] = $this->clean($value); + } + return $return; + } + $this->_tainted = $output; + $this->_clean(); + return $this->_cleaned; + } + +/** + * Returns a space-delimited string with items of the $options array. If a + * key of $options array happens to be one of: + * + * - 'compact' + * - 'checked' + * - 'declare' + * - 'readonly' + * - 'disabled' + * - 'selected' + * - 'defer' + * - 'ismap' + * - 'nohref' + * - 'noshade' + * - 'nowrap' + * - 'multiple' + * - 'noresize' + * + * And its value is one of: + * + * - '1' (string) + * - 1 (integer) + * - true (boolean) + * - 'true' (string) + * + * Then the value will be reset to be identical with key's name. + * If the value is not one of these 3, the parameter is not output. + * + * 'escape' is a special option in that it controls the conversion of + * attributes to their html-entity encoded equivalents. Set to false to disable html-encoding. + * + * If value for any option key is set to `null` or `false`, that option will be excluded from output. + * + * @param array $options Array of options. + * @param array $exclude Array of options to be excluded, the options here will not be part of the return. + * @param string $insertBefore String to be inserted before options. + * @param string $insertAfter String to be inserted after options. + * @return string Composed attributes. + * @deprecated This method will be moved to HtmlHelper in 3.0 + */ + protected function _parseAttributes($options, $exclude = null, $insertBefore = ' ', $insertAfter = null) { + if (!is_string($options)) { + $options = (array)$options + array('escape' => true); + + if (!is_array($exclude)) { + $exclude = array(); + } + + $exclude = array('escape' => true) + array_flip($exclude); + $escape = $options['escape']; + $attributes = array(); + + foreach ($options as $key => $value) { + if (!isset($exclude[$key]) && $value !== false && $value !== null) { + $attributes[] = $this->_formatAttribute($key, $value, $escape); + } + } + $out = implode(' ', $attributes); + } else { + $out = $options; + } + return $out ? $insertBefore . $out . $insertAfter : ''; + } + +/** + * Formats an individual attribute, and returns the string value of the composed attribute. + * Works with minimized attributes that have the same value as their name such as 'disabled' and 'checked' + * + * @param string $key The name of the attribute to create + * @param string $value The value of the attribute to create. + * @param boolean $escape Define if the value must be escaped + * @return string The composed attribute. + * @deprecated This method will be moved to HtmlHelper in 3.0 + */ + protected function _formatAttribute($key, $value, $escape = true) { + $attribute = ''; + if (is_array($value)) { + $value = implode(' ' , $value); + } + + if (is_numeric($key)) { + $attribute = sprintf($this->_minimizedAttributeFormat, $value, $value); + } elseif (in_array($key, $this->_minimizedAttributes)) { + if ($value === 1 || $value === true || $value === 'true' || $value === '1' || $value == $key) { + $attribute = sprintf($this->_minimizedAttributeFormat, $key, $key); + } + } else { + $attribute = sprintf($this->_attributeFormat, $key, ($escape ? h($value) : $value)); + } + return $attribute; + } + +/** + * Sets this helper's model and field properties to the dot-separated value-pair in $entity. + * + * @param string $entity A field name, like "ModelName.fieldName" or "ModelName.ID.fieldName" + * @param boolean $setScope Sets the view scope to the model specified in $tagValue + * @return void + */ + public function setEntity($entity, $setScope = false) { + if ($entity === null) { + $this->_modelScope = false; + } + if ($setScope === true) { + $this->_modelScope = $entity; + } + $parts = array_values(Hash::filter(explode('.', $entity))); + if (empty($parts)) { + return; + } + $count = count($parts); + $lastPart = isset($parts[$count - 1]) ? $parts[$count - 1] : null; + + // Either 'body' or 'date.month' type inputs. + if ( + ($count === 1 && $this->_modelScope && $setScope == false) || + ( + $count === 2 && + in_array($lastPart, $this->_fieldSuffixes) && + $this->_modelScope && + $parts[0] !== $this->_modelScope + ) + ) { + $entity = $this->_modelScope . '.' . $entity; + } + + // 0.name, 0.created.month style inputs. Excludes inputs with the modelScope in them. + if ( + $count >= 2 && + is_numeric($parts[0]) && + !is_numeric($parts[1]) && + $this->_modelScope && + strpos($entity, $this->_modelScope) === false + ) { + $entity = $this->_modelScope . '.' . $entity; + } + + $this->_association = null; + + $isHabtm = ( + isset($this->fieldset[$this->_modelScope]['fields'][$parts[0]]['type']) && + $this->fieldset[$this->_modelScope]['fields'][$parts[0]]['type'] === 'multiple' && + $count == 1 + ); + + // habtm models are special + if ($count == 1 && $isHabtm) { + $this->_association = $parts[0]; + $entity = $parts[0] . '.' . $parts[0]; + } else { + // check for associated model. + $reversed = array_reverse($parts); + foreach ($reversed as $i => $part) { + if ($i > 0 && preg_match('/^[A-Z]/', $part)) { + $this->_association = $part; + break; + } + } + } + $this->_entityPath = $entity; + } + +/** + * Returns the entity reference of the current context as an array of identity parts + * + * @return array An array containing the identity elements of an entity + */ + public function entity() { + return explode('.', $this->_entityPath); + } + +/** + * Gets the currently-used model of the rendering context. + * + * @return string + */ + public function model() { + if ($this->_association) { + return $this->_association; + } + return $this->_modelScope; + } + +/** + * Gets the currently-used model field of the rendering context. + * Strips off field suffixes such as year, month, day, hour, min, meridian + * when the current entity is longer than 2 elements. + * + * @return string + */ + public function field() { + $entity = $this->entity(); + $count = count($entity); + $last = $entity[$count - 1]; + if ($count > 2 && in_array($last, $this->_fieldSuffixes)) { + $last = isset($entity[$count - 2]) ? $entity[$count - 2] : null; + } + return $last; + } + +/** + * Generates a DOM ID for the selected element, if one is not set. + * Uses the current View::entity() settings to generate a CamelCased id attribute. + * + * @param array|string $options Either an array of html attributes to add $id into, or a string + * with a view entity path to get a domId for. + * @param string $id The name of the 'id' attribute. + * @return mixed If $options was an array, an array will be returned with $id set. If a string + * was supplied, a string will be returned. + * @todo Refactor this method to not have as many input/output options. + */ + public function domId($options = null, $id = 'id') { + if (is_array($options) && array_key_exists($id, $options) && $options[$id] === null) { + unset($options[$id]); + return $options; + } elseif (!is_array($options) && $options !== null) { + $this->setEntity($options); + return $this->domId(); + } + + $entity = $this->entity(); + $model = array_shift($entity); + $dom = $model . join('', array_map(array('Inflector', 'camelize'), $entity)); + + if (is_array($options) && !array_key_exists($id, $options)) { + $options[$id] = $dom; + } elseif ($options === null) { + return $dom; + } + return $options; + } + +/** + * Gets the input field name for the current tag. Creates input name attributes + * using CakePHP's data[Model][field] formatting. + * + * @param array|string $options If an array, should be an array of attributes that $key needs to be added to. + * If a string or null, will be used as the View entity. + * @param string $field + * @param string $key The name of the attribute to be set, defaults to 'name' + * @return mixed If an array was given for $options, an array with $key set will be returned. + * If a string was supplied a string will be returned. + * @todo Refactor this method to not have as many input/output options. + */ + protected function _name($options = array(), $field = null, $key = 'name') { + if ($options === null) { + $options = array(); + } elseif (is_string($options)) { + $field = $options; + $options = 0; + } + + if (!empty($field)) { + $this->setEntity($field); + } + + if (is_array($options) && array_key_exists($key, $options)) { + return $options; + } + + switch ($field) { + case '_method': + $name = $field; + break; + default: + $name = 'data[' . implode('][', $this->entity()) . ']'; + break; + } + + if (is_array($options)) { + $options[$key] = $name; + return $options; + } else { + return $name; + } + } + +/** + * Gets the data for the current tag + * + * @param array|string $options If an array, should be an array of attributes that $key needs to be added to. + * If a string or null, will be used as the View entity. + * @param string $field + * @param string $key The name of the attribute to be set, defaults to 'value' + * @return mixed If an array was given for $options, an array with $key set will be returned. + * If a string was supplied a string will be returned. + * @todo Refactor this method to not have as many input/output options. + */ + public function value($options = array(), $field = null, $key = 'value') { + if ($options === null) { + $options = array(); + } elseif (is_string($options)) { + $field = $options; + $options = 0; + } + + if (is_array($options) && isset($options[$key])) { + return $options; + } + + if (!empty($field)) { + $this->setEntity($field); + } + $result = null; + $data = $this->request->data; + + $entity = $this->entity(); + if (!empty($data) && !empty($entity)) { + $result = Hash::get($data, implode('.', $entity)); + } + + $habtmKey = $this->field(); + if (empty($result) && isset($data[$habtmKey][$habtmKey]) && is_array($data[$habtmKey])) { + $result = $data[$habtmKey][$habtmKey]; + } elseif (empty($result) && isset($data[$habtmKey]) && is_array($data[$habtmKey])) { + if (ClassRegistry::isKeySet($habtmKey)) { + $model = ClassRegistry::getObject($habtmKey); + $result = $this->_selectedArray($data[$habtmKey], $model->primaryKey); + } + } + + if (is_array($options)) { + if ($result === null && isset($options['default'])) { + $result = $options['default']; + } + unset($options['default']); + } + + if (is_array($options)) { + $options[$key] = $result; + return $options; + } else { + return $result; + } + } + +/** + * Sets the defaults for an input tag. Will set the + * name, value, and id attributes for an array of html attributes. Will also + * add a 'form-error' class if the field contains validation errors. + * + * @param string $field The field name to initialize. + * @param array $options Array of options to use while initializing an input field. + * @return array Array options for the form input. + */ + protected function _initInputField($field, $options = array()) { + if ($field !== null) { + $this->setEntity($field); + } + $options = (array)$options; + $options = $this->_name($options); + $options = $this->value($options); + $options = $this->domId($options); + return $options; + } + +/** + * Adds the given class to the element options + * + * @param array $options Array options/attributes to add a class to + * @param string $class The classname being added. + * @param string $key the key to use for class. + * @return array Array of options with $key set. + */ + public function addClass($options = array(), $class = null, $key = 'class') { + if (isset($options[$key]) && trim($options[$key]) != '') { + $options[$key] .= ' ' . $class; + } else { + $options[$key] = $class; + } + return $options; + } + +/** + * Returns a string generated by a helper method + * + * This method can be overridden in subclasses to do generalized output post-processing + * + * @param string $str String to be output. + * @return string + * @deprecated This method will be removed in future versions. + */ + public function output($str) { + return $str; + } + +/** + * Before render callback. beforeRender is called before the view file is rendered. + * + * Overridden in subclasses. + * + * @param string $viewFile The view file that is going to be rendered + * @return void + */ + public function beforeRender($viewFile) { + } + +/** + * After render callback. afterRender is called after the view file is rendered + * but before the layout has been rendered. + * + * Overridden in subclasses. + * + * @param string $viewFile The view file that was rendered. + * @return void + */ + public function afterRender($viewFile) { + } + +/** + * Before layout callback. beforeLayout is called before the layout is rendered. + * + * Overridden in subclasses. + * + * @param string $layoutFile The layout about to be rendered. + * @return void + */ + public function beforeLayout($layoutFile) { + } + +/** + * After layout callback. afterLayout is called after the layout has rendered. + * + * Overridden in subclasses. + * + * @param string $layoutFile The layout file that was rendered. + * @return void + */ + public function afterLayout($layoutFile) { + } + +/** + * Before render file callback. + * Called before any view fragment is rendered. + * + * Overridden in subclasses. + * + * @param string $viewFile The file about to be rendered. + * @return void + */ + public function beforeRenderFile($viewfile) { + } + +/** + * After render file callback. + * Called after any view fragment is rendered. + * + * Overridden in subclasses. + * + * @param string $viewFile The file just be rendered. + * @param string $content The content that was rendered. + * @return void + */ + public function afterRenderFile($viewfile, $content) { + } + +/** + * Transforms a recordset from a hasAndBelongsToMany association to a list of selected + * options for a multiple select element + * + * @param string|array $data + * @param string $key + * @return array + */ + protected function _selectedArray($data, $key = 'id') { + if (!is_array($data)) { + $model = $data; + if (!empty($this->request->data[$model][$model])) { + return $this->request->data[$model][$model]; + } + if (!empty($this->request->data[$model])) { + $data = $this->request->data[$model]; + } + } + $array = array(); + if (!empty($data)) { + foreach ($data as $row) { + if (isset($row[$key])) { + $array[$row[$key]] = $row[$key]; + } + } + } + return empty($array) ? null : $array; + } + +/** + * Resets the vars used by Helper::clean() to null + * + * @return void + */ + protected function _reset() { + $this->_tainted = null; + $this->_cleaned = null; + } + +/** + * Removes harmful content from output + * + * @return void + */ + protected function _clean() { + if (get_magic_quotes_gpc()) { + $this->_cleaned = stripslashes($this->_tainted); + } else { + $this->_cleaned = $this->_tainted; + } + + $this->_cleaned = str_replace(array("&", "<", ">"), array("&", "<", ">"), $this->_cleaned); + $this->_cleaned = preg_replace('#(&\#*\w+)[\x00-\x20]+;#u', "$1;", $this->_cleaned); + $this->_cleaned = preg_replace('#(&\#x*)([0-9A-F]+);*#iu', "$1$2;", $this->_cleaned); + $this->_cleaned = html_entity_decode($this->_cleaned, ENT_COMPAT, "UTF-8"); + $this->_cleaned = preg_replace('#(<[^>]+[\x00-\x20\"\'\/])(on|xmlns)[^>]*>#iUu', "$1>", $this->_cleaned); + $this->_cleaned = preg_replace('#([a-z]*)[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*)[\\x00-\x20]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iUu', '$1=$2nojavascript...', $this->_cleaned); + $this->_cleaned = preg_replace('#([a-z]*)[\x00-\x20]*=([\'\"]*)[\x00-\x20]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iUu', '$1=$2novbscript...', $this->_cleaned); + $this->_cleaned = preg_replace('#([a-z]*)[\x00-\x20]*=*([\'\"]*)[\x00-\x20]*-moz-binding[\x00-\x20]*:#iUu', '$1=$2nomozbinding...', $this->_cleaned); + $this->_cleaned = preg_replace('#([a-z]*)[\x00-\x20]*=([\'\"]*)[\x00-\x20]*data[\x00-\x20]*:#Uu', '$1=$2nodata...', $this->_cleaned); + $this->_cleaned = preg_replace('#(<[^>]+)style[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*).*expression[\x00-\x20]*\([^>]*>#iU', "$1>", $this->_cleaned); + $this->_cleaned = preg_replace('#(<[^>]+)style[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*).*behaviour[\x00-\x20]*\([^>]*>#iU', "$1>", $this->_cleaned); + $this->_cleaned = preg_replace('#(<[^>]+)style[\x00-\x20]*=[\x00-\x20]*([\`\'\"]*).*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:*[^>]*>#iUu', "$1>", $this->_cleaned); + $this->_cleaned = preg_replace('#*\w+:\w[^>]*>#i', "", $this->_cleaned); + do { + $oldstring = $this->_cleaned; + $this->_cleaned = preg_replace('#*(applet|meta|xml|blink|link|style|script|embed|object|iframe|frame|frameset|ilayer|layer|bgsound|title|base)[^>]*>#i', "", $this->_cleaned); + } while ($oldstring != $this->_cleaned); + $this->_cleaned = str_replace(array("&", "<", ">"), array("&", "<", ">"), $this->_cleaned); + } + +} diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Helper/CacheHelper.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Helper/CacheHelper.php new file mode 100644 index 0000000..a480886 --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Helper/CacheHelper.php @@ -0,0 +1,321 @@ +` in views + * + * @var array + */ + protected $_replace = array(); + +/** + * Array of string that are replace with there var replace above. + * The strings are any content inside `` and includes the tags in views + * + * @var array + */ + protected $_match = array(); + +/** + * Counter used for counting nocache section tags. + * + * @var integer + */ + protected $_counter = 0; + +/** + * Is CacheHelper enabled? should files + output be parsed. + * + * @return boolean + */ + protected function _enabled() { + return (($this->_View->cacheAction != false)) && (Configure::read('Cache.check') === true); + } + +/** + * Parses the view file and stores content for cache file building. + * + * @param string $viewFile + * @return void + */ + public function afterRenderFile($viewFile, $output) { + if ($this->_enabled()) { + return $this->_parseContent($viewFile, $output); + } + } + +/** + * Parses the layout file and stores content for cache file building. + * + * @param string $layoutFile + * @return void + */ + public function afterLayout($layoutFile) { + if ($this->_enabled()) { + $this->_View->output = $this->cache($layoutFile, $this->_View->output); + } + $this->_View->output = preg_replace('//', '', $this->_View->output); + } + +/** + * Parse a file + output. Matches nocache tags between the current output and the current file + * stores a reference of the file, so the generated can be swapped back with the file contents when + * writing the cache file. + * + * @param string $file The filename to process. + * @param string $out The output for the file. + * @return string Updated content. + */ + protected function _parseContent($file, $out) { + $out = preg_replace_callback('//', array($this, '_replaceSection'), $out); + $this->_parseFile($file, $out); + return $out; + } + +/** + * Main method used to cache a view + * + * @param string $file File to cache + * @param string $out output to cache + * @return string view ouput + * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/cache.html + */ + public function cache($file, $out) { + $cacheTime = 0; + $useCallbacks = false; + $cacheAction = $this->_View->cacheAction; + + if (is_array($cacheAction)) { + $keys = array_keys($cacheAction); + $index = null; + + foreach ($keys as $action) { + if ($action == $this->request->params['action']) { + $index = $action; + break; + } + } + + if (!isset($index) && $this->request->params['action'] == 'index') { + $index = 'index'; + } + + $options = $cacheAction; + if (isset($cacheAction[$index])) { + if (is_array($cacheAction[$index])) { + $options = array_merge(array('duration' => 0, 'callbacks' => false), $cacheAction[$index]); + } else { + $cacheTime = $cacheAction[$index]; + } + } + if (isset($options['duration'])) { + $cacheTime = $options['duration']; + } + if (isset($options['callbacks'])) { + $useCallbacks = $options['callbacks']; + } + } else { + $cacheTime = $cacheAction; + } + + if ($cacheTime != '' && $cacheTime > 0) { + $cached = $this->_parseOutput($out); + $this->_writeFile($cached, $cacheTime, $useCallbacks); + $out = $this->_stripTags($out); + } + return $out; + } + +/** + * Parse file searching for no cache tags + * + * @param string $file The filename that needs to be parsed. + * @param string $cache The cached content + * @return void + */ + protected function _parseFile($file, $cache) { + if (is_file($file)) { + $file = file_get_contents($file); + } elseif ($file = fileExistsInPath($file)) { + $file = file_get_contents($file); + } + preg_match_all('/((?<=)[\\s\\S]*?(?=))/i', $cache, $outputResult, PREG_PATTERN_ORDER); + preg_match_all('/(?<=)([\\s\\S]*?)(?=)/i', $file, $fileResult, PREG_PATTERN_ORDER); + $fileResult = $fileResult[0]; + $outputResult = $outputResult[0]; + + if (!empty($this->_replace)) { + foreach ($outputResult as $i => $element) { + $index = array_search($element, $this->_match); + if ($index !== false) { + unset($outputResult[$i]); + } + } + $outputResult = array_values($outputResult); + } + + if (!empty($fileResult)) { + $i = 0; + foreach ($fileResult as $cacheBlock) { + if (isset($outputResult[$i])) { + $this->_replace[] = $cacheBlock; + $this->_match[] = $outputResult[$i]; + } + $i++; + } + } + } + +/** + * Munges the output from a view with cache tags, and numbers the sections. + * This helps solve issues with empty/duplicate content. + * + * @return string The content with cake:nocache tags replaced. + */ + protected function _replaceSection() { + $this->_counter += 1; + return sprintf('', $this->_counter); + } + +/** + * Strip cake:nocache tags from a string. Since View::render() + * only removes un-numbered nocache tags, remove all the numbered ones. + * This is the complement to _replaceSection. + * + * @param string $content String to remove tags from. + * @return string String with tags removed. + */ + protected function _stripTags($content) { + return preg_replace('##', '', $content); + } + +/** + * Parse the output and replace cache tags + * + * @param string $cache Output to replace content in. + * @return string with all replacements made to + */ + protected function _parseOutput($cache) { + $count = 0; + if (!empty($this->_match)) { + foreach ($this->_match as $found) { + $original = $cache; + $length = strlen($found); + $position = 0; + + for ($i = 1; $i <= 1; $i++) { + $position = strpos($cache, $found, $position); + + if ($position !== false) { + $cache = substr($original, 0, $position); + $cache .= $this->_replace[$count]; + $cache .= substr($original, $position + $length); + } else { + break; + } + } + $count++; + } + return $cache; + } + return $cache; + } + +/** + * Write a cached version of the file + * + * @param string $content view content to write to a cache file. + * @param string $timestamp Duration to set for cache file. + * @param boolean $useCallbacks + * @return boolean success of caching view. + */ + protected function _writeFile($content, $timestamp, $useCallbacks = false) { + $now = time(); + + if (is_numeric($timestamp)) { + $cacheTime = $now + $timestamp; + } else { + $cacheTime = strtotime($timestamp, $now); + } + $path = $this->request->here(); + if ($path == '/') { + $path = 'home'; + } + $cache = strtolower(Inflector::slug($path)); + + if (empty($cache)) { + return; + } + $cache = $cache . '.php'; + $file = '_View->plugin)) { + $file .= " + App::uses('{$this->_View->name}Controller', 'Controller'); + "; + } else { + $file .= " + App::uses('{$this->_View->plugin}AppController', '{$this->_View->plugin}.Controller'); + App::uses('{$this->_View->name}Controller', '{$this->_View->plugin}.Controller'); + "; + } + + $file .= ' + $request = unserialize(base64_decode(\'' . base64_encode(serialize($this->request)) . '\')); + $response = new CakeResponse(array("charset" => Configure::read("App.encoding"))); + $controller = new ' . $this->_View->name . 'Controller($request, $response); + $controller->plugin = $this->plugin = \'' . $this->_View->plugin . '\'; + $controller->helpers = $this->helpers = unserialize(base64_decode(\'' . base64_encode(serialize($this->_View->helpers)) . '\')); + $controller->layout = $this->layout = \'' . $this->_View->layout . '\'; + $controller->theme = $this->theme = \'' . $this->_View->theme . '\'; + $controller->viewVars = unserialize(base64_decode(\'' . base64_encode(serialize($this->_View->viewVars)) . '\')); + Router::setRequestInfo($controller->request); + $this->request = $request;'; + + if ($useCallbacks == true) { + $file .= ' + $controller->constructClasses(); + $controller->startupProcess();'; + } + + $file .= ' + $this->viewVars = $controller->viewVars; + $this->loadHelpers(); + extract($this->viewVars, EXTR_SKIP); + ?>'; + $content = preg_replace("/(<\\?xml)/", "", $content); + $file .= $content; + return cache('views' . DS . $cache, $file, $timestamp); + } + +} diff --git a/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Helper/FormHelper.php b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Helper/FormHelper.php new file mode 100644 index 0000000..e63614a --- /dev/null +++ b/poc/poc02-compiling-cake/src/vendor/cakephp-2.2.1-0-gcc44130/lib/Cake/View/Helper/FormHelper.php @@ -0,0 +1,2604 @@ + array(), 'minute' => array(), 'hour' => array(), + 'month' => array(), 'year' => array(), 'meridian' => array() + ); + +/** + * List of fields created, used with secure forms. + * + * @var array + */ + public $fields = array(); + +/** + * Constant used internally to skip the securing process, + * and neither add the field to the hash or to the unlocked fields. + * + * @var string + */ + const SECURE_SKIP = 'skip'; + +/** + * Defines the type of form being created. Set by FormHelper::create(). + * + * @var string + */ + public $requestType = null; + +/** + * The default model being used for the current form. + * + * @var string + */ + public $defaultModel = null; + +/** + * Persistent default options used by input(). Set by FormHelper::create(). + * + * @var array + */ + protected $_inputDefaults = array(); + +/** + * An array of field names that have been excluded from + * the Token hash used by SecurityComponent's validatePost method + * + * @see FormHelper::_secure() + * @see SecurityComponent::validatePost() + * @var array + */ + protected $_unlockedFields = array(); + +/** + * Holds the model references already loaded by this helper + * product of trying to inspect them out of field names + * + * @var array + */ + protected $_models = array(); + +/** + * Holds all the validation errors for models loaded and inspected + * it can also be set manually to be able to display custom error messages + * in the any of the input fields generated by this helper + * + * @var array + */ + public $validationErrors = array(); + +/** + * Copies the validationErrors variable from the View object into this instance + * + * @param View $View The View this helper is being attached to. + * @param array $settings Configuration settings for the helper. + */ + public function __construct(View $View, $settings = array()) { + parent::__construct($View, $settings); + $this->validationErrors =& $View->validationErrors; + } + +/** + * Guess the location for a model based on its name and tries to create a new instance + * or get an already created instance of the model + * + * @param string $model + * @return Model model instance + */ + protected function _getModel($model) { + $object = null; + if (!$model || $model === 'Model') { + return $object; + } + + if (array_key_exists($model, $this->_models)) { + return $this->_models[$model]; + } + + if (ClassRegistry::isKeySet($model)) { + $object = ClassRegistry::getObject($model); + } elseif (isset($this->request->params['models'][$model])) { + $plugin = $this->request->params['models'][$model]['plugin']; + $plugin .= ($plugin) ? '.' : null; + $object = ClassRegistry::init(array( + 'class' => $plugin . $this->request->params['models'][$model]['className'], + 'alias' => $model + )); + } elseif (ClassRegistry::isKeySet($this->defaultModel)) { + $defaultObject = ClassRegistry::getObject($this->defaultModel); + if (in_array($model, array_keys($defaultObject->getAssociated()), true) && isset($defaultObject->{$model})) { + $object = $defaultObject->{$model}; + } + } else { + $object = ClassRegistry::init($model, true); + } + + $this->_models[$model] = $object; + if (!$object) { + return null; + } + + $this->fieldset[$model] = array('fields' => null, 'key' => $object->primaryKey, 'validates' => null); + return $object; + } + +/** + * Inspects the model properties to extract information from them. + * Currently it can extract information from the the fields, the primary key and required fields + * + * The $key parameter accepts the following list of values: + * + * - key: Returns the name of the primary key for the model + * - fields: Returns the model schema + * - validates: returns the list of fields that are required + * - errors: returns the list of validation errors + * + * If the $field parameter is passed if will return the information for that sole field. + * + * `$this->_introspectModel('Post', 'fields', 'title');` will return the schema information for title column + * + * @param string $model name of the model to extract information from + * @param string $key name of the special information key to obtain (key, fields, validates, errors) + * @param string $field name of the model field to get information from + * @return mixed information extracted for the special key and field in a model + */ + protected function _introspectModel($model, $key, $field = null) { + $object = $this->_getModel($model); + if (!$object) { + return; + } + + if ($key === 'key') { + return $this->fieldset[$model]['key'] = $object->primaryKey; + } + + if ($key === 'fields') { + if (!isset($this->fieldset[$model]['fields'])) { + $fields = $this->fieldset[$model]['fields'] = $object->schema(); + foreach ($object->hasAndBelongsToMany as $alias => $assocData) { + $this->fieldset[$object->alias]['fields'][$alias] = array('type' => 'multiple'); + } + } + if (empty($field)) { + return $this->fieldset[$model]['fields']; + } elseif (isset($this->fieldset[$model]['fields'][$field])) { + return $this->fieldset[$model]['fields'][$field]; + } else { + return isset($object->hasAndBelongsToMany[$field]) ? array('type' => 'multiple') : null; + } + } + + if ($key === 'errors' && !isset($this->validationErrors[$model])) { + $this->validationErrors[$model] =& $object->validationErrors; + return $this->validationErrors[$model]; + } elseif ($key === 'errors' && isset($this->validationErrors[$model])) { + return $this->validationErrors[$model]; + } + + if ($key === 'validates' && !isset($this->fieldset[$model]['validates'])) { + $validates = array(); + if (!empty($object->validate)) { + foreach ($object->validator() as $validateField => $validateProperties) { + if ($this->_isRequiredField($validateProperties)) { + $validates[$validateField] = true; + } + } + } + $this->fieldset[$model]['validates'] = $validates; + } + + if ($key === 'validates') { + if (empty($field)) { + return $this->fieldset[$model]['validates']; + } else { + return isset($this->fieldset[$model]['validates'][$field]) ? + $this->fieldset[$model]['validates'] : null; + } + } + } + +/** + * Returns if a field is required to be filled based on validation properties from the validating object. + * + * @param CakeValidationSet $validationRules + * @return boolean true if field is required to be filled, false otherwise + */ + protected function _isRequiredField($validationRules) { + foreach ($validationRules as $rule) { + $rule->isUpdate($this->requestType === 'put'); + if (!$rule->isEmptyAllowed()) { + return true; + } + } + return false; + } + +/** + * Returns false if given form field described by the current entity has no errors. + * Otherwise it returns the validation message + * + * @return mixed Either false when there or no errors, or an array of error + * strings. An error string could be ''. + * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::tagIsInvalid + */ + public function tagIsInvalid() { + $entity = $this->entity(); + $model = array_shift($entity); + $errors = array(); + if (!empty($entity) && isset($this->validationErrors[$model])) { + $errors = $this->validationErrors[$model]; + } + if (!empty($entity) && empty($errors)) { + $errors = $this->_introspectModel($model, 'errors'); + } + if (empty($errors)) { + return false; + } + $errors = Hash::get($errors, join('.', $entity)); + return $errors === null ? false : $errors; + } + +/** + * Returns an HTML FORM element. + * + * ### Options: + * + * - `type` Form method defaults to POST + * - `action` The controller action the form submits to, (optional). + * - `url` The url the form submits to. Can be a string or a url array. If you use 'url' + * you should leave 'action' undefined. + * - `default` Allows for the creation of Ajax forms. Set this to false to prevent the default event handler. + * Will create an onsubmit attribute if it doesn't not exist. If it does, default action suppression + * will be appended. + * - `onsubmit` Used in conjunction with 'default' to create ajax forms. + * - `inputDefaults` set the default $options for FormHelper::input(). Any options that would + * be set when using FormHelper::input() can be set here. Options set with `inputDefaults` + * can be overridden when calling input() + * - `encoding` Set the accept-charset encoding for the form. Defaults to `Configure::read('App.encoding')` + * + * @param string $model The model object which the form is being defined for. Should + * include the plugin name for plugin forms. e.g. `ContactManager.Contact`. + * @param array $options An array of html attributes and options. + * @return string An formatted opening FORM tag. + * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-create + */ + public function create($model = null, $options = array()) { + $created = $id = false; + $append = ''; + + if (is_array($model) && empty($options)) { + $options = $model; + $model = null; + } + + if (empty($model) && $model !== false && !empty($this->request->params['models'])) { + $model = key($this->request->params['models']); + } elseif (empty($model) && empty($this->request->params['models'])) { + $model = false; + } + $this->defaultModel = $model; + + $key = null; + if ($model !== false) { + $object = $this->_getModel($model); + $key = $this->_introspectModel($model, 'key'); + $this->setEntity($model, true); + } + + if ($model !== false && $key) { + $recordExists = ( + isset($this->request->data[$model]) && + !empty($this->request->data[$model][$key]) && + !is_array($this->request->data[$model][$key]) + ); + + if ($recordExists) { + $created = true; + $id = $this->request->data[$model][$key]; + } + } + + $options = array_merge(array( + 'type' => ($created && empty($options['action'])) ? 'put' : 'post', + 'action' => null, + 'url' => null, + 'default' => true, + 'encoding' => strtolower(Configure::read('App.encoding')), + 'inputDefaults' => array()), + $options); + $this->inputDefaults($options['inputDefaults']); + unset($options['inputDefaults']); + + if (!isset($options['id'])) { + $domId = isset($options['action']) ? $options['action'] : $this->request['action']; + $options['id'] = $this->domId($domId . 'Form'); + } + + if ($options['action'] === null && $options['url'] === null) { + $options['action'] = $this->request->here(false); + } elseif (empty($options['url']) || is_array($options['url'])) { + if (empty($options['url']['controller'])) { + if (!empty($model)) { + $options['url']['controller'] = Inflector::underscore(Inflector::pluralize($model)); + } elseif (!empty($this->request->params['controller'])) { + $options['url']['controller'] = Inflector::underscore($this->request->params['controller']); + } + } + if (empty($options['action'])) { + $options['action'] = $this->request->params['action']; + } + + $plugin = null; + if ($this->plugin) { + $plugin = Inflector::underscore($this->plugin); + } + $actionDefaults = array( + 'plugin' => $plugin, + 'controller' => $this->_View->viewPath, + 'action' => $options['action'], + ); + $options['action'] = array_merge($actionDefaults, (array)$options['url']); + if (empty($options['action'][0]) && !empty($id)) { + $options['action'][0] = $id; + } + } elseif (is_string($options['url'])) { + $options['action'] = $options['url']; + } + unset($options['url']); + + switch (strtolower($options['type'])) { + case 'get': + $htmlAttributes['method'] = 'get'; + break; + case 'file': + $htmlAttributes['enctype'] = 'multipart/form-data'; + $options['type'] = ($created) ? 'put' : 'post'; + case 'post': + case 'put': + case 'delete': + $append .= $this->hidden('_method', array( + 'name' => '_method', 'value' => strtoupper($options['type']), 'id' => null, + 'secure' => self::SECURE_SKIP + )); + default: + $htmlAttributes['method'] = 'post'; + break; + } + $this->requestType = strtolower($options['type']); + + $action = $this->url($options['action']); + unset($options['type'], $options['action']); + + if ($options['default'] == false) { + if (!isset($options['onsubmit'])) { + $options['onsubmit'] = ''; + } + $htmlAttributes['onsubmit'] = $options['onsubmit'] . 'event.returnValue = false; return false;'; + } + unset($options['default']); + + if (!empty($options['encoding'])) { + $htmlAttributes['accept-charset'] = $options['encoding']; + unset($options['encoding']); + } + + $htmlAttributes = array_merge($options, $htmlAttributes); + + $this->fields = array(); + $append .= $this->_csrfField(); + + if (!empty($append)) { + $append = $this->Html->useTag('block', ' style="display:none;"', $append); + } + + if ($model !== false) { + $this->setEntity($model, true); + $this->_introspectModel($model, 'fields'); + } + return $this->Html->useTag('form', $action, $htmlAttributes) . $append; + } + +/** + * Return a CSRF input if the _Token is present. + * Used to secure forms in conjunction with SecurityComponent + * + * @return string + */ + protected function _csrfField() { + if (empty($this->request->params['_Token'])) { + return ''; + } + if (!empty($this->request['_Token']['unlockedFields'])) { + foreach ((array)$this->request['_Token']['unlockedFields'] as $unlocked) { + $this->_unlockedFields[] = $unlocked; + } + } + return $this->hidden('_Token.key', array( + 'value' => $this->request->params['_Token']['key'], 'id' => 'Token' . mt_rand(), + 'secure' => self::SECURE_SKIP + )); + } + +/** + * Closes an HTML form, cleans up values set by FormHelper::create(), and writes hidden + * input fields where appropriate. + * + * If $options is set a form submit button will be created. Options can be either a string or an array. + * + * {{{ + * array usage: + * + * array('label' => 'save'); value="save" + * array('label' => 'save', 'name' => 'Whatever'); value="save" name="Whatever" + * array('name' => 'Whatever'); value="Submit" name="Whatever" + * array('label' => 'save', 'name' => 'Whatever', 'div' => 'good')
+ +} + +