From ed23d52168066e67db7a5135badbb90c6520208d Mon Sep 17 00:00:00 2001 From: nivcoo Date: Sat, 28 Aug 2021 02:00:43 +0200 Subject: [PATCH] feat. up to cake 2.10.24, the latest version of cakephp --- lib/Cake/Console/Command/CompletionShell.php | 238 +- lib/Cake/Console/Command/SchemaShell.php | 80 +- lib/Cake/Console/Command/TestShell.php | 808 +-- lib/Cake/Console/ConsoleOutput.php | 552 +- lib/Cake/Event/CakeEventManager.php | 508 +- lib/Cake/I18n/L10n.php | 919 +-- lib/Cake/Network/CakeRequest.php | 2249 +++---- lib/Cake/Network/CakeSocket.php | 974 +-- lib/Cake/Utility/CakeText.php | 1325 ++-- lib/Cake/Utility/ObjectCollection.php | 594 +- lib/Cake/VERSION.txt | 2 +- lib/Cake/View/Helper/FormHelper.php | 6276 +++++++++--------- lib/Cake/View/Helper/HtmlHelper.php | 2536 +++---- 13 files changed, 8497 insertions(+), 8564 deletions(-) diff --git a/lib/Cake/Console/Command/CompletionShell.php b/lib/Cake/Console/Command/CompletionShell.php index 3d6fa17e..ac7ffeef 100755 --- a/lib/Cake/Console/Command/CompletionShell.php +++ b/lib/Cake/Console/Command/CompletionShell.php @@ -18,138 +18,138 @@ /** * Provide command completion shells such as bash. - * + * * @package Cake.Console.Command */ class CompletionShell extends AppShell { -/** - * Contains tasks to load and instantiate - * - * @var array - */ - public $tasks = array('Command'); + /** + * Contains tasks to load and instantiate + * + * @var array + */ + public $tasks = array('Command'); -/** - * Echo no header by overriding the startup method - * - * @return void - */ - public function startup() { - } + /** + * Echo no header by overriding the startup method + * + * @return void + */ + public function startup() { + } -/** - * Not called by the autocomplete shell - this is for curious users - * - * @return void - */ - public function main() { - return $this->out($this->getOptionParser()->help()); - } + /** + * Not called by the autocomplete shell - this is for curious users + * + * @return void + */ + public function main() { + return $this->out($this->getOptionParser()->help()); + } -/** - * list commands - * - * @return void - */ - public function commands() { - $options = $this->Command->commands(); - return $this->_output($options); - } + /** + * list commands + * + * @return void + */ + public function commands() { + $options = $this->Command->commands(); + return $this->_output($options); + } -/** - * list options for the named command - * - * @return void - */ - public function options() { - $commandName = ''; - if (!empty($this->args[0])) { - $commandName = $this->args[0]; - } - $options = $this->Command->options($commandName); + /** + * list options for the named command + * + * @return void + */ + public function options() { + $commandName = ''; + if (!empty($this->args[0])) { + $commandName = $this->args[0]; + } + $options = $this->Command->options($commandName); - return $this->_output($options); - } + return $this->_output($options); + } -/** - * list subcommands for the named command - * - * @return void - */ - public function subCommands() { - if (!$this->args) { - return $this->_output(); - } + /** + * list subcommands for the named command + * + * @return void + */ + public function subCommands() { + if (!$this->args) { + return $this->_output(); + } - $options = $this->Command->subCommands($this->args[0]); - return $this->_output($options); - } + $options = $this->Command->subCommands($this->args[0]); + return $this->_output($options); + } -/** - * Guess autocomplete from the whole argument string - * - * @return void - */ - public function fuzzy() { - return $this->_output(); - } + /** + * Guess autocomplete from the whole argument string + * + * @return void + */ + public function fuzzy() { + return $this->_output(); + } -/** - * Gets the option parser instance and configures it. - * - * @return ConsoleOptionParser - */ - public function getOptionParser() { - $parser = parent::getOptionParser(); + /** + * Gets the option parser instance and configures it. + * + * @return ConsoleOptionParser + */ + public function getOptionParser() { + $parser = parent::getOptionParser(); - $parser->description( - __d('cake_console', 'Used by shells like bash to autocomplete command name, options and arguments') - )->addSubcommand('commands', array( - 'help' => __d('cake_console', 'Output a list of available commands'), - 'parser' => array( - 'description' => __d('cake_console', 'List all availables'), - 'arguments' => array( - ) - ) - ))->addSubcommand('subcommands', array( - 'help' => __d('cake_console', 'Output a list of available subcommands'), - 'parser' => array( - 'description' => __d('cake_console', 'List subcommands for a command'), - 'arguments' => array( - 'command' => array( - 'help' => __d('cake_console', 'The command name'), - 'required' => true, - ) - ) - ) - ))->addSubcommand('options', array( - 'help' => __d('cake_console', 'Output a list of available options'), - 'parser' => array( - 'description' => __d('cake_console', 'List options'), - 'arguments' => array( - 'command' => array( - 'help' => __d('cake_console', 'The command name'), - 'required' => false, - ) - ) - ) - ))->epilog( - __d('cake_console', 'This command is not intended to be called manually') - ); + $parser->description( + __d('cake_console', 'Used by shells like bash to autocomplete command name, options and arguments') + )->addSubcommand('commands', array( + 'help' => __d('cake_console', 'Output a list of available commands'), + 'parser' => array( + 'description' => __d('cake_console', 'List all availables'), + 'arguments' => array( + ) + ) + ))->addSubcommand('subcommands', array( + 'help' => __d('cake_console', 'Output a list of available subcommands'), + 'parser' => array( + 'description' => __d('cake_console', 'List subcommands for a command'), + 'arguments' => array( + 'command' => array( + 'help' => __d('cake_console', 'The command name'), + 'required' => true, + ) + ) + ) + ))->addSubcommand('options', array( + 'help' => __d('cake_console', 'Output a list of available options'), + 'parser' => array( + 'description' => __d('cake_console', 'List options'), + 'arguments' => array( + 'command' => array( + 'help' => __d('cake_console', 'The command name'), + 'required' => false, + ) + ) + ) + ))->epilog( + __d('cake_console', 'This command is not intended to be called manually') + ); - return $parser; - } + return $parser; + } -/** - * Emit results as a string, space delimited - * - * @param array $options The options to output - * @return void - */ - protected function _output($options = array()) { - if ($options) { - return $this->out(implode($options, ' ')); - } - } -} + /** + * Emit results as a string, space delimited + * + * @param array $options The options to output + * @return void + */ + protected function _output($options = array()) { + if ($options) { + return $this->out(implode(' ', $options)); + } + } +} \ No newline at end of file diff --git a/lib/Cake/Console/Command/SchemaShell.php b/lib/Cake/Console/Command/SchemaShell.php index a9ad403d..46c7f810 100755 --- a/lib/Cake/Console/Command/SchemaShell.php +++ b/lib/Cake/Console/Command/SchemaShell.php @@ -27,8 +27,7 @@ * @package Cake.Console.Command * @link https://book.cakephp.org/2.0/en/console-and-shells/schema-management-and-migrations.html */ -class SchemaShell extends AppShell -{ +class SchemaShell extends AppShell { /** * Schema class being used. @@ -49,13 +48,12 @@ class SchemaShell extends AppShell * * @return void */ - public function startup() - { + public function startup() { $this->_welcome(); $this->out('Cake Schema Shell'); $this->hr(); - Configure::write('Cache.disable', 1); + Configure::write('Cache.disable', true); $name = $path = $connection = $plugin = null; if (!empty($this->params['name'])) { @@ -76,7 +74,6 @@ public function startup() if (strpos($this->params['file'], '.php') === false) { $this->params['file'] .= '.php'; } - $file = $this->params['file']; if (!empty($this->params['path'])) { @@ -102,8 +99,7 @@ public function startup() * * @return void */ - public function view() - { + public function view() { $File = new File($this->Schema->path . DS . $this->params['file']); if ($File->exists()) { $this->out($File->read()); @@ -120,8 +116,7 @@ public function view() * * @return void */ - public function generate() - { + public function generate() { $this->out(__d('cake_console', 'Generating Schema...')); $options = array(); if ($this->params['force']) { @@ -135,15 +130,6 @@ public function generate() $snapshot = true; } - $plugin = false; - if (isset($this->args[0]) && explode('-', $this->args[0])[0] === 'plugin') { - $plugin = ucfirst(explode('-', $this->args[0])[1]); - - $this->Schema->path = ROOT . DS . 'app' . DS . 'Plugin' . DS . $plugin . DS . 'SQL'; - $this->params['file'] = 'schema.php'; - $options['models'] = false; // forced - } - if (!$snapshot && file_exists($this->Schema->path . DS . $this->params['file'])) { $snapshot = true; $prompt = __d('cake_console', "Schema file exists.\n [O]verwrite\n [S]napshot\n [Q]uit\nWould you like to do?"); @@ -164,34 +150,8 @@ public function generate() Configure::write('Cache.disable', $cacheDisable); - if ((!empty($this->params['exclude']) || $plugin !== false) && !empty($content)) { - - foreach ($content['tables'] as $tableName => $tableStructure) { // on parcours les tables - - $tableHaveUpdate = false; // pour savoir si y'a des colonnes rajoutés ou non - - foreach ($tableStructure as $columnName => $columnStructure) { - - if (explode('__', $tableName)[0] != strtolower($plugin)) { // si c'est une table du CMS - - if (explode('-', $columnName)[0] != strtolower($plugin)) { // on supprime les colonnes qui non pas le prefix du plugin comme nom - unset($content['tables'][$tableName][$columnName]); - } else { - $tableHaveUpdate = true; // on a une nouvelle colonne utile au plugin - } - - } - - } - - if (explode('__', $tableName)[0] != strtolower($plugin) && !$tableHaveUpdate) { // on supprime les tables qui non pas le prefix du plugin comme nom & qui n'ont pas de colonne utile pour le plugin - $excluded[] = $tableName; - } - } - - if (!$plugin) { - $excluded = CakeText::tokenize($this->params['exclude']); - } + if (!empty($this->params['exclude']) && !empty($content)) { + $excluded = CakeText::tokenize($this->params['exclude']); foreach ($excluded as $table) { unset($content['tables'][$table]); } @@ -242,8 +202,7 @@ public function generate() * * @return string */ - public function dump() - { + public function dump() { $write = false; $Schema = $this->Schema->load(); if (!$Schema) { @@ -286,8 +245,7 @@ public function dump() * * @return void */ - public function create() - { + public function create() { list($Schema, $table) = $this->_loadSchema(); $this->_create($Schema, $table); } @@ -297,8 +255,7 @@ public function create() * * @return void */ - public function update() - { + public function update() { list($Schema, $table) = $this->_loadSchema(); $this->_update($Schema, $table); } @@ -308,8 +265,7 @@ public function update() * * @return void */ - protected function _loadSchema() - { + protected function _loadSchema() { $name = $plugin = null; if (!empty($this->params['name'])) { $name = $this->params['name']; @@ -356,8 +312,7 @@ protected function _loadSchema() * @param string $table The table name. * @return void */ - protected function _create(CakeSchema $Schema, $table = null) - { + protected function _create(CakeSchema $Schema, $table = null) { $db = ConnectionManager::getDataSource($this->Schema->connection); $drop = $create = array(); @@ -406,8 +361,7 @@ protected function _create(CakeSchema $Schema, $table = null) * @param string $table The table name. * @return void */ - protected function _update(&$Schema, $table = null) - { + protected function _update(&$Schema, $table = null) { $db = ConnectionManager::getDataSource($this->Schema->connection); $this->out(__d('cake_console', 'Comparing Database to Schema...')); @@ -465,8 +419,7 @@ protected function _update(&$Schema, $table = null) * @param CakeSchema $Schema The schema instance. * @return void */ - protected function _run($contents, $event, CakeSchema $Schema) - { + protected function _run($contents, $event, CakeSchema $Schema) { if (empty($contents)) { $this->err(__d('cake_console', 'Sql could not be run')); return; @@ -509,8 +462,7 @@ protected function _run($contents, $event, CakeSchema $Schema) * * @return ConsoleOptionParser */ - public function getOptionParser() - { + public function getOptionParser() { $parser = parent::getOptionParser(); $plugin = array( @@ -618,4 +570,4 @@ public function getOptionParser() return $parser; } -} +} \ No newline at end of file diff --git a/lib/Cake/Console/Command/TestShell.php b/lib/Cake/Console/Command/TestShell.php index ebaab5bd..7d82c560 100755 --- a/lib/Cake/Console/Command/TestShell.php +++ b/lib/Cake/Console/Command/TestShell.php @@ -30,407 +30,407 @@ */ class TestShell extends Shell { -/** - * Dispatcher object for the run. - * - * @var CakeTestDispatcher - */ - protected $_dispatcher = null; - -/** - * Gets the option parser instance and configures it. - * - * @return ConsoleOptionParser - */ - public function getOptionParser() { - $parser = new ConsoleOptionParser($this->name); - - $parser->description( - __d('cake_console', 'The CakePHP Testsuite allows you to run test cases from the command line') - )->addArgument('category', array( - 'help' => __d('cake_console', 'The category for the test, or test file, to test.'), - 'required' => false - ))->addArgument('file', array( - 'help' => __d('cake_console', 'The path to the file, or test file, to test.'), - 'required' => false - ))->addOption('log-junit', array( - 'help' => __d('cake_console', ' Log test execution in JUnit XML format to file.'), - 'default' => false - ))->addOption('log-json', array( - 'help' => __d('cake_console', ' Log test execution in JSON format to file.'), - 'default' => false - ))->addOption('log-tap', array( - 'help' => __d('cake_console', ' Log test execution in TAP format to file.'), - 'default' => false - ))->addOption('log-dbus', array( - 'help' => __d('cake_console', 'Log test execution to DBUS.'), - 'default' => false - ))->addOption('coverage-html', array( - 'help' => __d('cake_console', ' Generate code coverage report in HTML format.'), - 'default' => false - ))->addOption('coverage-clover', array( - 'help' => __d('cake_console', ' Write code coverage data in Clover XML format.'), - 'default' => false - ))->addOption('coverage-text', array( - 'help' => __d('cake_console', 'Output code coverage report in Text format.'), - 'boolean' => true - ))->addOption('testdox-html', array( - 'help' => __d('cake_console', ' Write agile documentation in HTML format to file.'), - 'default' => false - ))->addOption('testdox-text', array( - 'help' => __d('cake_console', ' Write agile documentation in Text format to file.'), - 'default' => false - ))->addOption('filter', array( - 'help' => __d('cake_console', ' Filter which tests to run.'), - 'default' => false - ))->addOption('group', array( - 'help' => __d('cake_console', ' Only runs tests from the specified group(s).'), - 'default' => false - ))->addOption('exclude-group', array( - 'help' => __d('cake_console', ' Exclude tests from the specified group(s).'), - 'default' => false - ))->addOption('list-groups', array( - 'help' => __d('cake_console', 'List available test groups.'), - 'boolean' => true - ))->addOption('loader', array( - 'help' => __d('cake_console', 'TestSuiteLoader implementation to use.'), - 'default' => false - ))->addOption('repeat', array( - 'help' => __d('cake_console', ' Runs the test(s) repeatedly.'), - 'default' => false - ))->addOption('tap', array( - 'help' => __d('cake_console', 'Report test execution progress in TAP format.'), - 'boolean' => true - ))->addOption('testdox', array( - 'help' => __d('cake_console', 'Report test execution progress in TestDox format.'), - 'default' => false, - 'boolean' => true - ))->addOption('no-colors', array( - 'help' => __d('cake_console', 'Do not use colors in output.'), - 'boolean' => true - ))->addOption('stderr', array( - 'help' => __d('cake_console', 'Write to STDERR instead of STDOUT.'), - 'boolean' => true - ))->addOption('stop-on-error', array( - 'help' => __d('cake_console', 'Stop execution upon first error or failure.'), - 'boolean' => true - ))->addOption('stop-on-failure', array( - 'help' => __d('cake_console', 'Stop execution upon first failure.'), - 'boolean' => true - ))->addOption('stop-on-skipped', array( - 'help' => __d('cake_console', 'Stop execution upon first skipped test.'), - 'boolean' => true - ))->addOption('stop-on-incomplete', array( - 'help' => __d('cake_console', 'Stop execution upon first incomplete test.'), - 'boolean' => true - ))->addOption('strict', array( - 'help' => __d('cake_console', 'Mark a test as incomplete if no assertions are made.'), - 'boolean' => true - ))->addOption('wait', array( - 'help' => __d('cake_console', 'Waits for a keystroke after each test.'), - 'boolean' => true - ))->addOption('process-isolation', array( - 'help' => __d('cake_console', 'Run each test in a separate PHP process.'), - 'boolean' => true - ))->addOption('no-globals-backup', array( - 'help' => __d('cake_console', 'Do not backup and restore $GLOBALS for each test.'), - 'boolean' => true - ))->addOption('static-backup', array( - 'help' => __d('cake_console', 'Backup and restore static attributes for each test.'), - 'boolean' => true - ))->addOption('syntax-check', array( - 'help' => __d('cake_console', 'Try to check source files for syntax errors.'), - 'boolean' => true - ))->addOption('bootstrap', array( - 'help' => __d('cake_console', ' A "bootstrap" PHP file that is run before the tests.'), - 'default' => false - ))->addOption('configuration', array( - 'help' => __d('cake_console', ' Read configuration from XML file.'), - 'default' => false - ))->addOption('no-configuration', array( - 'help' => __d('cake_console', 'Ignore default configuration file (phpunit.xml).'), - 'boolean' => true - ))->addOption('include-path', array( - 'help' => __d('cake_console', ' Prepend PHP include_path with given path(s).'), - 'default' => false - ))->addOption('directive', array( - 'help' => __d('cake_console', 'key[=value] Sets a php.ini value.'), - 'short' => 'd', - 'default' => false - ))->addOption('fixture', array( - 'help' => __d('cake_console', 'Choose a custom fixture manager.') - ))->addOption('debug', array( - 'help' => __d('cake_console', 'More verbose output.') - )); - - return $parser; - } - -/** - * Initialization method installs PHPUnit and loads all plugins - * - * @return void - * @throws Exception - */ - public function initialize() { - $this->_dispatcher = new CakeTestSuiteDispatcher(); - $success = $this->_dispatcher->loadTestFramework(); - if (!$success) { - throw new Exception(__d('cake_dev', 'Please install PHPUnit framework v3.7 (http://www.phpunit.de)')); - } - } - -/** - * Parse the CLI options into an array CakeTestDispatcher can use. - * - * @return array|null Array of params for CakeTestDispatcher or null. - */ - protected function _parseArgs() { - if (empty($this->args)) { - return null; - } - $params = array( - 'core' => false, - 'app' => false, - 'plugin' => null, - 'output' => 'text', - ); - - if (strpos($this->args[0], '.php')) { - $category = $this->_mapFileToCategory($this->args[0]); - $params['case'] = $this->_mapFileToCase($this->args[0], $category); - } else { - $category = $this->args[0]; - if (isset($this->args[1])) { - $params['case'] = $this->args[1]; - } - } - - if ($category === 'core') { - $params['core'] = true; - } elseif ($category === 'app') { - $params['app'] = true; - } else { - $params['plugin'] = $category; - } - - return $params; - } - -/** - * Converts the options passed to the shell as options for the PHPUnit cli runner - * - * @return array Array of params for CakeTestDispatcher - */ - protected function _runnerOptions() { - $options = array(); - $params = $this->params; - unset($params['help']); - unset($params['quiet']); - - if (!empty($params['no-colors'])) { - unset($params['no-colors'], $params['colors']); - } else { - $params['colors'] = true; - } - - foreach ($params as $param => $value) { - if ($value === false) { - continue; - } - if ($param === 'directive') { - $options[] = '-d'; - } else { - $options[] = '--' . $param; - } - if (is_string($value)) { - $options[] = $value; - } - } - return $options; - } - -/** - * Main entry point to this shell - * - * @return void - */ - public function main() { - $this->out(__d('cake_console', 'CakePHP Test Shell')); - $this->hr(); - - $args = $this->_parseArgs(); - - if (empty($args['case'])) { - return $this->available(); - } - - $this->_run($args, $this->_runnerOptions()); - } - -/** - * Runs the test case from $runnerArgs - * - * @param array $runnerArgs list of arguments as obtained from _parseArgs() - * @param array $options list of options as constructed by _runnerOptions() - * @return void - */ - protected function _run($runnerArgs, $options = array()) { - restore_error_handler(); - restore_error_handler(); - - $testCli = new CakeTestSuiteCommand('CakeTestLoader', $runnerArgs); - $testCli->run($options); - } - -/** - * Shows a list of available test cases and gives the option to run one of them - * - * @return void - */ - public function available() { - $params = $this->_parseArgs(); - $testCases = CakeTestLoader::generateTestList($params); - $app = $params['app']; - $plugin = $params['plugin']; - - $title = "Core Test Cases:"; - $category = 'core'; - if ($app) { - $title = "App Test Cases:"; - $category = 'app'; - } elseif ($plugin) { - $title = Inflector::humanize($plugin) . " Test Cases:"; - $category = $plugin; - } - - if (empty($testCases)) { - $this->out(__d('cake_console', "No test cases available \n\n")); - return $this->out($this->OptionParser->help()); - } - - $this->out($title); - $i = 1; - $cases = array(); - foreach ($testCases as $testCase) { - $case = str_replace('Test.php', '', $testCase); - $this->out("[$i] $case"); - $cases[$i] = $case; - $i++; - } - - while ($choice = $this->in(__d('cake_console', 'What test case would you like to run?'), null, 'q')) { - if (is_numeric($choice) && isset($cases[$choice])) { - $this->args[0] = $category; - $this->args[1] = $cases[$choice]; - $this->_run($this->_parseArgs(), $this->_runnerOptions()); - break; - } - - if (is_string($choice) && in_array($choice, $cases)) { - $this->args[0] = $category; - $this->args[1] = $choice; - $this->_run($this->_parseArgs(), $this->_runnerOptions()); - break; - } - - if ($choice === 'q') { - break; - } - } - } - -/** - * Find the test case for the passed file. The file could itself be a test. - * - * @param string $file The file to map. - * @param string $category The test file category. - * @param bool $throwOnMissingFile Whether or not to throw an exception. - * @return array array(type, case) - * @throws Exception - */ - protected function _mapFileToCase($file, $category, $throwOnMissingFile = true) { - if (!$category || (substr($file, -4) !== '.php')) { - return false; - } - - $_file = realpath($file); - if ($_file) { - $file = $_file; - } - - $testFile = $testCase = null; - $testCaseFolder = str_replace(APP, '', APP_TEST_CASES); - if (preg_match('@Test[\\\/]@', $file)) { - if (substr($file, -8) === 'Test.php') { - $testCase = substr($file, 0, -8); - $testCase = str_replace(DS, '/', $testCase); - $testCaseFolderEscaped = str_replace('/', '\/', $testCaseFolder); - $testCase = preg_replace('@.*' . $testCaseFolderEscaped . '\/@', '', $testCase); - if (!empty($testCase)) { - if ($category === 'core') { - $testCase = str_replace('lib/Cake', '', $testCase); - } - return $testCase; - } - throw new Exception(__d('cake_dev', 'Test case %s cannot be run via this shell', $testFile)); - } - } - - $file = substr($file, 0, -4); - if ($category === 'core') { - - $testCase = str_replace(DS, '/', $file); - $testCase = preg_replace('@.*lib/Cake/@', '', $file); - $testCase[0] = strtoupper($testCase[0]); - $testFile = CAKE . 'Test/Case/' . $testCase . 'Test.php'; - - if (!file_exists($testFile) && $throwOnMissingFile) { - throw new Exception(__d('cake_dev', 'Test case %s not found', $testFile)); - } - - return $testCase; - } - - if ($category === 'app') { - $testFile = str_replace(APP, APP_TEST_CASES . '/', $file) . 'Test.php'; - } else { - $testFile = preg_replace( - "@((?:plugins|Plugin)[\\/]{$category}[\\/])(.*)$@", - '\1' . $testCaseFolder . '/\2Test.php', - $file - ); - } - - if (!file_exists($testFile) && $throwOnMissingFile) { - throw new Exception(__d('cake_dev', 'Test case %s not found', $testFile)); - } - - $testCase = substr($testFile, 0, -8); - $testCase = str_replace(DS, '/', $testCase); - $testCase = preg_replace('@.*' . $testCaseFolder . '/@', '', $testCase); - return $testCase; - } - -/** - * For the given file, what category of test is it? returns app, core or the name of the plugin - * - * @param string $file The file to map. - * @return string - */ - protected function _mapFileToCategory($file) { - $_file = realpath($file); - if ($_file) { - $file = $_file; - } - - $file = str_replace(DS, '/', $file); - if (strpos($file, 'lib/Cake/') !== false) { - return 'core'; - } elseif (preg_match('@(?:plugins|Plugin)/([^/]*)@', $file, $match)) { - return $match[1]; - } - return 'app'; - } - -} + /** + * Dispatcher object for the run. + * + * @var CakeTestDispatcher + */ + protected $_dispatcher = null; + + /** + * Gets the option parser instance and configures it. + * + * @return ConsoleOptionParser + */ + public function getOptionParser() { + $parser = new ConsoleOptionParser($this->name); + + $parser->description( + __d('cake_console', 'The CakePHP Testsuite allows you to run test cases from the command line') + )->addArgument('category', array( + 'help' => __d('cake_console', 'The category for the test, or test file, to test.'), + 'required' => false + ))->addArgument('file', array( + 'help' => __d('cake_console', 'The path to the file, or test file, to test.'), + 'required' => false + ))->addOption('log-junit', array( + 'help' => __d('cake_console', ' Log test execution in JUnit XML format to file.'), + 'default' => false + ))->addOption('log-json', array( + 'help' => __d('cake_console', ' Log test execution in JSON format to file.'), + 'default' => false + ))->addOption('log-tap', array( + 'help' => __d('cake_console', ' Log test execution in TAP format to file.'), + 'default' => false + ))->addOption('log-dbus', array( + 'help' => __d('cake_console', 'Log test execution to DBUS.'), + 'default' => false + ))->addOption('coverage-html', array( + 'help' => __d('cake_console', ' Generate code coverage report in HTML format.'), + 'default' => false + ))->addOption('coverage-clover', array( + 'help' => __d('cake_console', ' Write code coverage data in Clover XML format.'), + 'default' => false + ))->addOption('coverage-text', array( + 'help' => __d('cake_console', 'Output code coverage report in Text format.'), + 'boolean' => true + ))->addOption('testdox-html', array( + 'help' => __d('cake_console', ' Write agile documentation in HTML format to file.'), + 'default' => false + ))->addOption('testdox-text', array( + 'help' => __d('cake_console', ' Write agile documentation in Text format to file.'), + 'default' => false + ))->addOption('filter', array( + 'help' => __d('cake_console', ' Filter which tests to run.'), + 'default' => false + ))->addOption('group', array( + 'help' => __d('cake_console', ' Only runs tests from the specified group(s).'), + 'default' => false + ))->addOption('exclude-group', array( + 'help' => __d('cake_console', ' Exclude tests from the specified group(s).'), + 'default' => false + ))->addOption('list-groups', array( + 'help' => __d('cake_console', 'List available test groups.'), + 'boolean' => true + ))->addOption('loader', array( + 'help' => __d('cake_console', 'TestSuiteLoader implementation to use.'), + 'default' => false + ))->addOption('repeat', array( + 'help' => __d('cake_console', ' Runs the test(s) repeatedly.'), + 'default' => false + ))->addOption('tap', array( + 'help' => __d('cake_console', 'Report test execution progress in TAP format.'), + 'boolean' => true + ))->addOption('testdox', array( + 'help' => __d('cake_console', 'Report test execution progress in TestDox format.'), + 'default' => false, + 'boolean' => true + ))->addOption('no-colors', array( + 'help' => __d('cake_console', 'Do not use colors in output.'), + 'boolean' => true + ))->addOption('stderr', array( + 'help' => __d('cake_console', 'Write to STDERR instead of STDOUT.'), + 'boolean' => true + ))->addOption('stop-on-error', array( + 'help' => __d('cake_console', 'Stop execution upon first error or failure.'), + 'boolean' => true + ))->addOption('stop-on-failure', array( + 'help' => __d('cake_console', 'Stop execution upon first failure.'), + 'boolean' => true + ))->addOption('stop-on-skipped', array( + 'help' => __d('cake_console', 'Stop execution upon first skipped test.'), + 'boolean' => true + ))->addOption('stop-on-incomplete', array( + 'help' => __d('cake_console', 'Stop execution upon first incomplete test.'), + 'boolean' => true + ))->addOption('strict', array( + 'help' => __d('cake_console', 'Mark a test as incomplete if no assertions are made.'), + 'boolean' => true + ))->addOption('wait', array( + 'help' => __d('cake_console', 'Waits for a keystroke after each test.'), + 'boolean' => true + ))->addOption('process-isolation', array( + 'help' => __d('cake_console', 'Run each test in a separate PHP process.'), + 'boolean' => true + ))->addOption('no-globals-backup', array( + 'help' => __d('cake_console', 'Do not backup and restore $GLOBALS for each test.'), + 'boolean' => true + ))->addOption('static-backup', array( + 'help' => __d('cake_console', 'Backup and restore static attributes for each test.'), + 'boolean' => true + ))->addOption('syntax-check', array( + 'help' => __d('cake_console', 'Try to check source files for syntax errors.'), + 'boolean' => true + ))->addOption('bootstrap', array( + 'help' => __d('cake_console', ' A "bootstrap" PHP file that is run before the tests.'), + 'default' => false + ))->addOption('configuration', array( + 'help' => __d('cake_console', ' Read configuration from XML file.'), + 'default' => false + ))->addOption('no-configuration', array( + 'help' => __d('cake_console', 'Ignore default configuration file (phpunit.xml).'), + 'boolean' => true + ))->addOption('include-path', array( + 'help' => __d('cake_console', ' Prepend PHP include_path with given path(s).'), + 'default' => false + ))->addOption('directive', array( + 'help' => __d('cake_console', 'key[=value] Sets a php.ini value.'), + 'short' => 'd', + 'default' => false + ))->addOption('fixture', array( + 'help' => __d('cake_console', 'Choose a custom fixture manager.') + ))->addOption('debug', array( + 'help' => __d('cake_console', 'More verbose output.') + )); + + return $parser; + } + + /** + * Initialization method installs PHPUnit and loads all plugins + * + * @return void + * @throws Exception + */ + public function initialize() { + $this->_dispatcher = new CakeTestSuiteDispatcher(); + $success = $this->_dispatcher->loadTestFramework(); + if (!$success) { + throw new Exception(__d('cake_dev', 'Please install PHPUnit framework v3.7 (http://www.phpunit.de)')); + } + } + + /** + * Parse the CLI options into an array CakeTestDispatcher can use. + * + * @return array|null Array of params for CakeTestDispatcher or null. + */ + protected function _parseArgs() { + if (empty($this->args)) { + return null; + } + $params = array( + 'core' => false, + 'app' => false, + 'plugin' => null, + 'output' => 'text', + ); + + if (strpos($this->args[0], '.php')) { + $category = $this->_mapFileToCategory($this->args[0]); + $params['case'] = $this->_mapFileToCase($this->args[0], $category); + } else { + $category = $this->args[0]; + if (isset($this->args[1])) { + $params['case'] = $this->args[1]; + } + } + + if ($category === 'core') { + $params['core'] = true; + } elseif ($category === 'app') { + $params['app'] = true; + } else { + $params['plugin'] = $category; + } + + return $params; + } + + /** + * Converts the options passed to the shell as options for the PHPUnit cli runner + * + * @return array Array of params for CakeTestDispatcher + */ + protected function _runnerOptions() { + $options = array(); + $params = $this->params; + unset($params['help']); + unset($params['quiet']); + + if (!empty($params['no-colors'])) { + unset($params['no-colors'], $params['colors']); + } else { + $params['colors'] = true; + } + + foreach ($params as $param => $value) { + if ($value === false) { + continue; + } + if ($param === 'directive') { + $options[] = '-d'; + } else { + $options[] = '--' . $param; + } + if (is_string($value)) { + $options[] = $value; + } + } + return $options; + } + + /** + * Main entry point to this shell + * + * @return void + */ + public function main() { + $this->out(__d('cake_console', 'CakePHP Test Shell')); + $this->hr(); + + $args = $this->_parseArgs(); + + if (empty($args['case'])) { + return $this->available(); + } + + $this->_run($args, $this->_runnerOptions()); + } + + /** + * Runs the test case from $runnerArgs + * + * @param array $runnerArgs list of arguments as obtained from _parseArgs() + * @param array $options list of options as constructed by _runnerOptions() + * @return void + */ + protected function _run($runnerArgs, $options = array()) { + restore_error_handler(); + restore_error_handler(); + + $testCli = new CakeTestSuiteCommand('CakeTestLoader', $runnerArgs); + $testCli->run($options); + } + + /** + * Shows a list of available test cases and gives the option to run one of them + * + * @return void + */ + public function available() { + $params = $this->_parseArgs(); + $testCases = CakeTestLoader::generateTestList($params); + $app = isset($params['app']) ? $params['app'] : null; + $plugin = isset($params['plugin']) ? $params['plugin'] : null; + + $title = "Core Test Cases:"; + $category = 'core'; + if ($app) { + $title = "App Test Cases:"; + $category = 'app'; + } elseif ($plugin) { + $title = Inflector::humanize($plugin) . " Test Cases:"; + $category = $plugin; + } + + if (empty($testCases)) { + $this->out(__d('cake_console', "No test cases available \n\n")); + return $this->out($this->OptionParser->help()); + } + + $this->out($title); + $i = 1; + $cases = array(); + foreach ($testCases as $testCase) { + $case = str_replace('Test.php', '', $testCase); + $this->out("[$i] $case"); + $cases[$i] = $case; + $i++; + } + + while ($choice = $this->in(__d('cake_console', 'What test case would you like to run?'), null, 'q')) { + if (is_numeric($choice) && isset($cases[$choice])) { + $this->args[0] = $category; + $this->args[1] = $cases[$choice]; + $this->_run($this->_parseArgs(), $this->_runnerOptions()); + break; + } + + if (is_string($choice) && in_array($choice, $cases)) { + $this->args[0] = $category; + $this->args[1] = $choice; + $this->_run($this->_parseArgs(), $this->_runnerOptions()); + break; + } + + if ($choice === 'q') { + break; + } + } + } + + /** + * Find the test case for the passed file. The file could itself be a test. + * + * @param string $file The file to map. + * @param string $category The test file category. + * @param bool $throwOnMissingFile Whether or not to throw an exception. + * @return array array(type, case) + * @throws Exception + */ + protected function _mapFileToCase($file, $category, $throwOnMissingFile = true) { + if (!$category || (substr($file, -4) !== '.php')) { + return false; + } + + $_file = realpath($file); + if ($_file) { + $file = $_file; + } + + $testFile = $testCase = null; + $testCaseFolder = str_replace(APP, '', APP_TEST_CASES); + if (preg_match('@Test[\\\/]@', $file)) { + if (substr($file, -8) === 'Test.php') { + $testCase = substr($file, 0, -8); + $testCase = str_replace(DS, '/', $testCase); + $testCaseFolderEscaped = str_replace('/', '\/', $testCaseFolder); + $testCase = preg_replace('@.*' . $testCaseFolderEscaped . '\/@', '', $testCase); + if (!empty($testCase)) { + if ($category === 'core') { + $testCase = str_replace('lib/Cake', '', $testCase); + } + return $testCase; + } + throw new Exception(__d('cake_dev', 'Test case %s cannot be run via this shell', $testFile)); + } + } + + $file = substr($file, 0, -4); + if ($category === 'core') { + + $testCase = str_replace(DS, '/', $file); + $testCase = preg_replace('@.*lib/Cake/@', '', $file); + $testCase[0] = strtoupper($testCase[0]); + $testFile = CAKE . 'Test/Case/' . $testCase . 'Test.php'; + + if (!file_exists($testFile) && $throwOnMissingFile) { + throw new Exception(__d('cake_dev', 'Test case %s not found', $testFile)); + } + + return $testCase; + } + + if ($category === 'app') { + $testFile = str_replace(APP, APP_TEST_CASES . '/', $file) . 'Test.php'; + } else { + $testFile = preg_replace( + "@((?:plugins|Plugin)[\\/]{$category}[\\/])(.*)$@", + '\1' . $testCaseFolder . '/\2Test.php', + $file + ); + } + + if (!file_exists($testFile) && $throwOnMissingFile) { + throw new Exception(__d('cake_dev', 'Test case %s not found', $testFile)); + } + + $testCase = substr($testFile, 0, -8); + $testCase = str_replace(DS, '/', $testCase); + $testCase = preg_replace('@.*' . $testCaseFolder . '/@', '', $testCase); + return $testCase; + } + + /** + * For the given file, what category of test is it? returns app, core or the name of the plugin + * + * @param string $file The file to map. + * @return string + */ + protected function _mapFileToCategory($file) { + $_file = realpath($file); + if ($_file) { + $file = $_file; + } + + $file = str_replace(DS, '/', $file); + if (strpos($file, 'lib/Cake/') !== false) { + return 'core'; + } elseif (preg_match('@(?:plugins|Plugin)/([^/]*)@', $file, $match)) { + return $match[1]; + } + return 'app'; + } + +} \ No newline at end of file diff --git a/lib/Cake/Console/ConsoleOutput.php b/lib/Cake/Console/ConsoleOutput.php index 4690a850..87e3fd2a 100755 --- a/lib/Cake/Console/ConsoleOutput.php +++ b/lib/Cake/Console/ConsoleOutput.php @@ -44,301 +44,301 @@ */ class ConsoleOutput { -/** - * Raw output constant - no modification of output text. - * - * @var int - */ - const RAW = 0; + /** + * Raw output constant - no modification of output text. + * + * @var int + */ + const RAW = 0; -/** - * Plain output - tags will be stripped. - * - * @var int - */ - const PLAIN = 1; + /** + * Plain output - tags will be stripped. + * + * @var int + */ + const PLAIN = 1; -/** - * Color output - Convert known tags in to ANSI color escape codes. - * - * @var int - */ - const COLOR = 2; + /** + * Color output - Convert known tags in to ANSI color escape codes. + * + * @var int + */ + const COLOR = 2; -/** - * Constant for a newline. - * - * @var string - */ - const LF = PHP_EOL; + /** + * Constant for a newline. + * + * @var string + */ + const LF = PHP_EOL; -/** - * File handle for output. - * - * @var resource - */ - protected $_output; + /** + * File handle for output. + * + * @var resource + */ + protected $_output; -/** - * The number of bytes last written to the output stream - * used when overwriting the previous message. - * - * @var int - */ - protected $_lastWritten = 0; + /** + * The number of bytes last written to the output stream + * used when overwriting the previous message. + * + * @var int + */ + protected $_lastWritten = 0; -/** - * The current output type. Manipulated with ConsoleOutput::outputAs(); - * - * @var int - */ - protected $_outputAs = self::COLOR; + /** + * The current output type. Manipulated with ConsoleOutput::outputAs(); + * + * @var int + */ + protected $_outputAs = self::COLOR; -/** - * text colors used in colored output. - * - * @var array - */ - protected static $_foregroundColors = array( - 'black' => 30, - 'red' => 31, - 'green' => 32, - 'yellow' => 33, - 'blue' => 34, - 'magenta' => 35, - 'cyan' => 36, - 'white' => 37 - ); + /** + * text colors used in colored output. + * + * @var array + */ + protected static $_foregroundColors = array( + 'black' => 30, + 'red' => 31, + 'green' => 32, + 'yellow' => 33, + 'blue' => 34, + 'magenta' => 35, + 'cyan' => 36, + 'white' => 37 + ); -/** - * background colors used in colored output. - * - * @var array - */ - protected static $_backgroundColors = array( - 'black' => 40, - 'red' => 41, - 'green' => 42, - 'yellow' => 43, - 'blue' => 44, - 'magenta' => 45, - 'cyan' => 46, - 'white' => 47 - ); + /** + * background colors used in colored output. + * + * @var array + */ + protected static $_backgroundColors = array( + 'black' => 40, + 'red' => 41, + 'green' => 42, + 'yellow' => 43, + 'blue' => 44, + 'magenta' => 45, + 'cyan' => 46, + 'white' => 47 + ); -/** - * formatting options for colored output - * - * @var string - */ - protected static $_options = array( - 'bold' => 1, - 'underline' => 4, - 'blink' => 5, - 'reverse' => 7, - ); + /** + * formatting options for colored output + * + * @var string + */ + protected static $_options = array( + 'bold' => 1, + 'underline' => 4, + 'blink' => 5, + 'reverse' => 7, + ); -/** - * Styles that are available as tags in console output. - * You can modify these styles with ConsoleOutput::styles() - * - * @var array - */ - protected static $_styles = array( - 'emergency' => array('text' => 'red', 'underline' => true), - 'alert' => array('text' => 'red', 'underline' => true), - 'critical' => array('text' => 'red', 'underline' => true), - 'error' => array('text' => 'red', 'underline' => true), - 'warning' => array('text' => 'yellow'), - 'info' => array('text' => 'cyan'), - 'debug' => array('text' => 'yellow'), - 'success' => array('text' => 'green'), - 'comment' => array('text' => 'blue'), - 'question' => array('text' => 'magenta'), - 'notice' => array('text' => 'cyan') - ); + /** + * Styles that are available as tags in console output. + * You can modify these styles with ConsoleOutput::styles() + * + * @var array + */ + protected static $_styles = array( + 'emergency' => array('text' => 'red', 'underline' => true), + 'alert' => array('text' => 'red', 'underline' => true), + 'critical' => array('text' => 'red', 'underline' => true), + 'error' => array('text' => 'red', 'underline' => true), + 'warning' => array('text' => 'yellow'), + 'info' => array('text' => 'cyan'), + 'debug' => array('text' => 'yellow'), + 'success' => array('text' => 'green'), + 'comment' => array('text' => 'blue'), + 'question' => array('text' => 'magenta'), + 'notice' => array('text' => 'cyan') + ); -/** - * Construct the output object. - * - * Checks for a pretty console environment. Ansicon and ConEmu allows - * pretty consoles on Windows, and is supported. - * - * @param string $stream The identifier of the stream to write output to. - */ - public function __construct($stream = 'php://stdout') { - $this->_output = fopen($stream, 'w'); + /** + * Construct the output object. + * + * Checks for a pretty console environment. Ansicon and ConEmu allows + * pretty consoles on Windows, and is supported. + * + * @param string $stream The identifier of the stream to write output to. + */ + public function __construct($stream = 'php://stdout') { + $this->_output = fopen($stream, 'w'); - if ((DS === '\\' && !(bool)env('ANSICON') && env('ConEmuANSI') !== 'ON') || - $stream === 'php://output' || - (function_exists('posix_isatty') && !posix_isatty($this->_output)) - ) { - $this->_outputAs = static::PLAIN; - } - } + if ((DS === '\\' && !(bool)env('ANSICON') && env('ConEmuANSI') !== 'ON') || + $stream === 'php://output' || + (function_exists('posix_isatty') && !posix_isatty($this->_output)) + ) { + $this->_outputAs = static::PLAIN; + } + } -/** - * Outputs a single or multiple messages to stdout. If no parameters - * are passed, outputs just a newline. - * - * @param string|array $message A string or an array of strings to output - * @param int $newlines Number of newlines to append - * @return int Returns the number of bytes returned from writing to stdout. - */ - public function write($message, $newlines = 1) { - if (is_array($message)) { - $message = implode(static::LF, $message); - } - return $this->_write($this->styleText($message . str_repeat(static::LF, $newlines))); - } + /** + * Outputs a single or multiple messages to stdout. If no parameters + * are passed, outputs just a newline. + * + * @param string|array $message A string or an array of strings to output + * @param int $newlines Number of newlines to append + * @return int Returns the number of bytes returned from writing to stdout. + */ + public function write($message, $newlines = 1) { + if (is_array($message)) { + $message = implode(static::LF, $message); + } + return $this->_write($this->styleText($message . str_repeat(static::LF, $newlines))); + } -/** - * Overwrite some already output text. - * - * Useful for building progress bars, or when you want to replace - * text already output to the screen with new text. - * - * **Warning** You cannot overwrite text that contains newlines. - * - * @param array|string $message The message to output. - * @param int $newlines Number of newlines to append. - * @param int|null $size The number of bytes to overwrite. Defaults to the - * length of the last message output. - * @return void - */ - public function overwrite($message, $newlines = 1, $size = null) { - $size = $size ?: $this->_lastWritten; - // Output backspaces. - $this->write(str_repeat("\x08", $size), 0); - $newBytes = $this->write($message, 0); - // Fill any remaining bytes with spaces. - $fill = $size - $newBytes; - if ($fill > 0) { - $this->write(str_repeat(' ', $fill), 0); - } - if ($newlines) { - $this->write("", $newlines); - } - } + /** + * Overwrite some already output text. + * + * Useful for building progress bars, or when you want to replace + * text already output to the screen with new text. + * + * **Warning** You cannot overwrite text that contains newlines. + * + * @param array|string $message The message to output. + * @param int $newlines Number of newlines to append. + * @param int|null $size The number of bytes to overwrite. Defaults to the + * length of the last message output. + * @return void + */ + public function overwrite($message, $newlines = 1, $size = null) { + $size = $size ?: $this->_lastWritten; + // Output backspaces. + $this->write(str_repeat("\x08", $size), 0); + $newBytes = $this->write($message, 0); + // Fill any remaining bytes with spaces. + $fill = $size - $newBytes; + if ($fill > 0) { + $this->write(str_repeat(' ', $fill), 0); + } + if ($newlines) { + $this->write("", $newlines); + } + } -/** - * Apply styling to text. - * - * @param string $text Text with styling tags. - * @return string String with color codes added. - */ - public function styleText($text) { - if ($this->_outputAs == static::RAW) { - return $text; - } - if ($this->_outputAs == static::PLAIN) { - $tags = implode('|', array_keys(static::$_styles)); - return preg_replace('##', '', $text); - } - return preg_replace_callback( - '/<(?P[a-z0-9-_]+)>(?P.*?)<\/(\1)>/ims', array($this, '_replaceTags'), $text - ); - } + /** + * Apply styling to text. + * + * @param string $text Text with styling tags. + * @return string String with color codes added. + */ + public function styleText($text) { + if ($this->_outputAs == static::RAW) { + return $text; + } + if ($this->_outputAs == static::PLAIN) { + $tags = implode('|', array_keys(static::$_styles)); + return preg_replace('##', '', $text); + } + return preg_replace_callback( + '/<(?P[a-z0-9-_]+)>(?P.*?)<\/(\1)>/ims', array($this, '_replaceTags'), $text + ); + } -/** - * Replace tags with color codes. - * - * @param array $matches An array of matches to replace. - * @return string - */ - protected function _replaceTags($matches) { - $style = $this->styles($matches['tag']); - if (empty($style)) { - return '<' . $matches['tag'] . '>' . $matches['text'] . ''; - } + /** + * Replace tags with color codes. + * + * @param array $matches An array of matches to replace. + * @return string + */ + protected function _replaceTags($matches) { + $style = $this->styles($matches['tag']); + if (empty($style)) { + return '<' . $matches['tag'] . '>' . $matches['text'] . ''; + } - $styleInfo = array(); - if (!empty($style['text']) && isset(static::$_foregroundColors[$style['text']])) { - $styleInfo[] = static::$_foregroundColors[$style['text']]; - } - if (!empty($style['background']) && isset(static::$_backgroundColors[$style['background']])) { - $styleInfo[] = static::$_backgroundColors[$style['background']]; - } - unset($style['text'], $style['background']); - foreach ($style as $option => $value) { - if ($value) { - $styleInfo[] = static::$_options[$option]; - } - } - return "\033[" . implode($styleInfo, ';') . 'm' . $matches['text'] . "\033[0m"; - } + $styleInfo = array(); + if (!empty($style['text']) && isset(static::$_foregroundColors[$style['text']])) { + $styleInfo[] = static::$_foregroundColors[$style['text']]; + } + if (!empty($style['background']) && isset(static::$_backgroundColors[$style['background']])) { + $styleInfo[] = static::$_backgroundColors[$style['background']]; + } + unset($style['text'], $style['background']); + foreach ($style as $option => $value) { + if ($value) { + $styleInfo[] = static::$_options[$option]; + } + } + return "\033[" . implode(';', $styleInfo) . 'm' . $matches['text'] . "\033[0m"; + } -/** - * Writes a message to the output stream. - * - * @param string $message Message to write. - * @return bool success - */ - protected function _write($message) { - $this->_lastWritten = fwrite($this->_output, $message); - return $this->_lastWritten; - } + /** + * Writes a message to the output stream. + * + * @param string $message Message to write. + * @return bool success + */ + protected function _write($message) { + $this->_lastWritten = fwrite($this->_output, $message); + return $this->_lastWritten; + } -/** - * Get the current styles offered, or append new ones in. - * - * ### Get a style definition - * - * `$this->output->styles('error');` - * - * ### Get all the style definitions - * - * `$this->output->styles();` - * - * ### Create or modify an existing style - * - * `$this->output->styles('annoy', array('text' => 'purple', 'background' => 'yellow', 'blink' => true));` - * - * ### Remove a style - * - * `$this->output->styles('annoy', false);` - * - * @param string $style The style to get or create. - * @param array $definition The array definition of the style to change or create a style - * or false to remove a style. - * @return mixed If you are getting styles, the style or null will be returned. If you are creating/modifying - * styles true will be returned. - */ - public function styles($style = null, $definition = null) { - if ($style === null && $definition === null) { - return static::$_styles; - } - if (is_string($style) && $definition === null) { - return isset(static::$_styles[$style]) ? static::$_styles[$style] : null; - } - if ($definition === false) { - unset(static::$_styles[$style]); - return true; - } - static::$_styles[$style] = $definition; - return true; - } + /** + * Get the current styles offered, or append new ones in. + * + * ### Get a style definition + * + * `$this->output->styles('error');` + * + * ### Get all the style definitions + * + * `$this->output->styles();` + * + * ### Create or modify an existing style + * + * `$this->output->styles('annoy', array('text' => 'purple', 'background' => 'yellow', 'blink' => true));` + * + * ### Remove a style + * + * `$this->output->styles('annoy', false);` + * + * @param string $style The style to get or create. + * @param array $definition The array definition of the style to change or create a style + * or false to remove a style. + * @return mixed If you are getting styles, the style or null will be returned. If you are creating/modifying + * styles true will be returned. + */ + public function styles($style = null, $definition = null) { + if ($style === null && $definition === null) { + return static::$_styles; + } + if (is_string($style) && $definition === null) { + return isset(static::$_styles[$style]) ? static::$_styles[$style] : null; + } + if ($definition === false) { + unset(static::$_styles[$style]); + return true; + } + static::$_styles[$style] = $definition; + return true; + } -/** - * Get/Set the output type to use. The output type how formatting tags are treated. - * - * @param int $type The output type to use. Should be one of the class constants. - * @return mixed Either null or the value if getting. - */ - public function outputAs($type = null) { - if ($type === null) { - return $this->_outputAs; - } - $this->_outputAs = $type; - } + /** + * Get/Set the output type to use. The output type how formatting tags are treated. + * + * @param int $type The output type to use. Should be one of the class constants. + * @return mixed Either null or the value if getting. + */ + public function outputAs($type = null) { + if ($type === null) { + return $this->_outputAs; + } + $this->_outputAs = $type; + } -/** - * Clean up and close handles - */ - public function __destruct() { - if (is_resource($this->_output)) { - fclose($this->_output); - } - } + /** + * Clean up and close handles + */ + public function __destruct() { + if (is_resource($this->_output)) { + fclose($this->_output); + } + } -} +} \ No newline at end of file diff --git a/lib/Cake/Event/CakeEventManager.php b/lib/Cake/Event/CakeEventManager.php index 98d73974..448bc530 100755 --- a/lib/Cake/Event/CakeEventManager.php +++ b/lib/Cake/Event/CakeEventManager.php @@ -27,274 +27,274 @@ */ class CakeEventManager { -/** - * The default priority queue value for new, attached listeners - * - * @var int - */ - public static $defaultPriority = 10; + /** + * The default priority queue value for new, attached listeners + * + * @var int + */ + public static $defaultPriority = 10; -/** - * The globally available instance, used for dispatching events attached from any scope - * - * @var CakeEventManager - */ - protected static $_generalManager = null; + /** + * The globally available instance, used for dispatching events attached from any scope + * + * @var CakeEventManager + */ + protected static $_generalManager = null; -/** - * List of listener callbacks associated to - * - * @var object - */ - protected $_listeners = array(); + /** + * List of listener callbacks associated to + * + * @var object + */ + protected $_listeners = array(); -/** - * Internal flag to distinguish a common manager from the singleton - * - * @var bool - */ - protected $_isGlobal = false; + /** + * Internal flag to distinguish a common manager from the singleton + * + * @var bool + */ + protected $_isGlobal = false; -/** - * Returns the globally available instance of a CakeEventManager - * this is used for dispatching events attached from outside the scope - * other managers were created. Usually for creating hook systems or inter-class - * communication - * - * If called with the first parameter, it will be set as the globally available instance - * - * @param CakeEventManager $manager Optional event manager instance. - * @return CakeEventManager the global event manager - */ - public static function instance($manager = null) { - if ($manager instanceof CakeEventManager) { - static::$_generalManager = $manager; - } - if (empty(static::$_generalManager)) { - static::$_generalManager = new CakeEventManager(); - } + /** + * Returns the globally available instance of a CakeEventManager + * this is used for dispatching events attached from outside the scope + * other managers were created. Usually for creating hook systems or inter-class + * communication + * + * If called with the first parameter, it will be set as the globally available instance + * + * @param CakeEventManager $manager Optional event manager instance. + * @return CakeEventManager the global event manager + */ + public static function instance($manager = null) { + if ($manager instanceof CakeEventManager) { + static::$_generalManager = $manager; + } + if (empty(static::$_generalManager)) { + static::$_generalManager = new CakeEventManager(); + } - static::$_generalManager->_isGlobal = true; - return static::$_generalManager; - } + static::$_generalManager->_isGlobal = true; + return static::$_generalManager; + } -/** - * Adds a new listener to an event. Listeners - * - * @param callback|CakeEventListener $callable PHP valid callback type or instance of CakeEventListener to be called - * when the event named with $eventKey is triggered. If a CakeEventListener instance is passed, then the `implementedEvents` - * method will be called on the object to register the declared events individually as methods to be managed by this class. - * It is possible to define multiple event handlers per event name. - * - * @param string $eventKey The event unique identifier name with which the callback will be associated. If $callable - * is an instance of CakeEventListener this argument will be ignored - * - * @param array $options used to set the `priority` and `passParams` flags to the listener. - * Priorities are handled like queues, and multiple attachments added to the same priority queue will be treated in - * the order of insertion. `passParams` means that the event data property will be converted to function arguments - * when the listener is called. If $called is an instance of CakeEventListener, this parameter will be ignored - * - * @return void - * @throws InvalidArgumentException When event key is missing or callable is not an - * instance of CakeEventListener. - */ - public function attach($callable, $eventKey = null, $options = array()) { - if (!$eventKey && !($callable instanceof CakeEventListener)) { - throw new InvalidArgumentException(__d('cake_dev', 'The eventKey variable is required')); - } - if ($callable instanceof CakeEventListener) { - $this->_attachSubscriber($callable); - return; - } - $options = $options + array('priority' => static::$defaultPriority, 'passParams' => false); - $this->_listeners[$eventKey][$options['priority']][] = array( - 'callable' => $callable, - 'passParams' => $options['passParams'], - ); - } + /** + * Adds a new listener to an event. Listeners + * + * @param callable|CakeEventListener $callable PHP valid callback type or instance of CakeEventListener to be called + * when the event named with $eventKey is triggered. If a CakeEventListener instance is passed, then the `implementedEvents` + * method will be called on the object to register the declared events individually as methods to be managed by this class. + * It is possible to define multiple event handlers per event name. + * + * @param string $eventKey The event unique identifier name with which the callback will be associated. If $callable + * is an instance of CakeEventListener this argument will be ignored + * + * @param array $options used to set the `priority` and `passParams` flags to the listener. + * Priorities are handled like queues, and multiple attachments added to the same priority queue will be treated in + * the order of insertion. `passParams` means that the event data property will be converted to function arguments + * when the listener is called. If $called is an instance of CakeEventListener, this parameter will be ignored + * + * @return void + * @throws InvalidArgumentException When event key is missing or callable is not an + * instance of CakeEventListener. + */ + public function attach($callable, $eventKey = null, $options = array()) { + if (!$eventKey && !($callable instanceof CakeEventListener)) { + throw new InvalidArgumentException(__d('cake_dev', 'The eventKey variable is required')); + } + if ($callable instanceof CakeEventListener) { + $this->_attachSubscriber($callable); + return; + } + $options = $options + array('priority' => static::$defaultPriority, 'passParams' => false); + $this->_listeners[$eventKey][$options['priority']][] = array( + 'callable' => $callable, + 'passParams' => $options['passParams'], + ); + } -/** - * Auxiliary function to attach all implemented callbacks of a CakeEventListener class instance - * as individual methods on this manager - * - * @param CakeEventListener $subscriber Event listener. - * @return void - */ - protected function _attachSubscriber(CakeEventListener $subscriber) { - foreach ((array)$subscriber->implementedEvents() as $eventKey => $function) { - $options = array(); - $method = $function; - if (is_array($function) && isset($function['callable'])) { - list($method, $options) = $this->_extractCallable($function, $subscriber); - } elseif (is_array($function) && is_numeric(key($function))) { - foreach ($function as $f) { - list($method, $options) = $this->_extractCallable($f, $subscriber); - $this->attach($method, $eventKey, $options); - } - continue; - } - if (is_string($method)) { - $method = array($subscriber, $function); - } - $this->attach($method, $eventKey, $options); - } - } + /** + * Auxiliary function to attach all implemented callbacks of a CakeEventListener class instance + * as individual methods on this manager + * + * @param CakeEventListener $subscriber Event listener. + * @return void + */ + protected function _attachSubscriber(CakeEventListener $subscriber) { + foreach ((array)$subscriber->implementedEvents() as $eventKey => $function) { + $options = array(); + $method = $function; + if (is_array($function) && isset($function['callable'])) { + list($method, $options) = $this->_extractCallable($function, $subscriber); + } elseif (is_array($function) && is_numeric(key($function))) { + foreach ($function as $f) { + list($method, $options) = $this->_extractCallable($f, $subscriber); + $this->attach($method, $eventKey, $options); + } + continue; + } + if (is_string($method)) { + $method = array($subscriber, $function); + } + $this->attach($method, $eventKey, $options); + } + } -/** - * Auxiliary function to extract and return a PHP callback type out of the callable definition - * from the return value of the `implementedEvents` method on a CakeEventListener - * - * @param array $function the array taken from a handler definition for an event - * @param CakeEventListener $object The handler object - * @return callback - */ - protected function _extractCallable($function, $object) { - $method = $function['callable']; - $options = $function; - unset($options['callable']); - if (is_string($method)) { - $method = array($object, $method); - } - return array($method, $options); - } + /** + * Auxiliary function to extract and return a PHP callback type out of the callable definition + * from the return value of the `implementedEvents` method on a CakeEventListener + * + * @param array $function the array taken from a handler definition for an event + * @param CakeEventListener $object The handler object + * @return callable + */ + protected function _extractCallable($function, $object) { + $method = $function['callable']; + $options = $function; + unset($options['callable']); + if (is_string($method)) { + $method = array($object, $method); + } + return array($method, $options); + } -/** - * Removes a listener from the active listeners. - * - * @param callback|CakeEventListener $callable any valid PHP callback type or an instance of CakeEventListener - * @param string $eventKey The event unique identifier name with which the callback has been associated - * @return void - */ - public function detach($callable, $eventKey = null) { - if ($callable instanceof CakeEventListener) { - return $this->_detachSubscriber($callable, $eventKey); - } - if (empty($eventKey)) { - foreach (array_keys($this->_listeners) as $eventKey) { - $this->detach($callable, $eventKey); - } - return; - } - if (empty($this->_listeners[$eventKey])) { - return; - } - foreach ($this->_listeners[$eventKey] as $priority => $callables) { - foreach ($callables as $k => $callback) { - if ($callback['callable'] === $callable) { - unset($this->_listeners[$eventKey][$priority][$k]); - break; - } - } - } - } + /** + * Removes a listener from the active listeners. + * + * @param callable|CakeEventListener $callable any valid PHP callback type or an instance of CakeEventListener + * @param string $eventKey The event unique identifier name with which the callback has been associated + * @return void + */ + public function detach($callable, $eventKey = null) { + if ($callable instanceof CakeEventListener) { + return $this->_detachSubscriber($callable, $eventKey); + } + if (empty($eventKey)) { + foreach (array_keys($this->_listeners) as $eventKey) { + $this->detach($callable, $eventKey); + } + return; + } + if (empty($this->_listeners[$eventKey])) { + return; + } + foreach ($this->_listeners[$eventKey] as $priority => $callables) { + foreach ($callables as $k => $callback) { + if ($callback['callable'] === $callable) { + unset($this->_listeners[$eventKey][$priority][$k]); + break; + } + } + } + } -/** - * Auxiliary function to help detach all listeners provided by an object implementing CakeEventListener - * - * @param CakeEventListener $subscriber the subscriber to be detached - * @param string $eventKey optional event key name to unsubscribe the listener from - * @return void - */ - protected function _detachSubscriber(CakeEventListener $subscriber, $eventKey = null) { - $events = (array)$subscriber->implementedEvents(); - if (!empty($eventKey) && empty($events[$eventKey])) { - return; - } elseif (!empty($eventKey)) { - $events = array($eventKey => $events[$eventKey]); - } - foreach ($events as $key => $function) { - if (is_array($function)) { - if (is_numeric(key($function))) { - foreach ($function as $handler) { - $handler = isset($handler['callable']) ? $handler['callable'] : $handler; - $this->detach(array($subscriber, $handler), $key); - } - continue; - } - $function = $function['callable']; - } - $this->detach(array($subscriber, $function), $key); - } - } + /** + * Auxiliary function to help detach all listeners provided by an object implementing CakeEventListener + * + * @param CakeEventListener $subscriber the subscriber to be detached + * @param string $eventKey optional event key name to unsubscribe the listener from + * @return void + */ + protected function _detachSubscriber(CakeEventListener $subscriber, $eventKey = null) { + $events = (array)$subscriber->implementedEvents(); + if (!empty($eventKey) && empty($events[$eventKey])) { + return; + } elseif (!empty($eventKey)) { + $events = array($eventKey => $events[$eventKey]); + } + foreach ($events as $key => $function) { + if (is_array($function)) { + if (is_numeric(key($function))) { + foreach ($function as $handler) { + $handler = isset($handler['callable']) ? $handler['callable'] : $handler; + $this->detach(array($subscriber, $handler), $key); + } + continue; + } + $function = $function['callable']; + } + $this->detach(array($subscriber, $function), $key); + } + } -/** - * Dispatches a new event to all configured listeners - * - * @param string|CakeEvent $event the event key name or instance of CakeEvent - * @return CakeEvent - * @triggers $event - */ - public function dispatch($event) { - if (is_string($event)) { - $event = new CakeEvent($event); - } + /** + * Dispatches a new event to all configured listeners + * + * @param string|CakeEvent $event the event key name or instance of CakeEvent + * @return CakeEvent + * @triggers $event + */ + public function dispatch($event) { + if (is_string($event)) { + $event = new CakeEvent($event); + } - $listeners = $this->listeners($event->name()); - if (empty($listeners)) { - return $event; - } + $listeners = $this->listeners($event->name()); + if (empty($listeners)) { + return $event; + } - foreach ($listeners as $listener) { - if ($event->isStopped()) { - break; - } - if ($listener['passParams'] === true) { - $result = call_user_func_array($listener['callable'], $event->data); - } else { - $result = call_user_func($listener['callable'], $event); - } - if ($result === false) { - $event->stopPropagation(); - } - if ($result !== null) { - $event->result = $result; - } - } - return $event; - } + foreach ($listeners as $listener) { + if ($event->isStopped()) { + break; + } + if ($listener['passParams'] === true) { + $result = call_user_func_array($listener['callable'], $event->data); + } else { + $result = call_user_func($listener['callable'], $event); + } + if ($result === false) { + $event->stopPropagation(); + } + if ($result !== null) { + $event->result = $result; + } + } + return $event; + } -/** - * Returns a list of all listeners for an eventKey in the order they should be called - * - * @param string $eventKey Event key. - * @return array - */ - public function listeners($eventKey) { - $localListeners = array(); - $priorities = array(); - if (!$this->_isGlobal) { - $localListeners = $this->prioritisedListeners($eventKey); - $localListeners = empty($localListeners) ? array() : $localListeners; - } - $globalListeners = static::instance()->prioritisedListeners($eventKey); - $globalListeners = empty($globalListeners) ? array() : $globalListeners; + /** + * Returns a list of all listeners for an eventKey in the order they should be called + * + * @param string $eventKey Event key. + * @return array + */ + public function listeners($eventKey) { + $localListeners = array(); + $priorities = array(); + if (!$this->_isGlobal) { + $localListeners = $this->prioritisedListeners($eventKey); + $localListeners = empty($localListeners) ? array() : $localListeners; + } + $globalListeners = static::instance()->prioritisedListeners($eventKey); + $globalListeners = empty($globalListeners) ? array() : $globalListeners; - $priorities = array_merge(array_keys($globalListeners), array_keys($localListeners)); - $priorities = array_unique($priorities); - asort($priorities); + $priorities = array_merge(array_keys($globalListeners), array_keys($localListeners)); + $priorities = array_unique($priorities); + asort($priorities); - $result = array(); - foreach ($priorities as $priority) { - if (isset($globalListeners[$priority])) { - $result = array_merge($result, $globalListeners[$priority]); - } - if (isset($localListeners[$priority])) { - $result = array_merge($result, $localListeners[$priority]); - } - } - return $result; - } + $result = array(); + foreach ($priorities as $priority) { + if (isset($globalListeners[$priority])) { + $result = array_merge($result, $globalListeners[$priority]); + } + if (isset($localListeners[$priority])) { + $result = array_merge($result, $localListeners[$priority]); + } + } + return $result; + } -/** - * Returns the listeners for the specified event key indexed by priority - * - * @param string $eventKey Event key. - * @return array - */ - public function prioritisedListeners($eventKey) { - if (empty($this->_listeners[$eventKey])) { - return array(); - } - return $this->_listeners[$eventKey]; - } -} + /** + * Returns the listeners for the specified event key indexed by priority + * + * @param string $eventKey Event key. + * @return array + */ + public function prioritisedListeners($eventKey) { + if (empty($this->_listeners[$eventKey])) { + return array(); + } + return $this->_listeners[$eventKey]; + } +} \ No newline at end of file diff --git a/lib/Cake/I18n/L10n.php b/lib/Cake/I18n/L10n.php index f8e28193..cdfdd747 100755 --- a/lib/Cake/I18n/L10n.php +++ b/lib/Cake/I18n/L10n.php @@ -25,483 +25,484 @@ */ class L10n { -/** - * The language for current locale - * - * @var string - */ - public $language = 'English (United States)'; + /** + * The language for current locale + * + * @var string + */ + public $language = 'English (United States)'; -/** - * Locale search paths - * - * @var array - */ - public $languagePath = array('en_us', 'eng'); + /** + * Locale search paths + * + * @var array + */ + public $languagePath = array('en_us', 'eng'); -/** - * ISO 639-3 for current locale - * - * @var string - */ - public $lang = 'eng'; + /** + * ISO 639-3 for current locale + * + * @var string + */ + public $lang = 'eng'; -/** - * Locale - * - * @var string - */ - public $locale = 'en_us'; + /** + * Locale + * + * @var string + */ + public $locale = 'en_us'; -/** - * Default language. - * - * If config value 'Config.language' is set in an application this will be set - * as a fall back else if DEFAULT_LANGUAGE it defined it will be used. - * Constant DEFAULT_LANGUAGE has been deprecated in 2.4 - * - * @var string - */ - public $default = null; + /** + * Default language. + * + * If config value 'Config.language' is set in an application this will be set + * as a fall back else if DEFAULT_LANGUAGE it defined it will be used. + * Constant DEFAULT_LANGUAGE has been deprecated in 2.4 + * + * @var string + */ + public $default = null; -/** - * Encoding used for current locale - * - * @var string - */ - public $charset = 'utf-8'; + /** + * Encoding used for current locale + * + * @var string + */ + public $charset = 'utf-8'; -/** - * Text direction for current locale - * - * @var string - */ - public $direction = 'ltr'; + /** + * Text direction for current locale + * + * @var string + */ + public $direction = 'ltr'; -/** - * Maps ISO 639-3 to I10n::_l10nCatalog - * The terminological codes (first one per language) should be used if possible. - * They are the ones building the path in `/APP/Locale/[code]/` - * The bibliographic codes are aliases. - * - * @var array - */ - protected $_l10nMap = array( - /* Afrikaans */ 'afr' => 'af', - /* Albanian */ 'sqi' => 'sq', - /* Albanian - bibliographic */ 'alb' => 'sq', - /* Arabic */ 'ara' => 'ar', - /* Armenian/Armenia */ 'hye' => 'hy', - /* Basque */ 'eus' => 'eu', - /* Basque */ 'baq' => 'eu', - /* Tibetan */ 'bod' => 'bo', - /* Tibetan - bibliographic */ 'tib' => 'bo', - /* Bosnian */ 'bos' => 'bs', - /* Bulgarian */ 'bul' => 'bg', - /* Byelorussian */ 'bel' => 'be', - /* Catalan */ 'cat' => 'ca', - /* Chinese */ 'zho' => 'zh', - /* Chinese - bibliographic */ 'chi' => 'zh', - /* Croatian */ 'hrv' => 'hr', - /* Czech */ 'ces' => 'cs', - /* Czech - bibliographic */ 'cze' => 'cs', - /* Danish */ 'dan' => 'da', - /* Dutch (Standard) */ 'nld' => 'nl', - /* Dutch (Standard) - bibliographic */ 'dut' => 'nl', - /* English */ 'eng' => 'en', - /* Estonian */ 'est' => 'et', - /* Faeroese */ 'fao' => 'fo', - /* Farsi/Persian */ 'fas' => 'fa', - /* Farsi/Persian - bibliographic */ 'per' => 'fa', - /* Finnish */ 'fin' => 'fi', - /* French (Standard) */ 'fra' => 'fr', - /* French (Standard) - bibliographic */ 'fre' => 'fr', - /* Gaelic (Scots) */ 'gla' => 'gd', - /* Galician */ 'glg' => 'gl', - /* German (Standard) */ 'deu' => 'de', - /* German (Standard) - bibliographic */ 'ger' => 'de', - /* Greek */ 'gre' => 'el', - /* Greek */ 'ell' => 'el', - /* Hebrew */ 'heb' => 'he', - /* Hindi */ 'hin' => 'hi', - /* Hungarian */ 'hun' => 'hu', - /* Icelandic */ 'isl' => 'is', - /* Icelandic - bibliographic */ 'ice' => 'is', - /* Indonesian */ 'ind' => 'id', - /* Irish */ 'gle' => 'ga', - /* Italian */ 'ita' => 'it', - /* Japanese */ 'jpn' => 'ja', - /* Kazakh */ 'kaz' => 'kk', - /* Kalaallisut (Greenlandic) */ 'kal' => 'kl', - /* Korean */ 'kor' => 'ko', - /* Latvian */ 'lav' => 'lv', - /* Limburgish */ 'lim' => 'li', - /* Lithuanian */ 'lit' => 'lt', - /* Luxembourgish */ 'ltz' => 'lb', - /* Macedonian */ 'mkd' => 'mk', - /* Macedonian - bibliographic */ 'mac' => 'mk', - /* Malaysian */ 'msa' => 'ms', - /* Malaysian - bibliographic */ 'may' => 'ms', - /* Maltese */ 'mlt' => 'mt', - /* Norwegian */ 'nor' => 'no', - /* Norwegian Bokmal */ 'nob' => 'nb', - /* Norwegian Nynorsk */ 'nno' => 'nn', - /* Polish */ 'pol' => 'pl', - /* Portuguese (Portugal) */ 'por' => 'pt', - /* Rhaeto-Romanic */ 'roh' => 'rm', - /* Romanian */ 'ron' => 'ro', - /* Romanian - bibliographic */ 'rum' => 'ro', - /* Russian */ 'rus' => 'ru', - /* Sami */ 'sme' => 'se', - /* Serbian */ 'srp' => 'sr', - /* Slovak */ 'slk' => 'sk', - /* Slovak - bibliographic */ 'slo' => 'sk', - /* Slovenian */ 'slv' => 'sl', - /* Sorbian */ 'wen' => 'sb', - /* Spanish (Spain - Traditional) */ 'spa' => 'es', - /* Swedish */ 'swe' => 'sv', - /* Thai */ 'tha' => 'th', - /* Tsonga */ 'tso' => 'ts', - /* Tswana */ 'tsn' => 'tn', - /* Turkish */ 'tur' => 'tr', - /* Ukrainian */ 'ukr' => 'uk', - /* Urdu */ 'urd' => 'ur', - /* Venda */ 'ven' => 've', - /* Vietnamese */ 'vie' => 'vi', - /* Welsh */ 'cym' => 'cy', - /* Welsh - bibliographic */ 'wel' => 'cy', - /* Xhosa */ 'xho' => 'xh', - /* Yiddish */ 'yid' => 'yi', - /* Zulu */ 'zul' => 'zu' - ); + /** + * Maps ISO 639-3 to I10n::_l10nCatalog + * The terminological codes (first one per language) should be used if possible. + * They are the ones building the path in `/APP/Locale/[code]/` + * The bibliographic codes are aliases. + * + * @var array + */ + protected $_l10nMap = array( + /* Afrikaans */ 'afr' => 'af', + /* Albanian */ 'sqi' => 'sq', + /* Albanian - bibliographic */ 'alb' => 'sq', + /* Arabic */ 'ara' => 'ar', + /* Armenian/Armenia */ 'hye' => 'hy', + /* Basque */ 'eus' => 'eu', + /* Basque */ 'baq' => 'eu', + /* Tibetan */ 'bod' => 'bo', + /* Tibetan - bibliographic */ 'tib' => 'bo', + /* Bosnian */ 'bos' => 'bs', + /* Bulgarian */ 'bul' => 'bg', + /* Byelorussian */ 'bel' => 'be', + /* Catalan */ 'cat' => 'ca', + /* Chinese */ 'zho' => 'zh', + /* Chinese - bibliographic */ 'chi' => 'zh', + /* Croatian */ 'hrv' => 'hr', + /* Czech */ 'ces' => 'cs', + /* Czech - bibliographic */ 'cze' => 'cs', + /* Danish */ 'dan' => 'da', + /* Dutch (Standard) */ 'nld' => 'nl', + /* Dutch (Standard) - bibliographic */ 'dut' => 'nl', + /* English */ 'eng' => 'en', + /* Estonian */ 'est' => 'et', + /* Faeroese */ 'fao' => 'fo', + /* Farsi/Persian */ 'fas' => 'fa', + /* Farsi/Persian - bibliographic */ 'per' => 'fa', + /* Finnish */ 'fin' => 'fi', + /* French (Standard) */ 'fra' => 'fr', + /* French (Standard) - bibliographic */ 'fre' => 'fr', + /* Gaelic (Scots) */ 'gla' => 'gd', + /* Galician */ 'glg' => 'gl', + /* German (Standard) */ 'deu' => 'de', + /* German (Standard) - bibliographic */ 'ger' => 'de', + /* Greek */ 'gre' => 'el', + /* Greek */ 'ell' => 'el', + /* Hebrew */ 'heb' => 'he', + /* Hindi */ 'hin' => 'hi', + /* Hungarian */ 'hun' => 'hu', + /* Icelandic */ 'isl' => 'is', + /* Icelandic - bibliographic */ 'ice' => 'is', + /* Indonesian */ 'ind' => 'id', + /* Irish */ 'gle' => 'ga', + /* Italian */ 'ita' => 'it', + /* Japanese */ 'jpn' => 'ja', + /* Kazakh */ 'kaz' => 'kk', + /* Kalaallisut (Greenlandic) */ 'kal' => 'kl', + /* Korean */ 'kor' => 'ko', + /* Latvian */ 'lav' => 'lv', + /* Limburgish */ 'lim' => 'li', + /* Lithuanian */ 'lit' => 'lt', + /* Luxembourgish */ 'ltz' => 'lb', + /* Macedonian */ 'mkd' => 'mk', + /* Macedonian - bibliographic */ 'mac' => 'mk', + /* Malaysian */ 'msa' => 'ms', + /* Malaysian - bibliographic */ 'may' => 'ms', + /* Maltese */ 'mlt' => 'mt', + /* Norwegian */ 'nor' => 'no', + /* Norwegian Bokmal */ 'nob' => 'nb', + /* Norwegian Nynorsk */ 'nno' => 'nn', + /* Polish */ 'pol' => 'pl', + /* Portuguese (Portugal) */ 'por' => 'pt', + /* Rhaeto-Romanic */ 'roh' => 'rm', + /* Romanian */ 'ron' => 'ro', + /* Romanian - bibliographic */ 'rum' => 'ro', + /* Russian */ 'rus' => 'ru', + /* Sami */ 'sme' => 'se', + /* Serbian */ 'srp' => 'sr', + /* Slovak */ 'slk' => 'sk', + /* Slovak - bibliographic */ 'slo' => 'sk', + /* Slovenian */ 'slv' => 'sl', + /* Sorbian */ 'wen' => 'sb', + /* Spanish (Spain - Traditional) */ 'spa' => 'es', + /* Swedish */ 'swe' => 'sv', + /* Thai */ 'tha' => 'th', + /* Tsonga */ 'tso' => 'ts', + /* Tswana */ 'tsn' => 'tn', + /* Turkish */ 'tur' => 'tr', + /* Ukrainian */ 'ukr' => 'uk', + /* Urdu */ 'urd' => 'ur', + /* Venda */ 'ven' => 've', + /* Vietnamese */ 'vie' => 'vi', + /* Welsh */ 'cym' => 'cy', + /* Welsh - bibliographic */ 'wel' => 'cy', + /* Xhosa */ 'xho' => 'xh', + /* Yiddish */ 'yid' => 'yi', + /* Zulu */ 'zul' => 'zu' + ); -/** - * HTTP_ACCEPT_LANGUAGE catalog - * - * holds all information related to a language - * - * @var array - */ - protected $_l10nCatalog = array( - 'af' => array('language' => 'Afrikaans', 'locale' => 'afr', 'localeFallback' => 'afr', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ar' => array('language' => 'Arabic', 'locale' => 'ara', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-ae' => array('language' => 'Arabic (U.A.E.)', 'locale' => 'ar_ae', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-bh' => array('language' => 'Arabic (Bahrain)', 'locale' => 'ar_bh', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-dz' => array('language' => 'Arabic (Algeria)', 'locale' => 'ar_dz', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-eg' => array('language' => 'Arabic (Egypt)', 'locale' => 'ar_eg', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-iq' => array('language' => 'Arabic (Iraq)', 'locale' => 'ar_iq', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-jo' => array('language' => 'Arabic (Jordan)', 'locale' => 'ar_jo', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-kw' => array('language' => 'Arabic (Kuwait)', 'locale' => 'ar_kw', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-lb' => array('language' => 'Arabic (Lebanon)', 'locale' => 'ar_lb', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-ly' => array('language' => 'Arabic (Libya)', 'locale' => 'ar_ly', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-ma' => array('language' => 'Arabic (Morocco)', 'locale' => 'ar_ma', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-om' => array('language' => 'Arabic (Oman)', 'locale' => 'ar_om', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-qa' => array('language' => 'Arabic (Qatar)', 'locale' => 'ar_qa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-sa' => array('language' => 'Arabic (Saudi Arabia)', 'locale' => 'ar_sa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-sy' => array('language' => 'Arabic (Syria)', 'locale' => 'ar_sy', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-tn' => array('language' => 'Arabic (Tunisia)', 'locale' => 'ar_tn', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'ar-ye' => array('language' => 'Arabic (Yemen)', 'locale' => 'ar_ye', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'be' => array('language' => 'Byelorussian', 'locale' => 'bel', 'localeFallback' => 'bel', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'bg' => array('language' => 'Bulgarian', 'locale' => 'bul', 'localeFallback' => 'bul', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'bo' => array('language' => 'Tibetan', 'locale' => 'bod', 'localeFallback' => 'bod', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'bo-cn' => array('language' => 'Tibetan (China)', 'locale' => 'bo_cn', 'localeFallback' => 'bod', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'bo-in' => array('language' => 'Tibetan (India)', 'locale' => 'bo_in', 'localeFallback' => 'bod', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'bs' => array('language' => 'Bosnian', 'locale' => 'bos', 'localeFallback' => 'bos', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ca' => array('language' => 'Catalan', 'locale' => 'cat', 'localeFallback' => 'cat', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'cs' => array('language' => 'Czech', 'locale' => 'ces', 'localeFallback' => 'ces', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'da' => array('language' => 'Danish', 'locale' => 'dan', 'localeFallback' => 'dan', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'da-dk' => array('language' => 'Danish (Denmark)', 'locale' => 'da_dk', 'localeFallback' => 'dan', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'de' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'de-at' => array('language' => 'German (Austria)', 'locale' => 'de_at', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'de-ch' => array('language' => 'German (Swiss)', 'locale' => 'de_ch', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'de-de' => array('language' => 'German (Germany)', 'locale' => 'de_de', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'de-li' => array('language' => 'German (Liechtenstein)', 'locale' => 'de_li', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'de-lu' => array('language' => 'German (Luxembourg)', 'locale' => 'de_lu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'el' => array('language' => 'Greek', 'locale' => 'ell', 'localeFallback' => 'ell', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-au' => array('language' => 'English (Australian)', 'locale' => 'en_au', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-bz' => array('language' => 'English (Belize)', 'locale' => 'en_bz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-ca' => array('language' => 'English (Canadian)', 'locale' => 'en_ca', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-gb' => array('language' => 'English (British)', 'locale' => 'en_gb', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-ie' => array('language' => 'English (Ireland)', 'locale' => 'en_ie', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-jm' => array('language' => 'English (Jamaica)', 'locale' => 'en_jm', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-nz' => array('language' => 'English (New Zealand)', 'locale' => 'en_nz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-tt' => array('language' => 'English (Trinidad)', 'locale' => 'en_tt', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-us' => array('language' => 'English (United States)', 'locale' => 'en_us', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'en-za' => array('language' => 'English (South Africa)', 'locale' => 'en_za', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es' => array('language' => 'Spanish (Spain - Traditional)', 'locale' => 'spa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-ar' => array('language' => 'Spanish (Argentina)', 'locale' => 'es_ar', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-bo' => array('language' => 'Spanish (Bolivia)', 'locale' => 'es_bo', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-cl' => array('language' => 'Spanish (Chile)', 'locale' => 'es_cl', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-co' => array('language' => 'Spanish (Colombia)', 'locale' => 'es_co', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-cr' => array('language' => 'Spanish (Costa Rica)', 'locale' => 'es_cr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-do' => array('language' => 'Spanish (Dominican Republic)', 'locale' => 'es_do', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-ec' => array('language' => 'Spanish (Ecuador)', 'locale' => 'es_ec', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-es' => array('language' => 'Spanish (Spain)', 'locale' => 'es_es', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-gt' => array('language' => 'Spanish (Guatemala)', 'locale' => 'es_gt', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-hn' => array('language' => 'Spanish (Honduras)', 'locale' => 'es_hn', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-mx' => array('language' => 'Spanish (Mexican)', 'locale' => 'es_mx', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-ni' => array('language' => 'Spanish (Nicaragua)', 'locale' => 'es_ni', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-pa' => array('language' => 'Spanish (Panama)', 'locale' => 'es_pa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-pe' => array('language' => 'Spanish (Peru)', 'locale' => 'es_pe', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-pr' => array('language' => 'Spanish (Puerto Rico)', 'locale' => 'es_pr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-py' => array('language' => 'Spanish (Paraguay)', 'locale' => 'es_py', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-sv' => array('language' => 'Spanish (El Salvador)', 'locale' => 'es_sv', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-uy' => array('language' => 'Spanish (Uruguay)', 'locale' => 'es_uy', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'es-ve' => array('language' => 'Spanish (Venezuela)', 'locale' => 'es_ve', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'et' => array('language' => 'Estonian', 'locale' => 'est', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'et-ee' => array('language' => 'Estonian (Estonia)', 'locale' => 'et_ee', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'eu' => array('language' => 'Basque', 'locale' => 'eus', 'localeFallback' => 'eus', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fa' => array('language' => 'Farsi', 'locale' => 'fas', 'localeFallback' => 'fas', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'fi' => array('language' => 'Finnish', 'locale' => 'fin', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fi-fi' => array('language' => 'Finnish (Finland)', 'locale' => 'fi_fi', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fo' => array('language' => 'Faeroese', 'locale' => 'fao', 'localeFallback' => 'fao', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fr' => array('language' => 'French (Standard)', 'locale' => 'fra', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fr-be' => array('language' => 'French (Belgium)', 'locale' => 'fr_be', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fr-ca' => array('language' => 'French (Canadian)', 'locale' => 'fr_ca', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fr-ch' => array('language' => 'French (Swiss)', 'locale' => 'fr_ch', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fr-fr' => array('language' => 'French (France)', 'locale' => 'fr_fr', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'fr-lu' => array('language' => 'French (Luxembourg)', 'locale' => 'fr_lu', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ga' => array('language' => 'Irish', 'locale' => 'gle', 'localeFallback' => 'gle', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'gd' => array('language' => 'Gaelic (Scots)', 'locale' => 'gla', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'gd-ie' => array('language' => 'Gaelic (Irish)', 'locale' => 'gd_ie', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'gl' => array('language' => 'Galician', 'locale' => 'glg', 'localeFallback' => 'glg', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'he' => array('language' => 'Hebrew', 'locale' => 'heb', 'localeFallback' => 'heb', 'charset' => 'utf-8', 'direction' => 'rtl'), - 'hi' => array('language' => 'Hindi', 'locale' => 'hin', 'localeFallback' => 'hin', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'hr' => array('language' => 'Croatian', 'locale' => 'hrv', 'localeFallback' => 'hrv', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'hu' => array('language' => 'Hungarian', 'locale' => 'hun', 'localeFallback' => 'hun', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'hu-hu' => array('language' => 'Hungarian (Hungary)', 'locale' => 'hu_hu', 'localeFallback' => 'hun', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'hy' => array('language' => 'Armenian - Armenia', 'locale' => 'hye', 'localeFallback' => 'hye', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'id' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'is' => array('language' => 'Icelandic', 'locale' => 'isl', 'localeFallback' => 'isl', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'is-is' => array('language' => 'Icelandic (Iceland)', 'locale' => 'is_is', 'localeFallback' => 'isl', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'it' => array('language' => 'Italian', 'locale' => 'ita', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'it-ch' => array('language' => 'Italian (Swiss) ', 'locale' => 'it_ch', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ja' => array('language' => 'Japanese', 'locale' => 'jpn', 'localeFallback' => 'jpn', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'kk' => array('language' => 'Kazakh', 'locale' => 'kaz', 'localeFallback' => 'kaz', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'kl' => array('language' => 'Kalaallisut (Greenlandic)', 'locale' => 'kal', 'localeFallback' => 'kal', 'charset' => 'kl', 'direction' => 'ltr'), - 'kl-gl' => array('language' => 'Kalaallisut (Greenland)', 'locale' => 'kl_gl', 'localeFallback' => 'kal', 'charset' => 'kl', 'direction' => 'ltr'), - 'ko' => array('language' => 'Korean', 'locale' => 'kor', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), - 'ko-kp' => array('language' => 'Korea (North)', 'locale' => 'ko_kp', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), - 'ko-kr' => array('language' => 'Korea (South)', 'locale' => 'ko_kr', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), - 'koi8-r' => array('language' => 'Russian', 'locale' => 'koi8_r', 'localeFallback' => 'rus', 'charset' => 'koi8-r', 'direction' => 'ltr'), - 'lb' => array('language' => 'Luxembourgish', 'locale' => 'ltz', 'localeFallback' => 'ltz', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'li' => array('language' => 'Limburgish', 'locale' => 'lim', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'lt' => array('language' => 'Lithuanian', 'locale' => 'lit', 'localeFallback' => 'lit', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'lv' => array('language' => 'Latvian', 'locale' => 'lav', 'localeFallback' => 'lav', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'lv-lv' => array('language' => 'Latvian (Latvia)', 'locale' => 'lv_lv', 'localeFallback' => 'lav', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mkd', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'mk-mk' => array('language' => 'Macedonian', 'locale' => 'mk_mk', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ms' => array('language' => 'Malaysian', 'locale' => 'msa', 'localeFallback' => 'msa', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'mt' => array('language' => 'Maltese', 'locale' => 'mlt', 'localeFallback' => 'mlt', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'nb' => array('language' => 'Norwegian Bokmal', 'locale' => 'nob', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'nb-no' => array('language' => 'Norwegian Bokmål (Norway)', 'locale' => 'nb_no', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'nl' => array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'nl-be' => array('language' => 'Dutch (Belgium)', 'locale' => 'nl_be', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'nl-nl' => array('language' => 'Dutch (Netherlands)', 'locale' => 'nl_nl', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'nn' => array('language' => 'Norwegian Nynorsk', 'locale' => 'nno', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'nn-no' => array('language' => 'Norwegian Nynorsk (Norway)', 'locale' => 'nn_no', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'no' => array('language' => 'Norwegian', 'locale' => 'nor', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'pl' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'pl-pl' => array('language' => 'Polish (Poland)', 'locale' => 'pl_pl', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'pt' => array('language' => 'Portuguese (Portugal)', 'locale' => 'por', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'rm' => array('language' => 'Rhaeto-Romanic', 'locale' => 'roh', 'localeFallback' => 'roh', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ro' => array('language' => 'Romanian', 'locale' => 'ron', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ro-mo' => array('language' => 'Romanian (Moldavia)', 'locale' => 'ro_mo', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ro-ro' => array('language' => 'Romanian (Romania)', 'locale' => 'ro_ro', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ru' => array('language' => 'Russian', 'locale' => 'rus', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ru-mo' => array('language' => 'Russian (Moldavia)', 'locale' => 'ru_mo', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ru-ru' => array('language' => 'Russian (Russia)', 'locale' => 'ru_ru', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sb' => array('language' => 'Sorbian', 'locale' => 'wen', 'localeFallback' => 'wen', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sk' => array('language' => 'Slovak', 'locale' => 'slk', 'localeFallback' => 'slk', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sl' => array('language' => 'Slovenian', 'locale' => 'slv', 'localeFallback' => 'slv', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sq' => array('language' => 'Albanian', 'locale' => 'sqi', 'localeFallback' => 'sqi', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sr' => array('language' => 'Serbian', 'locale' => 'srp', 'localeFallback' => 'srp', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sv' => array('language' => 'Swedish', 'locale' => 'swe', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sv-se' => array('language' => 'Swedish (Sweden)', 'locale' => 'sv_se', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'sv-fi' => array('language' => 'Swedish (Finland)', 'locale' => 'sv_fi', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'se' => array('language' => 'Sami', 'locale' => 'sme', 'localeFallback' => 'sme', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'th' => array('language' => 'Thai', 'locale' => 'tha', 'localeFallback' => 'tha', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'tn' => array('language' => 'Tswana', 'locale' => 'tsn', 'localeFallback' => 'tsn', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'tr' => array('language' => 'Turkish', 'locale' => 'tur', 'localeFallback' => 'tur', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ts' => array('language' => 'Tsonga', 'locale' => 'tso', 'localeFallback' => 'tso', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'uk' => array('language' => 'Ukrainian', 'locale' => 'ukr', 'localeFallback' => 'ukr', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'ur' => array('language' => 'Urdu', 'locale' => 'urd', 'localeFallback' => 'urd', 'charset' => 'utf-8', 'direction' => 'rtl'), - 've' => array('language' => 'Venda', 'locale' => 'ven', 'localeFallback' => 'ven', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'vi' => array('language' => 'Vietnamese', 'locale' => 'vie', 'localeFallback' => 'vie', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'cy' => array('language' => 'Welsh', 'locale' => 'cym', 'localeFallback' => 'cym', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'xh' => array('language' => 'Xhosa', 'locale' => 'xho', 'localeFallback' => 'xho', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'yi' => array('language' => 'Yiddish', 'locale' => 'yid', 'localeFallback' => 'yid', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'zh' => array('language' => 'Chinese', 'locale' => 'zho', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'zh-cn' => array('language' => 'Chinese (PRC)', 'locale' => 'zh_cn', 'localeFallback' => 'zho', 'charset' => 'GB2312', 'direction' => 'ltr'), - 'zh-hk' => array('language' => 'Chinese (Hong Kong)', 'locale' => 'zh_hk', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'zh-sg' => array('language' => 'Chinese (Singapore)', 'locale' => 'zh_sg', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'zh-tw' => array('language' => 'Chinese (Taiwan)', 'locale' => 'zh_tw', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), - 'zu' => array('language' => 'Zulu', 'locale' => 'zul', 'localeFallback' => 'zul', 'charset' => 'utf-8', 'direction' => 'ltr') - ); + /** + * HTTP_ACCEPT_LANGUAGE catalog + * + * holds all information related to a language + * + * @var array + */ + protected $_l10nCatalog = array( + 'af' => array('language' => 'Afrikaans', 'locale' => 'afr', 'localeFallback' => 'afr', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ar' => array('language' => 'Arabic', 'locale' => 'ara', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ae' => array('language' => 'Arabic (U.A.E.)', 'locale' => 'ar_ae', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-bh' => array('language' => 'Arabic (Bahrain)', 'locale' => 'ar_bh', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-dz' => array('language' => 'Arabic (Algeria)', 'locale' => 'ar_dz', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-eg' => array('language' => 'Arabic (Egypt)', 'locale' => 'ar_eg', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-iq' => array('language' => 'Arabic (Iraq)', 'locale' => 'ar_iq', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-jo' => array('language' => 'Arabic (Jordan)', 'locale' => 'ar_jo', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-kw' => array('language' => 'Arabic (Kuwait)', 'locale' => 'ar_kw', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-lb' => array('language' => 'Arabic (Lebanon)', 'locale' => 'ar_lb', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ly' => array('language' => 'Arabic (Libya)', 'locale' => 'ar_ly', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ma' => array('language' => 'Arabic (Morocco)', 'locale' => 'ar_ma', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-om' => array('language' => 'Arabic (Oman)', 'locale' => 'ar_om', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-qa' => array('language' => 'Arabic (Qatar)', 'locale' => 'ar_qa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-sa' => array('language' => 'Arabic (Saudi Arabia)', 'locale' => 'ar_sa', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-sy' => array('language' => 'Arabic (Syria)', 'locale' => 'ar_sy', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-tn' => array('language' => 'Arabic (Tunisia)', 'locale' => 'ar_tn', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'ar-ye' => array('language' => 'Arabic (Yemen)', 'locale' => 'ar_ye', 'localeFallback' => 'ara', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'be' => array('language' => 'Byelorussian', 'locale' => 'bel', 'localeFallback' => 'bel', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'bg' => array('language' => 'Bulgarian', 'locale' => 'bul', 'localeFallback' => 'bul', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'bo' => array('language' => 'Tibetan', 'locale' => 'bod', 'localeFallback' => 'bod', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'bo-cn' => array('language' => 'Tibetan (China)', 'locale' => 'bo_cn', 'localeFallback' => 'bod', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'bo-in' => array('language' => 'Tibetan (India)', 'locale' => 'bo_in', 'localeFallback' => 'bod', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'bs' => array('language' => 'Bosnian', 'locale' => 'bos', 'localeFallback' => 'bos', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ca' => array('language' => 'Catalan', 'locale' => 'cat', 'localeFallback' => 'cat', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'cs' => array('language' => 'Czech', 'locale' => 'ces', 'localeFallback' => 'ces', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'da' => array('language' => 'Danish', 'locale' => 'dan', 'localeFallback' => 'dan', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'da-dk' => array('language' => 'Danish (Denmark)', 'locale' => 'da_dk', 'localeFallback' => 'dan', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de' => array('language' => 'German (Standard)', 'locale' => 'deu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-at' => array('language' => 'German (Austria)', 'locale' => 'de_at', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-ch' => array('language' => 'German (Swiss)', 'locale' => 'de_ch', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-de' => array('language' => 'German (Germany)', 'locale' => 'de_de', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-li' => array('language' => 'German (Liechtenstein)', 'locale' => 'de_li', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'de-lu' => array('language' => 'German (Luxembourg)', 'locale' => 'de_lu', 'localeFallback' => 'deu', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'el' => array('language' => 'Greek', 'locale' => 'ell', 'localeFallback' => 'ell', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en' => array('language' => 'English', 'locale' => 'eng', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-au' => array('language' => 'English (Australian)', 'locale' => 'en_au', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-bz' => array('language' => 'English (Belize)', 'locale' => 'en_bz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-ca' => array('language' => 'English (Canadian)', 'locale' => 'en_ca', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-gb' => array('language' => 'English (British)', 'locale' => 'en_gb', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-ie' => array('language' => 'English (Ireland)', 'locale' => 'en_ie', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-jm' => array('language' => 'English (Jamaica)', 'locale' => 'en_jm', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-nz' => array('language' => 'English (New Zealand)', 'locale' => 'en_nz', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-tt' => array('language' => 'English (Trinidad)', 'locale' => 'en_tt', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-us' => array('language' => 'English (United States)', 'locale' => 'en_us', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'en-za' => array('language' => 'English (South Africa)', 'locale' => 'en_za', 'localeFallback' => 'eng', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es' => array('language' => 'Spanish (Spain - Traditional)', 'locale' => 'spa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ar' => array('language' => 'Spanish (Argentina)', 'locale' => 'es_ar', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-bo' => array('language' => 'Spanish (Bolivia)', 'locale' => 'es_bo', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-cl' => array('language' => 'Spanish (Chile)', 'locale' => 'es_cl', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-co' => array('language' => 'Spanish (Colombia)', 'locale' => 'es_co', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-cr' => array('language' => 'Spanish (Costa Rica)', 'locale' => 'es_cr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-do' => array('language' => 'Spanish (Dominican Republic)', 'locale' => 'es_do', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ec' => array('language' => 'Spanish (Ecuador)', 'locale' => 'es_ec', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-es' => array('language' => 'Spanish (Spain)', 'locale' => 'es_es', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-gt' => array('language' => 'Spanish (Guatemala)', 'locale' => 'es_gt', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-hn' => array('language' => 'Spanish (Honduras)', 'locale' => 'es_hn', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-mx' => array('language' => 'Spanish (Mexican)', 'locale' => 'es_mx', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ni' => array('language' => 'Spanish (Nicaragua)', 'locale' => 'es_ni', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-pa' => array('language' => 'Spanish (Panama)', 'locale' => 'es_pa', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-pe' => array('language' => 'Spanish (Peru)', 'locale' => 'es_pe', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-pr' => array('language' => 'Spanish (Puerto Rico)', 'locale' => 'es_pr', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-py' => array('language' => 'Spanish (Paraguay)', 'locale' => 'es_py', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-sv' => array('language' => 'Spanish (El Salvador)', 'locale' => 'es_sv', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-uy' => array('language' => 'Spanish (Uruguay)', 'locale' => 'es_uy', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'es-ve' => array('language' => 'Spanish (Venezuela)', 'locale' => 'es_ve', 'localeFallback' => 'spa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'et' => array('language' => 'Estonian', 'locale' => 'est', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'et-ee' => array('language' => 'Estonian (Estonia)', 'locale' => 'et_ee', 'localeFallback' => 'est', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'eu' => array('language' => 'Basque', 'locale' => 'eus', 'localeFallback' => 'eus', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fa' => array('language' => 'Farsi', 'locale' => 'fas', 'localeFallback' => 'fas', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'fi' => array('language' => 'Finnish', 'locale' => 'fin', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fi-fi' => array('language' => 'Finnish (Finland)', 'locale' => 'fi_fi', 'localeFallback' => 'fin', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fo' => array('language' => 'Faeroese', 'locale' => 'fao', 'localeFallback' => 'fao', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fo-fo' => array('language' => 'Faeroese (Faroe Island)', 'locale' => 'fo_fo', 'localeFallback' => 'fao', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr' => array('language' => 'French (Standard)', 'locale' => 'fra', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-be' => array('language' => 'French (Belgium)', 'locale' => 'fr_be', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-ca' => array('language' => 'French (Canadian)', 'locale' => 'fr_ca', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-ch' => array('language' => 'French (Swiss)', 'locale' => 'fr_ch', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-fr' => array('language' => 'French (France)', 'locale' => 'fr_fr', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'fr-lu' => array('language' => 'French (Luxembourg)', 'locale' => 'fr_lu', 'localeFallback' => 'fra', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ga' => array('language' => 'Irish', 'locale' => 'gle', 'localeFallback' => 'gle', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'gd' => array('language' => 'Gaelic (Scots)', 'locale' => 'gla', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'gd-ie' => array('language' => 'Gaelic (Irish)', 'locale' => 'gd_ie', 'localeFallback' => 'gla', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'gl' => array('language' => 'Galician', 'locale' => 'glg', 'localeFallback' => 'glg', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'he' => array('language' => 'Hebrew', 'locale' => 'heb', 'localeFallback' => 'heb', 'charset' => 'utf-8', 'direction' => 'rtl'), + 'hi' => array('language' => 'Hindi', 'locale' => 'hin', 'localeFallback' => 'hin', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'hr' => array('language' => 'Croatian', 'locale' => 'hrv', 'localeFallback' => 'hrv', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'hu' => array('language' => 'Hungarian', 'locale' => 'hun', 'localeFallback' => 'hun', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'hu-hu' => array('language' => 'Hungarian (Hungary)', 'locale' => 'hu_hu', 'localeFallback' => 'hun', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'hy' => array('language' => 'Armenian - Armenia', 'locale' => 'hye', 'localeFallback' => 'hye', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'id' => array('language' => 'Indonesian', 'locale' => 'ind', 'localeFallback' => 'ind', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'is' => array('language' => 'Icelandic', 'locale' => 'isl', 'localeFallback' => 'isl', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'is-is' => array('language' => 'Icelandic (Iceland)', 'locale' => 'is_is', 'localeFallback' => 'isl', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'it' => array('language' => 'Italian', 'locale' => 'ita', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'it-ch' => array('language' => 'Italian (Swiss) ', 'locale' => 'it_ch', 'localeFallback' => 'ita', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ja' => array('language' => 'Japanese', 'locale' => 'jpn', 'localeFallback' => 'jpn', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'kk' => array('language' => 'Kazakh', 'locale' => 'kaz', 'localeFallback' => 'kaz', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'kl' => array('language' => 'Kalaallisut (Greenlandic)', 'locale' => 'kal', 'localeFallback' => 'kal', 'charset' => 'kl', 'direction' => 'ltr'), + 'kl-gl' => array('language' => 'Kalaallisut (Greenland)', 'locale' => 'kl_gl', 'localeFallback' => 'kal', 'charset' => 'kl', 'direction' => 'ltr'), + 'ko' => array('language' => 'Korean', 'locale' => 'kor', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), + 'ko-kp' => array('language' => 'Korea (North)', 'locale' => 'ko_kp', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), + 'ko-kr' => array('language' => 'Korea (South)', 'locale' => 'ko_kr', 'localeFallback' => 'kor', 'charset' => 'kr', 'direction' => 'ltr'), + 'koi8-r' => array('language' => 'Russian', 'locale' => 'koi8_r', 'localeFallback' => 'rus', 'charset' => 'koi8-r', 'direction' => 'ltr'), + 'lb' => array('language' => 'Luxembourgish', 'locale' => 'ltz', 'localeFallback' => 'ltz', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'li' => array('language' => 'Limburgish', 'locale' => 'lim', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'lt' => array('language' => 'Lithuanian', 'locale' => 'lit', 'localeFallback' => 'lit', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'lv' => array('language' => 'Latvian', 'locale' => 'lav', 'localeFallback' => 'lav', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'lv-lv' => array('language' => 'Latvian (Latvia)', 'locale' => 'lv_lv', 'localeFallback' => 'lav', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'mk' => array('language' => 'FYRO Macedonian', 'locale' => 'mkd', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'mk-mk' => array('language' => 'Macedonian', 'locale' => 'mk_mk', 'localeFallback' => 'mkd', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ms' => array('language' => 'Malaysian', 'locale' => 'msa', 'localeFallback' => 'msa', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'mt' => array('language' => 'Maltese', 'locale' => 'mlt', 'localeFallback' => 'mlt', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nb' => array('language' => 'Norwegian Bokmal', 'locale' => 'nob', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nb-no' => array('language' => 'Norwegian Bokmål (Norway)', 'locale' => 'nb_no', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nl' => array('language' => 'Dutch (Standard)', 'locale' => 'nld', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nl-be' => array('language' => 'Dutch (Belgium)', 'locale' => 'nl_be', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nl-nl' => array('language' => 'Dutch (Netherlands)', 'locale' => 'nl_nl', 'localeFallback' => 'nld', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nn' => array('language' => 'Norwegian Nynorsk', 'locale' => 'nno', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'nn-no' => array('language' => 'Norwegian Nynorsk (Norway)', 'locale' => 'nn_no', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'no' => array('language' => 'Norwegian', 'locale' => 'nor', 'localeFallback' => 'nor', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pl' => array('language' => 'Polish', 'locale' => 'pol', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pl-pl' => array('language' => 'Polish (Poland)', 'locale' => 'pl_pl', 'localeFallback' => 'pol', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pt' => array('language' => 'Portuguese (Portugal)', 'locale' => 'por', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'pt-br' => array('language' => 'Portuguese (Brazil)', 'locale' => 'pt_br', 'localeFallback' => 'por', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'rm' => array('language' => 'Rhaeto-Romanic', 'locale' => 'roh', 'localeFallback' => 'roh', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ro' => array('language' => 'Romanian', 'locale' => 'ron', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ro-mo' => array('language' => 'Romanian (Moldavia)', 'locale' => 'ro_mo', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ro-ro' => array('language' => 'Romanian (Romania)', 'locale' => 'ro_ro', 'localeFallback' => 'ron', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ru' => array('language' => 'Russian', 'locale' => 'rus', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ru-mo' => array('language' => 'Russian (Moldavia)', 'locale' => 'ru_mo', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ru-ru' => array('language' => 'Russian (Russia)', 'locale' => 'ru_ru', 'localeFallback' => 'rus', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sb' => array('language' => 'Sorbian', 'locale' => 'wen', 'localeFallback' => 'wen', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sk' => array('language' => 'Slovak', 'locale' => 'slk', 'localeFallback' => 'slk', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sl' => array('language' => 'Slovenian', 'locale' => 'slv', 'localeFallback' => 'slv', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sq' => array('language' => 'Albanian', 'locale' => 'sqi', 'localeFallback' => 'sqi', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sr' => array('language' => 'Serbian', 'locale' => 'srp', 'localeFallback' => 'srp', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sv' => array('language' => 'Swedish', 'locale' => 'swe', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sv-se' => array('language' => 'Swedish (Sweden)', 'locale' => 'sv_se', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'sv-fi' => array('language' => 'Swedish (Finland)', 'locale' => 'sv_fi', 'localeFallback' => 'swe', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'se' => array('language' => 'Sami', 'locale' => 'sme', 'localeFallback' => 'sme', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'th' => array('language' => 'Thai', 'locale' => 'tha', 'localeFallback' => 'tha', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'tn' => array('language' => 'Tswana', 'locale' => 'tsn', 'localeFallback' => 'tsn', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'tr' => array('language' => 'Turkish', 'locale' => 'tur', 'localeFallback' => 'tur', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ts' => array('language' => 'Tsonga', 'locale' => 'tso', 'localeFallback' => 'tso', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'uk' => array('language' => 'Ukrainian', 'locale' => 'ukr', 'localeFallback' => 'ukr', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'ur' => array('language' => 'Urdu', 'locale' => 'urd', 'localeFallback' => 'urd', 'charset' => 'utf-8', 'direction' => 'rtl'), + 've' => array('language' => 'Venda', 'locale' => 'ven', 'localeFallback' => 'ven', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'vi' => array('language' => 'Vietnamese', 'locale' => 'vie', 'localeFallback' => 'vie', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'cy' => array('language' => 'Welsh', 'locale' => 'cym', 'localeFallback' => 'cym', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'xh' => array('language' => 'Xhosa', 'locale' => 'xho', 'localeFallback' => 'xho', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'yi' => array('language' => 'Yiddish', 'locale' => 'yid', 'localeFallback' => 'yid', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh' => array('language' => 'Chinese', 'locale' => 'zho', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh-cn' => array('language' => 'Chinese (PRC)', 'locale' => 'zh_cn', 'localeFallback' => 'zho', 'charset' => 'GB2312', 'direction' => 'ltr'), + 'zh-hk' => array('language' => 'Chinese (Hong Kong)', 'locale' => 'zh_hk', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh-sg' => array('language' => 'Chinese (Singapore)', 'locale' => 'zh_sg', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zh-tw' => array('language' => 'Chinese (Taiwan)', 'locale' => 'zh_tw', 'localeFallback' => 'zho', 'charset' => 'utf-8', 'direction' => 'ltr'), + 'zu' => array('language' => 'Zulu', 'locale' => 'zul', 'localeFallback' => 'zul', 'charset' => 'utf-8', 'direction' => 'ltr') + ); -/** - * Class constructor - */ - public function __construct() { - if (defined('DEFAULT_LANGUAGE')) { - $this->default = DEFAULT_LANGUAGE; - } - $default = Configure::read('Config.language'); - if ($default) { - $this->default = $default; - } - } + /** + * Class constructor + */ + public function __construct() { + if (defined('DEFAULT_LANGUAGE')) { + $this->default = DEFAULT_LANGUAGE; + } + $default = Configure::read('Config.language'); + if ($default) { + $this->default = $default; + } + } -/** - * Gets the settings for $language. - * If $language is null it attempt to get settings from L10n::_autoLanguage(); if this fails - * the method will get the settings from L10n::_setLanguage(); - * - * @param string $language Language (if null will use DEFAULT_LANGUAGE if defined) - * @return mixed - */ - public function get($language = null) { - if ($language !== null) { - return $this->_setLanguage($language); - } + /** + * Gets the settings for $language. + * If $language is null it attempt to get settings from L10n::_autoLanguage(); if this fails + * the method will get the settings from L10n::_setLanguage(); + * + * @param string $language Language (if null will use DEFAULT_LANGUAGE if defined) + * @return mixed + */ + public function get($language = null) { + if ($language !== null) { + return $this->_setLanguage($language); + } - if (!$this->_autoLanguage()) { - $this->_setLanguage(); - } - return $this->lang; - } + if (!$this->_autoLanguage()) { + $this->_setLanguage(); + } + return $this->lang; + } -/** - * Sets the class vars to correct values for $language. - * If $language is null it will use the L10n::$default if defined - * - * @param string $language Language (if null will use L10n::$default if defined) - * @return mixed - */ - protected function _setLanguage($language = null) { - $catalog = false; - if ($language !== null) { - $catalog = $this->catalog($language); - } + /** + * Sets the class vars to correct values for $language. + * If $language is null it will use the L10n::$default if defined + * + * @param string $language Language (if null will use L10n::$default if defined) + * @return mixed + */ + protected function _setLanguage($language = null) { + $catalog = false; + if ($language !== null) { + $catalog = $this->catalog($language); + } - if (!$catalog && $this->default) { - $language = $this->default; - $catalog = $this->catalog($language); - } + if (!$catalog && $this->default) { + $language = $this->default; + $catalog = $this->catalog($language); + } - if ($catalog) { - $this->language = $catalog['language']; - $this->languagePath = array_unique(array( - $catalog['locale'], - $catalog['localeFallback'] - )); - $this->lang = $language; - $this->locale = $catalog['locale']; - $this->charset = $catalog['charset']; - $this->direction = $catalog['direction']; - } elseif ($language) { - $this->lang = $language; - $this->languagePath = array($language); - } + if ($catalog) { + $this->language = $catalog['language']; + $this->languagePath = array_unique(array( + $catalog['locale'], + $catalog['localeFallback'] + )); + $this->lang = $language; + $this->locale = $catalog['locale']; + $this->charset = $catalog['charset']; + $this->direction = $catalog['direction']; + } elseif ($language) { + $this->lang = $language; + $this->languagePath = array($language); + } - if ($this->default && $language !== $this->default) { - $catalog = $this->catalog($this->default); - $fallback = $catalog['localeFallback']; - if (!in_array($fallback, $this->languagePath)) { - $this->languagePath[] = $fallback; - } - } + if ($this->default && $language !== $this->default) { + $catalog = $this->catalog($this->default); + $fallback = $catalog['localeFallback']; + if (!in_array($fallback, $this->languagePath)) { + $this->languagePath[] = $fallback; + } + } - if (Configure::read('Config.language') === null) { - Configure::write('Config.language', $this->lang); - } + if (Configure::read('Config.language') === null) { + Configure::write('Config.language', $this->lang); + } - if ($language) { - return $language; - } - } + if ($language) { + return $language; + } + } -/** - * Attempts to find the locale settings based on the HTTP_ACCEPT_LANGUAGE variable - * - * @return bool Success - */ - protected function _autoLanguage() { - $_detectableLanguages = CakeRequest::acceptLanguage(); - foreach ($_detectableLanguages as $langKey) { - if (isset($this->_l10nCatalog[$langKey])) { - $this->_setLanguage($langKey); - return true; - } - if (strpos($langKey, '-') !== false) { - $langKey = substr($langKey, 0, 2); - if (isset($this->_l10nCatalog[$langKey])) { - $this->_setLanguage($langKey); - return true; - } - } - } - return false; - } + /** + * Attempts to find the locale settings based on the HTTP_ACCEPT_LANGUAGE variable + * + * @return bool Success + */ + protected function _autoLanguage() { + $_detectableLanguages = CakeRequest::acceptLanguage(); + foreach ($_detectableLanguages as $langKey) { + if (isset($this->_l10nCatalog[$langKey])) { + $this->_setLanguage($langKey); + return true; + } + if (strpos($langKey, '-') !== false) { + $langKey = substr($langKey, 0, 2); + if (isset($this->_l10nCatalog[$langKey])) { + $this->_setLanguage($langKey); + return true; + } + } + } + return false; + } -/** - * Attempts to find locale for language, or language for locale - * - * @param string|array $mixed 2/3 char string (language/locale), array of those strings, or null - * @return string|array|bool string language/locale, array of those values, whole map as an array, - * or false when language/locale doesn't exist - */ - public function map($mixed = null) { - if (is_array($mixed)) { - $result = array(); - foreach ($mixed as $_mixed) { - if ($_result = $this->map($_mixed)) { - $result[$_mixed] = $_result; - } - } - return $result; - } - if (is_string($mixed)) { - if (strlen($mixed) === 2 && in_array($mixed, $this->_l10nMap)) { - return array_search($mixed, $this->_l10nMap); - } - if (isset($this->_l10nMap[$mixed])) { - return $this->_l10nMap[$mixed]; - } - return false; - } - return $this->_l10nMap; - } + /** + * Attempts to find locale for language, or language for locale + * + * @param string|array $mixed 2/3 char string (language/locale), array of those strings, or null + * @return string|array|bool string language/locale, array of those values, whole map as an array, + * or false when language/locale doesn't exist + */ + public function map($mixed = null) { + if (is_array($mixed)) { + $result = array(); + foreach ($mixed as $_mixed) { + if ($_result = $this->map($_mixed)) { + $result[$_mixed] = $_result; + } + } + return $result; + } + if (is_string($mixed)) { + if (strlen($mixed) === 2 && in_array($mixed, $this->_l10nMap)) { + return array_search($mixed, $this->_l10nMap); + } + if (isset($this->_l10nMap[$mixed])) { + return $this->_l10nMap[$mixed]; + } + return false; + } + return $this->_l10nMap; + } -/** - * Attempts to find catalog record for requested language - * - * @param string|array $language string requested language, array of requested languages, or null for whole catalog - * @return array|bool array catalog record for requested language, array of catalog records, whole catalog, - * or false when language doesn't exist - */ - public function catalog($language = null) { - if (is_array($language)) { - $result = array(); - foreach ($language as $_language) { - if ($_result = $this->catalog($_language)) { - $result[$_language] = $_result; - } - } - return $result; - } - if (is_string($language)) { - if (isset($this->_l10nCatalog[$language])) { - return $this->_l10nCatalog[$language]; - } - if (isset($this->_l10nMap[$language]) && isset($this->_l10nCatalog[$this->_l10nMap[$language]])) { - return $this->_l10nCatalog[$this->_l10nMap[$language]]; - } - return false; - } - return $this->_l10nCatalog; - } + /** + * Attempts to find catalog record for requested language + * + * @param string|array $language string requested language, array of requested languages, or null for whole catalog + * @return array|bool array catalog record for requested language, array of catalog records, whole catalog, + * or false when language doesn't exist + */ + public function catalog($language = null) { + if (is_array($language)) { + $result = array(); + foreach ($language as $_language) { + if ($_result = $this->catalog($_language)) { + $result[$_language] = $_result; + } + } + return $result; + } + if (is_string($language)) { + if (isset($this->_l10nCatalog[$language])) { + return $this->_l10nCatalog[$language]; + } + if (isset($this->_l10nMap[$language]) && isset($this->_l10nCatalog[$this->_l10nMap[$language]])) { + return $this->_l10nCatalog[$this->_l10nMap[$language]]; + } + return false; + } + return $this->_l10nCatalog; + } -} +} \ No newline at end of file diff --git a/lib/Cake/Network/CakeRequest.php b/lib/Cake/Network/CakeRequest.php index ebc2138a..93cc3b9f 100755 --- a/lib/Cake/Network/CakeRequest.php +++ b/lib/Cake/Network/CakeRequest.php @@ -34,1122 +34,1133 @@ */ class CakeRequest implements ArrayAccess { -/** - * Array of parameters parsed from the URL. - * - * @var array - */ - public $params = array( - 'plugin' => null, - 'controller' => null, - 'action' => null, - 'named' => array(), - 'pass' => array(), - ); - -/** - * Array of POST data. Will contain form data as well as uploaded files. - * Inputs prefixed with 'data' will have the data prefix removed. If there is - * overlap between an input prefixed with data and one without, the 'data' prefixed - * value will take precedence. - * - * @var array - */ - public $data = array(); - -/** - * Array of querystring arguments - * - * @var array - */ - public $query = array(); - -/** - * The URL string used for the request. - * - * @var string - */ - public $url; - -/** - * Base URL path. - * - * @var string - */ - public $base = false; - -/** - * webroot path segment for the request. - * - * @var string - */ - public $webroot = '/'; - -/** - * The full address to the current request - * - * @var string - */ - public $here = null; - -/** - * The built in detectors used with `is()` can be modified with `addDetector()`. - * - * There are several ways to specify a detector, see CakeRequest::addDetector() for the - * various formats and ways to define detectors. - * - * @var array - */ - protected $_detectors = array( - 'get' => array('env' => 'REQUEST_METHOD', 'value' => 'GET'), - 'patch' => array('env' => 'REQUEST_METHOD', 'value' => 'PATCH'), - 'post' => array('env' => 'REQUEST_METHOD', 'value' => 'POST'), - 'put' => array('env' => 'REQUEST_METHOD', 'value' => 'PUT'), - 'delete' => array('env' => 'REQUEST_METHOD', 'value' => 'DELETE'), - 'head' => array('env' => 'REQUEST_METHOD', 'value' => 'HEAD'), - 'options' => array('env' => 'REQUEST_METHOD', 'value' => 'OPTIONS'), - 'ssl' => array('env' => 'HTTPS', 'value' => 1), - 'ajax' => array('env' => 'HTTP_X_REQUESTED_WITH', 'value' => 'XMLHttpRequest'), - 'flash' => array('env' => 'HTTP_USER_AGENT', 'pattern' => '/^(Shockwave|Adobe) Flash/'), - 'mobile' => array('env' => 'HTTP_USER_AGENT', 'options' => array( - 'Android', 'AvantGo', 'BB10', 'BlackBerry', 'DoCoMo', 'Fennec', 'iPod', 'iPhone', 'iPad', - 'J2ME', 'MIDP', 'NetFront', 'Nokia', 'Opera Mini', 'Opera Mobi', 'PalmOS', 'PalmSource', - 'portalmmm', 'Plucker', 'ReqwirelessWeb', 'SonyEricsson', 'Symbian', 'UP\\.Browser', - 'webOS', 'Windows CE', 'Windows Phone OS', 'Xiino' - )), - 'requested' => array('param' => 'requested', 'value' => 1), - 'json' => array('accept' => array('application/json'), 'param' => 'ext', 'value' => 'json'), - 'xml' => array('accept' => array('application/xml', 'text/xml'), 'param' => 'ext', 'value' => 'xml'), - ); - -/** - * Copy of php://input. Since this stream can only be read once in most SAPI's - * keep a copy of it so users don't need to know about that detail. - * - * @var string - */ - protected $_input = ''; - -/** - * Constructor - * - * @param string $url Trimmed URL string to use. Should not contain the application base path. - * @param bool $parseEnvironment Set to false to not auto parse the environment. ie. GET, POST and FILES. - */ - public function __construct($url = null, $parseEnvironment = true) { - $this->_base(); - if (empty($url)) { - $url = $this->_url(); - } - if ($url[0] === '/') { - $url = substr($url, 1); - } - $this->url = $url; - - if ($parseEnvironment) { - $this->_processPost(); - $this->_processGet(); - $this->_processFiles(); - } - $this->here = $this->base . '/' . $this->url; - } - -/** - * process the post data and set what is there into the object. - * processed data is available at `$this->data` - * - * Will merge POST vars prefixed with `data`, and ones without - * into a single array. Variables prefixed with `data` will overwrite those without. - * - * If you have mixed POST values be careful not to make any top level keys numeric - * containing arrays. Hash::merge() is used to merge data, and it has possibly - * unexpected behavior in this situation. - * - * @return void - */ - protected function _processPost() { - if ($_POST) { - $this->data = $_POST; - } elseif (($this->is('put') || $this->is('delete')) && - strpos($this->contentType(), 'application/x-www-form-urlencoded') === 0 - ) { - $data = $this->_readInput(); - parse_str($data, $this->data); - } - if (ini_get('magic_quotes_gpc') === '1') { - $this->data = stripslashes_deep($this->data); - } - - $override = null; - if (env('HTTP_X_HTTP_METHOD_OVERRIDE')) { - $this->data['_method'] = env('HTTP_X_HTTP_METHOD_OVERRIDE'); - $override = $this->data['_method']; - } - - $isArray = is_array($this->data); - if ($isArray && isset($this->data['_method'])) { - if (!empty($_SERVER)) { - $_SERVER['REQUEST_METHOD'] = $this->data['_method']; - } else { - $_ENV['REQUEST_METHOD'] = $this->data['_method']; - } - $override = $this->data['_method']; - unset($this->data['_method']); - } - - if ($override && !in_array($override, array('POST', 'PUT', 'PATCH', 'DELETE'))) { - $this->data = array(); - } - - if ($isArray && isset($this->data['data'])) { - $data = $this->data['data']; - if (count($this->data) <= 1) { - $this->data = $data; - } else { - unset($this->data['data']); - $this->data = Hash::merge($this->data, $data); - } - } - } - -/** - * Process the GET parameters and move things into the object. - * - * @return void - */ - protected function _processGet() { - if (ini_get('magic_quotes_gpc') === '1') { - $query = stripslashes_deep($_GET); - } else { - $query = $_GET; - } - - $unsetUrl = '/' . str_replace(array('.', ' '), '_', urldecode($this->url)); - unset($query[$unsetUrl]); - unset($query[$this->base . $unsetUrl]); - if (strpos($this->url, '?') !== false) { - list($this->url, $querystr) = explode('?', $this->url); - parse_str($querystr, $queryArgs); - $query += $queryArgs; - } - if (isset($this->params['url'])) { - $query = array_merge($this->params['url'], $query); - } - $this->query = $query; - } - -/** - * Get the request uri. Looks in PATH_INFO first, as this is the exact value we need prepared - * by PHP. Following that, REQUEST_URI, PHP_SELF, HTTP_X_REWRITE_URL and argv are checked in that order. - * Each of these server variables have the base path, and query strings stripped off - * - * @return string URI The CakePHP request path that is being accessed. - */ - protected function _url() { - $uri = ''; - if (!empty($_SERVER['PATH_INFO'])) { - return $_SERVER['PATH_INFO']; - } elseif (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '://') === false) { - $uri = $_SERVER['REQUEST_URI']; - } elseif (isset($_SERVER['REQUEST_URI'])) { - $qPosition = strpos($_SERVER['REQUEST_URI'], '?'); - if ($qPosition !== false && strpos($_SERVER['REQUEST_URI'], '://') > $qPosition) { - $uri = $_SERVER['REQUEST_URI']; - } else { - $uri = substr($_SERVER['REQUEST_URI'], strlen(Configure::read('App.fullBaseUrl'))); - } - } elseif (isset($_SERVER['PHP_SELF']) && isset($_SERVER['SCRIPT_NAME'])) { - $uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']); - } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { - $uri = $_SERVER['HTTP_X_REWRITE_URL']; - } elseif ($var = env('argv')) { - $uri = $var[0]; - } - - $base = $this->base; - - if (strlen($base) > 0 && strpos($uri, $base) === 0) { - $uri = substr($uri, strlen($base)); - } - if (strpos($uri, '?') !== false) { - list($uri) = explode('?', $uri, 2); - } - if (empty($uri) || $uri === '/' || $uri === '//' || $uri === '/index.php') { - $uri = '/'; - } - $endsWithIndex = '/webroot/index.php'; - $endsWithLength = strlen($endsWithIndex); - if (strlen($uri) >= $endsWithLength && - substr($uri, -$endsWithLength) === $endsWithIndex - ) { - $uri = '/'; - } - return $uri; - } - -/** - * Returns a base URL and sets the proper webroot - * - * If CakePHP is called with index.php in the URL even though - * URL Rewriting is activated (and thus not needed) it swallows - * the unnecessary part from $base to prevent issue #3318. - * - * @return string Base URL - */ - protected function _base() { - $dir = $webroot = null; - $config = Configure::read('App'); - extract($config); - - if (!isset($base)) { - $base = $this->base; - } - if ($base !== false) { - $this->webroot = $base . '/'; - return $this->base = $base; - } - - if (empty($baseUrl)) { - $base = dirname(env('PHP_SELF')); - // Clean up additional / which cause following code to fail.. - $base = preg_replace('#/+#', '/', $base); - - $indexPos = strpos($base, '/webroot/index.php'); - if ($indexPos !== false) { - $base = substr($base, 0, $indexPos) . '/webroot'; - } - if ($webroot === 'webroot' && $webroot === basename($base)) { - $base = dirname($base); - } - if ($dir === 'app' && $dir === basename($base)) { - $base = dirname($base); - } - - if ($base === DS || $base === '.') { - $base = ''; - } - $base = implode('/', array_map('rawurlencode', explode('/', $base))); - $this->webroot = $base . '/'; - - return $this->base = $base; - } - - $file = '/' . basename($baseUrl); - $base = dirname($baseUrl); - - if ($base === DS || $base === '.') { - $base = ''; - } - $this->webroot = $base . '/'; - - $docRoot = env('DOCUMENT_ROOT'); - $docRootContainsWebroot = strpos($docRoot, $dir . DS . $webroot); - - if (!empty($base) || !$docRootContainsWebroot) { - if (strpos($this->webroot, '/' . $dir . '/') === false) { - $this->webroot .= $dir . '/'; - } - if (strpos($this->webroot, '/' . $webroot . '/') === false) { - $this->webroot .= $webroot . '/'; - } - } - return $this->base = $base . $file; - } - -/** - * Process $_FILES and move things into the object. - * - * @return void - */ - protected function _processFiles() { - if (isset($_FILES) && is_array($_FILES)) { - foreach ($_FILES as $name => $data) { - if ($name !== 'data') { - $this->params['form'][$name] = $data; - } - } - } - - if (isset($_FILES['data'])) { - foreach ($_FILES['data'] as $key => $data) { - $this->_processFileData('', $data, $key); - } - } - } - -/** - * Recursively walks the FILES array restructuring the data - * into something sane and useable. - * - * @param string $path The dot separated path to insert $data into. - * @param array $data The data to traverse/insert. - * @param string $field The terminal field name, which is the top level key in $_FILES. - * @return void - */ - protected function _processFileData($path, $data, $field) { - foreach ($data as $key => $fields) { - $newPath = $key; - if (strlen($path) > 0) { - $newPath = $path . '.' . $key; - } - if (is_array($fields)) { - $this->_processFileData($newPath, $fields, $field); - } else { - $newPath .= '.' . $field; - $this->data = Hash::insert($this->data, $newPath, $fields); - } - } - } - -/** - * Get the content type used in this request. - * - * @return string - */ - public function contentType() { - $type = env('CONTENT_TYPE'); - if ($type) { - return $type; - } - return env('HTTP_CONTENT_TYPE'); - } - -/** - * Get the IP the client is using, or says they are using. - * - * @param bool $safe Use safe = false when you think the user might manipulate their HTTP_CLIENT_IP - * header. Setting $safe = false will also look at HTTP_X_FORWARDED_FOR - * @return string The client IP. - */ - public function clientIp($safe = true) { - if (!$safe && env('HTTP_X_FORWARDED_FOR')) { - $ipaddr = preg_replace('/(?:,.*)/', '', env('HTTP_X_FORWARDED_FOR')); - } elseif (!$safe && env('HTTP_CLIENT_IP')) { - $ipaddr = env('HTTP_CLIENT_IP'); - } else { - $ipaddr = env('REMOTE_ADDR'); - } - return trim($ipaddr); - } - -/** - * Returns the referer that referred this request. - * - * @param bool $local Attempt to return a local address. Local addresses do not contain hostnames. - * @return string The referring address for this request. - */ - public function referer($local = false) { - $ref = env('HTTP_REFERER'); - - $base = Configure::read('App.fullBaseUrl') . $this->webroot; - if (!empty($ref) && !empty($base)) { - if ($local && strpos($ref, $base) === 0) { - $ref = substr($ref, strlen($base)); - if (!strlen($ref) || strpos($ref, '//') === 0) { - $ref = '/'; - } - if ($ref[0] !== '/') { - $ref = '/' . $ref; - } - return $ref; - } elseif (!$local) { - return $ref; - } - } - return '/'; - } - -/** - * Missing method handler, handles wrapping older style isAjax() type methods - * - * @param string $name The method called - * @param array $params Array of parameters for the method call - * @return mixed - * @throws CakeException when an invalid method is called. - */ - public function __call($name, $params) { - if (strpos($name, 'is') === 0) { - $type = strtolower(substr($name, 2)); - return $this->is($type); - } - throw new CakeException(__d('cake_dev', 'Method %s does not exist', $name)); - } - -/** - * Magic get method allows access to parsed routing parameters directly on the object. - * - * Allows access to `$this->params['controller']` via `$this->controller` - * - * @param string $name The property being accessed. - * @return mixed Either the value of the parameter or null. - */ - public function __get($name) { - if (isset($this->params[$name])) { - return $this->params[$name]; - } - return null; - } - -/** - * Magic isset method allows isset/empty checks - * on routing parameters. - * - * @param string $name The property being accessed. - * @return bool Existence - */ - public function __isset($name) { - return isset($this->params[$name]); - } - -/** - * Check whether or not a Request is a certain type. - * - * Uses the built in detection rules as well as additional rules - * defined with CakeRequest::addDetector(). Any detector can be called - * as `is($type)` or `is$Type()`. - * - * @param string|array $type The type of request you want to check. If an array - * this method will return true if the request matches any type. - * @return bool Whether or not the request is the type you are checking. - */ - public function is($type) { - if (is_array($type)) { - $result = array_map(array($this, 'is'), $type); - return count(array_filter($result)) > 0; - } - $type = strtolower($type); - if (!isset($this->_detectors[$type])) { - return false; - } - $detect = $this->_detectors[$type]; - if (isset($detect['env']) && $this->_environmentDetector($detect)) { - return true; - } - if (isset($detect['header']) && $this->_headerDetector($detect)) { - return true; - } - if (isset($detect['accept']) && $this->_acceptHeaderDetector($detect)) { - return true; - } - if (isset($detect['param']) && $this->_paramDetector($detect)) { - return true; - } - if (isset($detect['callback']) && is_callable($detect['callback'])) { - return call_user_func($detect['callback'], $this); - } - return false; - } - -/** - * Detects if a URL extension is present. - * - * @param array $detect Detector options array. - * @return bool Whether or not the request is the type you are checking. - */ - protected function _extensionDetector($detect) { - if (is_string($detect['extension'])) { - $detect['extension'] = array($detect['extension']); - } - if (in_array($this->params['ext'], $detect['extension'])) { - return true; - } - return false; - } - -/** - * Detects if a specific accept header is present. - * - * @param array $detect Detector options array. - * @return bool Whether or not the request is the type you are checking. - */ - protected function _acceptHeaderDetector($detect) { - $acceptHeaders = explode(',', (string)env('HTTP_ACCEPT')); - foreach ($detect['accept'] as $header) { - if (in_array($header, $acceptHeaders)) { - return true; - } - } - return false; - } - -/** - * Detects if a specific header is present. - * - * @param array $detect Detector options array. - * @return bool Whether or not the request is the type you are checking. - */ - protected function _headerDetector($detect) { - foreach ($detect['header'] as $header => $value) { - $header = env('HTTP_' . strtoupper($header)); - if (!is_null($header)) { - if (!is_string($value) && !is_bool($value) && is_callable($value)) { - return call_user_func($value, $header); - } - return ($header === $value); - } - } - return false; - } - -/** - * Detects if a specific request parameter is present. - * - * @param array $detect Detector options array. - * @return bool Whether or not the request is the type you are checking. - */ - protected function _paramDetector($detect) { - $key = $detect['param']; - if (isset($detect['value'])) { - $value = $detect['value']; - return isset($this->params[$key]) ? $this->params[$key] == $value : false; - } - if (isset($detect['options'])) { - return isset($this->params[$key]) ? in_array($this->params[$key], $detect['options']) : false; - } - return false; - } - -/** - * Detects if a specific environment variable is present. - * - * @param array $detect Detector options array. - * @return bool Whether or not the request is the type you are checking. - */ - protected function _environmentDetector($detect) { - if (isset($detect['env'])) { - if (isset($detect['value'])) { - return env($detect['env']) == $detect['value']; - } - if (isset($detect['pattern'])) { - return (bool)preg_match($detect['pattern'], env($detect['env'])); - } - if (isset($detect['options'])) { - $pattern = '/' . implode('|', $detect['options']) . '/i'; - return (bool)preg_match($pattern, env($detect['env'])); - } - } - return false; - } - -/** - * Check that a request matches all the given types. - * - * Allows you to test multiple types and union the results. - * See CakeRequest::is() for how to add additional types and the - * built-in types. - * - * @param array $types The types to check. - * @return bool Success. - * @see CakeRequest::is() - */ - public function isAll(array $types) { - $result = array_filter(array_map(array($this, 'is'), $types)); - return count($result) === count($types); - } - -/** - * Add a new detector to the list of detectors that a request can use. - * There are several different formats and types of detectors that can be set. - * - * ### Environment value comparison - * - * An environment value comparison, compares a value fetched from `env()` to a known value - * the environment value is equality checked against the provided value. - * - * e.g `addDetector('post', array('env' => 'REQUEST_METHOD', 'value' => 'POST'))` - * - * ### Pattern value comparison - * - * Pattern value comparison allows you to compare a value fetched from `env()` to a regular expression. - * - * e.g `addDetector('iphone', array('env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'));` - * - * ### Option based comparison - * - * Option based comparisons use a list of options to create a regular expression. Subsequent calls - * to add an already defined options detector will merge the options. - * - * e.g `addDetector('mobile', array('env' => 'HTTP_USER_AGENT', 'options' => array('Fennec')));` - * - * ### Callback detectors - * - * Callback detectors allow you to provide a 'callback' type to handle the check. The callback will - * receive the request object as its only parameter. - * - * e.g `addDetector('custom', array('callback' => array('SomeClass', 'somemethod')));` - * - * ### Request parameter detectors - * - * Allows for custom detectors on the request parameters. - * - * e.g `addDetector('requested', array('param' => 'requested', 'value' => 1)` - * - * You can also make parameter detectors that accept multiple values - * using the `options` key. This is useful when you want to check - * if a request parameter is in a list of options. - * - * `addDetector('extension', array('param' => 'ext', 'options' => array('pdf', 'csv'))` - * - * @param string $name The name of the detector. - * @param array $options The options for the detector definition. See above. - * @return void - */ - public function addDetector($name, $options) { - $name = strtolower($name); - if (isset($this->_detectors[$name]) && isset($options['options'])) { - $options = Hash::merge($this->_detectors[$name], $options); - } - $this->_detectors[$name] = $options; - } - -/** - * Add parameters to the request's parsed parameter set. This will overwrite any existing parameters. - * This modifies the parameters available through `$request->params`. - * - * @param array $params Array of parameters to merge in - * @return self - */ - public function addParams($params) { - $this->params = array_merge($this->params, (array)$params); - return $this; - } - -/** - * Add paths to the requests' paths vars. This will overwrite any existing paths. - * Provides an easy way to modify, here, webroot and base. - * - * @param array $paths Array of paths to merge in - * @return self - */ - public function addPaths($paths) { - foreach (array('webroot', 'here', 'base') as $element) { - if (isset($paths[$element])) { - $this->{$element} = $paths[$element]; - } - } - return $this; - } - -/** - * Get the value of the current requests URL. Will include named parameters and querystring arguments. - * - * @param bool $base Include the base path, set to false to trim the base path off. - * @return string the current request URL including query string args. - */ - public function here($base = true) { - $url = $this->here; - if (!empty($this->query)) { - $url .= '?' . http_build_query($this->query, null, '&'); - } - if (!$base) { - $url = preg_replace('/^' . preg_quote($this->base, '/') . '/', '', $url, 1); - } - return $url; - } - -/** - * Read an HTTP header from the Request information. - * - * @param string $name Name of the header you want. - * @return mixed Either false on no header being set or the value of the header. - */ - public static function header($name) { - $httpName = 'HTTP_' . strtoupper(str_replace('-', '_', $name)); - if (isset($_SERVER[$httpName])) { - return $_SERVER[$httpName]; - } - // Use the provided value, in some configurations apache will - // pass Authorization with no prefix and in Titlecase. - if (isset($_SERVER[$name])) { - return $_SERVER[$name]; - } - return false; - } - -/** - * Get the HTTP method used for this request. - * There are a few ways to specify a method. - * - * - If your client supports it you can use native HTTP methods. - * - You can set the HTTP-X-Method-Override header. - * - You can submit an input with the name `_method` - * - * Any of these 3 approaches can be used to set the HTTP method used - * by CakePHP internally, and will effect the result of this method. - * - * @return string The name of the HTTP method used. - */ - public function method() { - return env('REQUEST_METHOD'); - } - -/** - * Get the host that the request was handled on. - * - * @param bool $trustProxy Whether or not to trust the proxy host. - * @return string - */ - public function host($trustProxy = false) { - if ($trustProxy) { - return env('HTTP_X_FORWARDED_HOST'); - } - return env('HTTP_HOST'); - } - -/** - * Get the domain name and include $tldLength segments of the tld. - * - * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld. - * While `example.co.uk` contains 2. - * @return string Domain name without subdomains. - */ - public function domain($tldLength = 1) { - $segments = explode('.', $this->host()); - $domain = array_slice($segments, -1 * ($tldLength + 1)); - return implode('.', $domain); - } - -/** - * Get the subdomains for a host. - * - * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld. - * While `example.co.uk` contains 2. - * @return array An array of subdomains. - */ - public function subdomains($tldLength = 1) { - $segments = explode('.', $this->host()); - return array_slice($segments, 0, -1 * ($tldLength + 1)); - } - -/** - * Find out which content types the client accepts or check if they accept a - * particular type of content. - * - * #### Get all types: - * - * `$this->request->accepts();` - * - * #### Check for a single type: - * - * `$this->request->accepts('application/json');` - * - * This method will order the returned content types by the preference values indicated - * by the client. - * - * @param string $type The content type to check for. Leave null to get all types a client accepts. - * @return mixed Either an array of all the types the client accepts or a boolean if they accept the - * provided type. - */ - public function accepts($type = null) { - $raw = $this->parseAccept(); - $accept = array(); - foreach ($raw as $types) { - $accept = array_merge($accept, $types); - } - if ($type === null) { - return $accept; - } - return in_array($type, $accept); - } - -/** - * Parse the HTTP_ACCEPT header and return a sorted array with content types - * as the keys, and pref values as the values. - * - * Generally you want to use CakeRequest::accept() to get a simple list - * of the accepted content types. - * - * @return array An array of prefValue => array(content/types) - */ - public function parseAccept() { - return $this->_parseAcceptWithQualifier($this->header('accept')); - } - -/** - * Get the languages accepted by the client, or check if a specific language is accepted. - * - * Get the list of accepted languages: - * - * ``` CakeRequest::acceptLanguage(); ``` - * - * Check if a specific language is accepted: - * - * ``` CakeRequest::acceptLanguage('es-es'); ``` - * - * @param string $language The language to test. - * @return mixed If a $language is provided, a boolean. Otherwise the array of accepted languages. - */ - public static function acceptLanguage($language = null) { - $raw = static::_parseAcceptWithQualifier(static::header('Accept-Language')); - $accept = array(); - foreach ($raw as $languages) { - foreach ($languages as &$lang) { - if (strpos($lang, '_')) { - $lang = str_replace('_', '-', $lang); - } - $lang = strtolower($lang); - } - $accept = array_merge($accept, $languages); - } - if ($language === null) { - return $accept; - } - return in_array(strtolower($language), $accept); - } - -/** - * Parse Accept* headers with qualifier options. - * - * Only qualifiers will be extracted, any other accept extensions will be - * discarded as they are not frequently used. - * - * @param string $header Header to parse. - * @return array - */ - protected static function _parseAcceptWithQualifier($header) { - $accept = array(); - $header = explode(',', $header); - foreach (array_filter($header) as $value) { - $prefValue = '1.0'; - $value = trim($value); - - $semiPos = strpos($value, ';'); - if ($semiPos !== false) { - $params = explode(';', $value); - $value = trim($params[0]); - foreach ($params as $param) { - $qPos = strpos($param, 'q='); - if ($qPos !== false) { - $prefValue = substr($param, $qPos + 2); - } - } - } - - if (!isset($accept[$prefValue])) { - $accept[$prefValue] = array(); - } - if ($prefValue) { - $accept[$prefValue][] = $value; - } - } - krsort($accept); - return $accept; - } - -/** - * Provides a read accessor for `$this->query`. Allows you - * to use a syntax similar to `CakeSession` for reading URL query data. - * - * @param string $name Query string variable name - * @return mixed The value being read - */ - public function query($name) { - return Hash::get($this->query, $name); - } - -/** - * Provides a read/write accessor for `$this->data`. Allows you - * to use a syntax similar to `CakeSession` for reading post data. - * - * ## Reading values. - * - * `$request->data('Post.title');` - * - * When reading values you will get `null` for keys/values that do not exist. - * - * ## Writing values - * - * `$request->data('Post.title', 'New post!');` - * - * You can write to any value, even paths/keys that do not exist, and the arrays - * will be created for you. - * - * @param string $name Dot separated name of the value to read/write, one or more args. - * @return mixed|self Either the value being read, or $this so you can chain consecutive writes. - */ - public function data($name) { - $args = func_get_args(); - if (count($args) === 2) { - $this->data = Hash::insert($this->data, $name, $args[1]); - return $this; - } - return Hash::get($this->data, $name); - } - -/** - * Safely access the values in $this->params. - * - * @param string $name The name of the parameter to get. - * @return mixed The value of the provided parameter. Will - * return false if the parameter doesn't exist or is falsey. - */ - public function param($name) { - $args = func_get_args(); - if (count($args) === 2) { - $this->params = Hash::insert($this->params, $name, $args[1]); - return $this; - } - if (!isset($this->params[$name])) { - return Hash::get($this->params, $name, false); - } - return $this->params[$name]; - } - -/** - * Read data from `php://input`. Useful when interacting with XML or JSON - * request body content. - * - * Getting input with a decoding function: - * - * `$this->request->input('json_decode');` - * - * Getting input using a decoding function, and additional params: - * - * `$this->request->input('Xml::build', array('return' => 'DOMDocument'));` - * - * Any additional parameters are applied to the callback in the order they are given. - * - * @param string $callback A decoding callback that will convert the string data to another - * representation. Leave empty to access the raw input data. You can also - * supply additional parameters for the decoding callback using var args, see above. - * @return mixed The decoded/processed request data. - */ - public function input($callback = null) { - $input = $this->_readInput(); - $args = func_get_args(); - if (!empty($args)) { - $callback = array_shift($args); - array_unshift($args, $input); - return call_user_func_array($callback, $args); - } - return $input; - } - -/** - * Modify data originally from `php://input`. Useful for altering json/xml data - * in middleware or DispatcherFilters before it gets to RequestHandlerComponent - * - * @param string $input A string to replace original parsed data from input() - * @return void - */ - public function setInput($input) { - $this->_input = $input; - } - -/** - * Allow only certain HTTP request methods. If the request method does not match - * a 405 error will be shown and the required "Allow" response header will be set. - * - * Example: - * - * $this->request->allowMethod('post', 'delete'); - * or - * $this->request->allowMethod(array('post', 'delete')); - * - * If the request would be GET, response header "Allow: POST, DELETE" will be set - * and a 405 error will be returned. - * - * @param string|array $methods Allowed HTTP request methods. - * @return bool true - * @throws MethodNotAllowedException - */ - public function allowMethod($methods) { - if (!is_array($methods)) { - $methods = func_get_args(); - } - foreach ($methods as $method) { - if ($this->is($method)) { - return true; - } - } - $allowed = strtoupper(implode(', ', $methods)); - $e = new MethodNotAllowedException(); - $e->responseHeader('Allow', $allowed); - throw $e; - } - -/** - * Alias of CakeRequest::allowMethod() for backwards compatibility. - * - * @param string|array $methods Allowed HTTP request methods. - * @return bool true - * @throws MethodNotAllowedException - * @see CakeRequest::allowMethod() - * @deprecated 3.0.0 Since 2.5, use CakeRequest::allowMethod() instead. - */ - public function onlyAllow($methods) { - if (!is_array($methods)) { - $methods = func_get_args(); - } - return $this->allowMethod($methods); - } - -/** - * Read data from php://input, mocked in tests. - * - * @return string contents of php://input - */ - protected function _readInput() { - if (empty($this->_input)) { - $fh = fopen('php://input', 'r'); - $content = stream_get_contents($fh); - fclose($fh); - $this->_input = $content; - } - return $this->_input; - } - -/** - * Array access read implementation - * - * @param string $name Name of the key being accessed. - * @return mixed - */ - public function offsetGet($name) { - if (isset($this->params[$name])) { - return $this->params[$name]; - } - if ($name === 'url') { - return $this->query; - } - if ($name === 'data') { - return $this->data; - } - return null; - } - -/** - * Array access write implementation - * - * @param string $name Name of the key being written - * @param mixed $value The value being written. - * @return void - */ - public function offsetSet($name, $value) { - $this->params[$name] = $value; - } - -/** - * Array access isset() implementation - * - * @param string $name thing to check. - * @return bool - */ - public function offsetExists($name) { - if ($name === 'url' || $name === 'data') { - return true; - } - return isset($this->params[$name]); - } - -/** - * Array access unset() implementation - * - * @param string $name Name to unset. - * @return void - */ - public function offsetUnset($name) { - unset($this->params[$name]); - } - -} + /** + * Array of parameters parsed from the URL. + * + * @var array + */ + public $params = array( + 'plugin' => null, + 'controller' => null, + 'action' => null, + 'named' => array(), + 'pass' => array(), + ); + + /** + * Array of POST data. Will contain form data as well as uploaded files. + * Inputs prefixed with 'data' will have the data prefix removed. If there is + * overlap between an input prefixed with data and one without, the 'data' prefixed + * value will take precedence. + * + * @var array + */ + public $data = array(); + + /** + * Array of querystring arguments + * + * @var array + */ + public $query = array(); + + /** + * The URL string used for the request. + * + * @var string + */ + public $url; + + /** + * Base URL path. + * + * @var string + */ + public $base = false; + + /** + * webroot path segment for the request. + * + * @var string + */ + public $webroot = '/'; + + /** + * The full address to the current request + * + * @var string + */ + public $here = null; + + /** + * The built in detectors used with `is()` can be modified with `addDetector()`. + * + * There are several ways to specify a detector, see CakeRequest::addDetector() for the + * various formats and ways to define detectors. + * + * @var array + */ + protected $_detectors = array( + 'get' => array('env' => 'REQUEST_METHOD', 'value' => 'GET'), + 'patch' => array('env' => 'REQUEST_METHOD', 'value' => 'PATCH'), + 'post' => array('env' => 'REQUEST_METHOD', 'value' => 'POST'), + 'put' => array('env' => 'REQUEST_METHOD', 'value' => 'PUT'), + 'delete' => array('env' => 'REQUEST_METHOD', 'value' => 'DELETE'), + 'head' => array('env' => 'REQUEST_METHOD', 'value' => 'HEAD'), + 'options' => array('env' => 'REQUEST_METHOD', 'value' => 'OPTIONS'), + 'ssl' => array('env' => 'HTTPS', 'value' => 1), + 'ajax' => array('env' => 'HTTP_X_REQUESTED_WITH', 'value' => 'XMLHttpRequest'), + 'flash' => array('env' => 'HTTP_USER_AGENT', 'pattern' => '/^(Shockwave|Adobe) Flash/'), + 'mobile' => array('env' => 'HTTP_USER_AGENT', 'options' => array( + 'Android', 'AvantGo', 'BB10', 'BlackBerry', 'DoCoMo', 'Fennec', 'iPod', 'iPhone', 'iPad', + 'J2ME', 'MIDP', 'NetFront', 'Nokia', 'Opera Mini', 'Opera Mobi', 'PalmOS', 'PalmSource', + 'portalmmm', 'Plucker', 'ReqwirelessWeb', 'SonyEricsson', 'Symbian', 'UP\\.Browser', + 'webOS', 'Windows CE', 'Windows Phone OS', 'Xiino' + )), + 'requested' => array('param' => 'requested', 'value' => 1), + 'json' => array('accept' => array('application/json'), 'param' => 'ext', 'value' => 'json'), + 'xml' => array('accept' => array('application/xml', 'text/xml'), 'param' => 'ext', 'value' => 'xml'), + ); + + /** + * Copy of php://input. Since this stream can only be read once in most SAPI's + * keep a copy of it so users don't need to know about that detail. + * + * @var string + */ + protected $_input = ''; + + /** + * Constructor + * + * @param string $url Trimmed URL string to use. Should not contain the application base path. + * @param bool $parseEnvironment Set to false to not auto parse the environment. ie. GET, POST and FILES. + */ + public function __construct($url = null, $parseEnvironment = true) { + $this->_base(); + if (empty($url)) { + $url = $this->_url(); + } + if ($url[0] === '/') { + $url = substr($url, 1); + } + $this->url = $url; + + if ($parseEnvironment) { + $this->_processPost(); + $this->_processGet(); + $this->_processFiles(); + } + $this->here = $this->base . '/' . $this->url; + } + + /** + * process the post data and set what is there into the object. + * processed data is available at `$this->data` + * + * Will merge POST vars prefixed with `data`, and ones without + * into a single array. Variables prefixed with `data` will overwrite those without. + * + * If you have mixed POST values be careful not to make any top level keys numeric + * containing arrays. Hash::merge() is used to merge data, and it has possibly + * unexpected behavior in this situation. + * + * @return void + */ + protected function _processPost() { + if ($_POST) { + $this->data = $_POST; + } elseif (($this->is('put') || $this->is('delete')) && + strpos($this->contentType(), 'application/x-www-form-urlencoded') === 0 + ) { + $data = $this->_readInput(); + parse_str($data, $this->data); + } + if (ini_get('magic_quotes_gpc') === '1') { + $this->data = stripslashes_deep($this->data); + } + + $override = null; + if (env('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $this->data['_method'] = env('HTTP_X_HTTP_METHOD_OVERRIDE'); + $override = $this->data['_method']; + } + + $isArray = is_array($this->data); + if ($isArray && isset($this->data['_method'])) { + if (!empty($_SERVER)) { + $_SERVER['REQUEST_METHOD'] = $this->data['_method']; + } else { + $_ENV['REQUEST_METHOD'] = $this->data['_method']; + } + $override = $this->data['_method']; + unset($this->data['_method']); + } + + if ($override && !in_array($override, array('POST', 'PUT', 'PATCH', 'DELETE'))) { + $this->data = array(); + } + + if ($isArray && isset($this->data['data'])) { + $data = $this->data['data']; + if (count($this->data) <= 1) { + $this->data = $data; + } else { + unset($this->data['data']); + $this->data = Hash::merge($this->data, $data); + } + } + } + + /** + * Process the GET parameters and move things into the object. + * + * @return void + */ + protected function _processGet() { + if (ini_get('magic_quotes_gpc') === '1') { + $query = stripslashes_deep($_GET); + } else { + $query = $_GET; + } + + $unsetUrl = '/' . str_replace(array('.', ' '), '_', urldecode($this->url)); + unset($query[$unsetUrl]); + unset($query[$this->base . $unsetUrl]); + if (strpos($this->url, '?') !== false) { + list($this->url, $querystr) = explode('?', $this->url); + parse_str($querystr, $queryArgs); + $query += $queryArgs; + } + if (isset($this->params['url'])) { + $query = array_merge($this->params['url'], $query); + } + $this->query = $query; + } + + /** + * Get the request uri. Looks in PATH_INFO first, as this is the exact value we need prepared + * by PHP. Following that, REQUEST_URI, PHP_SELF, HTTP_X_REWRITE_URL and argv are checked in that order. + * Each of these server variables have the base path, and query strings stripped off + * + * @return string URI The CakePHP request path that is being accessed. + */ + protected function _url() { + $uri = ''; + if (!empty($_SERVER['PATH_INFO'])) { + return $_SERVER['PATH_INFO']; + } elseif (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '://') === false) { + $uri = $_SERVER['REQUEST_URI']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $qPosition = strpos($_SERVER['REQUEST_URI'], '?'); + if ($qPosition !== false && strpos($_SERVER['REQUEST_URI'], '://') > $qPosition) { + $uri = $_SERVER['REQUEST_URI']; + } else { + $baseUrl = Configure::read('App.fullBaseUrl'); + if (substr($_SERVER['REQUEST_URI'], 0, strlen($baseUrl)) === $baseUrl) { + $uri = substr($_SERVER['REQUEST_URI'], strlen($baseUrl)); + } + } + } elseif (isset($_SERVER['PHP_SELF']) && isset($_SERVER['SCRIPT_NAME'])) { + $uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']); + } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { + $uri = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif ($var = env('argv')) { + $uri = $var[0]; + } + + $base = $this->base; + + if (strlen($base) > 0 && strpos($uri, $base) === 0) { + $uri = substr($uri, strlen($base)); + } + if (strpos($uri, '?') !== false) { + list($uri) = explode('?', $uri, 2); + } + if (empty($uri) || $uri === '/' || $uri === '//' || $uri === '/index.php') { + $uri = '/'; + } + $endsWithIndex = '/webroot/index.php'; + $endsWithLength = strlen($endsWithIndex); + if (strlen($uri) >= $endsWithLength && + substr($uri, -$endsWithLength) === $endsWithIndex + ) { + $uri = '/'; + } + return $uri; + } + + /** + * Returns a base URL and sets the proper webroot + * + * If CakePHP is called with index.php in the URL even though + * URL Rewriting is activated (and thus not needed) it swallows + * the unnecessary part from $base to prevent issue #3318. + * + * @return string Base URL + */ + protected function _base() { + $dir = $webroot = null; + $config = Configure::read('App'); + extract($config); + + if (!isset($base)) { + $base = $this->base; + } + if ($base !== false) { + $this->webroot = $base . '/'; + return $this->base = $base; + } + + if (empty($baseUrl)) { + $base = dirname(env('PHP_SELF')); + // Clean up additional / which cause following code to fail.. + $base = preg_replace('#/+#', '/', $base); + + $indexPos = strpos($base, '/webroot/index.php'); + if ($indexPos !== false) { + $base = substr($base, 0, $indexPos) . '/webroot'; + } + if ($webroot === 'webroot' && $webroot === basename($base)) { + $base = dirname($base); + } + if ($dir === 'app' && $dir === basename($base)) { + $base = dirname($base); + } + + if ($base === DS || $base === '.') { + $base = ''; + } + $base = implode('/', array_map('rawurlencode', explode('/', $base))); + $this->webroot = $base . '/'; + + return $this->base = $base; + } + + $file = '/' . basename($baseUrl); + $base = dirname($baseUrl); + + if ($base === DS || $base === '.') { + $base = ''; + } + $this->webroot = $base . '/'; + + $docRoot = env('DOCUMENT_ROOT'); + $docRootContainsWebroot = strpos($docRoot, $dir . DS . $webroot); + + if (!empty($base) || !$docRootContainsWebroot) { + if (strpos($this->webroot, '/' . $dir . '/') === false) { + $this->webroot .= $dir . '/'; + } + if (strpos($this->webroot, '/' . $webroot . '/') === false) { + $this->webroot .= $webroot . '/'; + } + } + return $this->base = $base . $file; + } + + /** + * Process $_FILES and move things into the object. + * + * @return void + */ + protected function _processFiles() { + if (isset($_FILES) && is_array($_FILES)) { + foreach ($_FILES as $name => $data) { + if ($name !== 'data') { + $this->params['form'][$name] = $data; + } + } + } + + if (isset($_FILES['data'])) { + foreach ($_FILES['data'] as $key => $data) { + $this->_processFileData('', $data, $key); + } + } + } + + /** + * Recursively walks the FILES array restructuring the data + * into something sane and useable. + * + * @param string $path The dot separated path to insert $data into. + * @param array $data The data to traverse/insert. + * @param string $field The terminal field name, which is the top level key in $_FILES. + * @return void + */ + protected function _processFileData($path, $data, $field) { + foreach ($data as $key => $fields) { + $newPath = $key; + if (strlen($path) > 0) { + $newPath = $path . '.' . $key; + } + if (is_array($fields)) { + $this->_processFileData($newPath, $fields, $field); + } else { + $newPath .= '.' . $field; + $this->data = Hash::insert($this->data, $newPath, $fields); + } + } + } + + /** + * Get the content type used in this request. + * + * @return string + */ + public function contentType() { + $type = env('CONTENT_TYPE'); + if ($type) { + return $type; + } + return env('HTTP_CONTENT_TYPE'); + } + + /** + * Get the IP the client is using, or says they are using. + * + * @param bool $safe Use safe = false when you think the user might manipulate their HTTP_CLIENT_IP + * header. Setting $safe = false will also look at HTTP_X_FORWARDED_FOR + * @return string The client IP. + */ + public function clientIp($safe = true) { + if (!$safe && env('HTTP_X_FORWARDED_FOR')) { + $ipaddr = preg_replace('/(?:,.*)/', '', env('HTTP_X_FORWARDED_FOR')); + } elseif (!$safe && env('HTTP_CLIENT_IP')) { + $ipaddr = env('HTTP_CLIENT_IP'); + } else { + $ipaddr = env('REMOTE_ADDR'); + } + return trim($ipaddr); + } + + /** + * Returns the referer that referred this request. + * + * @param bool $local Attempt to return a local address. Local addresses do not contain hostnames. + * @return string The referring address for this request. + */ + public function referer($local = false) { + $ref = env('HTTP_REFERER'); + + $base = Configure::read('App.fullBaseUrl') . $this->webroot; + if (!empty($ref) && !empty($base)) { + if ($local && strpos($ref, $base) === 0) { + $ref = substr($ref, strlen($base)); + if (!strlen($ref) || strpos($ref, '//') === 0) { + $ref = '/'; + } + if ($ref[0] !== '/') { + $ref = '/' . $ref; + } + return $ref; + } elseif (!$local) { + return $ref; + } + } + return '/'; + } + + /** + * Missing method handler, handles wrapping older style isAjax() type methods + * + * @param string $name The method called + * @param array $params Array of parameters for the method call + * @return mixed + * @throws CakeException when an invalid method is called. + */ + public function __call($name, $params) { + if (strpos($name, 'is') === 0) { + $type = strtolower(substr($name, 2)); + return $this->is($type); + } + throw new CakeException(__d('cake_dev', 'Method %s does not exist', $name)); + } + + /** + * Magic get method allows access to parsed routing parameters directly on the object. + * + * Allows access to `$this->params['controller']` via `$this->controller` + * + * @param string $name The property being accessed. + * @return mixed Either the value of the parameter or null. + */ + public function __get($name) { + if (isset($this->params[$name])) { + return $this->params[$name]; + } + return null; + } + + /** + * Magic isset method allows isset/empty checks + * on routing parameters. + * + * @param string $name The property being accessed. + * @return bool Existence + */ + public function __isset($name) { + return isset($this->params[$name]); + } + + /** + * Check whether or not a Request is a certain type. + * + * Uses the built in detection rules as well as additional rules + * defined with CakeRequest::addDetector(). Any detector can be called + * as `is($type)` or `is$Type()`. + * + * @param string|string[] $type The type of request you want to check. If an array + * this method will return true if the request matches any type. + * @return bool Whether or not the request is the type you are checking. + */ + public function is($type) { + if (is_array($type)) { + foreach ($type as $_type) { + if ($this->is($_type)) { + return true; + } + } + return false; + } + $type = strtolower($type); + if (!isset($this->_detectors[$type])) { + return false; + } + $detect = $this->_detectors[$type]; + if (isset($detect['env']) && $this->_environmentDetector($detect)) { + return true; + } + if (isset($detect['header']) && $this->_headerDetector($detect)) { + return true; + } + if (isset($detect['accept']) && $this->_acceptHeaderDetector($detect)) { + return true; + } + if (isset($detect['param']) && $this->_paramDetector($detect)) { + return true; + } + if (isset($detect['callback']) && is_callable($detect['callback'])) { + return call_user_func($detect['callback'], $this); + } + return false; + } + + /** + * Detects if a URL extension is present. + * + * @param array $detect Detector options array. + * @return bool Whether or not the request is the type you are checking. + */ + protected function _extensionDetector($detect) { + if (is_string($detect['extension'])) { + $detect['extension'] = array($detect['extension']); + } + if (in_array($this->params['ext'], $detect['extension'])) { + return true; + } + return false; + } + + /** + * Detects if a specific accept header is present. + * + * @param array $detect Detector options array. + * @return bool Whether or not the request is the type you are checking. + */ + protected function _acceptHeaderDetector($detect) { + $acceptHeaders = explode(',', (string)env('HTTP_ACCEPT')); + foreach ($detect['accept'] as $header) { + if (in_array($header, $acceptHeaders)) { + return true; + } + } + return false; + } + + /** + * Detects if a specific header is present. + * + * @param array $detect Detector options array. + * @return bool Whether or not the request is the type you are checking. + */ + protected function _headerDetector($detect) { + foreach ($detect['header'] as $header => $value) { + $header = env('HTTP_' . strtoupper($header)); + if (!is_null($header)) { + if (!is_string($value) && !is_bool($value) && is_callable($value)) { + return call_user_func($value, $header); + } + return ($header === $value); + } + } + return false; + } + + /** + * Detects if a specific request parameter is present. + * + * @param array $detect Detector options array. + * @return bool Whether or not the request is the type you are checking. + */ + protected function _paramDetector($detect) { + $key = $detect['param']; + if (isset($detect['value'])) { + $value = $detect['value']; + return isset($this->params[$key]) ? $this->params[$key] == $value : false; + } + if (isset($detect['options'])) { + return isset($this->params[$key]) ? in_array($this->params[$key], $detect['options']) : false; + } + return false; + } + + /** + * Detects if a specific environment variable is present. + * + * @param array $detect Detector options array. + * @return bool Whether or not the request is the type you are checking. + */ + protected function _environmentDetector($detect) { + if (isset($detect['env'])) { + if (isset($detect['value'])) { + return env($detect['env']) == $detect['value']; + } + if (isset($detect['pattern'])) { + return (bool)preg_match($detect['pattern'], env($detect['env'])); + } + if (isset($detect['options'])) { + $pattern = '/' . implode('|', $detect['options']) . '/i'; + return (bool)preg_match($pattern, env($detect['env'])); + } + } + return false; + } + + /** + * Check that a request matches all the given types. + * + * Allows you to test multiple types and union the results. + * See CakeRequest::is() for how to add additional types and the + * built-in types. + * + * @param array $types The types to check. + * @return bool Success. + * @see CakeRequest::is() + */ + public function isAll(array $types) { + foreach ($types as $type) { + if (!$this->is($type)) { + return false; + } + } + return true; + } + + /** + * Add a new detector to the list of detectors that a request can use. + * There are several different formats and types of detectors that can be set. + * + * ### Environment value comparison + * + * An environment value comparison, compares a value fetched from `env()` to a known value + * the environment value is equality checked against the provided value. + * + * e.g `addDetector('post', array('env' => 'REQUEST_METHOD', 'value' => 'POST'))` + * + * ### Pattern value comparison + * + * Pattern value comparison allows you to compare a value fetched from `env()` to a regular expression. + * + * e.g `addDetector('iphone', array('env' => 'HTTP_USER_AGENT', 'pattern' => '/iPhone/i'));` + * + * ### Option based comparison + * + * Option based comparisons use a list of options to create a regular expression. Subsequent calls + * to add an already defined options detector will merge the options. + * + * e.g `addDetector('mobile', array('env' => 'HTTP_USER_AGENT', 'options' => array('Fennec')));` + * + * ### Callback detectors + * + * Callback detectors allow you to provide a 'callback' type to handle the check. The callback will + * receive the request object as its only parameter. + * + * e.g `addDetector('custom', array('callback' => array('SomeClass', 'somemethod')));` + * + * ### Request parameter detectors + * + * Allows for custom detectors on the request parameters. + * + * e.g `addDetector('requested', array('param' => 'requested', 'value' => 1)` + * + * You can also make parameter detectors that accept multiple values + * using the `options` key. This is useful when you want to check + * if a request parameter is in a list of options. + * + * `addDetector('extension', array('param' => 'ext', 'options' => array('pdf', 'csv'))` + * + * @param string $name The name of the detector. + * @param array $options The options for the detector definition. See above. + * @return void + */ + public function addDetector($name, $options) { + $name = strtolower($name); + if (isset($this->_detectors[$name]) && isset($options['options'])) { + $options = Hash::merge($this->_detectors[$name], $options); + } + $this->_detectors[$name] = $options; + } + + /** + * Add parameters to the request's parsed parameter set. This will overwrite any existing parameters. + * This modifies the parameters available through `$request->params`. + * + * @param array $params Array of parameters to merge in + * @return self + */ + public function addParams($params) { + $this->params = array_merge($this->params, (array)$params); + return $this; + } + + /** + * Add paths to the requests' paths vars. This will overwrite any existing paths. + * Provides an easy way to modify, here, webroot and base. + * + * @param array $paths Array of paths to merge in + * @return self + */ + public function addPaths($paths) { + foreach (array('webroot', 'here', 'base') as $element) { + if (isset($paths[$element])) { + $this->{$element} = $paths[$element]; + } + } + return $this; + } + + /** + * Get the value of the current requests URL. Will include named parameters and querystring arguments. + * + * @param bool $base Include the base path, set to false to trim the base path off. + * @return string the current request URL including query string args. + */ + public function here($base = true) { + $url = $this->here; + if (!empty($this->query)) { + $url .= '?' . http_build_query($this->query, null, '&'); + } + if (!$base) { + $url = preg_replace('/^' . preg_quote($this->base, '/') . '/', '', $url, 1); + } + return $url; + } + + /** + * Read an HTTP header from the Request information. + * + * @param string $name Name of the header you want. + * @return mixed Either false on no header being set or the value of the header. + */ + public static function header($name) { + $httpName = 'HTTP_' . strtoupper(str_replace('-', '_', $name)); + if (isset($_SERVER[$httpName])) { + return $_SERVER[$httpName]; + } + // Use the provided value, in some configurations apache will + // pass Authorization with no prefix and in Titlecase. + if (isset($_SERVER[$name])) { + return $_SERVER[$name]; + } + return false; + } + + /** + * Get the HTTP method used for this request. + * There are a few ways to specify a method. + * + * - If your client supports it you can use native HTTP methods. + * - You can set the HTTP-X-Method-Override header. + * - You can submit an input with the name `_method` + * + * Any of these 3 approaches can be used to set the HTTP method used + * by CakePHP internally, and will effect the result of this method. + * + * @return string The name of the HTTP method used. + */ + public function method() { + return env('REQUEST_METHOD'); + } + + /** + * Get the host that the request was handled on. + * + * @param bool $trustProxy Whether or not to trust the proxy host. + * @return string + */ + public function host($trustProxy = false) { + if ($trustProxy) { + return env('HTTP_X_FORWARDED_HOST'); + } + return env('HTTP_HOST'); + } + + /** + * Get the domain name and include $tldLength segments of the tld. + * + * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld. + * While `example.co.uk` contains 2. + * @return string Domain name without subdomains. + */ + public function domain($tldLength = 1) { + $segments = explode('.', $this->host()); + $domain = array_slice($segments, -1 * ($tldLength + 1)); + return implode('.', $domain); + } + + /** + * Get the subdomains for a host. + * + * @param int $tldLength Number of segments your tld contains. For example: `example.com` contains 1 tld. + * While `example.co.uk` contains 2. + * @return array An array of subdomains. + */ + public function subdomains($tldLength = 1) { + $segments = explode('.', $this->host()); + return array_slice($segments, 0, -1 * ($tldLength + 1)); + } + + /** + * Find out which content types the client accepts or check if they accept a + * particular type of content. + * + * #### Get all types: + * + * `$this->request->accepts();` + * + * #### Check for a single type: + * + * `$this->request->accepts('application/json');` + * + * This method will order the returned content types by the preference values indicated + * by the client. + * + * @param string $type The content type to check for. Leave null to get all types a client accepts. + * @return mixed Either an array of all the types the client accepts or a boolean if they accept the + * provided type. + */ + public function accepts($type = null) { + $raw = $this->parseAccept(); + $accept = array(); + foreach ($raw as $types) { + $accept = array_merge($accept, $types); + } + if ($type === null) { + return $accept; + } + return in_array($type, $accept); + } + + /** + * Parse the HTTP_ACCEPT header and return a sorted array with content types + * as the keys, and pref values as the values. + * + * Generally you want to use CakeRequest::accept() to get a simple list + * of the accepted content types. + * + * @return array An array of prefValue => array(content/types) + */ + public function parseAccept() { + return $this->_parseAcceptWithQualifier($this->header('accept')); + } + + /** + * Get the languages accepted by the client, or check if a specific language is accepted. + * + * Get the list of accepted languages: + * + * ``` CakeRequest::acceptLanguage(); ``` + * + * Check if a specific language is accepted: + * + * ``` CakeRequest::acceptLanguage('es-es'); ``` + * + * @param string $language The language to test. + * @return mixed If a $language is provided, a boolean. Otherwise the array of accepted languages. + */ + public static function acceptLanguage($language = null) { + $raw = static::_parseAcceptWithQualifier(static::header('Accept-Language')); + $accept = array(); + foreach ($raw as $languages) { + foreach ($languages as &$lang) { + if (strpos($lang, '_')) { + $lang = str_replace('_', '-', $lang); + } + $lang = strtolower($lang); + } + $accept = array_merge($accept, $languages); + } + if ($language === null) { + return $accept; + } + return in_array(strtolower($language), $accept); + } + + /** + * Parse Accept* headers with qualifier options. + * + * Only qualifiers will be extracted, any other accept extensions will be + * discarded as they are not frequently used. + * + * @param string $header Header to parse. + * @return array + */ + protected static function _parseAcceptWithQualifier($header) { + $accept = array(); + $header = explode(',', $header); + foreach (array_filter($header) as $value) { + $prefValue = '1.0'; + $value = trim($value); + + $semiPos = strpos($value, ';'); + if ($semiPos !== false) { + $params = explode(';', $value); + $value = trim($params[0]); + foreach ($params as $param) { + $qPos = strpos($param, 'q='); + if ($qPos !== false) { + $prefValue = substr($param, $qPos + 2); + } + } + } + + if (!isset($accept[$prefValue])) { + $accept[$prefValue] = array(); + } + if ($prefValue) { + $accept[$prefValue][] = $value; + } + } + krsort($accept); + return $accept; + } + + /** + * Provides a read accessor for `$this->query`. Allows you + * to use a syntax similar to `CakeSession` for reading URL query data. + * + * @param string $name Query string variable name + * @return mixed The value being read + */ + public function query($name) { + return Hash::get($this->query, $name); + } + + /** + * Provides a read/write accessor for `$this->data`. Allows you + * to use a syntax similar to `CakeSession` for reading post data. + * + * ## Reading values. + * + * `$request->data('Post.title');` + * + * When reading values you will get `null` for keys/values that do not exist. + * + * ## Writing values + * + * `$request->data('Post.title', 'New post!');` + * + * You can write to any value, even paths/keys that do not exist, and the arrays + * will be created for you. + * + * @param string $name Dot separated name of the value to read/write, one or more args. + * @return mixed|self Either the value being read, or $this so you can chain consecutive writes. + */ + public function data($name) { + $args = func_get_args(); + if (count($args) === 2) { + $this->data = Hash::insert($this->data, $name, $args[1]); + return $this; + } + return Hash::get($this->data, $name); + } + + /** + * Safely access the values in $this->params. + * + * @param string $name The name of the parameter to get. + * @return mixed The value of the provided parameter. Will + * return false if the parameter doesn't exist or is falsey. + */ + public function param($name) { + $args = func_get_args(); + if (count($args) === 2) { + $this->params = Hash::insert($this->params, $name, $args[1]); + return $this; + } + if (!isset($this->params[$name])) { + return Hash::get($this->params, $name, false); + } + return $this->params[$name]; + } + + /** + * Read data from `php://input`. Useful when interacting with XML or JSON + * request body content. + * + * Getting input with a decoding function: + * + * `$this->request->input('json_decode');` + * + * Getting input using a decoding function, and additional params: + * + * `$this->request->input('Xml::build', array('return' => 'DOMDocument'));` + * + * Any additional parameters are applied to the callback in the order they are given. + * + * @param string $callback A decoding callback that will convert the string data to another + * representation. Leave empty to access the raw input data. You can also + * supply additional parameters for the decoding callback using var args, see above. + * @return mixed The decoded/processed request data. + */ + public function input($callback = null) { + $input = $this->_readInput(); + $args = func_get_args(); + if (!empty($args)) { + $callback = array_shift($args); + array_unshift($args, $input); + return call_user_func_array($callback, $args); + } + return $input; + } + + /** + * Modify data originally from `php://input`. Useful for altering json/xml data + * in middleware or DispatcherFilters before it gets to RequestHandlerComponent + * + * @param string $input A string to replace original parsed data from input() + * @return void + */ + public function setInput($input) { + $this->_input = $input; + } + + /** + * Allow only certain HTTP request methods. If the request method does not match + * a 405 error will be shown and the required "Allow" response header will be set. + * + * Example: + * + * $this->request->allowMethod('post', 'delete'); + * or + * $this->request->allowMethod(array('post', 'delete')); + * + * If the request would be GET, response header "Allow: POST, DELETE" will be set + * and a 405 error will be returned. + * + * @param string|array $methods Allowed HTTP request methods. + * @return bool true + * @throws MethodNotAllowedException + */ + public function allowMethod($methods) { + if (!is_array($methods)) { + $methods = func_get_args(); + } + foreach ($methods as $method) { + if ($this->is($method)) { + return true; + } + } + $allowed = strtoupper(implode(', ', $methods)); + $e = new MethodNotAllowedException(); + $e->responseHeader('Allow', $allowed); + throw $e; + } + + /** + * Alias of CakeRequest::allowMethod() for backwards compatibility. + * + * @param string|array $methods Allowed HTTP request methods. + * @return bool true + * @throws MethodNotAllowedException + * @see CakeRequest::allowMethod() + * @deprecated 3.0.0 Since 2.5, use CakeRequest::allowMethod() instead. + */ + public function onlyAllow($methods) { + if (!is_array($methods)) { + $methods = func_get_args(); + } + return $this->allowMethod($methods); + } + + /** + * Read data from php://input, mocked in tests. + * + * @return string contents of php://input + */ + protected function _readInput() { + if (empty($this->_input)) { + $fh = fopen('php://input', 'r'); + $content = stream_get_contents($fh); + fclose($fh); + $this->_input = $content; + } + return $this->_input; + } + + /** + * Array access read implementation + * + * @param string $name Name of the key being accessed. + * @return mixed + */ + public function offsetGet($name) { + if (isset($this->params[$name])) { + return $this->params[$name]; + } + if ($name === 'url') { + return $this->query; + } + if ($name === 'data') { + return $this->data; + } + return null; + } + + /** + * Array access write implementation + * + * @param string $name Name of the key being written + * @param mixed $value The value being written. + * @return void + */ + public function offsetSet($name, $value) { + $this->params[$name] = $value; + } + + /** + * Array access isset() implementation + * + * @param string $name thing to check. + * @return bool + */ + public function offsetExists($name) { + if ($name === 'url' || $name === 'data') { + return true; + } + return isset($this->params[$name]); + } + + /** + * Array access unset() implementation + * + * @param string $name Name to unset. + * @return void + */ + public function offsetUnset($name) { + unset($this->params[$name]); + } + +} \ No newline at end of file diff --git a/lib/Cake/Network/CakeSocket.php b/lib/Cake/Network/CakeSocket.php index a4c472fe..74799825 100755 --- a/lib/Cake/Network/CakeSocket.php +++ b/lib/Cake/Network/CakeSocket.php @@ -9,11 +9,11 @@ * For full copyright and license information, please see the LICENSE.txt * Redistributions of files must retain the above copyright notice. * - * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) - * @link https://cakephp.org CakePHP(tm) Project - * @package Cake.Network - * @since CakePHP(tm) v 1.2.0 - * @license https://opensource.org/licenses/mit-license.php MIT License + * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org) + * @link https://cakephp.org CakePHP(tm) Project + * @package Cake.Network + * @since CakePHP(tm) v 1.2.0 + * @license https://opensource.org/licenses/mit-license.php MIT License */ App::uses('Validation', 'Utility'); @@ -23,481 +23,495 @@ * * Core base class for network communication. * - * @package Cake.Network + * @package Cake.Network */ class CakeSocket { -/** - * CakeSocket description - * - * @var string - */ - public $description = 'Remote DataSource Network Socket Interface'; - -/** - * Base configuration settings for the socket connection - * - * @var array - */ - protected $_baseConfig = array( - 'persistent' => false, - 'host' => 'localhost', - 'protocol' => 'tcp', - 'port' => 80, - 'timeout' => 30, - 'cryptoType' => 'tls', - ); - -/** - * Configuration settings for the socket connection - * - * @var array - */ - public $config = array(); - -/** - * Reference to socket connection resource - * - * @var resource - */ - public $connection = null; - -/** - * This boolean contains the current state of the CakeSocket class - * - * @var bool - */ - public $connected = false; - -/** - * This variable contains an array with the last error number (num) and string (str) - * - * @var array - */ - public $lastError = array(); - -/** - * True if the socket stream is encrypted after a CakeSocket::enableCrypto() call - * - * @var bool - */ - public $encrypted = false; - -/** - * Contains all the encryption methods available - * - * @var array - */ - protected $_encryptMethods = array( - // @codingStandardsIgnoreStart - 'sslv2_client' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT, - 'sslv3_client' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT, - 'sslv23_client' => STREAM_CRYPTO_METHOD_SSLv23_CLIENT, - 'tls_client' => STREAM_CRYPTO_METHOD_TLS_CLIENT, - 'sslv2_server' => STREAM_CRYPTO_METHOD_SSLv2_SERVER, - 'sslv3_server' => STREAM_CRYPTO_METHOD_SSLv3_SERVER, - 'sslv23_server' => STREAM_CRYPTO_METHOD_SSLv23_SERVER, - 'tls_server' => STREAM_CRYPTO_METHOD_TLS_SERVER, - // @codingStandardsIgnoreEnd - ); - -/** - * Used to capture connection warnings which can happen when there are - * SSL errors for example. - * - * @var array - */ - protected $_connectionErrors = array(); - -/** - * Constructor. - * - * @param array $config Socket configuration, which will be merged with the base configuration - * @see CakeSocket::$_baseConfig - */ - public function __construct($config = array()) { - $this->config = array_merge($this->_baseConfig, $config); - - $this->_addTlsVersions(); - } - -/** - * Add TLS versions that are dependent on specific PHP versions. - * - * These TLS versions are not supported by older PHP versions, - * so we have to conditionally set them if they are supported. - * - * As of PHP5.6.6, STREAM_CRYPTO_METHOD_TLS_CLIENT does not include - * TLS1.1 or 1.2. If we have TLS1.2 support we need to update the method map. - * - * @see https://bugs.php.net/bug.php?id=69195 - * @see https://github.com/php/php-src/commit/10bc5fd4c4c8e1dd57bd911b086e9872a56300a0 - * @return void - */ - protected function _addTlsVersions() { - $conditionalCrypto = array( - 'tlsv1_1_client' => 'STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT', - 'tlsv1_2_client' => 'STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT', - 'tlsv1_1_server' => 'STREAM_CRYPTO_METHOD_TLSv1_1_SERVER', - 'tlsv1_2_server' => 'STREAM_CRYPTO_METHOD_TLSv1_2_SERVER' - ); - foreach ($conditionalCrypto as $key => $const) { - if (defined($const)) { - $this->_encryptMethods[$key] = constant($const); - } - } - - // @codingStandardsIgnoreStart - if (isset($this->_encryptMethods['tlsv1_2_client'])) { - $this->_encryptMethods['tls_client'] = STREAM_CRYPTO_METHOD_TLS_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; - } - if (isset($this->_encryptMethods['tlsv1_2_server'])) { - $this->_encryptMethods['tls_server'] = STREAM_CRYPTO_METHOD_TLS_SERVER | STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; - } - // @codingStandardsIgnoreEnd - } - -/** - * Connects the socket to the given host and port. - * - * @return bool Success - * @throws SocketException - */ - public function connect() { - if ($this->connection) { - $this->disconnect(); - } - - $hasProtocol = strpos($this->config['host'], '://') !== false; - if ($hasProtocol) { - list($this->config['protocol'], $this->config['host']) = explode('://', $this->config['host']); - } - $scheme = null; - if (!empty($this->config['protocol'])) { - $scheme = $this->config['protocol'] . '://'; - } - if (!empty($this->config['proxy'])) { - $scheme = 'tcp://'; - } - - $host = $this->config['host']; - if (isset($this->config['request']['uri']['host'])) { - $host = $this->config['request']['uri']['host']; - } - $this->_setSslContext($host); - - if (!empty($this->config['context'])) { - $context = stream_context_create($this->config['context']); - } else { - $context = stream_context_create(); - } - - $connectAs = STREAM_CLIENT_CONNECT; - if ($this->config['persistent']) { - $connectAs |= STREAM_CLIENT_PERSISTENT; - } - - set_error_handler(array($this, '_connectionErrorHandler')); - $this->connection = stream_socket_client( - $scheme . $this->config['host'] . ':' . $this->config['port'], - $errNum, - $errStr, - $this->config['timeout'], - $connectAs, - $context - ); - restore_error_handler(); - - if (!empty($errNum) || !empty($errStr)) { - $this->setLastError($errNum, $errStr); - throw new SocketException($errStr, $errNum); - } - - if (!$this->connection && $this->_connectionErrors) { - $message = implode("\n", $this->_connectionErrors); - throw new SocketException($message, E_WARNING); - } - - $this->connected = is_resource($this->connection); - if ($this->connected) { - stream_set_timeout($this->connection, $this->config['timeout']); - - if (!empty($this->config['request']) && - $this->config['request']['uri']['scheme'] === 'https' && - !empty($this->config['proxy']) - ) { - $req = array(); - $req[] = 'CONNECT ' . $this->config['request']['uri']['host'] . ':' . - $this->config['request']['uri']['port'] . ' HTTP/1.1'; - $req[] = 'Host: ' . $this->config['host']; - $req[] = 'User-Agent: php proxy'; - if (!empty($this->config['proxyauth'])) { - $req[] = 'Proxy-Authorization: ' . $this->config['proxyauth']; - } - - fwrite($this->connection, implode("\r\n", $req) . "\r\n\r\n"); - - while (!feof($this->connection)) { - $s = rtrim(fgets($this->connection, 4096)); - if (preg_match('/^$/', $s)) { - break; - } - } - - $this->enableCrypto($this->config['cryptoType'], 'client'); - } - } - return $this->connected; - } - -/** - * Configure the SSL context options. - * - * @param string $host The host name being connected to. - * @return void - */ - protected function _setSslContext($host) { - foreach ($this->config as $key => $value) { - if (substr($key, 0, 4) !== 'ssl_') { - continue; - } - $contextKey = substr($key, 4); - if (empty($this->config['context']['ssl'][$contextKey])) { - $this->config['context']['ssl'][$contextKey] = $value; - } - unset($this->config[$key]); - } - if (version_compare(PHP_VERSION, '5.3.2', '>=')) { - if (!isset($this->config['context']['ssl']['SNI_enabled'])) { - $this->config['context']['ssl']['SNI_enabled'] = true; - } - if (version_compare(PHP_VERSION, '5.6.0', '>=')) { - if (empty($this->config['context']['ssl']['peer_name'])) { - $this->config['context']['ssl']['peer_name'] = $host; - } - } else { - if (empty($this->config['context']['ssl']['SNI_server_name'])) { - $this->config['context']['ssl']['SNI_server_name'] = $host; - } - } - } - if (empty($this->config['context']['ssl']['cafile'])) { - $this->config['context']['ssl']['cafile'] = CAKE . 'Config' . DS . 'cacert.pem'; - } - if (!empty($this->config['context']['ssl']['verify_host'])) { - $this->config['context']['ssl']['CN_match'] = $host; - } - unset($this->config['context']['ssl']['verify_host']); - } - -/** - * socket_stream_client() does not populate errNum, or $errStr when there are - * connection errors, as in the case of SSL verification failure. - * - * Instead we need to handle those errors manually. - * - * @param int $code Code. - * @param string $message Message. - * @return void - */ - protected function _connectionErrorHandler($code, $message) { - $this->_connectionErrors[] = $message; - } - -/** - * Gets the connection context. - * - * @return null|array Null when there is no connection, an array when there is. - */ - public function context() { - if (!$this->connection) { - return null; - } - return stream_context_get_options($this->connection); - } - -/** - * Gets the host name of the current connection. - * - * @return string Host name - */ - public function host() { - if (Validation::ip($this->config['host'])) { - return gethostbyaddr($this->config['host']); - } - return gethostbyaddr($this->address()); - } - -/** - * Gets the IP address of the current connection. - * - * @return string IP address - */ - public function address() { - if (Validation::ip($this->config['host'])) { - return $this->config['host']; - } - return gethostbyname($this->config['host']); - } - -/** - * Gets all IP addresses associated with the current connection. - * - * @return array IP addresses - */ - public function addresses() { - if (Validation::ip($this->config['host'])) { - return array($this->config['host']); - } - return gethostbynamel($this->config['host']); - } - -/** - * Gets the last error as a string. - * - * @return string|null Last error - */ - public function lastError() { - if (!empty($this->lastError)) { - return $this->lastError['num'] . ': ' . $this->lastError['str']; - } - return null; - } - -/** - * Sets the last error. - * - * @param int $errNum Error code - * @param string $errStr Error string - * @return void - */ - public function setLastError($errNum, $errStr) { - $this->lastError = array('num' => $errNum, 'str' => $errStr); - } - -/** - * Writes data to the socket. - * - * @param string $data The data to write to the socket - * @return bool Success - */ - public function write($data) { - if (!$this->connected) { - if (!$this->connect()) { - return false; - } - } - $totalBytes = strlen($data); - for ($written = 0, $rv = 0; $written < $totalBytes; $written += $rv) { - $rv = fwrite($this->connection, substr($data, $written)); - if ($rv === false || $rv === 0) { - return $written; - } - } - return $written; - } - -/** - * Reads data from the socket. Returns false if no data is available or no connection could be - * established. - * - * @param int $length Optional buffer length to read; defaults to 1024 - * @return mixed Socket data - */ - public function read($length = 1024) { - if (!$this->connected) { - if (!$this->connect()) { - return false; - } - } - - if (!feof($this->connection)) { - $buffer = fread($this->connection, $length); - $info = stream_get_meta_data($this->connection); - if ($info['timed_out']) { - $this->setLastError(E_WARNING, __d('cake_dev', 'Connection timed out')); - return false; - } - return $buffer; - } - return false; - } - -/** - * Disconnects the socket from the current connection. - * - * @return bool Success - */ - public function disconnect() { - if (!is_resource($this->connection)) { - $this->connected = false; - return true; - } - $this->connected = !fclose($this->connection); - - if (!$this->connected) { - $this->connection = null; - } - return !$this->connected; - } - -/** - * Destructor, used to disconnect from current connection. - */ - public function __destruct() { - $this->disconnect(); - } - -/** - * Resets the state of this Socket instance to it's initial state (before CakeObject::__construct got executed) - * - * @param array $state Array with key and values to reset - * @return bool True on success - */ - public function reset($state = null) { - if (empty($state)) { - static $initalState = array(); - if (empty($initalState)) { - $initalState = get_class_vars(__CLASS__); - } - $state = $initalState; - } - - foreach ($state as $property => $value) { - $this->{$property} = $value; - } - return true; - } - -/** - * Encrypts current stream socket, using one of the defined encryption methods. - * - * @param string $type Type which can be one of 'sslv2', 'sslv3', 'sslv23', 'tls', 'tlsv1_1' or 'tlsv1_2'. - * @param string $clientOrServer Can be one of 'client', 'server'. Default is 'client'. - * @param bool $enable Enable or disable encryption. Default is true (enable) - * @return bool True on success - * @throws InvalidArgumentException When an invalid encryption scheme is chosen. - * @throws SocketException When attempting to enable SSL/TLS fails. - * @see stream_socket_enable_crypto - */ - public function enableCrypto($type, $clientOrServer = 'client', $enable = true) { - if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) { - throw new InvalidArgumentException(__d('cake_dev', 'Invalid encryption scheme chosen')); - } - $enableCryptoResult = false; - try { - $enableCryptoResult = stream_socket_enable_crypto($this->connection, $enable, - $this->_encryptMethods[$type . '_' . $clientOrServer]); - } catch (Exception $e) { - $this->setLastError(null, $e->getMessage()); - throw new SocketException($e->getMessage()); - } - if ($enableCryptoResult === true) { - $this->encrypted = $enable; - return true; - } - $errorMessage = __d('cake_dev', 'Unable to perform enableCrypto operation on CakeSocket'); - $this->setLastError(null, $errorMessage); - throw new SocketException($errorMessage); - } -} + /** + * CakeSocket description + * + * @var string + */ + public $description = 'Remote DataSource Network Socket Interface'; + + /** + * Base configuration settings for the socket connection + * + * @var array + */ + protected $_baseConfig = array( + 'persistent' => false, + 'host' => 'localhost', + 'protocol' => 'tcp', + 'port' => 80, + 'timeout' => 30, + 'cryptoType' => 'tls', + ); + + /** + * Configuration settings for the socket connection + * + * @var array + */ + public $config = array(); + + /** + * Reference to socket connection resource + * + * @var resource + */ + public $connection = null; + + /** + * This boolean contains the current state of the CakeSocket class + * + * @var bool + */ + public $connected = false; + + /** + * This variable contains an array with the last error number (num) and string (str) + * + * @var array + */ + public $lastError = array(); + + /** + * True if the socket stream is encrypted after a CakeSocket::enableCrypto() call + * + * @var bool + */ + public $encrypted = false; + + /** + * Contains all the encryption methods available + * + * @var array + */ + protected $_encryptMethods = array( + // @codingStandardsIgnoreStart + 'sslv2_client' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT, + 'sslv3_client' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT, + 'sslv23_client' => STREAM_CRYPTO_METHOD_SSLv23_CLIENT, + 'tls_client' => STREAM_CRYPTO_METHOD_TLS_CLIENT, + 'sslv2_server' => STREAM_CRYPTO_METHOD_SSLv2_SERVER, + 'sslv3_server' => STREAM_CRYPTO_METHOD_SSLv3_SERVER, + 'sslv23_server' => STREAM_CRYPTO_METHOD_SSLv23_SERVER, + 'tls_server' => STREAM_CRYPTO_METHOD_TLS_SERVER, + // @codingStandardsIgnoreEnd + ); + + /** + * Used to capture connection warnings which can happen when there are + * SSL errors for example. + * + * @var array + */ + protected $_connectionErrors = array(); + + /** + * Constructor. + * + * @param array $config Socket configuration, which will be merged with the base configuration + * @see CakeSocket::$_baseConfig + */ + public function __construct($config = array()) { + $this->config = array_merge($this->_baseConfig, $config); + + $this->_addTlsVersions(); + } + + /** + * Add TLS versions that are dependent on specific PHP versions. + * + * These TLS versions are not supported by older PHP versions, + * so we have to conditionally set them if they are supported. + * + * As of PHP5.6.6, STREAM_CRYPTO_METHOD_TLS_CLIENT does not include + * TLS1.1 or 1.2. If we have TLS1.2 support we need to update the method map. + * + * @see https://bugs.php.net/bug.php?id=69195 + * @see https://github.com/php/php-src/commit/10bc5fd4c4c8e1dd57bd911b086e9872a56300a0 + * @return void + */ + protected function _addTlsVersions() { + $conditionalCrypto = array( + 'tlsv1_1_client' => 'STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT', + 'tlsv1_2_client' => 'STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT', + 'tlsv1_1_server' => 'STREAM_CRYPTO_METHOD_TLSv1_1_SERVER', + 'tlsv1_2_server' => 'STREAM_CRYPTO_METHOD_TLSv1_2_SERVER', + 'tlsv1_3_server' => 'STREAM_CRYPTO_METHOD_TLSv1_3_SERVER', + 'tlsv1_3_client' => 'STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT' + ); + foreach ($conditionalCrypto as $key => $const) { + if (defined($const)) { + $this->_encryptMethods[$key] = constant($const); + } + } + + // @codingStandardsIgnoreStart + if (isset($this->_encryptMethods['tlsv1_2_client'])) { + $this->_encryptMethods['tls_client'] = STREAM_CRYPTO_METHOD_TLS_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + } + if (isset($this->_encryptMethods['tlsv1_2_server'])) { + $this->_encryptMethods['tls_server'] = STREAM_CRYPTO_METHOD_TLS_SERVER | STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | STREAM_CRYPTO_METHOD_TLSv1_2_SERVER; + } + if (isset($this->_encryptMethods['tlsv1_3_client'])) { + $this->_encryptMethods['tls_client'] = STREAM_CRYPTO_METHOD_TLS_CLIENT | + STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT | + STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT | + STREAM_CRYPTO_METHOD_TLSv1_3_CLIENT; + } + if (isset($this->_encryptMethods['tlsv1_3_server'])) { + $this->_encryptMethods['tls_server'] = STREAM_CRYPTO_METHOD_TLS_SERVER | + STREAM_CRYPTO_METHOD_TLSv1_1_SERVER | + STREAM_CRYPTO_METHOD_TLSv1_2_SERVER | + STREAM_CRYPTO_METHOD_TLSv1_3_SERVER; + } + // @codingStandardsIgnoreEnd + } + + /** + * Connects the socket to the given host and port. + * + * @return bool Success + * @throws SocketException + */ + public function connect() { + if ($this->connection) { + $this->disconnect(); + } + + $hasProtocol = strpos($this->config['host'], '://') !== false; + if ($hasProtocol) { + list($this->config['protocol'], $this->config['host']) = explode('://', $this->config['host']); + } + $scheme = null; + if (!empty($this->config['protocol'])) { + $scheme = $this->config['protocol'] . '://'; + } + if (!empty($this->config['proxy'])) { + $scheme = 'tcp://'; + } + + $host = $this->config['host']; + if (isset($this->config['request']['uri']['host'])) { + $host = $this->config['request']['uri']['host']; + } + $this->_setSslContext($host); + + if (!empty($this->config['context'])) { + $context = stream_context_create($this->config['context']); + } else { + $context = stream_context_create(); + } + + $connectAs = STREAM_CLIENT_CONNECT; + if ($this->config['persistent']) { + $connectAs |= STREAM_CLIENT_PERSISTENT; + } + + set_error_handler(array($this, '_connectionErrorHandler')); + $this->connection = stream_socket_client( + $scheme . $this->config['host'] . ':' . $this->config['port'], + $errNum, + $errStr, + $this->config['timeout'], + $connectAs, + $context + ); + restore_error_handler(); + + if (!empty($errNum) || !empty($errStr)) { + $this->setLastError($errNum, $errStr); + throw new SocketException($errStr, $errNum); + } + + if (!$this->connection && $this->_connectionErrors) { + $message = implode("\n", $this->_connectionErrors); + throw new SocketException($message, E_WARNING); + } + + $this->connected = is_resource($this->connection); + if ($this->connected) { + stream_set_timeout($this->connection, $this->config['timeout']); + + if (!empty($this->config['request']) && + $this->config['request']['uri']['scheme'] === 'https' && + !empty($this->config['proxy']) + ) { + $req = array(); + $req[] = 'CONNECT ' . $this->config['request']['uri']['host'] . ':' . + $this->config['request']['uri']['port'] . ' HTTP/1.1'; + $req[] = 'Host: ' . $this->config['host']; + $req[] = 'User-Agent: php proxy'; + if (!empty($this->config['proxyauth'])) { + $req[] = 'Proxy-Authorization: ' . $this->config['proxyauth']; + } + + fwrite($this->connection, implode("\r\n", $req) . "\r\n\r\n"); + + while (!feof($this->connection)) { + $s = rtrim(fgets($this->connection, 4096)); + if (preg_match('/^$/', $s)) { + break; + } + } + + $this->enableCrypto($this->config['cryptoType'], 'client'); + } + } + return $this->connected; + } + + /** + * Configure the SSL context options. + * + * @param string $host The host name being connected to. + * @return void + */ + protected function _setSslContext($host) { + foreach ($this->config as $key => $value) { + if (substr($key, 0, 4) !== 'ssl_') { + continue; + } + $contextKey = substr($key, 4); + if (empty($this->config['context']['ssl'][$contextKey])) { + $this->config['context']['ssl'][$contextKey] = $value; + } + unset($this->config[$key]); + } + if (version_compare(PHP_VERSION, '5.3.2', '>=')) { + if (!isset($this->config['context']['ssl']['SNI_enabled'])) { + $this->config['context']['ssl']['SNI_enabled'] = true; + } + if (version_compare(PHP_VERSION, '5.6.0', '>=')) { + if (empty($this->config['context']['ssl']['peer_name'])) { + $this->config['context']['ssl']['peer_name'] = $host; + } + } else { + if (empty($this->config['context']['ssl']['SNI_server_name'])) { + $this->config['context']['ssl']['SNI_server_name'] = $host; + } + } + } + if (empty($this->config['context']['ssl']['cafile'])) { + $this->config['context']['ssl']['cafile'] = CAKE . 'Config' . DS . 'cacert.pem'; + } + if (!empty($this->config['context']['ssl']['verify_host'])) { + $this->config['context']['ssl']['CN_match'] = $host; + } + unset($this->config['context']['ssl']['verify_host']); + } + + /** + * socket_stream_client() does not populate errNum, or $errStr when there are + * connection errors, as in the case of SSL verification failure. + * + * Instead we need to handle those errors manually. + * + * @param int $code Code. + * @param string $message Message. + * @return void + */ + protected function _connectionErrorHandler($code, $message) { + $this->_connectionErrors[] = $message; + } + + /** + * Gets the connection context. + * + * @return null|array Null when there is no connection, an array when there is. + */ + public function context() { + if (!$this->connection) { + return null; + } + return stream_context_get_options($this->connection); + } + + /** + * Gets the host name of the current connection. + * + * @return string Host name + */ + public function host() { + if (Validation::ip($this->config['host'])) { + return gethostbyaddr($this->config['host']); + } + return gethostbyaddr($this->address()); + } + + /** + * Gets the IP address of the current connection. + * + * @return string IP address + */ + public function address() { + if (Validation::ip($this->config['host'])) { + return $this->config['host']; + } + return gethostbyname($this->config['host']); + } + + /** + * Gets all IP addresses associated with the current connection. + * + * @return array IP addresses + */ + public function addresses() { + if (Validation::ip($this->config['host'])) { + return array($this->config['host']); + } + return gethostbynamel($this->config['host']); + } + + /** + * Gets the last error as a string. + * + * @return string|null Last error + */ + public function lastError() { + if (!empty($this->lastError)) { + return $this->lastError['num'] . ': ' . $this->lastError['str']; + } + return null; + } + + /** + * Sets the last error. + * + * @param int $errNum Error code + * @param string $errStr Error string + * @return void + */ + public function setLastError($errNum, $errStr) { + $this->lastError = array('num' => $errNum, 'str' => $errStr); + } + + /** + * Writes data to the socket. + * + * @param string $data The data to write to the socket + * @return bool Success + */ + public function write($data) { + if (!$this->connected) { + if (!$this->connect()) { + return false; + } + } + $totalBytes = strlen($data); + for ($written = 0, $rv = 0; $written < $totalBytes; $written += $rv) { + $rv = fwrite($this->connection, substr($data, $written)); + if ($rv === false || $rv === 0) { + return $written; + } + } + return $written; + } + + /** + * Reads data from the socket. Returns false if no data is available or no connection could be + * established. + * + * @param int $length Optional buffer length to read; defaults to 1024 + * @return mixed Socket data + */ + public function read($length = 1024) { + if (!$this->connected) { + if (!$this->connect()) { + return false; + } + } + + if (!feof($this->connection)) { + $buffer = fread($this->connection, $length); + $info = stream_get_meta_data($this->connection); + if ($info['timed_out']) { + $this->setLastError(E_WARNING, __d('cake_dev', 'Connection timed out')); + return false; + } + return $buffer; + } + return false; + } + + /** + * Disconnects the socket from the current connection. + * + * @return bool Success + */ + public function disconnect() { + if (!is_resource($this->connection)) { + $this->connected = false; + return true; + } + $this->connected = !fclose($this->connection); + + if (!$this->connected) { + $this->connection = null; + } + return !$this->connected; + } + + /** + * Destructor, used to disconnect from current connection. + */ + public function __destruct() { + $this->disconnect(); + } + + /** + * Resets the state of this Socket instance to it's initial state (before CakeObject::__construct got executed) + * + * @param array $state Array with key and values to reset + * @return bool True on success + */ + public function reset($state = null) { + if (empty($state)) { + static $initalState = array(); + if (empty($initalState)) { + $initalState = get_class_vars(__CLASS__); + } + $state = $initalState; + } + + foreach ($state as $property => $value) { + $this->{$property} = $value; + } + return true; + } + + /** + * Encrypts current stream socket, using one of the defined encryption methods. + * + * @param string $type Type which can be one of 'sslv2', 'sslv3', 'sslv23', 'tls', 'tlsv1_1' or 'tlsv1_2'. + * @param string $clientOrServer Can be one of 'client', 'server'. Default is 'client'. + * @param bool $enable Enable or disable encryption. Default is true (enable) + * @return bool True on success + * @throws InvalidArgumentException When an invalid encryption scheme is chosen. + * @throws SocketException When attempting to enable SSL/TLS fails. + * @see stream_socket_enable_crypto + */ + public function enableCrypto($type, $clientOrServer = 'client', $enable = true) { + if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) { + throw new InvalidArgumentException(__d('cake_dev', 'Invalid encryption scheme chosen')); + } + $enableCryptoResult = false; + try { + $enableCryptoResult = stream_socket_enable_crypto($this->connection, $enable, + $this->_encryptMethods[$type . '_' . $clientOrServer]); + } catch (Exception $e) { + $this->setLastError(null, $e->getMessage()); + throw new SocketException($e->getMessage()); + } + if ($enableCryptoResult === true) { + $this->encrypted = $enable; + return true; + } + $errorMessage = __d('cake_dev', 'Unable to perform enableCrypto operation on CakeSocket'); + $this->setLastError(null, $errorMessage); + throw new SocketException($errorMessage); + } +} \ No newline at end of file diff --git a/lib/Cake/Utility/CakeText.php b/lib/Cake/Utility/CakeText.php index 11242026..9cebf2f0 100644 --- a/lib/Cake/Utility/CakeText.php +++ b/lib/Cake/Utility/CakeText.php @@ -23,688 +23,643 @@ */ class CakeText { -/** - * Generate a random UUID - * - * @see http://www.ietf.org/rfc/rfc4122.txt - * @return string RFC 4122 UUID - */ - public static function uuid() { - $node = env('SERVER_ADDR'); - - if (strpos($node, ':') !== false) { - if (substr_count($node, '::')) { - $node = str_replace( - '::', str_repeat(':0000', 8 - substr_count($node, ':')) . ':', $node - ); - } - $node = explode(':', $node); - $ipSix = ''; - - foreach ($node as $id) { - $ipSix .= str_pad(base_convert($id, 16, 2), 16, 0, STR_PAD_LEFT); - } - $node = base_convert($ipSix, 2, 10); - - if (strlen($node) < 38) { - $node = null; - } else { - $node = crc32($node); - } - } elseif (empty($node)) { - $host = env('HOSTNAME'); - - if (empty($host)) { - $host = env('HOST'); - } - - if (!empty($host)) { - $ip = gethostbyname($host); - - if ($ip === $host) { - $node = crc32($host); - } else { - $node = ip2long($ip); - } - } - } elseif ($node !== '127.0.0.1') { - $node = ip2long($node); - } else { - $node = null; - } - - if (empty($node)) { - $node = crc32(Configure::read('Security.salt')); - } - - if (function_exists('hphp_get_thread_id')) { - $pid = hphp_get_thread_id(); - } elseif (function_exists('zend_thread_id')) { - $pid = zend_thread_id(); - } else { - $pid = getmypid(); - } - - if (!$pid || $pid > 65535) { - $pid = mt_rand(0, 0xfff) | 0x4000; - } - - list($timeMid, $timeLow) = explode(' ', microtime()); - return sprintf( - "%08x-%04x-%04x-%02x%02x-%04x%08x", (int)$timeLow, (int)substr($timeMid, 2) & 0xffff, - mt_rand(0, 0xfff) | 0x4000, mt_rand(0, 0x3f) | 0x80, mt_rand(0, 0xff), $pid, $node - ); - } - -/** - * Tokenizes a string using $separator, ignoring any instance of $separator that appears between - * $leftBound and $rightBound. - * - * @param string $data The data to tokenize. - * @param string $separator The token to split the data on. - * @param string $leftBound The left boundary to ignore separators in. - * @param string $rightBound The right boundary to ignore separators in. - * @return mixed Array of tokens in $data or original input if empty. - */ - public static function tokenize($data, $separator = ',', $leftBound = '(', $rightBound = ')') { - if (empty($data)) { - return array(); - } - - $depth = 0; - $offset = 0; - $buffer = ''; - $results = array(); - $length = mb_strlen($data); - $open = false; - - while ($offset <= $length) { - $tmpOffset = -1; - $offsets = array( - mb_strpos($data, $separator, $offset), - mb_strpos($data, $leftBound, $offset), - mb_strpos($data, $rightBound, $offset) - ); - for ($i = 0; $i < 3; $i++) { - if ($offsets[$i] !== false && ($offsets[$i] < $tmpOffset || $tmpOffset == -1)) { - $tmpOffset = $offsets[$i]; - } - } - if ($tmpOffset !== -1) { - $buffer .= mb_substr($data, $offset, ($tmpOffset - $offset)); - $char = mb_substr($data, $tmpOffset, 1); - if (!$depth && $char === $separator) { - $results[] = $buffer; - $buffer = ''; - } else { - $buffer .= $char; - } - if ($leftBound !== $rightBound) { - if ($char === $leftBound) { - $depth++; - } - if ($char === $rightBound) { - $depth--; - } - } else { - if ($char === $leftBound) { - if (!$open) { - $depth++; - $open = true; - } else { - $depth--; - } - } - } - $offset = ++$tmpOffset; - } else { - $results[] = $buffer . mb_substr($data, $offset); - $offset = $length + 1; - } - } - if (empty($results) && !empty($buffer)) { - $results[] = $buffer; - } - - if (!empty($results)) { - return array_map('trim', $results); - } - - return array(); - } - -/** - * Replaces variable placeholders inside a $str with any given $data. Each key in the $data array - * corresponds to a variable placeholder name in $str. - * Example: `CakeText::insert(':name is :age years old.', array('name' => 'Bob', '65'));` - * Returns: Bob is 65 years old. - * - * Available $options are: - * - * - before: The character or string in front of the name of the variable placeholder (Defaults to `:`) - * - after: The character or string after the name of the variable placeholder (Defaults to null) - * - escape: The character or string used to escape the before character / string (Defaults to `\`) - * - format: A regex to use for matching variable placeholders. Default is: `/(? val array where each key stands for a placeholder variable name - * to be replaced with val - * @param array $options An array of options, see description above - * @return string - */ - public static function insert($str, $data, $options = array()) { - $defaults = array( - 'before' => ':', 'after' => null, 'escape' => '\\', 'format' => null, 'clean' => false - ); - $options += $defaults; - $format = $options['format']; - $data = (array)$data; - if (empty($data)) { - return ($options['clean']) ? CakeText::cleanInsert($str, $options) : $str; - } - - if (!isset($format)) { - $format = sprintf( - '/(? $hashVal) { - $key = sprintf($format, preg_quote($key, '/')); - $str = preg_replace($key, $hashVal, $str); - } - $dataReplacements = array_combine($hashKeys, array_values($data)); - foreach ($dataReplacements as $tmpHash => $tmpValue) { - $tmpValue = (is_array($tmpValue)) ? '' : $tmpValue; - $str = str_replace($tmpHash, $tmpValue, $str); - } - - if (!isset($options['format']) && isset($options['before'])) { - $str = str_replace($options['escape'] . $options['before'], $options['before'], $str); - } - return ($options['clean']) ? CakeText::cleanInsert($str, $options) : $str; - } - -/** - * Cleans up a CakeText::insert() formatted string with given $options depending on the 'clean' key in - * $options. The default method used is text but html is also available. The goal of this function - * is to replace all whitespace and unneeded markup around placeholders that did not get replaced - * by CakeText::insert(). - * - * @param string $str CakeText to clean. - * @param array $options Options list. - * @return string - * @see CakeText::insert() - */ - public static function cleanInsert($str, $options) { - $clean = $options['clean']; - if (!$clean) { - return $str; - } - if ($clean === true) { - $clean = array('method' => 'text'); - } - if (!is_array($clean)) { - $clean = array('method' => $options['clean']); - } - switch ($clean['method']) { - case 'html': - $clean = array_merge(array( - 'word' => '[\w,.]+', - 'andText' => true, - 'replacement' => '', - ), $clean); - $kleenex = sprintf( - '/[\s]*[a-z]+=(")(%s%s%s[\s]*)+\\1/i', - preg_quote($options['before'], '/'), - $clean['word'], - preg_quote($options['after'], '/') - ); - $str = preg_replace($kleenex, $clean['replacement'], $str); - if ($clean['andText']) { - $options['clean'] = array('method' => 'text'); - $str = CakeText::cleanInsert($str, $options); - } - break; - case 'text': - $clean = array_merge(array( - 'word' => '[\w,.]+', - 'gap' => '[\s]*(?:(?:and|or)[\s]*)?', - 'replacement' => '', - ), $clean); - - $kleenex = sprintf( - '/(%s%s%s%s|%s%s%s%s)/', - preg_quote($options['before'], '/'), - $clean['word'], - preg_quote($options['after'], '/'), - $clean['gap'], - $clean['gap'], - preg_quote($options['before'], '/'), - $clean['word'], - preg_quote($options['after'], '/') - ); - $str = preg_replace($kleenex, $clean['replacement'], $str); - break; - } - return $str; - } - -/** - * Wraps text to a specific width, can optionally wrap at word breaks. - * - * ### Options - * - * - `width` The width to wrap to. Defaults to 72. - * - `wordWrap` Only wrap on words breaks (spaces) Defaults to true. - * - `indent` CakeText to indent with. Defaults to null. - * - `indentAt` 0 based index to start indenting at. Defaults to 0. - * - * @param string $text The text to format. - * @param array|int $options Array of options to use, or an integer to wrap the text to. - * @return string Formatted text. - */ - public static function wrap($text, $options = array()) { - if (is_numeric($options)) { - $options = array('width' => $options); - } - $options += array('width' => 72, 'wordWrap' => true, 'indent' => null, 'indentAt' => 0); - if ($options['wordWrap']) { - $wrapped = static::wordWrap($text, $options['width'], "\n"); - } else { - $wrapped = trim(chunk_split($text, $options['width'] - 1, "\n")); - } - if (!empty($options['indent'])) { - $chunks = explode("\n", $wrapped); - for ($i = $options['indentAt'], $len = count($chunks); $i < $len; $i++) { - $chunks[$i] = $options['indent'] . $chunks[$i]; - } - $wrapped = implode("\n", $chunks); - } - return $wrapped; - } - -/** - * Unicode aware version of wordwrap. - * - * @param string $text The text to format. - * @param int $width The width to wrap to. Defaults to 72. - * @param string $break The line is broken using the optional break parameter. Defaults to '\n'. - * @param bool $cut If the cut is set to true, the string is always wrapped at the specified width. - * @return string Formatted text. - */ - public static function wordWrap($text, $width = 72, $break = "\n", $cut = false) { - $paragraphs = explode($break, $text); - foreach ($paragraphs as &$paragraph) { - $paragraph = static::_wordWrap($paragraph, $width, $break, $cut); - } - return implode($break, $paragraphs); - } - -/** - * Helper method for wordWrap(). - * - * @param string $text The text to format. - * @param int $width The width to wrap to. Defaults to 72. - * @param string $break The line is broken using the optional break parameter. Defaults to '\n'. - * @param bool $cut If the cut is set to true, the string is always wrapped at the specified width. - * @return string Formatted text. - */ - protected static function _wordWrap($text, $width = 72, $break = "\n", $cut = false) { - if ($cut) { - $parts = array(); - while (mb_strlen($text) > 0) { - $part = mb_substr($text, 0, $width); - $parts[] = trim($part); - $text = trim(mb_substr($text, mb_strlen($part))); - } - return implode($break, $parts); - } - - $parts = array(); - while (mb_strlen($text) > 0) { - if ($width >= mb_strlen($text)) { - $parts[] = trim($text); - break; - } - - $part = mb_substr($text, 0, $width); - $nextChar = mb_substr($text, $width, 1); - if ($nextChar !== ' ') { - $breakAt = mb_strrpos($part, ' '); - if ($breakAt === false) { - $breakAt = mb_strpos($text, ' ', $width); - } - if ($breakAt === false) { - $parts[] = trim($text); - break; - } - $part = mb_substr($text, 0, $breakAt); - } - - $part = trim($part); - $parts[] = $part; - $text = trim(mb_substr($text, mb_strlen($part))); - } - - return implode($break, $parts); - } - -/** - * Highlights a given phrase in a text. You can specify any expression in highlighter that - * may include the \1 expression to include the $phrase found. - * - * ### Options: - * - * - `format` The piece of html with that the phrase will be highlighted - * - `html` If true, will ignore any HTML tags, ensuring that only the correct text is highlighted - * - `regex` a custom regex rule that is used to match words, default is '|$tag|iu' - * - * @param string $text Text to search the phrase in. - * @param string|array $phrase The phrase or phrases that will be searched. - * @param array $options An array of html attributes and options. - * @return string The highlighted text - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::highlight - */ - public static function highlight($text, $phrase, $options = array()) { - if (empty($phrase)) { - return $text; - } - - $defaults = array( - 'format' => '\1', - 'html' => false, - 'regex' => "|%s|iu" - ); - $options += $defaults; - extract($options); - - if (is_array($phrase)) { - $replace = array(); - $with = array(); - - foreach ($phrase as $key => $segment) { - $segment = '(' . preg_quote($segment, '|') . ')'; - if ($html) { - $segment = "(?![^<]+>)$segment(?![^<]+>)"; - } - - $with[] = (is_array($format)) ? $format[$key] : $format; - $replace[] = sprintf($options['regex'], $segment); - } - - return preg_replace($replace, $with, $text); - } - - $phrase = '(' . preg_quote($phrase, '|') . ')'; - if ($html) { - $phrase = "(?![^<]+>)$phrase(?![^<]+>)"; - } - - return preg_replace(sprintf($options['regex'], $phrase), $format, $text); - } - -/** - * Strips given text of all links (]+>|im', '', preg_replace('|<\/a>|im', '', $text)); - } - -/** - * Truncates text starting from the end. - * - * Cuts a string to the length of $length and replaces the first characters - * with the ellipsis if the text is longer than length. - * - * ### Options: - * - * - `ellipsis` Will be used as Beginning and prepended to the trimmed string - * - `exact` If false, $text will not be cut mid-word - * - * @param string $text CakeText to truncate. - * @param int $length Length of returned string, including ellipsis. - * @param array $options An array of options. - * @return string Trimmed string. - */ - public static function tail($text, $length = 100, $options = array()) { - $defaults = array( - 'ellipsis' => '...', 'exact' => true - ); - $options += $defaults; - extract($options); - - if (!function_exists('mb_strlen')) { - class_exists('Multibyte'); - } - - if (mb_strlen($text) <= $length) { - return $text; - } - - $truncate = mb_substr($text, mb_strlen($text) - $length + mb_strlen($ellipsis)); - if (!$exact) { - $spacepos = mb_strpos($truncate, ' '); - $truncate = $spacepos === false ? '' : trim(mb_substr($truncate, $spacepos)); - } - - return $ellipsis . $truncate; - } - -/** - * Truncates text. - * - * Cuts a string to the length of $length and replaces the last characters - * with the ellipsis if the text is longer than length. - * - * ### Options: - * - * - `ellipsis` Will be used as Ending and appended to the trimmed string (`ending` is deprecated) - * - `exact` If false, $text will not be cut mid-word - * - `html` If true, HTML tags would be handled correctly - * - * @param string $text CakeText to truncate. - * @param int $length Length of returned string, including ellipsis. - * @param array $options An array of html attributes and options. - * @return string Trimmed string. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::truncate - */ - public static function truncate($text, $length = 100, $options = array()) { - $defaults = array( - 'ellipsis' => '...', 'exact' => true, 'html' => false - ); - if (isset($options['ending'])) { - $defaults['ellipsis'] = $options['ending']; - } elseif (!empty($options['html']) && Configure::read('App.encoding') === 'UTF-8') { - $defaults['ellipsis'] = "\xe2\x80\xa6"; - } - $options += $defaults; - extract($options); - - if (!function_exists('mb_strlen')) { - class_exists('Multibyte'); - } - - if ($html) { - if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { - return $text; - } - $totalLength = mb_strlen(strip_tags($ellipsis)); - $openTags = array(); - $truncate = ''; - - preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER); - foreach ($tags as $tag) { - if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) { - if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) { - array_unshift($openTags, $tag[2]); - } elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) { - $pos = array_search($closeTag[1], $openTags); - if ($pos !== false) { - array_splice($openTags, $pos, 1); - } - } - } - $truncate .= $tag[1]; - - $contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3])); - if ($contentLength + $totalLength > $length) { - $left = $length - $totalLength; - $entitiesLength = 0; - if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) { - foreach ($entities[0] as $entity) { - if ($entity[1] + 1 - $entitiesLength <= $left) { - $left--; - $entitiesLength += mb_strlen($entity[0]); - } else { - break; - } - } - } - - $truncate .= mb_substr($tag[3], 0, $left + $entitiesLength); - break; - } else { - $truncate .= $tag[3]; - $totalLength += $contentLength; - } - if ($totalLength >= $length) { - break; - } - } - } else { - if (mb_strlen($text) <= $length) { - return $text; - } - $truncate = mb_substr($text, 0, $length - mb_strlen($ellipsis)); - } - if (!$exact) { - $spacepos = mb_strrpos($truncate, ' '); - if ($html) { - $truncateCheck = mb_substr($truncate, 0, $spacepos); - $lastOpenTag = mb_strrpos($truncateCheck, '<'); - $lastCloseTag = mb_strrpos($truncateCheck, '>'); - if ($lastOpenTag > $lastCloseTag) { - preg_match_all('/<[\w]+[^>]*>/s', $truncate, $lastTagMatches); - $lastTag = array_pop($lastTagMatches[0]); - $spacepos = mb_strrpos($truncate, $lastTag) + mb_strlen($lastTag); - } - $bits = mb_substr($truncate, $spacepos); - preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER); - if (!empty($droppedTags)) { - if (!empty($openTags)) { - foreach ($droppedTags as $closingTag) { - if (!in_array($closingTag[1], $openTags)) { - array_unshift($openTags, $closingTag[1]); - } - } - } else { - foreach ($droppedTags as $closingTag) { - $openTags[] = $closingTag[1]; - } - } - } - } - $truncate = mb_substr($truncate, 0, $spacepos); - } - $truncate .= $ellipsis; - - if ($html) { - foreach ($openTags as $tag) { - $truncate .= ''; - } - } - - return $truncate; - } - -/** - * Extracts an excerpt from the text surrounding the phrase with a number of characters on each side - * determined by radius. - * - * @param string $text CakeText to search the phrase in - * @param string $phrase Phrase that will be searched for - * @param int $radius The amount of characters that will be returned on each side of the founded phrase - * @param string $ellipsis Ending that will be appended - * @return string Modified string - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::excerpt - */ - public static function excerpt($text, $phrase, $radius = 100, $ellipsis = '...') { - if (empty($text) || empty($phrase)) { - return static::truncate($text, $radius * 2, array('ellipsis' => $ellipsis)); - } - - $append = $prepend = $ellipsis; - - $phraseLen = mb_strlen($phrase); - $textLen = mb_strlen($text); - - $pos = mb_strpos(mb_strtolower($text), mb_strtolower($phrase)); - if ($pos === false) { - return mb_substr($text, 0, $radius) . $ellipsis; - } - - $startPos = $pos - $radius; - if ($startPos <= 0) { - $startPos = 0; - $prepend = ''; - } - - $endPos = $pos + $phraseLen + $radius; - if ($endPos >= $textLen) { - $endPos = $textLen; - $append = ''; - } - - $excerpt = mb_substr($text, $startPos, $endPos - $startPos); - $excerpt = $prepend . $excerpt . $append; - - return $excerpt; - } - -/** - * Creates a comma separated list where the last two items are joined with 'and', forming natural language. - * - * @param array $list The list to be joined. - * @param string $and The word used to join the last and second last items together with. Defaults to 'and'. - * @param string $separator The separator used to join all the other items together. Defaults to ', '. - * @return string The glued together string. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::toList - */ - public static function toList($list, $and = null, $separator = ', ') { - if ($and === null) { - $and = __d('cake', 'and'); - } - if (count($list) > 1) { - return implode($separator, array_slice($list, null, -1)) . ' ' . $and . ' ' . array_pop($list); - } - - return array_pop($list); - } -} + /** + * Generate a random UUID + * + * @see http://www.ietf.org/rfc/rfc4122.txt + * @return string RFC 4122 UUID + */ + public static function uuid() { + $random = function_exists('random_int') ? 'random_int' : 'mt_rand'; + return sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + // 32 bits for "time_low" + $random(0, 65535), + $random(0, 65535), + // 16 bits for "time_mid" + $random(0, 65535), + // 12 bits before the 0100 of (version) 4 for "time_hi_and_version" + $random(0, 4095) | 0x4000, + // 16 bits, 8 bits for "clk_seq_hi_res", + // 8 bits for "clk_seq_low", + // two most significant bits holds zero and one for variant DCE1.1 + $random(0, 0x3fff) | 0x8000, + // 48 bits for "node" + $random(0, 65535), + $random(0, 65535), + $random(0, 65535) + ); + } + + /** + * Tokenizes a string using $separator, ignoring any instance of $separator that appears between + * $leftBound and $rightBound. + * + * @param string $data The data to tokenize. + * @param string $separator The token to split the data on. + * @param string $leftBound The left boundary to ignore separators in. + * @param string $rightBound The right boundary to ignore separators in. + * @return mixed Array of tokens in $data or original input if empty. + */ + public static function tokenize($data, $separator = ',', $leftBound = '(', $rightBound = ')') { + if (empty($data)) { + return array(); + } + + $depth = 0; + $offset = 0; + $buffer = ''; + $results = array(); + $length = mb_strlen($data); + $open = false; + + while ($offset <= $length) { + $tmpOffset = -1; + $offsets = array( + mb_strpos($data, $separator, $offset), + mb_strpos($data, $leftBound, $offset), + mb_strpos($data, $rightBound, $offset) + ); + for ($i = 0; $i < 3; $i++) { + if ($offsets[$i] !== false && ($offsets[$i] < $tmpOffset || $tmpOffset == -1)) { + $tmpOffset = $offsets[$i]; + } + } + if ($tmpOffset !== -1) { + $buffer .= mb_substr($data, $offset, ($tmpOffset - $offset)); + $char = mb_substr($data, $tmpOffset, 1); + if (!$depth && $char === $separator) { + $results[] = $buffer; + $buffer = ''; + } else { + $buffer .= $char; + } + if ($leftBound !== $rightBound) { + if ($char === $leftBound) { + $depth++; + } + if ($char === $rightBound) { + $depth--; + } + } else { + if ($char === $leftBound) { + if (!$open) { + $depth++; + $open = true; + } else { + $depth--; + } + } + } + $offset = ++$tmpOffset; + } else { + $results[] = $buffer . mb_substr($data, $offset); + $offset = $length + 1; + } + } + if (empty($results) && !empty($buffer)) { + $results[] = $buffer; + } + + if (!empty($results)) { + return array_map('trim', $results); + } + + return array(); + } + + /** + * Replaces variable placeholders inside a $str with any given $data. Each key in the $data array + * corresponds to a variable placeholder name in $str. + * Example: `CakeText::insert(':name is :age years old.', array('name' => 'Bob', '65'));` + * Returns: Bob is 65 years old. + * + * Available $options are: + * + * - before: The character or string in front of the name of the variable placeholder (Defaults to `:`) + * - after: The character or string after the name of the variable placeholder (Defaults to null) + * - escape: The character or string used to escape the before character / string (Defaults to `\`) + * - format: A regex to use for matching variable placeholders. Default is: `/(? val array where each key stands for a placeholder variable name + * to be replaced with val + * @param array $options An array of options, see description above + * @return string + */ + public static function insert($str, $data, $options = array()) { + $defaults = array( + 'before' => ':', 'after' => null, 'escape' => '\\', 'format' => null, 'clean' => false + ); + $options += $defaults; + $format = $options['format']; + $data = (array)$data; + if (empty($data)) { + return ($options['clean']) ? CakeText::cleanInsert($str, $options) : $str; + } + + if (!isset($format)) { + $format = sprintf( + '/(? $hashVal) { + $key = sprintf($format, preg_quote($key, '/')); + $str = preg_replace($key, $hashVal, $str); + } + $dataReplacements = array_combine($hashKeys, array_values($data)); + foreach ($dataReplacements as $tmpHash => $tmpValue) { + $tmpValue = (is_array($tmpValue)) ? '' : $tmpValue; + $str = str_replace($tmpHash, $tmpValue, $str); + } + + if (!isset($options['format']) && isset($options['before'])) { + $str = str_replace($options['escape'] . $options['before'], $options['before'], $str); + } + return ($options['clean']) ? CakeText::cleanInsert($str, $options) : $str; + } + + /** + * Cleans up a CakeText::insert() formatted string with given $options depending on the 'clean' key in + * $options. The default method used is text but html is also available. The goal of this function + * is to replace all whitespace and unneeded markup around placeholders that did not get replaced + * by CakeText::insert(). + * + * @param string $str CakeText to clean. + * @param array $options Options list. + * @return string + * @see CakeText::insert() + */ + public static function cleanInsert($str, $options) { + $clean = $options['clean']; + if (!$clean) { + return $str; + } + if ($clean === true) { + $clean = array('method' => 'text'); + } + if (!is_array($clean)) { + $clean = array('method' => $options['clean']); + } + switch ($clean['method']) { + case 'html': + $clean = array_merge(array( + 'word' => '[\w,.]+', + 'andText' => true, + 'replacement' => '', + ), $clean); + $kleenex = sprintf( + '/[\s]*[a-z]+=(")(%s%s%s[\s]*)+\\1/i', + preg_quote($options['before'], '/'), + $clean['word'], + preg_quote($options['after'], '/') + ); + $str = preg_replace($kleenex, $clean['replacement'], $str); + if ($clean['andText']) { + $options['clean'] = array('method' => 'text'); + $str = CakeText::cleanInsert($str, $options); + } + break; + case 'text': + $clean = array_merge(array( + 'word' => '[\w,.]+', + 'gap' => '[\s]*(?:(?:and|or)[\s]*)?', + 'replacement' => '', + ), $clean); + + $kleenex = sprintf( + '/(%s%s%s%s|%s%s%s%s)/', + preg_quote($options['before'], '/'), + $clean['word'], + preg_quote($options['after'], '/'), + $clean['gap'], + $clean['gap'], + preg_quote($options['before'], '/'), + $clean['word'], + preg_quote($options['after'], '/') + ); + $str = preg_replace($kleenex, $clean['replacement'], $str); + break; + } + return $str; + } + + /** + * Wraps text to a specific width, can optionally wrap at word breaks. + * + * ### Options + * + * - `width` The width to wrap to. Defaults to 72. + * - `wordWrap` Only wrap on words breaks (spaces) Defaults to true. + * - `indent` CakeText to indent with. Defaults to null. + * - `indentAt` 0 based index to start indenting at. Defaults to 0. + * + * @param string $text The text to format. + * @param array|int $options Array of options to use, or an integer to wrap the text to. + * @return string Formatted text. + */ + public static function wrap($text, $options = array()) { + if (is_numeric($options)) { + $options = array('width' => $options); + } + $options += array('width' => 72, 'wordWrap' => true, 'indent' => null, 'indentAt' => 0); + if ($options['wordWrap']) { + $wrapped = static::wordWrap($text, $options['width'], "\n"); + } else { + $wrapped = trim(chunk_split($text, $options['width'] - 1, "\n")); + } + if (!empty($options['indent'])) { + $chunks = explode("\n", $wrapped); + for ($i = $options['indentAt'], $len = count($chunks); $i < $len; $i++) { + $chunks[$i] = $options['indent'] . $chunks[$i]; + } + $wrapped = implode("\n", $chunks); + } + return $wrapped; + } + + /** + * Unicode aware version of wordwrap. + * + * @param string $text The text to format. + * @param int $width The width to wrap to. Defaults to 72. + * @param string $break The line is broken using the optional break parameter. Defaults to '\n'. + * @param bool $cut If the cut is set to true, the string is always wrapped at the specified width. + * @return string Formatted text. + */ + public static function wordWrap($text, $width = 72, $break = "\n", $cut = false) { + $paragraphs = explode($break, $text); + foreach ($paragraphs as &$paragraph) { + $paragraph = static::_wordWrap($paragraph, $width, $break, $cut); + } + return implode($break, $paragraphs); + } + + /** + * Helper method for wordWrap(). + * + * @param string $text The text to format. + * @param int $width The width to wrap to. Defaults to 72. + * @param string $break The line is broken using the optional break parameter. Defaults to '\n'. + * @param bool $cut If the cut is set to true, the string is always wrapped at the specified width. + * @return string Formatted text. + */ + protected static function _wordWrap($text, $width = 72, $break = "\n", $cut = false) { + if ($cut) { + $parts = array(); + while (mb_strlen($text) > 0) { + $part = mb_substr($text, 0, $width); + $parts[] = trim($part); + $text = trim(mb_substr($text, mb_strlen($part))); + } + return implode($break, $parts); + } + + $parts = array(); + while (mb_strlen($text) > 0) { + if ($width >= mb_strlen($text)) { + $parts[] = trim($text); + break; + } + + $part = mb_substr($text, 0, $width); + $nextChar = mb_substr($text, $width, 1); + if ($nextChar !== ' ') { + $breakAt = mb_strrpos($part, ' '); + if ($breakAt === false) { + $breakAt = mb_strpos($text, ' ', $width); + } + if ($breakAt === false) { + $parts[] = trim($text); + break; + } + $part = mb_substr($text, 0, $breakAt); + } + + $part = trim($part); + $parts[] = $part; + $text = trim(mb_substr($text, mb_strlen($part))); + } + + return implode($break, $parts); + } + + /** + * Highlights a given phrase in a text. You can specify any expression in highlighter that + * may include the \1 expression to include the $phrase found. + * + * ### Options: + * + * - `format` The piece of html with that the phrase will be highlighted + * - `html` If true, will ignore any HTML tags, ensuring that only the correct text is highlighted + * - `regex` a custom regex rule that is used to match words, default is '|$tag|iu' + * + * @param string $text Text to search the phrase in. + * @param string|array $phrase The phrase or phrases that will be searched. + * @param array $options An array of html attributes and options. + * @return string The highlighted text + * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::highlight + */ + public static function highlight($text, $phrase, $options = array()) { + if (empty($phrase)) { + return $text; + } + + $defaults = array( + 'format' => '\1', + 'html' => false, + 'regex' => "|%s|iu" + ); + $options += $defaults; + extract($options); + + if (is_array($phrase)) { + $replace = array(); + $with = array(); + + foreach ($phrase as $key => $segment) { + $segment = '(' . preg_quote($segment, '|') . ')'; + if ($html) { + $segment = "(?![^<]+>)$segment(?![^<]+>)"; + } + + $with[] = (is_array($format)) ? $format[$key] : $format; + $replace[] = sprintf($options['regex'], $segment); + } + + return preg_replace($replace, $with, $text); + } + + $phrase = '(' . preg_quote($phrase, '|') . ')'; + if ($html) { + $phrase = "(?![^<]+>)$phrase(?![^<]+>)"; + } + + return preg_replace(sprintf($options['regex'], $phrase), $format, $text); + } + + /** + * Strips given text of all links (]+>|im', '', preg_replace('|<\/a>|im', '', $text)); + } + + /** + * Truncates text starting from the end. + * + * Cuts a string to the length of $length and replaces the first characters + * with the ellipsis if the text is longer than length. + * + * ### Options: + * + * - `ellipsis` Will be used as Beginning and prepended to the trimmed string + * - `exact` If false, $text will not be cut mid-word + * + * @param string $text CakeText to truncate. + * @param int $length Length of returned string, including ellipsis. + * @param array $options An array of options. + * @return string Trimmed string. + */ + public static function tail($text, $length = 100, $options = array()) { + $defaults = array( + 'ellipsis' => '...', 'exact' => true + ); + $options += $defaults; + extract($options); + + if (!function_exists('mb_strlen')) { + class_exists('Multibyte'); + } + + if (mb_strlen($text) <= $length) { + return $text; + } + + $truncate = mb_substr($text, mb_strlen($text) - $length + mb_strlen($ellipsis)); + if (!$exact) { + $spacepos = mb_strpos($truncate, ' '); + $truncate = $spacepos === false ? '' : trim(mb_substr($truncate, $spacepos)); + } + + return $ellipsis . $truncate; + } + + /** + * Truncates text. + * + * Cuts a string to the length of $length and replaces the last characters + * with the ellipsis if the text is longer than length. + * + * ### Options: + * + * - `ellipsis` Will be used as Ending and appended to the trimmed string (`ending` is deprecated) + * - `exact` If false, $text will not be cut mid-word + * - `html` If true, HTML tags would be handled correctly + * + * @param string $text CakeText to truncate. + * @param int $length Length of returned string, including ellipsis. + * @param array $options An array of html attributes and options. + * @return string Trimmed string. + * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::truncate + */ + public static function truncate($text, $length = 100, $options = array()) { + $defaults = array( + 'ellipsis' => '...', 'exact' => true, 'html' => false + ); + if (isset($options['ending'])) { + $defaults['ellipsis'] = $options['ending']; + } elseif (!empty($options['html']) && Configure::read('App.encoding') === 'UTF-8') { + $defaults['ellipsis'] = "\xe2\x80\xa6"; + } + $options += $defaults; + extract($options); + + if (!function_exists('mb_strlen')) { + class_exists('Multibyte'); + } + + if ($html) { + if (mb_strlen(preg_replace('/<.*?>/', '', $text)) <= $length) { + return $text; + } + $totalLength = mb_strlen(strip_tags($ellipsis)); + $openTags = array(); + $truncate = ''; + + preg_match_all('/(<\/?([\w+]+)[^>]*>)?([^<>]*)/', $text, $tags, PREG_SET_ORDER); + foreach ($tags as $tag) { + if (!preg_match('/img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param/s', $tag[2])) { + if (preg_match('/<[\w]+[^>]*>/s', $tag[0])) { + array_unshift($openTags, $tag[2]); + } elseif (preg_match('/<\/([\w]+)[^>]*>/s', $tag[0], $closeTag)) { + $pos = array_search($closeTag[1], $openTags); + if ($pos !== false) { + array_splice($openTags, $pos, 1); + } + } + } + $truncate .= $tag[1]; + + $contentLength = mb_strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', ' ', $tag[3])); + if ($contentLength + $totalLength > $length) { + $left = $length - $totalLength; + $entitiesLength = 0; + if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|&#x[0-9a-f]{1,6};/i', $tag[3], $entities, PREG_OFFSET_CAPTURE)) { + foreach ($entities[0] as $entity) { + if ($entity[1] + 1 - $entitiesLength <= $left) { + $left--; + $entitiesLength += mb_strlen($entity[0]); + } else { + break; + } + } + } + + $truncate .= mb_substr($tag[3], 0, $left + $entitiesLength); + break; + } else { + $truncate .= $tag[3]; + $totalLength += $contentLength; + } + if ($totalLength >= $length) { + break; + } + } + } else { + if (mb_strlen($text) <= $length) { + return $text; + } + $truncate = mb_substr($text, 0, $length - mb_strlen($ellipsis)); + } + if (!$exact) { + $spacepos = mb_strrpos($truncate, ' '); + if ($html) { + $truncateCheck = mb_substr($truncate, 0, $spacepos); + $lastOpenTag = mb_strrpos($truncateCheck, '<'); + $lastCloseTag = mb_strrpos($truncateCheck, '>'); + if ($lastOpenTag > $lastCloseTag) { + preg_match_all('/<[\w]+[^>]*>/s', $truncate, $lastTagMatches); + $lastTag = array_pop($lastTagMatches[0]); + $spacepos = mb_strrpos($truncate, $lastTag) + mb_strlen($lastTag); + } + $bits = mb_substr($truncate, $spacepos); + preg_match_all('/<\/([a-z]+)>/', $bits, $droppedTags, PREG_SET_ORDER); + if (!empty($droppedTags)) { + if (!empty($openTags)) { + foreach ($droppedTags as $closingTag) { + if (!in_array($closingTag[1], $openTags)) { + array_unshift($openTags, $closingTag[1]); + } + } + } else { + foreach ($droppedTags as $closingTag) { + $openTags[] = $closingTag[1]; + } + } + } + } + $truncate = mb_substr($truncate, 0, $spacepos); + } + $truncate .= $ellipsis; + + if ($html) { + foreach ($openTags as $tag) { + $truncate .= ''; + } + } + + return $truncate; + } + + /** + * Extracts an excerpt from the text surrounding the phrase with a number of characters on each side + * determined by radius. + * + * @param string $text CakeText to search the phrase in + * @param string $phrase Phrase that will be searched for + * @param int $radius The amount of characters that will be returned on each side of the founded phrase + * @param string $ellipsis Ending that will be appended + * @return string Modified string + * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::excerpt + */ + public static function excerpt($text, $phrase, $radius = 100, $ellipsis = '...') { + if (empty($text) || empty($phrase)) { + return static::truncate($text, $radius * 2, array('ellipsis' => $ellipsis)); + } + + $append = $prepend = $ellipsis; + + $phraseLen = mb_strlen($phrase); + $textLen = mb_strlen($text); + + $pos = mb_strpos(mb_strtolower($text), mb_strtolower($phrase)); + if ($pos === false) { + return mb_substr($text, 0, $radius) . $ellipsis; + } + + $startPos = $pos - $radius; + if ($startPos <= 0) { + $startPos = 0; + $prepend = ''; + } + + $endPos = $pos + $phraseLen + $radius; + if ($endPos >= $textLen) { + $endPos = $textLen; + $append = ''; + } + + $excerpt = mb_substr($text, $startPos, $endPos - $startPos); + $excerpt = $prepend . $excerpt . $append; + + return $excerpt; + } + + /** + * Creates a comma separated list where the last two items are joined with 'and', forming natural language. + * + * @param array $list The list to be joined. + * @param string $and The word used to join the last and second last items together with. Defaults to 'and'. + * @param string $separator The separator used to join all the other items together. Defaults to ', '. + * @return string The glued together string. + * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/text.html#TextHelper::toList + */ + public static function toList($list, $and = null, $separator = ', ') { + if ($and === null) { + $and = __d('cake', 'and'); + } + if (count($list) > 1) { + return implode($separator, array_slice($list, null, -1)) . ' ' . $and . ' ' . array_pop($list); + } + + return array_pop($list); + } +} \ No newline at end of file diff --git a/lib/Cake/Utility/ObjectCollection.php b/lib/Cake/Utility/ObjectCollection.php index 1e36e4c4..98e137da 100755 --- a/lib/Cake/Utility/ObjectCollection.php +++ b/lib/Cake/Utility/ObjectCollection.php @@ -27,318 +27,318 @@ */ abstract class ObjectCollection { -/** - * List of the currently-enabled objects - * - * @var array - */ - protected $_enabled = array(); + /** + * List of the currently-enabled objects + * + * @var array + */ + protected $_enabled = array(); -/** - * A hash of loaded objects, indexed by name - * - * @var array - */ - protected $_loaded = array(); + /** + * A hash of loaded objects, indexed by name + * + * @var array + */ + protected $_loaded = array(); -/** - * Default object priority. A non zero integer. - * - * @var int - */ - public $defaultPriority = 10; + /** + * Default object priority. A non zero integer. + * + * @var int + */ + public $defaultPriority = 10; -/** - * Loads a new object onto the collection. Can throw a variety of exceptions - * - * Implementations of this class support a `$options['enabled']` flag which enables/disables - * a loaded object. - * - * @param string $name Name of object to load. - * @param array $options Array of configuration options for the object to be constructed. - * @return CakeObject the constructed object - */ - abstract public function load($name, $options = array()); + /** + * Loads a new object onto the collection. Can throw a variety of exceptions + * + * Implementations of this class support a `$options['enabled']` flag which enables/disables + * a loaded object. + * + * @param string $name Name of object to load. + * @param array $options Array of configuration options for the object to be constructed. + * @return CakeObject the constructed object + */ + abstract public function load($name, $options = array()); -/** - * Trigger a callback method on every object in the collection. - * Used to trigger methods on objects in the collection. Will fire the methods in the - * order they were attached. - * - * ### Options - * - * - `breakOn` Set to the value or values you want the callback propagation to stop on. - * Can either be a scalar value, or an array of values to break on. Defaults to `false`. - * - * - `break` Set to true to enabled breaking. When a trigger is broken, the last returned value - * will be returned. If used in combination with `collectReturn` the collected results will be returned. - * Defaults to `false`. - * - * - `collectReturn` Set to true to collect the return of each object into an array. - * This array of return values will be returned from the trigger() call. Defaults to `false`. - * - * - `modParams` Allows each object the callback gets called on to modify the parameters to the next object. - * Setting modParams to an integer value will allow you to modify the parameter with that index. - * Any non-null value will modify the parameter index indicated. - * Defaults to false. - * - * @param string|CakeEvent $callback Method to fire on all the objects. Its assumed all the objects implement - * the method you are calling. If an instance of CakeEvent is provided, then then Event name will parsed to - * get the callback name. This is done by getting the last word after any dot in the event name - * (eg. `Model.afterSave` event will trigger the `afterSave` callback) - * @param array $params Array of parameters for the triggered callback. - * @param array $options Array of options. - * @return mixed Either the last result or all results if collectReturn is on. - * @throws CakeException when modParams is used with an index that does not exist. - */ - public function trigger($callback, $params = array(), $options = array()) { - if (empty($this->_enabled)) { - return true; - } - $subject = null; - if ($callback instanceof CakeEvent) { - $event = $callback; - if (is_array($event->data)) { - $params =& $event->data; - } - if (empty($event->omitSubject)) { - $subject = $event->subject(); - } + /** + * Trigger a callback method on every object in the collection. + * Used to trigger methods on objects in the collection. Will fire the methods in the + * order they were attached. + * + * ### Options + * + * - `breakOn` Set to the value or values you want the callback propagation to stop on. + * Can either be a scalar value, or an array of values to break on. Defaults to `false`. + * + * - `break` Set to true to enabled breaking. When a trigger is broken, the last returned value + * will be returned. If used in combination with `collectReturn` the collected results will be returned. + * Defaults to `false`. + * + * - `collectReturn` Set to true to collect the return of each object into an array. + * This array of return values will be returned from the trigger() call. Defaults to `false`. + * + * - `modParams` Allows each object the callback gets called on to modify the parameters to the next object. + * Setting modParams to an integer value will allow you to modify the parameter with that index. + * Any non-null value will modify the parameter index indicated. + * Defaults to false. + * + * @param string|CakeEvent $callback Method to fire on all the objects. Its assumed all the objects implement + * the method you are calling. If an instance of CakeEvent is provided, then then Event name will parsed to + * get the callback name. This is done by getting the last word after any dot in the event name + * (eg. `Model.afterSave` event will trigger the `afterSave` callback) + * @param array $params Array of parameters for the triggered callback. + * @param array $options Array of options. + * @return mixed Either the last result or all results if collectReturn is on. + * @throws CakeException when modParams is used with an index that does not exist. + */ + public function trigger($callback, $params = array(), $options = array()) { + if (empty($this->_enabled)) { + return true; + } + $subject = null; + if ($callback instanceof CakeEvent) { + $event = $callback; + if (is_array($event->data)) { + $params =& $event->data; + } + if (empty($event->omitSubject)) { + $subject = $event->subject(); + } - foreach (array('break', 'breakOn', 'collectReturn', 'modParams') as $opt) { - if (isset($event->{$opt})) { - $options[$opt] = $event->{$opt}; - } - } - $parts = explode('.', $event->name()); - $callback = array_pop($parts); - } - $options += array( - 'break' => false, - 'breakOn' => false, - 'collectReturn' => false, - 'modParams' => false - ); - $collected = array(); - $list = array_keys($this->_enabled); - if ($options['modParams'] !== false && !isset($params[$options['modParams']])) { - throw new CakeException(__d('cake_dev', 'Cannot use modParams with indexes that do not exist.')); - } - $result = null; - foreach ($list as $name) { - $result = call_user_func_array(array($this->_loaded[$name], $callback), array_filter(compact('subject')) + $params); - if ($options['collectReturn'] === true) { - $collected[] = $result; - } - if ($options['break'] && ($result === $options['breakOn'] || - (is_array($options['breakOn']) && in_array($result, $options['breakOn'], true))) - ) { - return $result; - } elseif ($options['modParams'] !== false && !in_array($result, array(true, false, null), true)) { - $params[$options['modParams']] = $result; - } - } - if ($options['modParams'] !== false) { - return $params[$options['modParams']]; - } - return $options['collectReturn'] ? $collected : $result; - } + foreach (array('break', 'breakOn', 'collectReturn', 'modParams') as $opt) { + if (isset($event->{$opt})) { + $options[$opt] = $event->{$opt}; + } + } + $parts = explode('.', $event->name()); + $callback = array_pop($parts); + } + $options += array( + 'break' => false, + 'breakOn' => false, + 'collectReturn' => false, + 'modParams' => false + ); + $collected = array(); + $list = array_keys($this->_enabled); + if ($options['modParams'] !== false && !isset($params[$options['modParams']])) { + throw new CakeException(__d('cake_dev', 'Cannot use modParams with indexes that do not exist.')); + } + $result = null; + foreach ($list as $name) { + $result = call_user_func_array(array($this->_loaded[$name], $callback), array_values(array_filter(compact('subject')) + $params)); + if ($options['collectReturn'] === true) { + $collected[] = $result; + } + if ($options['break'] && ($result === $options['breakOn'] || + (is_array($options['breakOn']) && in_array($result, $options['breakOn'], true))) + ) { + return $result; + } elseif ($options['modParams'] !== false && !in_array($result, array(true, false, null), true)) { + $params[$options['modParams']] = $result; + } + } + if ($options['modParams'] !== false) { + return $params[$options['modParams']]; + } + return $options['collectReturn'] ? $collected : $result; + } -/** - * Provide public read access to the loaded objects - * - * @param string $name Name of property to read - * @return mixed - */ - public function __get($name) { - if (isset($this->_loaded[$name])) { - return $this->_loaded[$name]; - } - return null; - } + /** + * Provide public read access to the loaded objects + * + * @param string $name Name of property to read + * @return mixed + */ + public function __get($name) { + if (isset($this->_loaded[$name])) { + return $this->_loaded[$name]; + } + return null; + } -/** - * Provide isset access to _loaded - * - * @param string $name Name of object being checked. - * @return bool - */ - public function __isset($name) { - return isset($this->_loaded[$name]); - } + /** + * Provide isset access to _loaded + * + * @param string $name Name of object being checked. + * @return bool + */ + public function __isset($name) { + return isset($this->_loaded[$name]); + } -/** - * Enables callbacks on an object or array of objects - * - * @param string|array $name CamelCased name of the object(s) to enable (string or array) - * @param bool $prioritize Prioritize enabled list after enabling object(s) - * @return void - */ - public function enable($name, $prioritize = true) { - $enabled = false; - foreach ((array)$name as $object) { - list(, $object) = pluginSplit($object); - if (isset($this->_loaded[$object]) && !isset($this->_enabled[$object])) { - $priority = $this->defaultPriority; - if (isset($this->_loaded[$object]->settings['priority'])) { - $priority = $this->_loaded[$object]->settings['priority']; - } - $this->_enabled[$object] = array($priority); - $enabled = true; - } - } - if ($prioritize && $enabled) { - $this->prioritize(); - } - } + /** + * Enables callbacks on an object or array of objects + * + * @param string|array $name CamelCased name of the object(s) to enable (string or array) + * @param bool $prioritize Prioritize enabled list after enabling object(s) + * @return void + */ + public function enable($name, $prioritize = true) { + $enabled = false; + foreach ((array)$name as $object) { + list(, $object) = pluginSplit($object); + if (isset($this->_loaded[$object]) && !isset($this->_enabled[$object])) { + $priority = $this->defaultPriority; + if (isset($this->_loaded[$object]->settings['priority'])) { + $priority = $this->_loaded[$object]->settings['priority']; + } + $this->_enabled[$object] = array($priority); + $enabled = true; + } + } + if ($prioritize && $enabled) { + $this->prioritize(); + } + } -/** - * Prioritize list of enabled object - * - * @return array Prioritized list of object - */ - public function prioritize() { - $i = 1; - foreach ($this->_enabled as $name => $priority) { - $priority[1] = $i++; - $this->_enabled[$name] = $priority; - } - asort($this->_enabled); - return $this->_enabled; - } + /** + * Prioritize list of enabled object + * + * @return array Prioritized list of object + */ + public function prioritize() { + $i = 1; + foreach ($this->_enabled as $name => $priority) { + $priority[1] = $i++; + $this->_enabled[$name] = $priority; + } + asort($this->_enabled); + return $this->_enabled; + } -/** - * Set priority for an object or array of objects - * - * @param string|array $name CamelCased name of the object(s) to enable (string or array) - * If string the second param $priority is used else it should be an associative array - * with keys as object names and values as priorities to set. - * @param int|null $priority Integer priority to set or null for default - * @return void - */ - public function setPriority($name, $priority = null) { - if (is_string($name)) { - $name = array($name => $priority); - } - foreach ($name as $object => $objectPriority) { - list(, $object) = pluginSplit($object); - if (isset($this->_loaded[$object])) { - if ($objectPriority === null) { - $objectPriority = $this->defaultPriority; - } - $this->_loaded[$object]->settings['priority'] = $objectPriority; - if (isset($this->_enabled[$object])) { - $this->_enabled[$object] = array($objectPriority); - } - } - } - $this->prioritize(); - } + /** + * Set priority for an object or array of objects + * + * @param string|array $name CamelCased name of the object(s) to enable (string or array) + * If string the second param $priority is used else it should be an associative array + * with keys as object names and values as priorities to set. + * @param int|null $priority Integer priority to set or null for default + * @return void + */ + public function setPriority($name, $priority = null) { + if (is_string($name)) { + $name = array($name => $priority); + } + foreach ($name as $object => $objectPriority) { + list(, $object) = pluginSplit($object); + if (isset($this->_loaded[$object])) { + if ($objectPriority === null) { + $objectPriority = $this->defaultPriority; + } + $this->_loaded[$object]->settings['priority'] = $objectPriority; + if (isset($this->_enabled[$object])) { + $this->_enabled[$object] = array($objectPriority); + } + } + } + $this->prioritize(); + } -/** - * Disables callbacks on an object or array of objects. Public object methods are still - * callable as normal. - * - * @param string|array $name CamelCased name of the objects(s) to disable (string or array) - * @return void - */ - public function disable($name) { - foreach ((array)$name as $object) { - list(, $object) = pluginSplit($object); - unset($this->_enabled[$object]); - } - } + /** + * Disables callbacks on an object or array of objects. Public object methods are still + * callable as normal. + * + * @param string|array $name CamelCased name of the objects(s) to disable (string or array) + * @return void + */ + public function disable($name) { + foreach ((array)$name as $object) { + list(, $object) = pluginSplit($object); + unset($this->_enabled[$object]); + } + } -/** - * Gets the list of currently-enabled objects, or, the current status of a single objects - * - * @param string $name Optional. The name of the object to check the status of. If omitted, - * returns an array of currently-enabled object - * @return mixed If $name is specified, returns the boolean status of the corresponding object. - * Otherwise, returns an array of all enabled objects. - */ - public function enabled($name = null) { - if (!empty($name)) { - list(, $name) = pluginSplit($name); - return isset($this->_enabled[$name]); - } - return array_keys($this->_enabled); - } + /** + * Gets the list of currently-enabled objects, or, the current status of a single objects + * + * @param string $name Optional. The name of the object to check the status of. If omitted, + * returns an array of currently-enabled object + * @return mixed If $name is specified, returns the boolean status of the corresponding object. + * Otherwise, returns an array of all enabled objects. + */ + public function enabled($name = null) { + if (!empty($name)) { + list(, $name) = pluginSplit($name); + return isset($this->_enabled[$name]); + } + return array_keys($this->_enabled); + } -/** - * Gets the list of attached objects, or, whether the given object is attached - * - * @param string $name Optional. The name of the object to check the status of. If omitted, - * returns an array of currently-attached objects - * @return mixed If $name is specified, returns the boolean status of the corresponding object. - * Otherwise, returns an array of all attached objects. - * @deprecated 3.0.0 Will be removed in 3.0. Use loaded instead. - */ - public function attached($name = null) { - return $this->loaded($name); - } + /** + * Gets the list of attached objects, or, whether the given object is attached + * + * @param string $name Optional. The name of the object to check the status of. If omitted, + * returns an array of currently-attached objects + * @return mixed If $name is specified, returns the boolean status of the corresponding object. + * Otherwise, returns an array of all attached objects. + * @deprecated 3.0.0 Will be removed in 3.0. Use loaded instead. + */ + public function attached($name = null) { + return $this->loaded($name); + } -/** - * Gets the list of loaded objects, or, whether the given object is loaded - * - * @param string $name Optional. The name of the object to check the status of. If omitted, - * returns an array of currently-loaded objects - * @return mixed If $name is specified, returns the boolean status of the corresponding object. - * Otherwise, returns an array of all loaded objects. - */ - public function loaded($name = null) { - if (!empty($name)) { - list(, $name) = pluginSplit($name); - return isset($this->_loaded[$name]); - } - return array_keys($this->_loaded); - } + /** + * Gets the list of loaded objects, or, whether the given object is loaded + * + * @param string $name Optional. The name of the object to check the status of. If omitted, + * returns an array of currently-loaded objects + * @return mixed If $name is specified, returns the boolean status of the corresponding object. + * Otherwise, returns an array of all loaded objects. + */ + public function loaded($name = null) { + if (!empty($name)) { + list(, $name) = pluginSplit($name); + return isset($this->_loaded[$name]); + } + return array_keys($this->_loaded); + } -/** - * Name of the object to remove from the collection - * - * @param string $name Name of the object to delete. - * @return void - */ - public function unload($name) { - list(, $name) = pluginSplit($name); - unset($this->_loaded[$name], $this->_enabled[$name]); - } + /** + * Name of the object to remove from the collection + * + * @param string $name Name of the object to delete. + * @return void + */ + public function unload($name) { + list(, $name) = pluginSplit($name); + unset($this->_loaded[$name], $this->_enabled[$name]); + } -/** - * Adds or overwrites an instantiated object to the collection - * - * @param string $name Name of the object - * @param CakeObject $object The object to use - * @return array Loaded objects - */ - public function set($name = null, $object = null) { - if (!empty($name) && !empty($object)) { - list(, $name) = pluginSplit($name); - $this->_loaded[$name] = $object; - } - return $this->_loaded; - } + /** + * Adds or overwrites an instantiated object to the collection + * + * @param string $name Name of the object + * @param CakeObject $object The object to use + * @return array Loaded objects + */ + public function set($name = null, $object = null) { + if (!empty($name) && !empty($object)) { + list(, $name) = pluginSplit($name); + $this->_loaded[$name] = $object; + } + return $this->_loaded; + } -/** - * Normalizes an object array, creates an array that makes lazy loading - * easier - * - * @param array $objects Array of child objects to normalize. - * @return array Array of normalized objects. - */ - public static function normalizeObjectArray($objects) { - $normal = array(); - foreach ($objects as $i => $objectName) { - $options = array(); - if (!is_int($i)) { - $options = (array)$objectName; - $objectName = $i; - } - list(, $name) = pluginSplit($objectName); - $normal[$name] = array('class' => $objectName, 'settings' => $options); - } - return $normal; - } + /** + * Normalizes an object array, creates an array that makes lazy loading + * easier + * + * @param array $objects Array of child objects to normalize. + * @return array Array of normalized objects. + */ + public static function normalizeObjectArray($objects) { + $normal = array(); + foreach ($objects as $i => $objectName) { + $options = array(); + if (!is_int($i)) { + $options = (array)$objectName; + $objectName = $i; + } + list(, $name) = pluginSplit($objectName); + $normal[$name] = array('class' => $objectName, 'settings' => $options); + } + return $normal; + } -} +} \ No newline at end of file diff --git a/lib/Cake/VERSION.txt b/lib/Cake/VERSION.txt index 844d3c12..d764bb71 100755 --- a/lib/Cake/VERSION.txt +++ b/lib/Cake/VERSION.txt @@ -17,4 +17,4 @@ // @license https://opensource.org/licenses/mit-license.php MIT License // +--------------------------------------------------------------------------------------------+ // //////////////////////////////////////////////////////////////////////////////////////////////////// -2.10.20 +2.10.24 \ No newline at end of file diff --git a/lib/Cake/View/Helper/FormHelper.php b/lib/Cake/View/Helper/FormHelper.php index af505a47..f758b224 100755 --- a/lib/Cake/View/Helper/FormHelper.php +++ b/lib/Cake/View/Helper/FormHelper.php @@ -30,3141 +30,3141 @@ */ class FormHelper extends AppHelper { -/** - * Other helpers used by FormHelper - * - * @var array - */ - public $helpers = array('Html'); - -/** - * Options used by DateTime fields - * - * @var array - */ - protected $_options = array( - 'day' => 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(); - -/** - * Holds already used DOM ID suffixes to avoid collisions with multiple form field elements. - * - * @var array - */ - protected $_domIdSuffixes = array(); - -/** - * The action attribute value of the last created form. - * Used to make form/request specific hashes for SecurityComponent. - * - * @var string - */ - protected $_lastAction = ''; - -/** - * 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 Model name. - * @return Model|null 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 ($defaultObject && 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 null; - } - - if ($key === 'key') { - return $this->fieldset[$model]['key'] = $object->primaryKey; - } - - if ($key === 'fields') { - if (!isset($this->fieldset[$model]['fields'])) { - $this->fieldset[$model]['fields'] = $object->schema(); - foreach ($object->hasAndBelongsToMany as $alias => $assocData) { - $this->fieldset[$object->alias]['fields'][$alias] = array('type' => 'multiple'); - } - } - if ($field === null || $field === false) { - return $this->fieldset[$model]['fields']; - } elseif (isset($this->fieldset[$model]['fields'][$field])) { - return $this->fieldset[$model]['fields'][$field]; - } - 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(); - foreach (iterator_to_array($object->validator(), true) 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']; - } - 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 Validation rules set. - * @return bool true if field is required to be filled, false otherwise - */ - protected function _isRequiredField($validationRules) { - if (empty($validationRules) || count($validationRules) === 0) { - return false; - } - - $isUpdate = $this->requestType === 'put'; - foreach ($validationRules as $rule) { - $rule->isUpdate($isUpdate); - if ($rule->skip()) { - continue; - } - - return !$rule->allowEmpty; - } - 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 are no errors, or an array of error - * strings. An error string could be ''. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::tagIsInvalid - */ - public function tagIsInvalid() { - $entity = $this->entity(); - $model = array_shift($entity); - - // 0.Model.field. Fudge entity path - if (empty($model) || is_numeric($model)) { - array_splice($entity, 1, 0, $model); - $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, implode('.', $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). Deprecated since 2.8, use `url`. - * - `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 mixed|null $model The model name for which the form is being defined. Should - * include the plugin name for plugin models. e.g. `ContactManager.Contact`. - * If an array is passed and $options argument is empty, the array will be used as options. - * If `false` no model is used. - * @param array $options An array of html attributes and options. - * @return string A formatted opening FORM tag. - * @link https://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) { - list($plugin, $model) = pluginSplit($model, true); - $key = $this->_introspectModel($plugin . $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( - 'type' => ($created && empty($options['action'])) ? 'put' : 'post', - 'action' => null, - 'url' => null, - 'default' => true, - 'encoding' => strtolower(Configure::read('App.encoding')), - 'inputDefaults' => array() - ); - $this->inputDefaults($options['inputDefaults']); - unset($options['inputDefaults']); - - if (isset($options['action'])) { - trigger_error('Using key `action` is deprecated, use `url` directly instead.', E_USER_DEPRECATED); - } - - if (is_array($options['url']) && isset($options['url']['action'])) { - $options['action'] = $options['url']['action']; - } - - 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 (!isset($options['action'][0]) && !empty($id)) { - $options['action'][0] = $id; - } - } elseif (is_string($options['url'])) { - $options['action'] = $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' => static::SECURE_SKIP - )); - default: - $htmlAttributes['method'] = 'post'; - } - $this->requestType = strtolower($options['type']); - - $action = null; - if ($options['action'] !== false && $options['url'] !== false) { - $action = $this->url($options['action']); - } - unset($options['url']); - - $this->_lastAction($options['action']); - unset($options['type'], $options['action']); - - if (!$options['default']) { - 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(); - if ($this->requestType !== 'get') { - $append .= $this->_csrfField(); - } - - if (!empty($append)) { - $append = $this->Html->useTag('hiddenblock', $append); - } - - if ($model !== false) { - $this->setEntity($model, true); - $this->_introspectModel($model, 'fields'); - } - - if ($action === null) { - return $this->Html->useTag('formwithoutaction', $htmlAttributes) . $append; - } - - 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' => static::SECURE_SKIP, - 'autocomplete' => 'off', - )); - } - -/** - * 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')
value="save" name="Whatever" - * array('label' => 'save', 'name' => 'Whatever', 'div' => array('class' => 'good'));
value="save" name="Whatever" - * ``` - * - * If $secureAttributes is set, these html attributes will be merged into the hidden input tags generated for the - * Security Component. This is especially useful to set HTML5 attributes like 'form' - * - * @param string|array $options as a string will use $options as the value of button, - * @param array $secureAttributes will be passed as html attributes into the hidden input elements generated for the - * Security Component. - * @return string a closing FORM tag optional submit button. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#closing-the-form - */ - public function end($options = null, $secureAttributes = array()) { - $out = null; - $submit = null; - - if ($options !== null) { - $submitOptions = array(); - if (is_string($options)) { - $submit = $options; - } else { - if (isset($options['label'])) { - $submit = $options['label']; - unset($options['label']); - } - $submitOptions = $options; - } - $out .= $this->submit($submit, $submitOptions); - } - if ($this->requestType !== 'get' && - isset($this->request['_Token']) && - !empty($this->request['_Token']) - ) { - $out .= $this->secure($this->fields, $secureAttributes); - $this->fields = array(); - } - $this->setEntity(null); - $out .= $this->Html->useTag('formend'); - - $this->_unlockedFields = array(); - $this->_View->modelScope = false; - $this->requestType = null; - return $out; - } - -/** - * Generates a hidden field with a security hash based on the fields used in - * the form. - * - * If $secureAttributes is set, these html attributes will be merged into - * the hidden input tags generated for the Security Component. This is - * especially useful to set HTML5 attributes like 'form'. - * - * @param array|null $fields If set specifies the list of fields to use when - * generating the hash, else $this->fields is being used. - * @param array $secureAttributes will be passed as html attributes into the hidden - * input elements generated for the Security Component. - * @return string|null A hidden input field with a security hash, otherwise null. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::secure - */ - public function secure($fields = array(), $secureAttributes = array()) { - if (!isset($this->request['_Token']) || empty($this->request['_Token'])) { - return null; - } - $debugSecurity = Configure::read('debug'); - if (isset($secureAttributes['debugSecurity'])) { - $debugSecurity = $debugSecurity && $secureAttributes['debugSecurity']; - unset($secureAttributes['debugSecurity']); - } - - $originalFields = $fields; - - $locked = array(); - $unlockedFields = $this->_unlockedFields; - - foreach ($fields as $key => $value) { - if (!is_int($key)) { - $locked[$key] = $value; - unset($fields[$key]); - } - } - - sort($unlockedFields, SORT_STRING); - sort($fields, SORT_STRING); - ksort($locked, SORT_STRING); - $fields += $locked; - - $locked = implode(array_keys($locked), '|'); - $unlocked = implode($unlockedFields, '|'); - $hashParts = array( - $this->_lastAction, - serialize($fields), - $unlocked, - Configure::read('Security.salt') - ); - $fields = Security::hash(implode('', $hashParts), 'sha1'); - - $tokenFields = array_merge($secureAttributes, array( - 'value' => urlencode($fields . ':' . $locked), - 'id' => 'TokenFields' . mt_rand(), - 'secure' => static::SECURE_SKIP, - 'autocomplete' => 'off', - )); - $out = $this->hidden('_Token.fields', $tokenFields); - $tokenUnlocked = array_merge($secureAttributes, array( - 'value' => urlencode($unlocked), - 'id' => 'TokenUnlocked' . mt_rand(), - 'secure' => static::SECURE_SKIP, - 'autocomplete' => 'off', - )); - $out .= $this->hidden('_Token.unlocked', $tokenUnlocked); - if ($debugSecurity) { - $tokenDebug = array_merge($secureAttributes, array( - 'value' => urlencode(json_encode(array( - $this->_lastAction, - $originalFields, - $this->_unlockedFields - ))), - 'id' => 'TokenDebug' . mt_rand(), - 'secure' => static::SECURE_SKIP, - )); - $out .= $this->hidden('_Token.debug', $tokenDebug); - } - - return $this->Html->useTag('hiddenblock', $out); - } - -/** - * Add to or get the list of fields that are currently unlocked. - * Unlocked fields are not included in the field hash used by SecurityComponent - * unlocking a field once its been added to the list of secured fields will remove - * it from the list of fields. - * - * @param string $name The dot separated name for the field. - * @return mixed Either null, or the list of fields. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::unlockField - */ - public function unlockField($name = null) { - if ($name === null) { - return $this->_unlockedFields; - } - if (!in_array($name, $this->_unlockedFields)) { - $this->_unlockedFields[] = $name; - } - $index = array_search($name, $this->fields); - if ($index !== false) { - unset($this->fields[$index]); - } - unset($this->fields[$name]); - } - -/** - * Determine which fields of a form should be used for hash. - * Populates $this->fields - * - * @param bool $lock Whether this field should be part of the validation - * or excluded as part of the unlockedFields. - * @param string|array $field Reference to field to be secured. Should be dot separated to indicate nesting. - * @param mixed $value Field value, if value should not be tampered with. - * @return void - */ - protected function _secure($lock, $field = null, $value = null) { - if (!$field) { - $field = $this->entity(); - } elseif (is_string($field)) { - $field = explode('.', $field); - } - if (is_array($field)) { - $field = Hash::filter($field); - } - - foreach ($this->_unlockedFields as $unlockField) { - $unlockParts = explode('.', $unlockField); - if (array_values(array_intersect($field, $unlockParts)) === $unlockParts) { - return; - } - } - - $field = implode('.', $field); - $field = preg_replace('/(\.\d+)+$/', '', $field); - - if ($lock) { - if (!in_array($field, $this->fields)) { - if ($value !== null) { - return $this->fields[$field] = $value; - } elseif (isset($this->fields[$field]) && $value === null) { - unset($this->fields[$field]); - } - $this->fields[] = $field; - } - } else { - $this->unlockField($field); - } - } - -/** - * Returns true if there is an error for the given field, otherwise false - * - * @param string $field This should be "Modelname.fieldname" - * @return bool If there are errors this method returns true, else false. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::isFieldError - */ - public function isFieldError($field) { - $this->setEntity($field); - return (bool)$this->tagIsInvalid(); - } - -/** - * Returns a formatted error message for given FORM field, NULL if no errors. - * - * ### Options: - * - * - `escape` boolean - Whether or not to html escape the contents of the error. - * - `wrap` mixed - Whether or not the error message should be wrapped in a div. If a - * string, will be used as the HTML tag to use. - * - `class` string - The class name for the error message - * - * @param string $field A field name, like "Modelname.fieldname" - * @param string|array $text Error message as string or array of messages. - * If array contains `attributes` key it will be used as options for error container - * @param array $options Rendering options for
wrapper tag - * @return string|null If there are errors this method returns an error message, otherwise null. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::error - */ - public function error($field, $text = null, $options = array()) { - $defaults = array('wrap' => true, 'class' => 'error-message', 'escape' => true); - $options += $defaults; - $this->setEntity($field); - - $error = $this->tagIsInvalid(); - if ($error === false) { - return null; - } - if (is_array($text)) { - if (isset($text['attributes']) && is_array($text['attributes'])) { - $options = array_merge($options, $text['attributes']); - unset($text['attributes']); - } - $tmp = array(); - foreach ($error as &$e) { - if (isset($text[$e])) { - $tmp[] = $text[$e]; - } else { - $tmp[] = $e; - } - } - $text = $tmp; - } - - if ($text !== null) { - $error = $text; - } - if (is_array($error)) { - foreach ($error as &$e) { - if (is_numeric($e)) { - $e = __d('cake', 'Error in field %s', Inflector::humanize($this->field())); - } - } - } - if ($options['escape']) { - $error = h($error); - unset($options['escape']); - } - if (is_array($error)) { - if (count($error) > 1) { - $listParams = array(); - if (isset($options['listOptions'])) { - if (is_string($options['listOptions'])) { - $listParams[] = $options['listOptions']; - } else { - if (isset($options['listOptions']['itemOptions'])) { - $listParams[] = $options['listOptions']['itemOptions']; - unset($options['listOptions']['itemOptions']); - } else { - $listParams[] = array(); - } - if (isset($options['listOptions']['tag'])) { - $listParams[] = $options['listOptions']['tag']; - unset($options['listOptions']['tag']); - } - array_unshift($listParams, $options['listOptions']); - } - unset($options['listOptions']); - } - array_unshift($listParams, $error); - $error = call_user_func_array(array($this->Html, 'nestedList'), $listParams); - } else { - $error = array_pop($error); - } - } - if ($options['wrap']) { - $tag = is_string($options['wrap']) ? $options['wrap'] : 'div'; - unset($options['wrap']); - return $this->Html->tag($tag, $error, $options); - } - return $error; - } - -/** - * Returns a formatted LABEL element for HTML FORMs. Will automatically generate - * a `for` attribute if one is not provided. - * - * ### Options - * - * - `for` - Set the for attribute, if its not defined the for attribute - * will be generated from the $fieldName parameter using - * FormHelper::domId(). - * - * Examples: - * - * The text and for attribute are generated off of the fieldname - * - * ``` - * echo $this->Form->label('Post.published'); - * - * ``` - * - * Custom text: - * - * ``` - * echo $this->Form->label('Post.published', 'Publish'); - * - * ``` - * - * Custom class name: - * - * ``` - * echo $this->Form->label('Post.published', 'Publish', 'required'); - * - * ``` - * - * Custom attributes: - * - * ``` - * echo $this->Form->label('Post.published', 'Publish', array( - * 'for' => 'post-publish' - * )); - * - * ``` - * - * *Warning* Unlike most FormHelper methods, this method does not automatically - * escape the $text parameter. You must escape the $text parameter yourself if you - * are using user supplied data. - * - * @param string $fieldName This should be "Modelname.fieldname" - * @param string $text Text that will appear in the label field. If - * $text is left undefined the text will be inflected from the - * fieldName. - * @param array|string $options An array of HTML attributes, or a string, to be used as a class name. - * @return string The formatted LABEL element - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::label - */ - public function label($fieldName = null, $text = null, $options = array()) { - if ($fieldName === null) { - $fieldName = implode('.', $this->entity()); - } - - if ($text === null) { - if (strpos($fieldName, '.') !== false) { - $fieldElements = explode('.', $fieldName); - $text = array_pop($fieldElements); - } else { - $text = $fieldName; - } - if (substr($text, -3) === '_id') { - $text = substr($text, 0, -3); - } - $text = __(Inflector::humanize(Inflector::underscore($text))); - } - - if (is_string($options)) { - $options = array('class' => $options); - } - - if (isset($options['for'])) { - $labelFor = $options['for']; - unset($options['for']); - } else { - $labelFor = $this->domId($fieldName); - } - - return $this->Html->useTag('label', $labelFor, $options, $text); - } - -/** - * Generate a set of inputs for `$fields`. If $fields is null the fields of current model - * will be used. - * - * You can customize individual inputs through `$fields`. - * ``` - * $this->Form->inputs(array( - * 'name' => array('label' => 'custom label') - * )); - * ``` - * - * In addition to controller fields output, `$fields` can be used to control legend - * and fieldset rendering. - * `$this->Form->inputs('My legend');` Would generate an input set with a custom legend. - * Passing `fieldset` and `legend` key in `$fields` array has been deprecated since 2.3, - * for more fine grained control use the `fieldset` and `legend` keys in `$options` param. - * - * @param array $fields An array of fields to generate inputs for, or null. - * @param array $blacklist A simple array of fields to not create inputs for. - * @param array $options Options array. Valid keys are: - * - `fieldset` Set to false to disable the fieldset. If a string is supplied it will be used as - * the class name for the fieldset element. - * - `legend` Set to false to disable the legend for the generated input set. Or supply a string - * to customize the legend text. - * @return string Completed form inputs. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::inputs - */ - public function inputs($fields = null, $blacklist = null, $options = array()) { - $fieldset = $legend = true; - $modelFields = array(); - $model = $this->model(); - if ($model) { - $modelFields = array_keys((array)$this->_introspectModel($model, 'fields')); - } - if (is_array($fields)) { - if (array_key_exists('legend', $fields) && !in_array('legend', $modelFields)) { - $legend = $fields['legend']; - unset($fields['legend']); - } - - if (isset($fields['fieldset']) && !in_array('fieldset', $modelFields)) { - $fieldset = $fields['fieldset']; - unset($fields['fieldset']); - } - } elseif ($fields !== null) { - $fieldset = $legend = $fields; - if (!is_bool($fieldset)) { - $fieldset = true; - } - $fields = array(); - } - - if (isset($options['legend'])) { - $legend = $options['legend']; - unset($options['legend']); - } - - if (isset($options['fieldset'])) { - $fieldset = $options['fieldset']; - unset($options['fieldset']); - } - - if (empty($fields)) { - $fields = $modelFields; - } - - if ($legend === true) { - $actionName = __d('cake', 'New %s'); - $isEdit = ( - strpos($this->request->params['action'], 'update') !== false || - strpos($this->request->params['action'], 'edit') !== false - ); - if ($isEdit) { - $actionName = __d('cake', 'Edit %s'); - } - $modelName = Inflector::humanize(Inflector::underscore($model)); - $legend = sprintf($actionName, __($modelName)); - } - - $out = null; - foreach ($fields as $name => $options) { - if (is_numeric($name) && !is_array($options)) { - $name = $options; - $options = array(); - } - $entity = explode('.', $name); - $blacklisted = ( - is_array($blacklist) && - (in_array($name, $blacklist) || in_array(end($entity), $blacklist)) - ); - if ($blacklisted) { - continue; - } - $out .= $this->input($name, $options); - } - - if (is_string($fieldset)) { - $fieldsetClass = array('class' => $fieldset); - } else { - $fieldsetClass = ''; - } - - if ($fieldset) { - if ($legend) { - $out = $this->Html->useTag('legend', $legend) . $out; - } - $out = $this->Html->useTag('fieldset', $fieldsetClass, $out); - } - return $out; - } - -/** - * Generates a form input element complete with label and wrapper div - * - * ### Options - * - * See each field type method for more information. Any options that are part of - * $attributes or $options for the different **type** methods can be included in `$options` for input().i - * Additionally, any unknown keys that are not in the list below, or part of the selected type's options - * will be treated as a regular html attribute for the generated input. - * - * - `type` - Force the type of widget you want. e.g. `type => 'select'` - * - `label` - Either a string label, or an array of options for the label. See FormHelper::label(). - * - `div` - Either `false` to disable the div, or an array of options for the div. - * See HtmlHelper::div() for more options. - * - `options` - For widgets that take options e.g. radio, select. - * - `error` - Control the error message that is produced. Set to `false` to disable any kind of error reporting (field - * error and error messages). - * - `errorMessage` - Boolean to control rendering error messages (field error will still occur). - * - `empty` - String or boolean to enable empty select box options. - * - `before` - Content to place before the label + input. - * - `after` - Content to place after the label + input. - * - `between` - Content to place between the label + input. - * - `format` - Format template for element order. Any element that is not in the array, will not be in the output. - * - Default input format order: array('before', 'label', 'between', 'input', 'after', 'error') - * - Default checkbox format order: array('before', 'input', 'between', 'label', 'after', 'error') - * - Hidden input will not be formatted - * - Radio buttons cannot have the order of input and label elements controlled with these settings. - * - * @param string $fieldName This should be "Modelname.fieldname" - * @param array $options Each type of input takes different options. - * @return string Completed form widget. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#creating-form-elements - */ - public function input($fieldName, $options = array()) { - $this->setEntity($fieldName); - $options = $this->_parseOptions($options); - - $divOptions = $this->_divOptions($options); - unset($options['div']); - - if ($options['type'] === 'radio' && isset($options['options'])) { - $radioOptions = (array)$options['options']; - unset($options['options']); - } else { - $radioOptions = array(); - } - - $label = $this->_getLabel($fieldName, $options); - if ($options['type'] !== 'radio') { - unset($options['label']); - } - - $error = $this->_extractOption('error', $options, null); - unset($options['error']); - - $errorMessage = $this->_extractOption('errorMessage', $options, true); - unset($options['errorMessage']); - - $selected = $this->_extractOption('selected', $options, null); - unset($options['selected']); - - if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time') { - $dateFormat = $this->_extractOption('dateFormat', $options, 'MDY'); - $timeFormat = $this->_extractOption('timeFormat', $options, 12); - unset($options['dateFormat'], $options['timeFormat']); - } else { - $dateFormat = 'MDY'; - $timeFormat = 12; - } - - $type = $options['type']; - $out = array('before' => $options['before'], 'label' => $label, 'between' => $options['between'], 'after' => $options['after']); - $format = $this->_getFormat($options); - - unset($options['type'], $options['before'], $options['between'], $options['after'], $options['format']); - - $out['error'] = null; - if ($type !== 'hidden' && $error !== false) { - $errMsg = $this->error($fieldName, $error); - if ($errMsg) { - $divOptions = $this->addClass($divOptions, Hash::get($divOptions, 'errorClass', 'error')); - if ($errorMessage) { - $out['error'] = $errMsg; - } - } - } - - if ($type === 'radio' && isset($out['between'])) { - $options['between'] = $out['between']; - $out['between'] = null; - } - $out['input'] = $this->_getInput(compact('type', 'fieldName', 'options', 'radioOptions', 'selected', 'dateFormat', 'timeFormat')); - - $output = ''; - foreach ($format as $element) { - $output .= $out[$element]; - } - - if (!empty($divOptions['tag'])) { - $tag = $divOptions['tag']; - unset($divOptions['tag'], $divOptions['errorClass']); - $output = $this->Html->tag($tag, $output, $divOptions); - } - return $output; - } - -/** - * Generates an input element - * - * @param array $args The options for the input element - * @return string The generated input element - */ - protected function _getInput($args) { - extract($args); - switch ($type) { - case 'hidden': - return $this->hidden($fieldName, $options); - case 'checkbox': - return $this->checkbox($fieldName, $options); - case 'radio': - return $this->radio($fieldName, $radioOptions, $options); - case 'file': - return $this->file($fieldName, $options); - case 'select': - $options += array('options' => array(), 'value' => $selected); - $list = $options['options']; - unset($options['options']); - return $this->select($fieldName, $list, $options); - case 'time': - $options += array('value' => $selected); - return $this->dateTime($fieldName, null, $timeFormat, $options); - case 'date': - $options += array('value' => $selected); - return $this->dateTime($fieldName, $dateFormat, null, $options); - case 'datetime': - $options += array('value' => $selected); - return $this->dateTime($fieldName, $dateFormat, $timeFormat, $options); - case 'textarea': - return $this->textarea($fieldName, $options + array('cols' => '30', 'rows' => '6')); - case 'url': - return $this->text($fieldName, array('type' => 'url') + $options); - default: - return $this->{$type}($fieldName, $options); - } - } - -/** - * Generates input options array - * - * @param array $options Options list. - * @return array Options - */ - protected function _parseOptions($options) { - $options = array_merge( - array('before' => null, 'between' => null, 'after' => null, 'format' => null), - $this->_inputDefaults, - $options - ); - - if (!isset($options['type'])) { - $options = $this->_magicOptions($options); - } - - if (in_array($options['type'], array('radio', 'select'))) { - $options = $this->_optionsOptions($options); - } - - $options = $this->_maxLength($options); - - if (isset($options['rows']) || isset($options['cols'])) { - $options['type'] = 'textarea'; - } - - if ($options['type'] === 'datetime' || $options['type'] === 'date' || $options['type'] === 'time' || $options['type'] === 'select') { - $options += array('empty' => false); - } - return $options; - } - -/** - * Generates list of options for multiple select - * - * @param array $options Options list. - * @return array - */ - protected function _optionsOptions($options) { - if (isset($options['options'])) { - return $options; - } - $varName = Inflector::variable( - Inflector::pluralize(preg_replace('/_id$/', '', $this->field())) - ); - $varOptions = $this->_View->get($varName); - if (!is_array($varOptions)) { - return $options; - } - if ($options['type'] !== 'radio') { - $options['type'] = 'select'; - } - $options['options'] = $varOptions; - return $options; - } - -/** - * Magically set option type and corresponding options - * - * @param array $options Options list. - * @return array - */ - protected function _magicOptions($options) { - $modelKey = $this->model(); - $fieldKey = $this->field(); - $options['type'] = 'text'; - if (isset($options['options'])) { - $options['type'] = 'select'; - } elseif (in_array($fieldKey, array('psword', 'passwd', 'password'))) { - $options['type'] = 'password'; - } elseif (in_array($fieldKey, array('tel', 'telephone', 'phone'))) { - $options['type'] = 'tel'; - } elseif ($fieldKey === 'email') { - $options['type'] = 'email'; - } elseif (isset($options['checked'])) { - $options['type'] = 'checkbox'; - } elseif ($fieldDef = $this->_introspectModel($modelKey, 'fields', $fieldKey)) { - $type = $fieldDef['type']; - $primaryKey = $this->fieldset[$modelKey]['key']; - $map = array( - 'string' => 'text', - 'datetime' => 'datetime', - 'boolean' => 'checkbox', - 'timestamp' => 'datetime', - 'text' => 'textarea', - 'time' => 'time', - 'date' => 'date', - 'float' => 'number', - 'integer' => 'number', - 'smallinteger' => 'number', - 'tinyinteger' => 'number', - 'decimal' => 'number', - 'binary' => 'file' - ); - - if (isset($this->map[$type])) { - $options['type'] = $this->map[$type]; - } elseif (isset($map[$type])) { - $options['type'] = $map[$type]; - } - if ($fieldKey === $primaryKey) { - $options['type'] = 'hidden'; - } - if ($options['type'] === 'number' && - !isset($options['step']) - ) { - if ($type === 'decimal' && isset($fieldDef['length'])) { - $decimalPlaces = substr($fieldDef['length'], strpos($fieldDef['length'], ',') + 1); - $options['step'] = sprintf('%.' . $decimalPlaces . 'F', pow(10, -1 * $decimalPlaces)); - } elseif ($type === 'float' || $type === 'decimal') { - $options['step'] = 'any'; - } - } - } - - if (preg_match('/_id$/', $fieldKey) && $options['type'] !== 'hidden') { - $options['type'] = 'select'; - } - - if ($modelKey === $fieldKey) { - $options['type'] = 'select'; - if (!isset($options['multiple'])) { - $options['multiple'] = 'multiple'; - } - } - if (in_array($options['type'], array('text', 'number'))) { - $options = $this->_optionsOptions($options); - } - if ($options['type'] === 'select' && array_key_exists('step', $options)) { - unset($options['step']); - } - - return $options; - } - -/** - * Generate format options - * - * @param array $options Options list. - * @return array - */ - protected function _getFormat($options) { - if ($options['type'] === 'hidden') { - return array('input'); - } - if (is_array($options['format']) && in_array('input', $options['format'])) { - return $options['format']; - } - if ($options['type'] === 'checkbox') { - return array('before', 'input', 'between', 'label', 'after', 'error'); - } - return array('before', 'label', 'between', 'input', 'after', 'error'); - } - -/** - * Generate label for input - * - * @param string $fieldName Field name. - * @param array $options Options list. - * @return bool|string false or Generated label element - */ - protected function _getLabel($fieldName, $options) { - if ($options['type'] === 'radio') { - return false; - } - - $label = false; - if (isset($options['label'])) { - $label = $options['label']; - } - - if ($label === false) { - return false; - } - return $this->_inputLabel($fieldName, $label, $options); - } - -/** - * Calculates maxlength option - * - * @param array $options Options list. - * @return array - */ - protected function _maxLength($options) { - $fieldDef = $this->_introspectModel($this->model(), 'fields', $this->field()); - $autoLength = ( - !array_key_exists('maxlength', $options) && - isset($fieldDef['length']) && - is_scalar($fieldDef['length']) && - $fieldDef['length'] < 1000000 && - $fieldDef['type'] !== 'decimal' && - $fieldDef['type'] !== 'time' && - $fieldDef['type'] !== 'datetime' && - $options['type'] !== 'select' - ); - if ($autoLength && - in_array($options['type'], array('text', 'textarea', 'email', 'tel', 'url', 'search')) - ) { - $options['maxlength'] = (int)$fieldDef['length']; - } - return $options; - } - -/** - * Generate div options for input - * - * @param array $options Options list. - * @return array - */ - protected function _divOptions($options) { - if ($options['type'] === 'hidden') { - return array(); - } - $div = $this->_extractOption('div', $options, true); - if (!$div) { - return array(); - } - - $divOptions = array('class' => 'input'); - $divOptions = $this->addClass($divOptions, $options['type']); - if (is_string($div)) { - $divOptions['class'] = $div; - } elseif (is_array($div)) { - $divOptions = array_merge($divOptions, $div); - } - if ($this->_extractOption('required', $options) !== false && - $this->_introspectModel($this->model(), 'validates', $this->field()) - ) { - $divOptions = $this->addClass($divOptions, 'required'); - } - if (!isset($divOptions['tag'])) { - $divOptions['tag'] = 'div'; - } - return $divOptions; - } - -/** - * Extracts a single option from an options array. - * - * @param string $name The name of the option to pull out. - * @param array $options The array of options you want to extract. - * @param mixed $default The default option value - * @return mixed the contents of the option or default - */ - protected function _extractOption($name, $options, $default = null) { - if (array_key_exists($name, $options)) { - return $options[$name]; - } - return $default; - } - -/** - * Generate a label for an input() call. - * - * $options can contain a hash of id overrides. These overrides will be - * used instead of the generated values if present. - * - * @param string $fieldName Field name. - * @param string|array $label Label text or array with text and options. - * @param array $options Options for the label element. 'NONE' option is - * deprecated and will be removed in 3.0 - * @return string Generated label element - */ - protected function _inputLabel($fieldName, $label, $options) { - $labelAttributes = $this->domId(array(), 'for'); - $idKey = null; - if ($options['type'] === 'date' || $options['type'] === 'datetime') { - $firstInput = 'M'; - if (array_key_exists('dateFormat', $options) && - ($options['dateFormat'] === null || $options['dateFormat'] === 'NONE') - ) { - $firstInput = 'H'; - } elseif (!empty($options['dateFormat'])) { - $firstInput = substr($options['dateFormat'], 0, 1); - } - switch ($firstInput) { - case 'D': - $idKey = 'day'; - $labelAttributes['for'] .= 'Day'; - break; - case 'Y': - $idKey = 'year'; - $labelAttributes['for'] .= 'Year'; - break; - case 'M': - $idKey = 'month'; - $labelAttributes['for'] .= 'Month'; - break; - case 'H': - $idKey = 'hour'; - $labelAttributes['for'] .= 'Hour'; - } - } - if ($options['type'] === 'time') { - $labelAttributes['for'] .= 'Hour'; - $idKey = 'hour'; - } - if (isset($idKey) && isset($options['id']) && isset($options['id'][$idKey])) { - $labelAttributes['for'] = $options['id'][$idKey]; - } - - if (is_array($label)) { - $labelText = null; - if (isset($label['text'])) { - $labelText = $label['text']; - unset($label['text']); - } - $labelAttributes = array_merge($labelAttributes, $label); - } else { - $labelText = $label; - } - - if (isset($options['id']) && is_string($options['id'])) { - $labelAttributes = array_merge($labelAttributes, array('for' => $options['id'])); - } - return $this->label($fieldName, $labelText, $labelAttributes); - } - -/** - * Creates a checkbox input widget. - * - * ### Options: - * - * - `value` - the value of the checkbox - * - `checked` - boolean indicate that this checkbox is checked. - * - `hiddenField` - boolean to indicate if you want the results of checkbox() to include - * a hidden input with a value of ''. - * - `disabled` - create a disabled input. - * - `default` - Set the default value for the checkbox. This allows you to start checkboxes - * as checked, without having to check the POST data. A matching POST data value, will overwrite - * the default value. - * - * @param string $fieldName Name of a field, like this "Modelname.fieldname" - * @param array $options Array of HTML attributes. - * @return string An HTML text input element. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs - */ - public function checkbox($fieldName, $options = array()) { - $valueOptions = array(); - if (isset($options['default'])) { - $valueOptions['default'] = $options['default']; - unset($options['default']); - } - - $options += array('value' => 1, 'required' => false); - $options = $this->_initInputField($fieldName, $options) + array('hiddenField' => true); - $value = current($this->value($valueOptions)); - $output = ''; - - if ((!isset($options['checked']) && !empty($value) && $value == $options['value']) || - !empty($options['checked']) - ) { - $options['checked'] = 'checked'; - } - if ($options['hiddenField']) { - $hiddenOptions = array( - 'id' => $options['id'] . '_', - 'name' => $options['name'], - 'value' => ($options['hiddenField'] !== true ? $options['hiddenField'] : '0'), - 'form' => isset($options['form']) ? $options['form'] : null, - 'secure' => false, - ); - if (isset($options['disabled']) && $options['disabled']) { - $hiddenOptions['disabled'] = 'disabled'; - } - $output = $this->hidden($fieldName, $hiddenOptions); - } - unset($options['hiddenField']); - - return $output . $this->Html->useTag('checkbox', $options['name'], array_diff_key($options, array('name' => null))); - } - -/** - * Creates a set of radio widgets. Will create a legend and fieldset - * by default. Use $options to control this - * - * You can also customize each radio input element using an array of arrays: - * - * ``` - * $options = array( - * array('name' => 'United states', 'value' => 'US', 'title' => 'My title'), - * array('name' => 'Germany', 'value' => 'DE', 'class' => 'de-de', 'title' => 'Another title'), - * ); - * ``` - * - * ### Attributes: - * - * - `separator` - define the string in between the radio buttons - * - `between` - the string between legend and input set or array of strings to insert - * strings between each input block - * - `legend` - control whether or not the widget set has a fieldset & legend - * - `fieldset` - sets the class of the fieldset. Fieldset is only generated if legend attribute is provided - * - `value` - indicate a value that is should be checked - * - `label` - boolean to indicate whether or not labels for widgets show be displayed - * - `hiddenField` - boolean to indicate if you want the results of radio() to include - * a hidden input with a value of ''. This is useful for creating radio sets that non-continuous - * - `disabled` - Set to `true` or `disabled` to disable all the radio buttons. - * - `empty` - Set to `true` to create an input with the value '' as the first option. When `true` - * the radio label will be 'empty'. Set this option to a string to control the label value. - * - * @param string $fieldName Name of a field, like this "Modelname.fieldname" - * @param array $options Radio button options array. - * @param array $attributes Array of HTML attributes, and special attributes above. - * @return string Completed radio widget set. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#options-for-select-checkbox-and-radio-inputs - */ - public function radio($fieldName, $options = array(), $attributes = array()) { - $attributes['options'] = $options; - $attributes = $this->_initInputField($fieldName, $attributes); - unset($attributes['options']); - - $showEmpty = $this->_extractOption('empty', $attributes); - if ($showEmpty) { - $showEmpty = ($showEmpty === true) ? __d('cake', 'empty') : $showEmpty; - $options = array('' => $showEmpty) + $options; - } - unset($attributes['empty']); - - $legend = false; - if (isset($attributes['legend'])) { - $legend = $attributes['legend']; - unset($attributes['legend']); - } elseif (count($options) > 1) { - $legend = __(Inflector::humanize($this->field())); - } - - $fieldsetAttrs = ''; - if (isset($attributes['fieldset'])) { - $fieldsetAttrs = array('class' => $attributes['fieldset']); - unset($attributes['fieldset']); - } - - $label = true; - if (isset($attributes['label'])) { - $label = $attributes['label']; - unset($attributes['label']); - } - - $separator = null; - if (isset($attributes['separator'])) { - $separator = $attributes['separator']; - unset($attributes['separator']); - } - - $between = null; - if (isset($attributes['between'])) { - $between = $attributes['between']; - unset($attributes['between']); - } - - $value = null; - if (isset($attributes['value'])) { - $value = $attributes['value']; - } else { - $value = $this->value($fieldName); - } - - $disabled = array(); - if (isset($attributes['disabled'])) { - $disabled = $attributes['disabled']; - } - - $out = array(); - - $hiddenField = isset($attributes['hiddenField']) ? $attributes['hiddenField'] : true; - unset($attributes['hiddenField']); - - if (isset($value) && is_bool($value)) { - $value = $value ? 1 : 0; - } - - $this->_domIdSuffixes = array(); - foreach ($options as $optValue => $optTitle) { - $optionsHere = array('value' => $optValue, 'disabled' => false); - if (is_array($optTitle)) { - if (isset($optTitle['value'])) { - $optionsHere['value'] = $optTitle['value']; - } - - $optionsHere += $optTitle; - $optTitle = $optionsHere['name']; - unset($optionsHere['name']); - } - - if (isset($value) && strval($optValue) === strval($value)) { - $optionsHere['checked'] = 'checked'; - } - $isNumeric = is_numeric($optValue); - if ($disabled && (!is_array($disabled) || in_array((string)$optValue, $disabled, !$isNumeric))) { - $optionsHere['disabled'] = true; - } - $tagName = $attributes['id'] . $this->domIdSuffix($optValue); - - if ($label) { - $labelOpts = is_array($label) ? $label : array(); - $labelOpts += array('for' => $tagName); - $optTitle = $this->label($tagName, $optTitle, $labelOpts); - } - - if (is_array($between)) { - $optTitle .= array_shift($between); - } - $allOptions = $optionsHere + $attributes; - $out[] = $this->Html->useTag('radio', $attributes['name'], $tagName, - array_diff_key($allOptions, array('name' => null, 'type' => null, 'id' => null)), - $optTitle - ); - } - $hidden = null; - - if ($hiddenField) { - if (!isset($value) || $value === '') { - $hidden = $this->hidden($fieldName, array( - 'form' => isset($attributes['form']) ? $attributes['form'] : null, - 'id' => $attributes['id'] . '_', - 'value' => $hiddenField === true ? '' : $hiddenField, - 'name' => $attributes['name'] - )); - } - } - $out = $hidden . implode($separator, $out); - - if (is_array($between)) { - $between = ''; - } - - if ($legend) { - $out = $this->Html->useTag('legend', $legend) . $between . $out; - $out = $this->Html->useTag('fieldset', $fieldsetAttrs, $out); - } - return $out; - } - -/** - * Missing method handler - implements various simple input types. Is used to create inputs - * of various types. e.g. `$this->Form->text();` will create `` while - * `$this->Form->range();` will create `` - * - * ### Usage - * - * `$this->Form->search('User.query', array('value' => 'test'));` - * - * Will make an input like: - * - * `` - * - * The first argument to an input type should always be the fieldname, in `Model.field` format. - * The second argument should always be an array of attributes for the input. - * - * @param string $method Method name / input type to make. - * @param array $params Parameters for the method call - * @return string Formatted input method. - * @throws CakeException When there are no params for the method call. - */ - public function __call($method, $params) { - $options = array(); - if (empty($params)) { - throw new CakeException(__d('cake_dev', 'Missing field name for FormHelper::%s', $method)); - } - if (isset($params[1])) { - $options = $params[1]; - } - if (!isset($options['type'])) { - $options['type'] = $method; - } - $options = $this->_initInputField($params[0], $options); - return $this->Html->useTag('input', $options['name'], array_diff_key($options, array('name' => null))); - } - -/** - * Creates a textarea widget. - * - * ### Options: - * - * - `escape` - Whether or not the contents of the textarea should be escaped. Defaults to true. - * - * @param string $fieldName Name of a field, in the form "Modelname.fieldname" - * @param array $options Array of HTML attributes, and special options above. - * @return string A generated HTML text input element - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::textarea - */ - public function textarea($fieldName, $options = array()) { - $options = $this->_initInputField($fieldName, $options); - $value = null; - - if (array_key_exists('value', $options)) { - $value = $options['value']; - if (!array_key_exists('escape', $options) || $options['escape'] !== false) { - $value = h($value); - } - unset($options['value']); - } - return $this->Html->useTag('textarea', $options['name'], array_diff_key($options, array('type' => null, 'name' => null)), $value); - } - -/** - * Creates a hidden input field. - * - * @param string $fieldName Name of a field, in the form of "Modelname.fieldname" - * @param array $options Array of HTML attributes. - * @return string A generated hidden input - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::hidden - */ - public function hidden($fieldName, $options = array()) { - $options += array('required' => false, 'secure' => true); - - $secure = $options['secure']; - unset($options['secure']); - - $options = $this->_initInputField($fieldName, array_merge( - $options, array('secure' => static::SECURE_SKIP) - )); - - if ($secure === true) { - $this->_secure(true, null, '' . $options['value']); - } - - return $this->Html->useTag('hidden', $options['name'], array_diff_key($options, array('name' => null))); - } - -/** - * Creates file input widget. - * - * @param string $fieldName Name of a field, in the form "Modelname.fieldname" - * @param array $options Array of HTML attributes. - * @return string A generated file input. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/form.html#FormHelper::file - */ - public function file($fieldName, $options = array()) { - $options += array('secure' => true); - $secure = $options['secure']; - $options['secure'] = static::SECURE_SKIP; - - $options = $this->_initInputField($fieldName, $options); - $field = $this->entity(); - - foreach (array('name', 'type', 'tmp_name', 'error', 'size') as $suffix) { - $this->_secure($secure, array_merge($field, array($suffix))); - } - - $exclude = array('name' => null, 'value' => null); - return $this->Html->useTag('file', $options['name'], array_diff_key($options, $exclude)); - } - -/** - * Creates a `', - 'image' => '', - 'tableheader' => '%s', - 'tableheaderrow' => '%s', - 'tablecell' => '%s', - 'tablerow' => '%s', - 'block' => '%s
', - 'blockstart' => '', - 'blockend' => '
', - 'hiddenblock' => '
%s
', - 'tag' => '<%s%s>%s', - 'tagstart' => '<%s%s>', - 'tagend' => '', - 'tagselfclosing' => '<%s%s/>', - 'para' => '%s

', - 'parastart' => '', - 'label' => '', - 'fieldset' => '%s', - 'fieldsetstart' => '
%s', - 'fieldsetend' => '
', - 'legend' => '%s', - 'css' => '', - 'style' => '', - 'charset' => '', - 'ul' => '%s', - 'ol' => '%s', - 'li' => '%s', - 'error' => '%s
', - 'javascriptblock' => '%s', - 'javascriptstart' => '', - 'javascriptend' => '' - ); - -/** - * Breadcrumbs. - * - * @var array - */ - protected $_crumbs = array(); - -/** - * Names of script & css files that have been included once - * - * @var array - */ - protected $_includedAssets = array(); - -/** - * Options for the currently opened script block buffer if any. - * - * @var array - */ - protected $_scriptBlockOptions = array(); - -/** - * Document type definitions - * - * @var array - */ - protected $_docTypes = array( - 'html4-strict' => '', - 'html4-trans' => '', - 'html4-frame' => '', - 'html5' => '', - 'xhtml-strict' => '', - 'xhtml-trans' => '', - 'xhtml-frame' => '', - 'xhtml11' => '' - ); - -/** - * Constructor - * - * ### Settings - * - * - `configFile` A file containing an array of tags you wish to redefine. - * - * ### Customizing tag sets - * - * Using the `configFile` option you can redefine the tag HtmlHelper will use. - * The file named should be compatible with HtmlHelper::loadConfig(). - * - * @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); - if (is_object($this->_View->response)) { - $this->response = $this->_View->response; - } else { - $this->response = new CakeResponse(); - } - if (!empty($settings['configFile'])) { - $this->loadConfig($settings['configFile']); - } - } - -/** - * Adds a link to the breadcrumbs array. - * - * ### Options - * - * - 'prepend' Prepend the breadcrumb to. Using this option - * - * @param string $name Text for link - * @param string $link URL for link (if empty it won't be a link) - * @param string|array $options Link attributes e.g. array('id' => 'selected') - * @return self - * @see HtmlHelper::link() for details on $options that can be used. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#creating-breadcrumb-trails-with-htmlhelper - */ - public function addCrumb($name, $link = null, $options = null) { - $prepend = false; - if (is_array($options) && isset($options['prepend'])) { - $prepend = $options['prepend']; - unset($options['prepend']); - } - if ($prepend) { - array_unshift($this->_crumbs, array($name, $link, $options)); - } else { - array_push($this->_crumbs, array($name, $link, $options)); - } - return $this; - } - -/** - * Returns a doctype string. - * - * Possible doctypes: - * - * - html4-strict: HTML4 Strict. - * - html4-trans: HTML4 Transitional. - * - html4-frame: HTML4 Frameset. - * - html5: HTML5. Default value. - * - xhtml-strict: XHTML1 Strict. - * - xhtml-trans: XHTML1 Transitional. - * - xhtml-frame: XHTML1 Frameset. - * - xhtml11: XHTML1.1. - * - * @param string $type Doctype to use. - * @return string|null Doctype string - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#HtmlHelper::docType - */ - public function docType($type = 'html5') { - if (isset($this->_docTypes[$type])) { - return $this->_docTypes[$type]; - } - return null; - } - -/** - * Creates a link to an external resource and handles basic meta tags - * - * Create a meta tag that is output inline: - * - * `$this->Html->meta('icon', 'favicon.ico'); - * - * Append the meta tag to `$scripts_for_layout`: - * - * `$this->Html->meta('description', 'A great page', array('inline' => false));` - * - * Append the meta tag to custom view block: - * - * `$this->Html->meta('description', 'A great page', array('block' => 'metaTags'));` - * - * ### Options - * - * - `inline` Whether or not the link element should be output inline. Set to false to - * have the meta tag included in `$scripts_for_layout`, and appended to the 'meta' view block. - * - `block` Choose a custom block to append the meta tag to. Using this option - * will override the inline option. - * - * @param string $type The title of the external resource - * @param string|array $url The address of the external resource or string for content attribute - * @param array $options Other attributes for the generated tag. If the type attribute is html, - * rss, atom, or icon, the mime-type is returned. - * @return string A completed `` element. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#HtmlHelper::meta - */ - public function meta($type, $url = null, $options = array()) { - $options += array('inline' => true, 'block' => null); - if (!$options['inline'] && empty($options['block'])) { - $options['block'] = __FUNCTION__; - } - unset($options['inline']); - - if (!is_array($type)) { - $types = array( - 'rss' => array('type' => 'application/rss+xml', 'rel' => 'alternate', 'title' => $type, 'link' => $url), - 'atom' => array('type' => 'application/atom+xml', 'title' => $type, 'link' => $url), - 'icon' => array('type' => 'image/x-icon', 'rel' => 'icon', 'link' => $url), - 'keywords' => array('name' => 'keywords', 'content' => $url), - 'description' => array('name' => 'description', 'content' => $url), - ); - - if ($type === 'icon' && $url === null) { - $types['icon']['link'] = 'favicon.ico'; - } - - if (isset($types[$type])) { - $type = $types[$type]; - } elseif (!isset($options['type']) && $url !== null) { - if (is_array($url) && isset($url['ext'])) { - $type = $types[$url['ext']]; - } else { - $type = $types['rss']; - } - } elseif (isset($options['type']) && isset($types[$options['type']])) { - $type = $types[$options['type']]; - unset($options['type']); - } else { - $type = array(); - } - } - - $options += $type; - $out = null; - - if (isset($options['link'])) { - $options['link'] = $this->assetUrl($options['link']); - if (isset($options['rel']) && $options['rel'] === 'icon') { - $out = sprintf($this->_tags['metalink'], $options['link'], $this->_parseAttributes($options, array('block', 'link'))); - $options['rel'] = 'shortcut icon'; - } - $out .= sprintf($this->_tags['metalink'], $options['link'], $this->_parseAttributes($options, array('block', 'link'))); - } else { - $out = sprintf($this->_tags['meta'], $this->_parseAttributes($options, array('block', 'type'))); - } - - if (empty($options['block'])) { - return $out; - } - $this->_View->append($options['block'], $out); - } - -/** - * Returns a charset META-tag. - * - * @param string $charset The character set to be used in the meta tag. If empty, - * The App.encoding value will be used. Example: "utf-8". - * @return string A meta tag containing the specified character set. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#HtmlHelper::charset - */ - public function charset($charset = null) { - if (empty($charset)) { - $charset = strtolower(Configure::read('App.encoding')); - } - return sprintf($this->_tags['charset'], (!empty($charset) ? $charset : 'utf-8')); - } - -/** - * Creates an HTML link. - * - * If $url starts with "http://" this is treated as an external link. Else, - * it is treated as a path to controller/action and parsed with the - * HtmlHelper::url() method. - * - * If the $url is empty, $title is used instead. - * - * ### Options - * - * - `escape` Set to false to disable escaping of title and attributes. - * - `escapeTitle` Set to false to disable escaping of title. (Takes precedence over value of `escape`) - * - `confirm` JavaScript confirmation message. - * - * @param string $title The content to be wrapped by `` tags. - * @param string|array $url Cake-relative URL or array of URL parameters, or external URL (starts with http://) - * @param array $options Array of options and HTML attributes. - * @param string|bool $confirmMessage JavaScript confirmation message. This - * argument is deprecated as of 2.6. Use `confirm` key in $options instead. - * @return string An `` element. - * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#HtmlHelper::link - */ - public function link($title, $url = null, $options = array(), $confirmMessage = false) { - $escapeTitle = true; - if ($url !== null) { - $url = $this->url($url); - } else { - $url = $this->url($title); - $title = htmlspecialchars_decode($url, ENT_QUOTES); - $title = h(urldecode($title)); - $escapeTitle = false; - } - - if (isset($options['escapeTitle'])) { - $escapeTitle = $options['escapeTitle']; - unset($options['escapeTitle']); - } elseif (isset($options['escape'])) { - $escapeTitle = $options['escape']; - } - - if ($escapeTitle === true) { - $title = h($title); - } elseif (is_string($escapeTitle)) { - $title = htmlentities($title, ENT_QUOTES, $escapeTitle); - } - - if (!empty($options['confirm'])) { - $confirmMessage = $options['confirm']; - unset($options['confirm']); - } - if ($confirmMessage) { - $options['onclick'] = $this->_confirm($confirmMessage, 'return true;', 'return false;', $options); - } elseif (isset($options['default']) && !$options['default']) { - if (isset($options['onclick'])) { - $options['onclick'] .= ' '; - } else { - $options['onclick'] = ''; - } - $options['onclick'] .= 'event.returnValue = false; return false;'; - unset($options['default']); - } - return sprintf($this->_tags['link'], $url, $this->_parseAttributes($options), $title); - } - -/** - * Creates a link element for CSS stylesheets. - * - * ### Usage - * - * Include one CSS file: - * - * `echo $this->Html->css('styles.css');` - * - * Include multiple CSS files: - * - * `echo $this->Html->css(array('one.css', 'two.css'));` - * - * Add the stylesheet to the `$scripts_for_layout` layout var: - * - * `$this->Html->css('styles.css', array('inline' => false));` - * - * Add the stylesheet to a custom block: - * - * `$this->Html->css('styles.css', array('block' => 'layoutCss'));` - * - * ### Options - * - * - `inline` If set to false, the generated tag will be appended to the 'css' block, - * and included in the `$scripts_for_layout` layout variable. Defaults to true. - * - `once` Whether or not the css file should be checked for uniqueness. If true css - * files will only be included once, use false to allow the same - * css to be included more than once per request. - * - `block` Set the name of the block link/style tag will be appended to. - * This overrides the `inline` option. - * - `plugin` False value will prevent parsing path as a plugin - * - `rel` Defaults to 'stylesheet'. If equal to 'import' the stylesheet will be imported. - * - `fullBase` If true the URL will get a full address for the css file. - * - * @param string|array $path The name of a CSS style sheet or an array containing names of - * CSS stylesheets. If `$path` is prefixed with '/', the path will be relative to the webroot - * of your application. Otherwise, the path will be relative to your CSS path, usually webroot/css. - * @param array $options Array of options and HTML arguments. - * @return string CSS `` or `', + 'charset' => '', + 'ul' => '%s', + 'ol' => '%s', + 'li' => '%s', + 'error' => '%s', + 'javascriptblock' => '%s', + 'javascriptstart' => '', + 'javascriptend' => '' + ); + + /** + * Breadcrumbs. + * + * @var array + */ + protected $_crumbs = array(); + + /** + * Names of script & css files that have been included once + * + * @var array + */ + protected $_includedAssets = array(); + + /** + * Options for the currently opened script block buffer if any. + * + * @var array + */ + protected $_scriptBlockOptions = array(); + + /** + * Document type definitions + * + * @var array + */ + protected $_docTypes = array( + 'html4-strict' => '', + 'html4-trans' => '', + 'html4-frame' => '', + 'html5' => '', + 'xhtml-strict' => '', + 'xhtml-trans' => '', + 'xhtml-frame' => '', + 'xhtml11' => '' + ); + + /** + * Constructor + * + * ### Settings + * + * - `configFile` A file containing an array of tags you wish to redefine. + * + * ### Customizing tag sets + * + * Using the `configFile` option you can redefine the tag HtmlHelper will use. + * The file named should be compatible with HtmlHelper::loadConfig(). + * + * @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); + if (is_object($this->_View->response)) { + $this->response = $this->_View->response; + } else { + $this->response = new CakeResponse(); + } + if (!empty($settings['configFile'])) { + $this->loadConfig($settings['configFile']); + } + } + + /** + * Adds a link to the breadcrumbs array. + * + * ### Options + * + * - 'prepend' Prepend the breadcrumb to. Using this option + * + * @param string $name Text for link + * @param string $link URL for link (if empty it won't be a link) + * @param string|array $options Link attributes e.g. array('id' => 'selected') + * @return self + * @see HtmlHelper::link() for details on $options that can be used. + * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#creating-breadcrumb-trails-with-htmlhelper + */ + public function addCrumb($name, $link = null, $options = null) { + $prepend = false; + if (is_array($options) && isset($options['prepend'])) { + $prepend = $options['prepend']; + unset($options['prepend']); + } + if ($prepend) { + array_unshift($this->_crumbs, array($name, $link, $options)); + } else { + array_push($this->_crumbs, array($name, $link, $options)); + } + return $this; + } + + /** + * Returns a doctype string. + * + * Possible doctypes: + * + * - html4-strict: HTML4 Strict. + * - html4-trans: HTML4 Transitional. + * - html4-frame: HTML4 Frameset. + * - html5: HTML5. Default value. + * - xhtml-strict: XHTML1 Strict. + * - xhtml-trans: XHTML1 Transitional. + * - xhtml-frame: XHTML1 Frameset. + * - xhtml11: XHTML1.1. + * + * @param string $type Doctype to use. + * @return string|null Doctype string + * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#HtmlHelper::docType + */ + public function docType($type = 'html5') { + if (isset($this->_docTypes[$type])) { + return $this->_docTypes[$type]; + } + return null; + } + + /** + * Creates a link to an external resource and handles basic meta tags + * + * Create a meta tag that is output inline: + * + * `$this->Html->meta('icon', 'favicon.ico'); + * + * Append the meta tag to `$scripts_for_layout`: + * + * `$this->Html->meta('description', 'A great page', array('inline' => false));` + * + * Append the meta tag to custom view block: + * + * `$this->Html->meta('description', 'A great page', array('block' => 'metaTags'));` + * + * ### Options + * + * - `inline` Whether or not the link element should be output inline. Set to false to + * have the meta tag included in `$scripts_for_layout`, and appended to the 'meta' view block. + * - `block` Choose a custom block to append the meta tag to. Using this option + * will override the inline option. + * + * @param string|array $type The title of the external resource + * @param string|array $url The address of the external resource or string for content attribute + * @param array $options Other attributes for the generated tag. If the type attribute is html, + * rss, atom, or icon, the mime-type is returned. + * @return string A completed `` element. + * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#HtmlHelper::meta + */ + public function meta($type, $url = null, $options = array()) { + $options += array('inline' => true, 'block' => null); + if (!$options['inline'] && empty($options['block'])) { + $options['block'] = __FUNCTION__; + } + unset($options['inline']); + + if (!is_array($type)) { + $types = array( + 'rss' => array('type' => 'application/rss+xml', 'rel' => 'alternate', 'title' => $type, 'link' => $url), + 'atom' => array('type' => 'application/atom+xml', 'title' => $type, 'link' => $url), + 'icon' => array('type' => 'image/x-icon', 'rel' => 'icon', 'link' => $url), + 'keywords' => array('name' => 'keywords', 'content' => $url), + 'description' => array('name' => 'description', 'content' => $url), + ); + + if ($type === 'icon' && $url === null) { + $types['icon']['link'] = 'favicon.ico'; + } + + if (isset($types[$type])) { + $type = $types[$type]; + } elseif (!isset($options['type']) && $url !== null) { + if (is_array($url) && isset($url['ext'])) { + $type = $types[$url['ext']]; + } else { + $type = $types['rss']; + } + } elseif (isset($options['type']) && isset($types[$options['type']])) { + $type = $types[$options['type']]; + unset($options['type']); + } else { + $type = array(); + } + } + + $options += $type; + $out = null; + + if (isset($options['link'])) { + $options['link'] = $this->assetUrl($options['link']); + if (isset($options['rel']) && $options['rel'] === 'icon') { + $out = sprintf($this->_tags['metalink'], $options['link'], $this->_parseAttributes($options, array('block', 'link'))); + $options['rel'] = 'shortcut icon'; + } + $out .= sprintf($this->_tags['metalink'], $options['link'], $this->_parseAttributes($options, array('block', 'link'))); + } else { + $out = sprintf($this->_tags['meta'], $this->_parseAttributes($options, array('block', 'type'))); + } + + if (empty($options['block'])) { + return $out; + } + $this->_View->append($options['block'], $out); + } + + /** + * Returns a charset META-tag. + * + * @param string $charset The character set to be used in the meta tag. If empty, + * The App.encoding value will be used. Example: "utf-8". + * @return string A meta tag containing the specified character set. + * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#HtmlHelper::charset + */ + public function charset($charset = null) { + if (empty($charset)) { + $charset = strtolower(Configure::read('App.encoding')); + } + return sprintf($this->_tags['charset'], (!empty($charset) ? $charset : 'utf-8')); + } + + /** + * Creates an HTML link. + * + * If $url starts with "http://" this is treated as an external link. Else, + * it is treated as a path to controller/action and parsed with the + * HtmlHelper::url() method. + * + * If the $url is empty, $title is used instead. + * + * ### Options + * + * - `escape` Set to false to disable escaping of title and attributes. + * - `escapeTitle` Set to false to disable escaping of title. (Takes precedence over value of `escape`) + * - `confirm` JavaScript confirmation message. + * + * @param string $title The content to be wrapped by `` tags. + * @param string|array $url Cake-relative URL or array of URL parameters, or external URL (starts with http://) + * @param array $options Array of options and HTML attributes. + * @param string|bool $confirmMessage JavaScript confirmation message. This + * argument is deprecated as of 2.6. Use `confirm` key in $options instead. + * @return string An `` element. + * @link https://book.cakephp.org/2.0/en/core-libraries/helpers/html.html#HtmlHelper::link + */ + public function link($title, $url = null, $options = array(), $confirmMessage = false) { + $escapeTitle = true; + if ($url !== null) { + $url = $this->url($url); + } else { + $url = $this->url($title); + $title = htmlspecialchars_decode($url, ENT_QUOTES); + $title = h(urldecode($title)); + $escapeTitle = false; + } + + if (isset($options['escapeTitle'])) { + $escapeTitle = $options['escapeTitle']; + unset($options['escapeTitle']); + } elseif (isset($options['escape'])) { + $escapeTitle = $options['escape']; + } + + if ($escapeTitle === true) { + $title = h($title); + } elseif (is_string($escapeTitle)) { + $title = htmlentities($title, ENT_QUOTES, $escapeTitle); + } + + if (!empty($options['confirm'])) { + $confirmMessage = $options['confirm']; + unset($options['confirm']); + } + if ($confirmMessage) { + $options['onclick'] = $this->_confirm($confirmMessage, 'return true;', 'return false;', $options); + } elseif (isset($options['default']) && !$options['default']) { + if (isset($options['onclick'])) { + $options['onclick'] .= ' '; + } else { + $options['onclick'] = ''; + } + $options['onclick'] .= 'event.returnValue = false; return false;'; + unset($options['default']); + } + return sprintf($this->_tags['link'], $url, $this->_parseAttributes($options), $title); + } + + /** + * Creates a link element for CSS stylesheets. + * + * ### Usage + * + * Include one CSS file: + * + * `echo $this->Html->css('styles.css');` + * + * Include multiple CSS files: + * + * `echo $this->Html->css(array('one.css', 'two.css'));` + * + * Add the stylesheet to the `$scripts_for_layout` layout var: + * + * `$this->Html->css('styles.css', array('inline' => false));` + * + * Add the stylesheet to a custom block: + * + * `$this->Html->css('styles.css', array('block' => 'layoutCss'));` + * + * ### Options + * + * - `inline` If set to false, the generated tag will be appended to the 'css' block, + * and included in the `$scripts_for_layout` layout variable. Defaults to true. + * - `once` Whether or not the css file should be checked for uniqueness. If true css + * files will only be included once, use false to allow the same + * css to be included more than once per request. + * - `block` Set the name of the block link/style tag will be appended to. + * This overrides the `inline` option. + * - `plugin` False value will prevent parsing path as a plugin + * - `rel` Defaults to 'stylesheet'. If equal to 'import' the stylesheet will be imported. + * - `fullBase` If true the URL will get a full address for the css file. + * + * @param string|array $path The name of a CSS style sheet or an array containing names of + * CSS stylesheets. If `$path` is prefixed with '/', the path will be relative to the webroot + * of your application. Otherwise, the path will be relative to your CSS path, usually webroot/css. + * @param array $options Array of options and HTML arguments. + * @return string CSS `` or `