diff --git a/lib/Cake/Cache/Engine/MemcacheEngine.php b/lib/Cake/Cache/Engine/MemcacheEngine.php index 2f7e5e1f4..250f02ad8 100644 --- a/lib/Cake/Cache/Engine/MemcacheEngine.php +++ b/lib/Cake/Cache/Engine/MemcacheEngine.php @@ -104,7 +104,7 @@ class MemcacheEngine extends CacheEngine { * @return array Array containing host, port */ protected function _parseServerString($server) { - if ($server[0] === 'u') { + if (strpos($server, 'unix://') === 0) { return array($server, 0); } if (substr($server, 0, 1) === '[') { diff --git a/lib/Cake/Cache/Engine/MemcachedEngine.php b/lib/Cake/Cache/Engine/MemcachedEngine.php index 74ea7c1c3..a2611396a 100644 --- a/lib/Cake/Cache/Engine/MemcachedEngine.php +++ b/lib/Cake/Cache/Engine/MemcachedEngine.php @@ -185,7 +185,7 @@ class MemcachedEngine extends CacheEngine { * @return array Array containing host, port */ protected function _parseServerString($server) { - if ($server[0] === 'u') { + if (strpos($server, 'unix://') === 0) { return array($server, 0); } if (substr($server, 0, 1) === '[') { diff --git a/lib/Cake/Console/Command/Task/ExtractTask.php b/lib/Cake/Console/Command/Task/ExtractTask.php index c9f5712cc..913c5680e 100644 --- a/lib/Cake/Console/Command/Task/ExtractTask.php +++ b/lib/Cake/Console/Command/Task/ExtractTask.php @@ -448,7 +448,7 @@ class ExtractTask extends AppShell { if ($categoryName !== 'LC_TIME') { $this->_addTranslation($categoryName, $domain, $singular, $details); } - } else { + } elseif (!is_array($this->_tokens[$count - 1]) || $this->_tokens[$count - 1][0] != T_FUNCTION) { $this->_markerError($this->_file, $line, $functionName, $count); } } diff --git a/lib/Cake/Console/Command/Task/PluginTask.php b/lib/Cake/Console/Command/Task/PluginTask.php index 95c3b3039..7cd507942 100644 --- a/lib/Cake/Console/Command/Task/PluginTask.php +++ b/lib/Cake/Console/Command/Task/PluginTask.php @@ -1,7 +1,5 @@ array( '%s' . 'Vendor' . DS, ROOT . DS . 'vendors' . DS, + dirname(dirname(CAKE)) . DS . 'vendors' . DS ), 'Plugin' => array( APP . 'Plugin' . DS, - ROOT . DS . 'plugins' . DS + ROOT . DS . 'plugins' . DS, + dirname(dirname(CAKE)) . DS . 'plugins' . DS ) ); } diff --git a/lib/Cake/Model/Datasource/Database/Postgres.php b/lib/Cake/Model/Datasource/Database/Postgres.php index 87b26798f..21d82b191 100644 --- a/lib/Cake/Model/Datasource/Database/Postgres.php +++ b/lib/Cake/Model/Datasource/Database/Postgres.php @@ -1,7 +1,5 @@ buildColumn($col)) . ' USING CASE WHEN TRUE THEN 1 ELSE 0 END'; } else { - $colList[] = 'ALTER COLUMN ' . $fieldName . ' TYPE ' . str_replace(array($fieldName, 'NOT NULL'), '', $this->buildColumn($col)); + if ($original['type'] === 'text' && $col['type'] === 'integer') { + $colList[] = 'ALTER COLUMN ' . $fieldName . ' TYPE ' . str_replace(array($fieldName, 'NOT NULL'), '', $this->buildColumn($col)) . " USING cast({$fieldName} as INTEGER)"; + } else { + $colList[] = 'ALTER COLUMN ' . $fieldName . ' TYPE ' . str_replace(array($fieldName, 'NOT NULL'), '', $this->buildColumn($col)); + } } if (isset($nullable)) { diff --git a/lib/Cake/Network/CakeSocket.php b/lib/Cake/Network/CakeSocket.php index d428a74cb..b4c378900 100644 --- a/lib/Cake/Network/CakeSocket.php +++ b/lib/Cake/Network/CakeSocket.php @@ -130,7 +130,7 @@ class CakeSocket { } $scheme = null; - if (!empty($this->config['protocol']) && strpos($this->config['host'], '://') === false) { + if (!empty($this->config['protocol']) && strpos($this->config['host'], '://') === false && empty($this->config['proxy'])) { $scheme = $this->config['protocol'] . '://'; } @@ -169,6 +169,28 @@ class CakeSocket { $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'; + + 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('tls', 'client'); + } } return $this->connected; } diff --git a/lib/Cake/Network/Http/HttpSocket.php b/lib/Cake/Network/Http/HttpSocket.php index 16fc0edcf..44b514d9f 100644 --- a/lib/Cake/Network/Http/HttpSocket.php +++ b/lib/Cake/Network/Http/HttpSocket.php @@ -652,6 +652,7 @@ class HttpSocket extends CakeSocket { } $this->config['host'] = $this->_proxy['host']; $this->config['port'] = $this->_proxy['port']; + $this->config['proxy'] = true; if (empty($this->_proxy['method']) || !isset($this->_proxy['user'], $this->_proxy['pass'])) { return; @@ -717,6 +718,20 @@ class HttpSocket extends CakeSocket { } unset($this->config[$key]); } + if (version_compare(PHP_VERSION, '5.3.2', '>=')) { + if (empty($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'; } @@ -926,7 +941,7 @@ class HttpSocket extends CakeSocket { $request['uri'] = $this->_parseUri($request['uri']); $request += array('method' => 'GET'); - if (!empty($this->_proxy['host'])) { + if (!empty($this->_proxy['host']) && $request['uri']['scheme'] !== 'https') { $request['uri'] = $this->_buildUri($request['uri'], '%scheme://%host:%port/%path?%query'); } else { $request['uri'] = $this->_buildUri($request['uri'], '/%path?%query'); diff --git a/lib/Cake/Test/Case/Cache/Engine/MemcacheEngineTest.php b/lib/Cake/Test/Case/Cache/Engine/MemcacheEngineTest.php index fbe99dbbb..36f8f0d8b 100644 --- a/lib/Cake/Test/Case/Cache/Engine/MemcacheEngineTest.php +++ b/lib/Cake/Test/Case/Cache/Engine/MemcacheEngineTest.php @@ -161,6 +161,17 @@ class MemcacheEngineTest extends CakeTestCase { $this->assertTrue($result); } +/** + * test domain starts with u + * + * @return void + */ + public function testParseServerStringWithU() { + $Memcached = new TestMemcachedEngine(); + $result = $Memcached->parseServerString('udomain.net:13211'); + $this->assertEquals(array('udomain.net', '13211'), $result); + } + /** * test non latin domains. * diff --git a/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php index 4f940ef41..17f1f9972 100644 --- a/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php +++ b/lib/Cake/Test/Case/Cache/Engine/MemcachedEngineTest.php @@ -400,6 +400,17 @@ class MemcachedEngineTest extends CakeTestCase { $this->assertTrue($result); } +/** + * test domain starts with u + * + * @return void + */ + public function testParseServerStringWithU() { + $Memcached = new TestMemcachedEngine(); + $result = $Memcached->parseServerString('udomain.net:13211'); + $this->assertEquals(array('udomain.net', '13211'), $result); + } + /** * test non latin domains. * diff --git a/lib/Cake/Test/Case/Console/Command/Task/PluginTaskTest.php b/lib/Cake/Test/Case/Console/Command/Task/PluginTaskTest.php index 82500e3aa..3405a4544 100644 --- a/lib/Cake/Test/Case/Console/Command/Task/PluginTaskTest.php +++ b/lib/Cake/Test/Case/Console/Command/Task/PluginTaskTest.php @@ -58,7 +58,7 @@ class PluginTaskTest extends CakeTestCase { array_splice($paths, $i, 1); } } - $this->_testPath = array_push($paths, TMP . 'tests' . DS); + $this->_testPath = array_push($paths, TMP . 'tests' . DS) - 1; App::build(array('plugins' => $paths)); } @@ -80,17 +80,23 @@ class PluginTaskTest extends CakeTestCase { * @return void */ public function testBakeFoldersAndFiles() { - $this->Task->expects($this->at(0))->method('in')->will($this->returnValue($this->_testPath)); - $this->Task->expects($this->at(1))->method('in')->will($this->returnValue('y')); + $this->Task->expects($this->at(0)) + ->method('in') + ->will($this->returnValue($this->_testPath)); + $this->Task->expects($this->at(1)) + ->method('in') + ->will($this->returnValue('y')); $path = $this->Task->path . 'BakeTestPlugin'; $file = $path . DS . 'Controller' . DS . 'BakeTestPluginAppController.php'; - $this->Task->expects($this->at(2))->method('createFile') + $this->Task->expects($this->at(2)) + ->method('createFile') ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); $file = $path . DS . 'Model' . DS . 'BakeTestPluginAppModel.php'; - $this->Task->expects($this->at(3))->method('createFile') + $this->Task->expects($this->at(3)) + ->method('createFile') ->with($file, new PHPUnit_Framework_Constraint_IsAnything()); $this->Task->bake('BakeTestPlugin'); diff --git a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php index 6fa681e31..c9e4528b7 100644 --- a/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php +++ b/lib/Cake/Test/Case/Model/Datasource/Database/PostgresTest.php @@ -731,6 +731,42 @@ class PostgresTest extends CakeTestCase { $this->Dbo->query($this->Dbo->dropSchema($Old)); } +/** + * Test the alterSchema changing text to integer + * + * @return void + */ + public function testAlterSchemaTextToIntegerField() { + $default = array( + 'connection' => 'test', + 'name' => 'TextField', + 'text_fields' => array( + 'id' => array('type' => 'integer', 'key' => 'primary'), + 'name' => array('type' => 'string', 'length' => 50), + 'active' => array('type' => 'text', 'null' => false), + ) + ); + $Old = new CakeSchema($default); + $result = $this->Dbo->query($this->Dbo->createSchema($Old)); + $this->assertTrue($result); + + $modified = $default; + $modified['text_fields']['active'] = array('type' => 'integer', 'null' => true); + + $New = new CakeSchema($modified); + $this->Dbo->query($this->Dbo->alterSchema($New->compare($Old))); + $result = $this->Dbo->describe('text_fields'); + + $this->Dbo->query($this->Dbo->dropSchema($Old)); + $expected = array( + 'type' => 'integer', + 'null' => true, + 'default' => null, + 'length' => null, + ); + $this->assertEquals($expected, $result['active']); + } + /** * Test the alter index capabilities of postgres * diff --git a/lib/Cake/Test/Case/Network/Http/HttpSocketTest.php b/lib/Cake/Test/Case/Network/Http/HttpSocketTest.php index 6456a0c21..de04f18ed 100644 --- a/lib/Cake/Test/Case/Network/Http/HttpSocketTest.php +++ b/lib/Cake/Test/Case/Network/Http/HttpSocketTest.php @@ -317,11 +317,18 @@ class HttpSocketTest extends CakeTestCase { 'verify_peer' => true, 'allow_self_signed' => false, 'verify_depth' => 5, + 'SNI_enabled' => true, 'CN_match' => 'www.cakephp.org', 'cafile' => CAKE . 'Config' . DS . 'cacert.pem' ) ); + if (version_compare(PHP_VERSION, '5.6.0', '>=')) { + $context['ssl']['peer_name'] = 'www.cakephp.org'; + } else { + $context['ssl']['SNI_server_name'] = 'www.cakephp.org'; + } + $tests = array( array( 'request' => 'http://www.cakephp.org/?foo=bar', diff --git a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php index 30d25eacc..4b6cfb7e1 100644 --- a/lib/Cake/Test/Case/View/Helper/FormHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/FormHelperTest.php @@ -8642,6 +8642,7 @@ class FormHelperTest extends CakeTestCase { 'escape' => false, 'url' => array( 'action' => 'edit', + '0', 'myparam' ) )); @@ -8649,7 +8650,7 @@ class FormHelperTest extends CakeTestCase { 'form' => array( 'id' => 'ContactAddForm', 'method' => 'post', - 'action' => '/contacts/edit/myparam', + 'action' => '/contacts/edit/0/myparam', 'accept-charset' => $encoding ), 'div' => array('style' => 'display:none;'), @@ -8667,8 +8668,8 @@ class FormHelperTest extends CakeTestCase { public function testCreateNoErrorsWithMockModel() { $encoding = strtolower(Configure::read('App.encoding')); $ContactMock = $this->getMockBuilder('Contact') - ->disableOriginalConstructor() - ->getMock(); + ->disableOriginalConstructor() + ->getMock(); ClassRegistry::removeObject('Contact'); ClassRegistry::addObject('Contact', $ContactMock); $result = $this->Form->create('Contact', array('type' => 'GET')); diff --git a/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php b/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php index 8c59e5bf8..5b37bf4f0 100644 --- a/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php +++ b/lib/Cake/Test/Case/View/Helper/PaginatorHelperTest.php @@ -1260,6 +1260,14 @@ class PaginatorHelperTest extends CakeTestCase { 'paramType' => 'named' ) ); + $result = $this->Paginator->sort('title', 'Title', array('model' => 'Client')); + $expected = array( + 'a' => array('href' => '/index/sort:title/direction:asc'), + 'Title', + '/a' + ); + $this->assertTags($result, $expected); + $result = $this->Paginator->next('Next', array('model' => 'Client')); $expected = array( 'span' => array('class' => 'next'), @@ -1277,6 +1285,39 @@ class PaginatorHelperTest extends CakeTestCase { $this->assertTags($result, $expected); } +/** + * Test creating paging links for missing models. + * + * @return void + */ + public function testPagingLinksMissingModel() { + $result = $this->Paginator->sort('title', 'Title', array('model' => 'Missing')); + $expected = array( + 'a' => array('href' => '/index/sort:title/direction:asc'), + 'Title', + '/a' + ); + $this->assertTags($result, $expected); + + $result = $this->Paginator->next('Next', array('model' => 'Missing')); + $expected = array( + 'span' => array('class' => 'next'), + 'a' => array('href' => '/index/page:2', 'rel' => 'next'), + 'Next', + '/a', + '/span' + ); + $this->assertTags($result, $expected); + + $result = $this->Paginator->prev('Prev', array('model' => 'Missing')); + $expected = array( + 'span' => array('class' => 'prev'), + 'Prev', + '/span' + ); + $this->assertTags($result, $expected); + } + /** * testGenericLinks method * diff --git a/lib/Cake/Test/Case/View/JsonViewTest.php b/lib/Cake/Test/Case/View/JsonViewTest.php index 5a661c78b..4e4eb2574 100644 --- a/lib/Cake/Test/Case/View/JsonViewTest.php +++ b/lib/Cake/Test/Case/View/JsonViewTest.php @@ -28,6 +28,11 @@ App::uses('JsonView', 'View'); */ class JsonViewTest extends CakeTestCase { +/** + * setUp method + * + * @return void + **/ public function setUp() { parent::setUp(); Configure::write('debug', 0); @@ -156,6 +161,20 @@ class JsonViewTest extends CakeTestCase { ); } +/** + * Custom error handler for use while testing methods that use json_encode + * @param int $errno + * @param string $errstr + * @param string $errfile + * @param int $errline + * @param array $errcontext + * @return void + * @throws CakeException + **/ + public function jsonEncodeErrorHandler($errno, $errstr, $errfile, $errline, $errcontext) { + throw new CakeException($errstr, 0, $errno, $errfile, $errline); + } + /** * Test render with a valid string in _serialize. * @@ -306,4 +325,56 @@ class JsonViewTest extends CakeTestCase { $this->assertSame($expected, $output); $this->assertSame('application/javascript', $Response->type()); } + +/** + * JsonViewTest::testRenderInvalidJSON() + * + * @return void + */ + public function testRenderInvalidJSON() { + $Request = new CakeRequest(); + $Response = new CakeResponse(); + $Controller = new Controller($Request, $Response); + + // non utf-8 stuff + $data = array('data' => array('foo' => 'bar' . chr('0x97'))); + + $Controller->set($data); + $Controller->set('_serialize', 'data'); + $View = new JsonView($Controller); + + // Use a custom error handler + set_error_handler(array($this, 'jsonEncodeErrorHandler')); + + try { + $View->render(); + restore_error_handler(); + $this->fail('Failed asserting that exception of type "CakeException" is thrown.'); + } catch (CakeException $e) { + restore_error_handler(); + $this->assertRegExp('/UTF-8/', $e->getMessage()); + return; + } + } + +/** + * JsonViewTest::testRenderJSONBoolFalse() + * + * @return void + */ + public function testRenderJSONBoolFalse() { + $Request = new CakeRequest(); + $Response = new CakeResponse(); + $Controller = new Controller($Request, $Response); + + // encoding a false, ensure this doesn't trigger exception + $data = false; + + $Controller->set($data); + $Controller->set('_serialize', 'data'); + $View = new JsonView($Controller); + $output = $View->render(); + $this->assertSame('null', $output); + } + } diff --git a/lib/Cake/View/Helper/FormHelper.php b/lib/Cake/View/Helper/FormHelper.php index 22fb6551d..880f5fd3f 100644 --- a/lib/Cake/View/Helper/FormHelper.php +++ b/lib/Cake/View/Helper/FormHelper.php @@ -408,7 +408,7 @@ class FormHelper extends AppHelper { 'action' => $options['action'], ); $options['action'] = array_merge($actionDefaults, (array)$options['url']); - if (empty($options['action'][0]) && !empty($id)) { + if (!isset($options['action'][0]) && !empty($id)) { $options['action'][0] = $id; } } elseif (is_string($options['url'])) { diff --git a/lib/Cake/View/Helper/PaginatorHelper.php b/lib/Cake/View/Helper/PaginatorHelper.php index f79e09e7e..f5e3e72f2 100644 --- a/lib/Cake/View/Helper/PaginatorHelper.php +++ b/lib/Cake/View/Helper/PaginatorHelper.php @@ -120,7 +120,7 @@ class PaginatorHelper extends AppHelper { * Gets the current paging parameters from the resultset for the given model * * @param string $model Optional model name. Uses the default if none is specified. - * @return array|null The array of paging parameters for the paginated resultset. + * @return array The array of paging parameters for the paginated resultset. * @link http://book.cakephp.org/2.0/en/core-libraries/helpers/paginator.html#PaginatorHelper::params */ public function params($model = null) { @@ -128,7 +128,14 @@ class PaginatorHelper extends AppHelper { $model = $this->defaultModel(); } if (!isset($this->request->params['paging']) || empty($this->request->params['paging'][$model])) { - return null; + return array( + 'prevPage' => false, + 'nextPage' => true, + 'paramType' => 'named', + 'pageCount' => 1, + 'options' => array(), + 'page' => 1 + ); } return $this->request->params['paging'][$model]; } diff --git a/lib/Cake/View/JsonView.php b/lib/Cake/View/JsonView.php index 2f184139d..7953803de 100644 --- a/lib/Cake/View/JsonView.php +++ b/lib/Cake/View/JsonView.php @@ -126,6 +126,7 @@ class JsonView extends View { * Serialize view vars * * @param array $serialize The viewVars that need to be serialized + * @throws CakeException * @return string The serialized data */ protected function _serialize($serialize) { @@ -145,10 +146,17 @@ class JsonView extends View { } if (version_compare(PHP_VERSION, '5.4.0', '>=') && Configure::read('debug')) { - return json_encode($data, JSON_PRETTY_PRINT); + $json = json_encode($data, JSON_PRETTY_PRINT); + } else { + $json = json_encode($data); } - return json_encode($data); + if (function_exists('json_last_error') && json_last_error() !== JSON_ERROR_NONE) { + throw new CakeException(json_last_error_msg()); + } elseif ($json === false) { + throw new CakeException('Failed to parse JSON'); + } + return $json; } } diff --git a/lib/Cake/basics.php b/lib/Cake/basics.php index 8dc20a015..9ca5a93db 100644 --- a/lib/Cake/basics.php +++ b/lib/Cake/basics.php @@ -1061,3 +1061,25 @@ if (!function_exists('convertSlash')) { } } + +if (!function_exists('json_last_error_msg')) { + +/** + * Provides the fallback implementation of json_last_error_msg() available in PHP 5.5 and above. + * + * @return string Error message. + */ + function json_last_error_msg() { + static $errors = array( + JSON_ERROR_NONE => '', + JSON_ERROR_DEPTH => 'Maximum stack depth exceeded', + JSON_ERROR_STATE_MISMATCH => 'Invalid or malformed JSON', + JSON_ERROR_CTRL_CHAR => 'Control character error, possibly incorrectly encoded', + JSON_ERROR_SYNTAX => 'Syntax error', + JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded' + ); + $error = json_last_error(); + return array_key_exists($error, $errors) ? $errors[$error] : "Unknown error ({$error})"; + } + +}