diff --git a/lib/Cake/Console/ConsoleOptionParser.php b/lib/Cake/Console/ConsoleOptionParser.php index 16371165a..97726f0be 100644 --- a/lib/Cake/Console/ConsoleOptionParser.php +++ b/lib/Cake/Console/ConsoleOptionParser.php @@ -585,7 +585,8 @@ class ConsoleOptionParser { $option = $this->_options[$name]; $isBoolean = $option->isBoolean(); $nextValue = $this->_nextToken(); - if (!$isBoolean && !empty($nextValue) && !$this->_optionExists($nextValue)) { + $emptyNextValue = (empty($nextValue) && $nextValue !== '0'); + if (!$isBoolean && !$emptyNextValue && !$this->_optionExists($nextValue)) { array_shift($this->_tokens); $value = $nextValue; } elseif ($isBoolean) { diff --git a/lib/Cake/Model/CakeSchema.php b/lib/Cake/Model/CakeSchema.php index 4208cfbd7..0e7771d52 100644 --- a/lib/Cake/Model/CakeSchema.php +++ b/lib/Cake/Model/CakeSchema.php @@ -580,13 +580,17 @@ class CakeSchema extends Object { if (is_array($values)) { foreach ($values as $key => $val) { if (is_array($val)) { - $vals[] = "'{$key}' => array('" . implode("', '", $val) . "')"; - } elseif (!is_numeric($key)) { + $vals[] = "'{$key}' => array(" . implode(", ", $this->_values($val)) . ")"; + } else { $val = var_export($val, true); if ($val === 'NULL') { $val = 'null'; } - $vals[] = "'{$key}' => {$val}"; + if (!is_numeric($key)) { + $vals[] = "'{$key}' => {$val}"; + } else { + $vals[] = "{$val}"; + } } } } diff --git a/lib/Cake/Model/Datasource/Database/Mysql.php b/lib/Cake/Model/Datasource/Database/Mysql.php index 2f274e9eb..b19df98ac 100644 --- a/lib/Cake/Model/Datasource/Database/Mysql.php +++ b/lib/Cake/Model/Datasource/Database/Mysql.php @@ -466,6 +466,12 @@ class Mysql extends DboSource { $col[] = $idx->Column_name; $index[$idx->Key_name]['column'] = $col; } + if (!empty($idx->Sub_part)) { + if (!isset($index[$idx->Key_name]['length'])) { + $index[$idx->Key_name]['length'] = array(); + } + $index[$idx->Key_name]['length'][$idx->Column_name] = $idx->Sub_part; + } } // @codingStandardsIgnoreEnd $indexes->closeCursor(); @@ -566,6 +572,55 @@ class Mysql extends DboSource { return array(); } +/** + * Format indexes for create table + * + * @param array $indexes An array of indexes to generate SQL from + * @param string $table Optional table name, not used + * @return array An array of SQL statements for indexes + * @see DboSource::buildIndex() + */ + public function buildIndex($indexes, $table = null) { + $join = array(); + foreach ($indexes as $name => $value) { + $out = ''; + if ($name === 'PRIMARY') { + $out .= 'PRIMARY '; + $name = null; + } else { + if (!empty($value['unique'])) { + $out .= 'UNIQUE '; + } + $name = $this->startQuote . $name . $this->endQuote; + } + // length attribute only used for MySQL datasource, for TEXT/BLOB index columns + $out .= 'KEY ' . $name . ' ('; + if (is_array($value['column'])) { + if (isset($value['length'])) { + $vals = array(); + foreach ($value['column'] as $column) { + $name = $this->name($column); + if (isset($value['length'])) { + $name .= $this->_buildIndexSubPart($value['length'], $column); + } + $vals[] = $name; + } + $out .= implode(', ', $vals); + } else { + $out .= implode(', ', array_map(array(&$this, 'name'), $value['column'])); + } + } else { + $out .= $this->name($value['column']); + if (isset($value['length'])) { + $out .= $this->_buildIndexSubPart($value['length'], $value['column']); + } + } + $out .= ')'; + $join[] = $out; + } + return $join; + } + /** * Generate MySQL index alteration statements for a table. * @@ -595,6 +650,23 @@ class Mysql extends DboSource { return $alter; } +/** + * Format length for text indexes + * + * @param array $lengths An array of lengths for a single index + * @param string $column The column for which to generate the index length + * @return string Formatted length part of an index field + */ + protected function _buildIndexSubPart($lengths, $column) { + if (is_null($lengths)) { + return ''; + } + if (!isset($lengths[$column])) { + return ''; + } + return '(' . $lengths[$column] . ')'; + } + /** * Returns an detailed array of sources (tables) in the database. * diff --git a/lib/Cake/Model/Model.php b/lib/Cake/Model/Model.php index 51dd5f159..75a7c131f 100644 --- a/lib/Cake/Model/Model.php +++ b/lib/Cake/Model/Model.php @@ -1574,7 +1574,9 @@ class Model extends Object implements CakeEventListener { * * @param string $name Name of the table field * @param mixed $value Value of the field - * @param array $validate See $options param in Model::save(). Does not respect 'fieldList' key if passed + * @param boolean|array $validate Either a boolean, or an array. + * If a boolean, indicates whether or not to validate before saving. + * If an array, allows control of 'validate' and 'callbacks' options. * @return boolean See Model::save() * @see Model::save() * @link http://book.cakephp.org/2.0/en/models/saving-your-data.html#model-savefield-string-fieldname-string-fieldvalue-validate-false diff --git a/lib/Cake/Network/Email/MailTransport.php b/lib/Cake/Network/Email/MailTransport.php index a037e3418..950459a75 100644 --- a/lib/Cake/Network/Email/MailTransport.php +++ b/lib/Cake/Network/Email/MailTransport.php @@ -42,11 +42,7 @@ class MailTransport extends AbstractTransport { $headers = $this->_headersToString($headers, $eol); $message = implode($eol, $email->message()); - $params = null; - if (!ini_get('safe_mode')) { - $params = isset($this->_config['additionalParameters']) ? $this->_config['additionalParameters'] : null; - } - + $params = isset($this->_config['additionalParameters']) ? $this->_config['additionalParameters'] : null; $this->_mail($to, $email->subject(), $message, $headers, $params); return array('headers' => $headers, 'message' => $message); } @@ -58,16 +54,20 @@ class MailTransport extends AbstractTransport { * @param string $subject email's subject * @param string $message email's body * @param string $headers email's custom headers - * @param string $params additional params for sending email + * @param string $params additional params for sending email, will be ignored when in safe_mode * @throws SocketException if mail could not be sent * @return void */ protected function _mail($to, $subject, $message, $headers, $params = null) { - //@codingStandardsIgnoreStart - if (!@mail($to, $subject, $message, $headers, $params)) { + if (ini_get('safe_mode')) { + //@codingStandardsIgnoreStart + if (!@mail($to, $subject, $message, $headers)) { + throw new SocketException(__d('cake_dev', 'Could not send email.')); + } + } elseif (!@mail($to, $subject, $message, $headers, $params)) { + //@codingStandardsIgnoreEnd throw new SocketException(__d('cake_dev', 'Could not send email.')); } - //@codingStandardsIgnoreEnd } } diff --git a/lib/Cake/Routing/Route/CakeRoute.php b/lib/Cake/Routing/Route/CakeRoute.php index 293aeb58c..83d822f7b 100644 --- a/lib/Cake/Routing/Route/CakeRoute.php +++ b/lib/Cake/Routing/Route/CakeRoute.php @@ -11,8 +11,7 @@ * @since CakePHP(tm) v 1.3 * @license MIT License (http://www.opensource.org/licenses/mit-license.php) */ - -App::uses('Set', 'Utility'); +App::uses('Hash', 'Utility'); /** * A single Route used by the Router to connect requests to diff --git a/lib/Cake/Routing/Router.php b/lib/Cake/Routing/Router.php index 801e88e7a..4b6dafe8a 100644 --- a/lib/Cake/Routing/Router.php +++ b/lib/Cake/Routing/Router.php @@ -266,18 +266,28 @@ class Router { * $options offers four 'special' keys. `pass`, `named`, `persist` and `routeClass` * have special meaning in the $options array. * - * `pass` is used to define which of the routed parameters should be shifted into the pass array. Adding a - * parameter to pass will remove it from the regular route array. Ex. `'pass' => array('slug')` + * - `pass` is used to define which of the routed parameters should be shifted into the pass array. Adding a + * parameter to pass will remove it from the regular route array. Ex. `'pass' => array('slug')` + * - `persist` is used to define which route parameters should be automatically included when generating + * new urls. You can override persistent parameters by redefining them in a url or remove them by + * setting the parameter to `false`. Ex. `'persist' => array('lang')` + * - `routeClass` is used to extend and change how individual routes parse requests and handle reverse routing, + * via a custom routing class. Ex. `'routeClass' => 'SlugRoute'` + * - `named` is used to configure named parameters at the route level. This key uses the same options + * as Router::connectNamed() * - * `persist` is used to define which route parameters should be automatically included when generating - * new urls. You can override persistent parameters by redefining them in a url or remove them by - * setting the parameter to `false`. Ex. `'persist' => array('lang')` + * You can also add additional conditions for matching routes to the $defaults array. + * The following conditions can be used: * - * `routeClass` is used to extend and change how individual routes parse requests and handle reverse routing, - * via a custom routing class. Ex. `'routeClass' => 'SlugRoute'` + * - `[type]` Only match requests for specific content types. + * - `[method]` Only match requests with specific HTTP verbs. + * - `[server]` Only match when $_SERVER['SERVER_NAME'] matches the given value. * - * `named` is used to configure named parameters at the route level. This key uses the same options - * as Router::connectNamed() + * Example of using the `[method]` condition: + * + * `Router::connect('/tasks', array('controller' => 'tasks', 'action' => 'index', '[method]' => 'GET'));` + * + * The above route will only be matched for GET requests. POST requests will fail to match this route. * * @param string $route A string describing the template of the route * @param array $defaults An array describing the default route parameters. These parameters will be used by default diff --git a/lib/Cake/Test/Case/Console/ConsoleOptionParserTest.php b/lib/Cake/Test/Case/Console/ConsoleOptionParserTest.php index 2cf265995..7d3a23ec1 100644 --- a/lib/Cake/Test/Case/Console/ConsoleOptionParserTest.php +++ b/lib/Cake/Test/Case/Console/ConsoleOptionParserTest.php @@ -78,6 +78,18 @@ class ConsoleOptionParserTest extends CakeTestCase { $this->assertEquals(array('test' => 'value', 'help' => false), $result[0], 'Long parameter did not parse out'); } +/** + * test adding an option with a zero value + * + * @return void + */ + public function testAddOptionZero() { + $parser = new ConsoleOptionParser('test', false); + $parser->addOption('count', array()); + $result = $parser->parse(array('--count', '0')); + $this->assertEquals(array('count' => '0', 'help' => false), $result[0], 'Zero parameter did not parse out'); + } + /** * test addOption with an object. * diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php index ea4dda39a..7c4f8dcef 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/MysqlTest.php @@ -330,6 +330,26 @@ class MysqlTest extends CakeTestCase { $result = $this->Dbo->index('with_fulltext', false); $this->Dbo->rawQuery('DROP TABLE ' . $name); $this->assertEquals($expected, $result); + + $name = $this->Dbo->fullTableName('with_text_index'); + $this->Dbo->rawQuery('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, text_field text, primary key(id), KEY `text_index` ( `text_field`(20) ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'text_index' => array('column' => 'text_field', 'unique' => 0, 'length' => array('text_field' => 20)), + ); + $result = $this->Dbo->index('with_text_index', false); + $this->Dbo->rawQuery('DROP TABLE ' . $name); + $this->assertEquals($expected, $result); + + $name = $this->Dbo->fullTableName('with_compound_text_index'); + $this->Dbo->rawQuery('CREATE TABLE ' . $name . ' (id int(11) AUTO_INCREMENT, text_field1 text, text_field2 text, primary key(id), KEY `text_index` ( `text_field1`(20), `text_field2`(20) ));'); + $expected = array( + 'PRIMARY' => array('column' => 'id', 'unique' => 1), + 'text_index' => array('column' => array('text_field1', 'text_field2'), 'unique' => 0, 'length' => array('text_field1' => 20, 'text_field2' => 20)), + ); + $result = $this->Dbo->index('with_compound_text_index', false); + $this->Dbo->rawQuery('DROP TABLE ' . $name); + $this->assertEquals($expected, $result); } /** @@ -2963,6 +2983,19 @@ class MysqlTest extends CakeTestCase { ); $result = $this->Dbo->buildIndex($data); $expected = array('FULLTEXT KEY `MyFtIndex` (`name`, `description`)'); + + $data = array( + 'MyTextIndex' => array('column' => 'text_field', 'length' => array('text_field' => 20)) + ); + $result = $this->Dbo->buildIndex($data); + $expected = array('KEY `MyTextIndex` (`text_field`(20))'); + $this->assertEquals($expected, $result); + + $data = array( + 'MyMultiTextIndex' => array('column' => array('text_field1', 'text_field2'), 'length' => array('text_field1' => 20, 'text_field2' => 20)) + ); + $result = $this->Dbo->buildIndex($data); + $expected = array('KEY `MyMultiTextIndex` (`text_field1`(20), `text_field2`(20))'); $this->assertEquals($expected, $result); } diff --git a/lib/Cake/Test/Case/Utility/DebuggerTest.php b/lib/Cake/Test/Case/Utility/DebuggerTest.php index dd07dc84c..c496c2c7e 100644 --- a/lib/Cake/Test/Case/Utility/DebuggerTest.php +++ b/lib/Cake/Test/Case/Utility/DebuggerTest.php @@ -84,7 +84,13 @@ class DebuggerTest extends CakeTestCase { $this->assertTrue(is_array($result)); $this->assertEquals(4, count($result)); - $pattern = '/<code><span style\="color\: \#\d+">.*?<\?php/'; + $pattern = '/<code>.*?<span style\="color\: \#\d+">.*?<\?php/'; + $this->assertRegExp($pattern, $result[0]); + + $result = Debugger::excerpt(__FILE__, 10, 2); + $this->assertEquals(5, count($result)); + + $pattern = '/<span style\="color\: \#\d{6}">\*<\/span>/'; $this->assertRegExp($pattern, $result[0]); $return = Debugger::excerpt('[internal]', 2, 2); diff --git a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php index e8de415d1..060ea31de 100644 --- a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php @@ -6164,6 +6164,10 @@ class FormHelperTest extends CakeTestCase { '/div' ); $this->assertTags($result, $expected); + + $this->Form->request->data['Model']['upload'] = 'no data should be set in value'; + $result = $this->Form->file('Model.upload'); + $this->assertTags($result, array('input' => array('type' => 'file', 'name' => 'data[Model][upload]', 'id' => 'ModelUpload'))); } /** diff --git a/lib/Cake/Utility/Debugger.php b/lib/Cake/Utility/Debugger.php index 5abcee676..d507073cb 100644 --- a/lib/Cake/Utility/Debugger.php +++ b/lib/Cake/Utility/Debugger.php @@ -396,12 +396,14 @@ class Debugger { return array(); } $data = file_get_contents($file); - if (!empty($data) && strpos($data, "\n") !== false) { + if (empty($data)) { + return $lines; + } + if (strpos($data, "\n") !== false) { $data = explode("\n", $data); } - - if (empty($data) || !isset($data[$line])) { - return; + if (!isset($data[$line])) { + return $lines; } for ($i = $line - ($context + 1); $i < $line + $context; $i++) { if (!isset($data[$i])) { @@ -425,13 +427,23 @@ class Debugger { * @return string */ protected static function _highlight($str) { - static $supportHighlight = null; - if (!$supportHighlight && function_exists('hphp_log')) { - $supportHighlight = false; + if (function_exists('hphp_log')) { return htmlentities($str); } - $supportHighlight = true; - return highlight_string($str, true); + $added = false; + if (strpos($str, '<?php') === false) { + $added = true; + $str = "<?php \n" . $str; + } + $highlight = highlight_string($str, true); + if ($added) { + $highlight = str_replace( + '<?php <br />', + '', + $highlight + ); + } + return $highlight; } /** diff --git a/lib/Cake/View/Helper/FormHelper.php b/lib/Cake/View/Helper/FormHelper.php index a00bef997..77efa1bb5 100644 --- a/lib/Cake/View/Helper/FormHelper.php +++ b/lib/Cake/View/Helper/FormHelper.php @@ -1433,7 +1433,7 @@ class FormHelper extends AppHelper { $showEmpty = $this->_extractOption('empty', $attributes); if ($showEmpty) { - $showEmpty = ($showEmpty === true) ? __('empty') : $showEmpty; + $showEmpty = ($showEmpty === true) ? __d('cake', 'empty') : $showEmpty; $options = array('' => $showEmpty) + $options; } unset($attributes['empty']); @@ -1628,7 +1628,8 @@ class FormHelper extends AppHelper { $this->_secure($secure, array_merge($field, array($suffix))); } - return $this->Html->useTag('file', $options['name'], array_diff_key($options, array('name' => ''))); + $exclude = array('name' => '', 'value' => ''); + return $this->Html->useTag('file', $options['name'], array_diff_key($options, $exclude)); } /** diff --git a/lib/Cake/View/Helper/HtmlHelper.php b/lib/Cake/View/Helper/HtmlHelper.php index 20736b950..0acc346a9 100644 --- a/lib/Cake/View/Helper/HtmlHelper.php +++ b/lib/Cake/View/Helper/HtmlHelper.php @@ -744,7 +744,7 @@ class HtmlHelper extends AppHelper { 'text' => $startText ); } - $startText += array('url' => '/', 'text' => __('Home')); + $startText += array('url' => '/', 'text' => __d('cake', 'Home')); list($url, $text) = array($startText['url'], $startText['text']); unset($startText['url'], $startText['text']); array_unshift($crumbs, array($text, $url, $startText));