Browse Source

Merge branch 'master' into 3.next

Mark Story 7 years ago
parent
commit
8d72a2d443

+ 1 - 1
.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

+ 1 - 1
src/Controller/Component/PaginatorComponent.php

@@ -329,7 +329,7 @@ class PaginatorComponent extends Component
      */
     public function configShallow($key, $value = null)
     {
-        $this->_paginator->configShallow($key, $value = null);
+        $this->_paginator->configShallow($key, null);
 
         return $this;
     }

+ 2 - 0
src/Core/PluginCollection.php

@@ -109,6 +109,8 @@ class PluginCollection implements Iterator, Countable
      */
     public function findPath($name)
     {
+        $this->loadConfig();
+
         $path = Configure::read('plugins.' . $name);
         if ($path) {
             return $path;

+ 2 - 4
src/Core/functions.php

@@ -207,10 +207,8 @@ if (!function_exists('env')) {
             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;

+ 1 - 1
src/Error/Debugger.php

@@ -486,7 +486,7 @@ class Debugger
      * 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
      */

+ 2 - 4
src/Filesystem/File.php

@@ -109,10 +109,8 @@ class File
     {
         $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;

+ 10 - 5
src/Http/Cookie/CookieCollection.php

@@ -17,6 +17,7 @@ use ArrayIterator;
 use Countable;
 use DateTimeImmutable;
 use DateTimeZone;
+use Exception;
 use InvalidArgumentException;
 use IteratorAggregate;
 use Psr\Http\Message\RequestInterface;
@@ -369,11 +370,15 @@ class CookieCollection implements IteratorAggregate, Countable
                     $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(

+ 6 - 5
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

+ 2 - 2
src/Http/ServerRequest.php

@@ -1152,8 +1152,8 @@ class ServerRequest implements ArrayAccess, ServerRequestInterface
                 $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;
             }
         }

+ 1 - 1
src/Mailer/Email.php

@@ -1833,7 +1833,7 @@ class Email implements JsonSerializable, Serializable
                     $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'])) {

+ 1 - 1
src/ORM/Marshaller.php

@@ -569,7 +569,7 @@ class Marshaller
         $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) {

+ 1 - 1
src/Routing/Route/RedirectRoute.php

@@ -88,7 +88,7 @@ class RedirectRoute extends Route
                     }
                 }
             }
-            $redirect = Router::reverse($redirect);
+            $redirect = Router::reverseToArray($redirect);
         }
         $status = 301;
         if (isset($this->options['status']) && ($this->options['status'] >= 300 && $this->options['status'] < 400)) {

+ 1 - 1
src/TestSuite/IntegrationTestTrait.php

@@ -699,7 +699,7 @@ trait IntegrationTestTrait
             }
 
             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;
                 }

+ 2 - 4
src/Validation/Validation.php

@@ -208,10 +208,8 @@ class Validation
             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' => [

+ 1 - 1
src/View/View.php

@@ -1529,7 +1529,7 @@ class View implements EventDispatcherInterface
         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 = '';
             }
         }

+ 1 - 1
src/View/Widget/MultiCheckboxWidget.php

@@ -191,7 +191,7 @@ class MultiCheckboxWidget implements WidgetInterface
             )
         ]);
 
-        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 = [

+ 22 - 0
tests/TestCase/Core/PluginCollectionTest.php

@@ -148,6 +148,28 @@ class PluginCollectionTest extends TestCase
         $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 = <<<PHP
+<?php
+return [
+    'plugins' => [
+        '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']);

+ 21 - 0
tests/TestCase/Http/Cookie/CookieCollectionTest.php

@@ -306,6 +306,27 @@ class CookieCollectionTest extends TestCase
     }
 
     /**
+     * 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.
      *
      * @return void

+ 21 - 2
tests/TestCase/Mailer/EmailTest.php

@@ -857,7 +857,7 @@ class EmailTest extends TestCase
      *
      * @return void
      */
-    public function testAttachments()
+    public function testSetAttachments()
     {
         $this->Email->setAttachments(CAKE . 'basics.php');
         $expected = [
@@ -892,6 +892,26 @@ class EmailTest extends TestCase
     }
 
     /**
+     * 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
      *
      * @return void
@@ -1317,7 +1337,6 @@ class EmailTest extends TestCase
      *
      * @return void
      */
-
     public function testSendNoTemplateWithDataStringAttachment()
     {
         $this->Email->setTransport('debug');

+ 21 - 0
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;
@@ -146,6 +147,26 @@ class RedirectRouteTest extends TestCase
     }
 
     /**
+     * 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
      *
      * @return void

+ 71 - 0
tests/TestCase/View/Widget/MultiCheckboxWidgetTest.php

@@ -370,6 +370,77 @@ class MultiCheckboxWidgetTest extends TestCase
     }
 
     /**
+     * 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' => '<div class="checkbox">{{label}}</div>',
+        ];
+        $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.
      *
      * @return void