diff --git a/.travis.yml b/.travis.yml index a25192117b6..1cf20cadf01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -72,7 +72,7 @@ script: - if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION != 7.0 ]]; then vendor/bin/phpunit; fi - if [[ $PHPCS = 1 ]]; then composer cs-check; fi - - if [[ $PHPSTAN = 1 ]]; then composer require --dev phpstan/phpstan:^0.9 && vendor/bin/phpstan analyse -c phpstan.neon -l 2 src; fi + - if [[ $PHPSTAN = 1 ]]; then composer require --dev "phpstan/phpstan:0.9.*" && vendor/bin/phpstan analyse -c phpstan.neon -l 2 src; fi after_success: - if [[ $DEFAULT = 1 && $TRAVIS_PHP_VERSION = 7.0 ]]; then bash <(curl -s https://codecov.io/bash); fi diff --git a/src/Controller/Component/PaginatorComponent.php b/src/Controller/Component/PaginatorComponent.php index 9ba5782665f..e44a36bc5a2 100644 --- a/src/Controller/Component/PaginatorComponent.php +++ b/src/Controller/Component/PaginatorComponent.php @@ -329,7 +329,7 @@ public function getConfig($key = null, $default = null) */ public function configShallow($key, $value = null) { - $this->_paginator->configShallow($key, $value = null); + $this->_paginator->configShallow($key, null); return $this; } diff --git a/src/Core/PluginCollection.php b/src/Core/PluginCollection.php index a1ce7058c3b..9dfff93c548 100644 --- a/src/Core/PluginCollection.php +++ b/src/Core/PluginCollection.php @@ -109,6 +109,8 @@ protected function loadConfig() */ public function findPath($name) { + $this->loadConfig(); + $path = Configure::read('plugins.' . $name); if ($path) { return $path; diff --git a/src/Core/functions.php b/src/Core/functions.php index 0fbd2069ada..b2ccfd7c7da 100644 --- a/src/Core/functions.php +++ b/src/Core/functions.php @@ -207,10 +207,8 @@ function env($key, $default = null) return (strpos((string)env('SCRIPT_URI'), 'https://') === 0); } - if ($key === 'SCRIPT_NAME') { - if (env('CGI_MODE') && isset($_ENV['SCRIPT_URL'])) { - $key = 'SCRIPT_URL'; - } + if ($key === 'SCRIPT_NAME' && env('CGI_MODE') && isset($_ENV['SCRIPT_URL'])) { + $key = 'SCRIPT_URL'; } $val = null; diff --git a/src/Error/Debugger.php b/src/Error/Debugger.php index 8819476eefb..dd49fc5ea26 100644 --- a/src/Error/Debugger.php +++ b/src/Error/Debugger.php @@ -486,7 +486,7 @@ protected static function _highlight($str) * This is done to protect database credentials, which could be accidentally * shown in an error message if CakePHP is deployed in development mode. * - * @param string $var Variable to convert. + * @param mixed $var Variable to convert. * @param int $depth The depth to output to. Defaults to 3. * @return string Variable as a formatted string */ diff --git a/src/Filesystem/File.php b/src/Filesystem/File.php index e0ebe95f7ba..e0ed1a44c92 100644 --- a/src/Filesystem/File.php +++ b/src/Filesystem/File.php @@ -109,10 +109,8 @@ public function create() { $dir = $this->Folder->pwd(); - if (is_dir($dir) && is_writable($dir) && !$this->exists()) { - if (touch($this->path)) { - return true; - } + if (is_dir($dir) && is_writable($dir) && !$this->exists() && touch($this->path)) { + return true; } return false; diff --git a/src/Http/Cookie/CookieCollection.php b/src/Http/Cookie/CookieCollection.php index 0186fb94377..f168f1930d1 100644 --- a/src/Http/Cookie/CookieCollection.php +++ b/src/Http/Cookie/CookieCollection.php @@ -17,6 +17,7 @@ use Countable; use DateTimeImmutable; use DateTimeZone; +use Exception; use InvalidArgumentException; use IteratorAggregate; use Psr\Http\Message\RequestInterface; @@ -369,11 +370,15 @@ protected static function parseSetCookieHeader($values) $cookie[$key] = $value; } } - $expires = null; - if ($cookie['max-age'] !== null) { - $expires = new DateTimeImmutable('@' . (time() + $cookie['max-age'])); - } elseif ($cookie['expires']) { - $expires = new DateTimeImmutable('@' . strtotime($cookie['expires'])); + try { + $expires = null; + if ($cookie['max-age'] !== null) { + $expires = new DateTimeImmutable('@' . (time() + $cookie['max-age'])); + } elseif ($cookie['expires']) { + $expires = new DateTimeImmutable('@' . strtotime($cookie['expires'])); + } + } catch (Exception $e) { + $expires = null; } $cookies[] = new Cookie( diff --git a/src/Http/Middleware/CsrfProtectionMiddleware.php b/src/Http/Middleware/CsrfProtectionMiddleware.php index bfa6e9f7ae0..14ef6294bf5 100644 --- a/src/Http/Middleware/CsrfProtectionMiddleware.php +++ b/src/Http/Middleware/CsrfProtectionMiddleware.php @@ -41,11 +41,12 @@ class CsrfProtectionMiddleware /** * Default config for the CSRF handling. * - * - `cookieName` = The name of the cookie to send. - * - `expiry` = How long the CSRF token should last. Defaults to browser session. - * - `secure` = Whether or not the cookie will be set with the Secure flag. Defaults to false. - * - `httpOnly` = Whether or not the cookie will be set with the HttpOnly flag. Defaults to false. - * - `field` = The form field to check. Changing this will also require configuring + * - `cookieName` The name of the cookie to send. + * - `expiry` A strotime compatible value of how long the CSRF token should last. + * Defaults to browser session. + * - `secure` Whether or not the cookie will be set with the Secure flag. Defaults to false. + * - `httpOnly` Whether or not the cookie will be set with the HttpOnly flag. Defaults to false. + * - `field` The form field to check. Changing this will also require configuring * FormHelper. * * @var array diff --git a/src/Http/ServerRequest.php b/src/Http/ServerRequest.php index 3ee99c8956d..c87018cc282 100644 --- a/src/Http/ServerRequest.php +++ b/src/Http/ServerRequest.php @@ -1152,8 +1152,8 @@ public function getHeaders() $name = $key; } if ($name !== null) { - $name = strtr(strtolower($name), '_', ' '); - $name = strtr(ucwords($name), ' ', '-'); + $name = str_replace('_', ' ', strtolower($name)); + $name = str_replace(' ', '-', ucwords($name)); $headers[$name] = (array)$value; } } diff --git a/src/Mailer/Email.php b/src/Mailer/Email.php index 95b9c4c2395..772f53d5622 100644 --- a/src/Mailer/Email.php +++ b/src/Mailer/Email.php @@ -1833,7 +1833,7 @@ public function setAttachments($attachments) $name = basename($fileInfo['file']); } } - if (!isset($fileInfo['mimetype']) && function_exists('mime_content_type')) { + if (!isset($fileInfo['mimetype']) && isset($fileInfo['file']) && function_exists('mime_content_type')) { $fileInfo['mimetype'] = mime_content_type($fileInfo['file']); } if (!isset($fileInfo['mimetype'])) { diff --git a/src/ORM/Marshaller.php b/src/ORM/Marshaller.php index 45ae6136c24..7eb25518777 100644 --- a/src/ORM/Marshaller.php +++ b/src/ORM/Marshaller.php @@ -569,7 +569,7 @@ public function merge(EntityInterface $entity, array $data, array $options = []) $errors = $this->_validate($data + $keys, $options, $isNew); $options['isMerge'] = true; $propertyMap = $this->_buildPropertyMap($data, $options); - $properties = $marshalledAssocs = []; + $properties = []; foreach ($data as $key => $value) { if (!empty($errors[$key])) { if ($entity instanceof InvalidPropertyInterface) { diff --git a/src/Routing/Route/RedirectRoute.php b/src/Routing/Route/RedirectRoute.php index 170c56f206c..e4ccc3d16dd 100644 --- a/src/Routing/Route/RedirectRoute.php +++ b/src/Routing/Route/RedirectRoute.php @@ -88,7 +88,7 @@ public function parse($url, $method = '') } } } - $redirect = Router::reverse($redirect); + $redirect = Router::reverseToArray($redirect); } $status = 301; if (isset($this->options['status']) && ($this->options['status'] >= 300 && $this->options['status'] < 400)) { diff --git a/src/TestSuite/IntegrationTestTrait.php b/src/TestSuite/IntegrationTestTrait.php index 56a76dc2b36..c0db7c967b6 100644 --- a/src/TestSuite/IntegrationTestTrait.php +++ b/src/TestSuite/IntegrationTestTrait.php @@ -699,7 +699,7 @@ protected function _castToString($data) } if (is_array($value)) { - $looksLikeFile = isset($value['error']) && isset($value['tmp_name']) && isset($value['size']); + $looksLikeFile = isset($value['error'], $value['tmp_name'], $value['size']); if ($looksLikeFile) { continue; } diff --git a/src/Validation/Validation.php b/src/Validation/Validation.php index ec34caba5ac..d443dde99d8 100644 --- a/src/Validation/Validation.php +++ b/src/Validation/Validation.php @@ -208,10 +208,8 @@ public static function cc($check, $type = 'fast', $deep = false, $regex = null) return false; } - if ($regex !== null) { - if (static::_check($check, $regex)) { - return !$deep || static::luhn($check); - } + if ($regex !== null && static::_check($check, $regex)) { + return !$deep || static::luhn($check); } $cards = [ 'all' => [ diff --git a/src/View/View.php b/src/View/View.php index 3368378b3de..d5b5f26bdf8 100644 --- a/src/View/View.php +++ b/src/View/View.php @@ -1529,7 +1529,7 @@ protected function _getViewFileName($name = null) if (strlen($this->subDir)) { $subDir = $this->subDir . DIRECTORY_SEPARATOR; // Check if templatePath already terminates with subDir - if ($templatePath != $subDir && substr($templatePath, -(strlen($subDir))) == $subDir) { + if ($templatePath != $subDir && substr($templatePath, -strlen($subDir)) == $subDir) { $subDir = ''; } } diff --git a/src/View/Widget/MultiCheckboxWidget.php b/src/View/Widget/MultiCheckboxWidget.php index 46affc2dae4..18ba16d1899 100644 --- a/src/View/Widget/MultiCheckboxWidget.php +++ b/src/View/Widget/MultiCheckboxWidget.php @@ -191,7 +191,7 @@ protected function _renderInput($checkbox, $context) ) ]); - if ($checkbox['label'] === false && strpos($this->_templates->get('radioWrapper'), '{{input}}') === false) { + if ($checkbox['label'] === false && strpos($this->_templates->get('checkboxWrapper'), '{{input}}') === false) { $label = $input; } else { $labelAttrs = [ diff --git a/tests/TestCase/Core/PluginCollectionTest.php b/tests/TestCase/Core/PluginCollectionTest.php index dede7e389e7..ae778ea1785 100644 --- a/tests/TestCase/Core/PluginCollectionTest.php +++ b/tests/TestCase/Core/PluginCollectionTest.php @@ -148,6 +148,28 @@ public function testFindPathNoConfigureData() $this->assertEquals(TEST_APP . 'Plugin' . DS . 'TestPlugin' . DS, $path); } + public function testFindPathLoadsConfigureData() + { + $configPath = ROOT . DS . 'cakephp-plugins.php'; + $this->skipIf(file_exists($configPath), 'cakephp-plugins.php exists, skipping overwrite'); + $file = << [ + 'TestPlugin' => '/config/path' + ] +]; +PHP; + file_put_contents($configPath, $file); + + Configure::delete('plugins'); + $plugins = new PluginCollection(); + $path = $plugins->findPath('TestPlugin'); + unlink($configPath); + + $this->assertEquals('/config/path', $path); + } + public function testFindPathConfigureData() { Configure::write('plugins', ['TestPlugin' => '/some/path']); diff --git a/tests/TestCase/Http/Cookie/CookieCollectionTest.php b/tests/TestCase/Http/Cookie/CookieCollectionTest.php index b01a9c68594..47dfd4e404e 100644 --- a/tests/TestCase/Http/Cookie/CookieCollectionTest.php +++ b/tests/TestCase/Http/Cookie/CookieCollectionTest.php @@ -305,6 +305,27 @@ public function testAddFromResponseRemoveExpired() $this->assertFalse($new->has('expired'), 'Should drop expired cookies'); } + /** + * Test adding cookies from a response with bad expires values + * + * @return void + */ + public function testAddFromResponseInvalidExpires() + { + $collection = new CookieCollection(); + $request = new ServerRequest([ + 'url' => '/app' + ]); + $response = (new Response()) + ->withAddedHeader('Set-Cookie', 'test=value') + ->withAddedHeader('Set-Cookie', 'expired=no; Expires=1w; Path=/; HttpOnly; Secure;'); + $new = $collection->addFromResponse($response, $request); + $this->assertTrue($new->has('test')); + $this->assertTrue($new->has('expired')); + $expired = $new->get('expired'); + $this->assertNull($expired->getExpiry()); + } + /** * Test adding cookies from responses updates cookie values. * diff --git a/tests/TestCase/Mailer/EmailTest.php b/tests/TestCase/Mailer/EmailTest.php index 909006b5870..fc58560576b 100644 --- a/tests/TestCase/Mailer/EmailTest.php +++ b/tests/TestCase/Mailer/EmailTest.php @@ -857,7 +857,7 @@ public function testViewVars() * * @return void */ - public function testAttachments() + public function testSetAttachments() { $this->Email->setAttachments(CAKE . 'basics.php'); $expected = [ @@ -891,6 +891,26 @@ public function testAttachments() $this->Email->setAttachments([['nofile' => CAKE . 'basics.php', 'mimetype' => 'text/plain']]); } + /** + * Test send() with no template and data string attachment and no mimetype + * + * @return void + */ + public function testSetAttachmentDataNoMimetype() + { + $this->Email->setAttachments(['cake.icon.gif' => [ + 'data' => 'test', + ]]); + $result = $this->Email->getAttachments(); + $expected = [ + 'cake.icon.gif' => [ + 'data' => base64_encode('test') . "\r\n", + 'mimetype' => 'application/octet-stream' + ], + ]; + $this->assertSame($expected, $this->Email->getAttachments()); + } + /** * testTransport method * @@ -1317,7 +1337,6 @@ public function testSendNoTemplateWithAttachments() * * @return void */ - public function testSendNoTemplateWithDataStringAttachment() { $this->Email->setTransport('debug'); diff --git a/tests/TestCase/Routing/Route/RedirectRouteTest.php b/tests/TestCase/Routing/Route/RedirectRouteTest.php index 731cf119d2b..41716eed447 100644 --- a/tests/TestCase/Routing/Route/RedirectRouteTest.php +++ b/tests/TestCase/Routing/Route/RedirectRouteTest.php @@ -14,6 +14,7 @@ */ namespace Cake\Test\TestCase\Routing\Route; +use Cake\Http\ServerRequest; use Cake\Routing\Router; use Cake\Routing\Route\RedirectRoute; use Cake\TestSuite\TestCase; @@ -145,6 +146,26 @@ public function testParsePersist() $route->parse('/posts/2'); } + /** + * test redirecting with persist and a base directory + * + * @return void + */ + public function testParsePersistBaseDirectory() + { + $request = new ServerRequest([ + 'base' => '/basedir', + 'url' => '/posts/2' + ]); + Router::pushRequest($request); + + $this->expectException(\Cake\Routing\Exception\RedirectException::class); + $this->expectExceptionMessage('http://localhost/basedir/posts/view/2'); + $this->expectExceptionCode(301); + $route = new RedirectRoute('/posts/*', ['controller' => 'posts', 'action' => 'view'], ['persist' => true]); + $route->parse('/posts/2'); + } + /** * test redirecting with persist and string target URLs * diff --git a/tests/TestCase/View/Widget/MultiCheckboxWidgetTest.php b/tests/TestCase/View/Widget/MultiCheckboxWidgetTest.php index 77ce89f193d..e411d1fa699 100644 --- a/tests/TestCase/View/Widget/MultiCheckboxWidgetTest.php +++ b/tests/TestCase/View/Widget/MultiCheckboxWidgetTest.php @@ -369,6 +369,77 @@ public function testRenderTemplateVars() $this->assertHtml($expected, $result); } + /** + * Test label = false with checkboxWrapper option. + * + * @return void + */ + public function testNoLabelWithCheckboxWrapperOption() + { + $data = [ + 'label' => false, + 'name' => 'test', + 'options' => [ + 1 => 'A', + 2 => 'B', + ], + ]; + + $label = new LabelWidget($this->templates); + $input = new MultiCheckboxWidget($this->templates, $label); + $result = $input->render($data, $this->context); + $expected = [ + ['div' => ['class' => 'checkbox']], + ['input' => [ + 'type' => 'checkbox', + 'name' => 'test[]', + 'value' => 1, + 'id' => 'test-1', + ]], + ['label' => ['for' => 'test-1']], + 'A', + '/label', + '/div', + ['div' => ['class' => 'checkbox']], + ['input' => [ + 'type' => 'checkbox', + 'name' => 'test[]', + 'value' => '2', + 'id' => 'test-2', + ]], + ['label' => ['for' => 'test-2']], + 'B', + '/label', + '/div', + ]; + $this->assertHtml($expected, $result); + + $templates = [ + 'checkboxWrapper' => '
{{label}}
', + ]; + $this->templates->add($templates); + $result = $input->render($data, $this->context); + $expected = [ + ['div' => ['class' => 'checkbox']], + ['input' => [ + 'type' => 'checkbox', + 'name' => 'test[]', + 'value' => 1, + 'id' => 'test-1', + ]], + '/div', + ['div' => ['class' => 'checkbox']], + ['input' => [ + 'type' => 'checkbox', + 'name' => 'test[]', + 'value' => '2', + 'id' => 'test-2', + ]], + '/div', + ]; + $this->assertHtml($expected, $result); + } + /** * Test render with groupings. *