Browse Source

Merge branch '4.x' into 4.next

ADmad 4 years ago
parent
commit
c10f158751

+ 1 - 1
Makefile

@@ -25,7 +25,7 @@ DASH_VERSION=$(shell echo $(VERSION) | sed -e s/\\./-/g)
 # correct tag in that repo.
 # For 3.1.x use 3.1.2
 # For 3.0.x use 3.0.5
-APP_VERSION:=master
+APP_VERSION:=4.x
 
 ALL: help
 .PHONY: help install test need-version bump-version tag-version

+ 1 - 1
composer.json

@@ -31,7 +31,7 @@
         "laminas/laminas-diactoros": "^2.2.2",
         "laminas/laminas-httphandlerrunner": "^1.1",
         "league/container": "^4.1.1",
-        "psr/container": "^2.0",
+        "psr/container": "^1.1 || ^2.0",
         "psr/http-client": "^1.0",
         "psr/http-server-handler": "^1.0",
         "psr/http-server-middleware": "^1.0",

+ 2 - 0
src/Controller/Controller.php

@@ -210,6 +210,8 @@ class Controller implements EventListenerInterface, EventDispatcherInterface
             $plugin = $this->request->getParam('plugin');
             $modelClass = ($plugin ? $plugin . '.' : '') . $this->name;
             $this->_setModelClass($modelClass);
+
+            $this->defaultTable = $modelClass;
         }
 
         if ($components !== null) {

+ 3 - 1
src/Controller/ControllerFactory.php

@@ -250,7 +250,7 @@ class ControllerFactory implements ControllerFactoryInterface, RequestHandlerInt
      *
      * @param string $argument Argument to coerce
      * @param \ReflectionNamedType $type Parameter type
-     * @return string|float|int|bool|null
+     * @return array|string|float|int|bool|null
      */
     protected function coerceStringToType(string $argument, ReflectionNamedType $type)
     {
@@ -263,6 +263,8 @@ class ControllerFactory implements ControllerFactoryInterface, RequestHandlerInt
                 return ctype_digit($argument) ? (int)$argument : null;
             case 'bool':
                 return $argument === '0' ? false : ($argument === '1' ? true : null);
+            case 'array':
+                return explode(',', $argument);
         }
 
         return null;

+ 4 - 4
src/Core/functions.php

@@ -298,10 +298,10 @@ if (!function_exists('deprecationWarning')) {
             }
 
             $message = sprintf(
-                '%s - %s, line: %s' . "\n" .
-                ' You can disable all deprecation warnings by setting `Error.errorLevel` to' .
-                ' `E_ALL & ~E_USER_DEPRECATED`, or add `%s` to ' .
-                ' `Error.ignoredDeprecationPaths` in your `config/app.php` to mute deprecations from only this file.',
+                "%s\n%s, line: %s\n" .
+                'You can disable all deprecation warnings by setting `Error.errorLevel` to ' .
+                '`E_ALL & ~E_USER_DEPRECATED`. Adding `%s` to `Error.ignoredDeprecationPaths` ' .
+                'in your `config/app.php` config will mute deprecations from that file only.',
                 $message,
                 $frame['file'],
                 $frame['line'],

+ 4 - 0
src/Http/Middleware/HttpsEnforcerMiddleware.php

@@ -95,6 +95,10 @@ class HttpsEnforcerMiddleware implements MiddlewareInterface
 
         if ($this->config['redirect'] && $request->getMethod() === 'GET') {
             $uri = $request->getUri()->withScheme('https');
+            $base = $request->getAttribute('base');
+            if ($base) {
+                $uri = $uri->withPath($base . $uri->getPath());
+            }
 
             return new RedirectResponse(
                 $uri,

+ 3 - 1
src/Log/Formatter/DefaultFormatter.php

@@ -16,6 +16,8 @@ declare(strict_types=1);
  */
 namespace Cake\Log\Formatter;
 
+use DateTime;
+
 class DefaultFormatter extends AbstractFormatter
 {
     /**
@@ -43,7 +45,7 @@ class DefaultFormatter extends AbstractFormatter
     public function format($level, string $message, array $context = []): string
     {
         if ($this->_config['includeDate']) {
-            $message = sprintf('%s %s: %s', date($this->_config['dateFormat']), $level, $message);
+            $message = sprintf('%s %s: %s', (new DateTime())->format($this->_config['dateFormat']), $level, $message);
         } else {
             $message = sprintf('%s: %s', $level, $message);
         }

+ 6 - 6
src/ORM/Behavior/TreeBehavior.php

@@ -601,12 +601,12 @@ class TreeBehavior extends Behavior
      * Reorders the node without changing its parent.
      *
      * If the node is the first child, or is a top level node with no previous node
-     * this method will return false
+     * this method will return the same node without any changes
      *
      * @param \Cake\Datasource\EntityInterface $node The node to move
      * @param int|true $number How many places to move the node, or true to move to first position
      * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found
-     * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false on failure
+     * @return \Cake\Datasource\EntityInterface|false $node The node after being moved or false if `$number` is < 1
      */
     public function moveUp(EntityInterface $node, $number = 1)
     {
@@ -626,7 +626,7 @@ class TreeBehavior extends Behavior
      *
      * @param \Cake\Datasource\EntityInterface $node The node to move
      * @param int|true $number How many places to move the node, or true to move to first position
-     * @return \Cake\Datasource\EntityInterface $node The node after being moved or false on failure
+     * @return \Cake\Datasource\EntityInterface $node The node after being moved
      * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found
      */
     protected function _moveUp(EntityInterface $node, $number): EntityInterface
@@ -693,12 +693,12 @@ class TreeBehavior extends Behavior
      * Reorders the node without changing the parent.
      *
      * If the node is the last child, or is a top level node with no subsequent node
-     * this method will return false
+     * this method will return the same node without any changes
      *
      * @param \Cake\Datasource\EntityInterface $node The node to move
      * @param int|true $number How many places to move the node or true to move to last position
      * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found
-     * @return \Cake\Datasource\EntityInterface|false the entity after being moved or false on failure
+     * @return \Cake\Datasource\EntityInterface|false the entity after being moved or false if `$number` is < 1
      */
     public function moveDown(EntityInterface $node, $number = 1)
     {
@@ -718,7 +718,7 @@ class TreeBehavior extends Behavior
      *
      * @param \Cake\Datasource\EntityInterface $node The node to move
      * @param int|true $number How many places to move the node, or true to move to last position
-     * @return \Cake\Datasource\EntityInterface $node The node after being moved or false on failure
+     * @return \Cake\Datasource\EntityInterface $node The node after being moved
      * @throws \Cake\Datasource\Exception\RecordNotFoundException When node was not found
      */
     protected function _moveDown(EntityInterface $node, $number): EntityInterface

+ 1 - 1
src/ORM/Table.php

@@ -1531,7 +1531,7 @@ class Table implements RepositoryInterface, EventListenerInterface, EventDispatc
         if ($cacheConfig) {
             if (!$cacheKey) {
                 $cacheKey = sprintf(
-                    'get:%s.%s%s',
+                    'get-%s-%s-%s',
                     $this->getConnection()->configName(),
                     $this->getTable(),
                     json_encode($primaryKey)

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

@@ -18,7 +18,7 @@ namespace Cake\Routing\Route;
 
 /**
  * Plugin short route, that copies the plugin param to the controller parameters
- * It is used for supporting /:plugin routes.
+ * It is used for supporting /{plugin} routes.
  */
 class PluginShortRoute extends InflectedRoute
 {

+ 9 - 2
src/Routing/Route/Route.php

@@ -128,6 +128,7 @@ class Route
      *   specific host names. You can use `.*` and to create wildcard subdomains/hosts
      *   e.g. `*.example.com` matches all subdomains on `example.com`.
      * - '_port` - Define the port if you want this route to only match specific port number.
+     * - '_urldecode' - Set to `false` to disable URL decoding before route parsing.
      *
      * @param string $template Template string with parameter placeholders
      * @param array $defaults Defaults for the route.
@@ -458,7 +459,12 @@ class Route
         $compiledRoute = $this->compile();
         [$url, $ext] = $this->_parseExtension($url);
 
-        if (!preg_match($compiledRoute, urldecode($url), $route)) {
+        $urldecode = $this->options['_urldecode'] ?? true;
+        if ($urldecode) {
+            $url = urldecode($url);
+        }
+
+        if (!preg_match($compiledRoute, $url, $route)) {
             return null;
         }
 
@@ -575,12 +581,13 @@ class Route
     {
         $pass = [];
         $args = explode('/', $args);
+        $urldecode = $this->options['_urldecode'] ?? true;
 
         foreach ($args as $param) {
             if (empty($param) && $param !== '0') {
                 continue;
             }
-            $pass[] = rawurldecode($param);
+            $pass[] = $urldecode ? rawurldecode($param) : $param;
         }
 
         return $pass;

+ 1 - 1
src/Routing/RouteBuilder.php

@@ -312,7 +312,7 @@ class RouteBuilder
      * });
      * ```
      *
-     * The above would generate both resource routes for `/articles`, and `/articles/:article_id/comments`.
+     * The above would generate both resource routes for `/articles`, and `/articles/{article_id}/comments`.
      * You can use the `map` option to connect additional resource methods:
      *
      * ```

+ 1 - 1
src/TestSuite/Fixture/FixtureInjector.php

@@ -75,7 +75,7 @@ class FixtureInjector implements TestListener
                 'You are using the listener based PHPUnit integration. ' .
                 'This fixture system is deprecated, and we recommend you ' .
                 'upgrade to the extension based PHPUnit integration. ' .
-                'See https://book.cakephp.org/4.x/en/appendixes/fixture-upgrade.html',
+                'See https://book.cakephp.org/4/en/appendices/fixture-upgrade.html',
                 0
             );
             $this->_first = $suite;

+ 1 - 0
src/TestSuite/IntegrationTestTrait.php

@@ -271,6 +271,7 @@ trait IntegrationTestTrait
      *
      * You can call this method multiple times to append into
      * the current state.
+     * Sub-keys like 'headers' will be reset, though.
      *
      * @param array $data The request data to use.
      * @return void

+ 3 - 3
tests/TestCase/Controller/ControllerFactoryTest.php

@@ -684,7 +684,7 @@ class ControllerFactoryTest extends TestCase
                 'plugin' => null,
                 'controller' => 'Dependencies',
                 'action' => 'requiredTyped',
-                'pass' => ['1.0', '02', '0'],
+                'pass' => ['1.0', '02', '0', '8,9'],
             ],
         ]);
         $controller = $this->factory->create($request);
@@ -693,7 +693,7 @@ class ControllerFactoryTest extends TestCase
         $data = json_decode((string)$result->getBody(), true);
 
         $this->assertNotNull($data);
-        $this->assertSame(['one' => 1.0, 'two' => 2, 'three' => false], $data);
+        $this->assertSame(['one' => 1.0, 'two' => 2, 'three' => false, 'four' => ['8', '9']], $data);
     }
 
     /**
@@ -799,7 +799,7 @@ class ControllerFactoryTest extends TestCase
         $controller = $this->factory->create($request);
 
         $this->expectException(InvalidParameterException::class);
-        $this->expectExceptionMessage('Unable to coerce "test" to `array` for `one` in action Dependencies::unsupportedTyped()');
+        $this->expectExceptionMessage('Unable to coerce "test" to `iterable` for `one` in action Dependencies::unsupportedTyped()');
         $this->factory->invoke($controller);
     }
 

+ 10 - 0
tests/TestCase/Controller/ControllerTest.php

@@ -209,6 +209,16 @@ class ControllerTest extends TestCase
         $this->assertInstanceOf('TestPlugin\Model\Table\CommentsTable', $controller->Comments);
     }
 
+    public function testConstructSetDefaultTable()
+    {
+        Configure::write('App.namespace', 'TestApp');
+
+        $controller = new PostsController();
+        $this->assertInstanceOf(PostsTable::class, $controller->fetchTable());
+
+        Configure::write('App.namespace', 'App');
+    }
+
     /**
      * testConstructClassesWithComponents method
      */

+ 2 - 2
tests/TestCase/Core/FunctionsTest.php

@@ -84,7 +84,7 @@ class FunctionsTest extends TestCase
     public function testDeprecationWarningEnabled(): void
     {
         $this->expectDeprecation();
-        $this->expectDeprecationMessageMatches('/This is going away - (.*?)[\/\\\]FunctionsTest.php, line\: \d+/');
+        $this->expectDeprecationMessageMatches('/This is going away\n(.*?)[\/\\\]FunctionsTest.php, line\: \d+/');
 
         $this->withErrorReporting(E_ALL, function (): void {
             deprecationWarning('This is going away', 2);
@@ -118,7 +118,7 @@ class FunctionsTest extends TestCase
     public function testDeprecationWarningEnabledDefaultFrame(): void
     {
         $this->expectDeprecation();
-        $this->expectDeprecationMessageMatches('/This is going away too - (.*?)[\/\\\]TestCase.php, line\: \d+/');
+        $this->expectDeprecationMessageMatches('/This is going away too\n(.*?)[\/\\\]TestCase.php, line\: \d+/');
 
         $this->withErrorReporting(E_ALL, function (): void {
             deprecationWarning('This is going away too');

+ 20 - 0
tests/TestCase/Http/Middleware/HttpsEnforcerMiddlewareTest.php

@@ -147,6 +147,26 @@ class HttpsEnforcerMiddlewareTest extends TestCase
         );
     }
 
+    public function testRedirectBasePath(): void
+    {
+        $request = new ServerRequest([
+            'url' => '/articles',
+            'base' => '/base',
+            'method' => 'GET',
+        ]);
+
+        $handler = new TestRequestHandler(function () {
+            return new Response();
+        });
+
+        $middleware = new HttpsEnforcerMiddleware();
+
+        $result = $middleware->process($request, $handler);
+        $this->assertInstanceOf(RedirectResponse::class, $result);
+        $this->assertSame(301, $result->getStatusCode());
+        $this->assertEquals(['location' => ['https://localhost/base/articles']], $result->getHeaders());
+    }
+
     /**
      * Test that exception is thrown when redirect is disabled.
      */

+ 1 - 1
tests/TestCase/ORM/TableTest.php

@@ -5346,7 +5346,7 @@ class TableTest extends TestCase
         return [
             [
                 ['fields' => ['id'], 'cache' => 'default'],
-                'get:test.table_name[10]', 'default',
+                'get-test-table_name-[10]', 'default',
             ],
             [
                 ['fields' => ['id'], 'cache' => 'default', 'key' => 'custom_key'],

+ 34 - 0
tests/TestCase/Routing/Route/RouteTest.php

@@ -1578,6 +1578,40 @@ class RouteTest extends TestCase
         $this->assertEquals($expected, $result);
     }
 
+    public function testUrlWithEncodedSlash(): void
+    {
+        $route = new Route(
+            '/products/tests/*',
+            ['controller' => 'Products', 'action' => 'test'],
+            ['_urldecode' => false]
+        );
+
+        $result = $route->parse('/products/tests/xx%2Fyy', 'GET');
+        $expected = [
+            'controller' => 'Products',
+            'action' => 'test',
+            'pass' => ['xx%2Fyy'],
+            '_matchedRoute' => '/products/tests/*',
+        ];
+        $this->assertEquals($expected, $result);
+
+        $route = new Route(
+            '/products/view/{slug}',
+            ['controller' => 'Products', 'action' => 'view'],
+            ['_urldecode' => false]
+        );
+
+        $result = $route->parse('/products/view/xx%2Fyy', 'GET');
+        $expected = [
+            'controller' => 'Products',
+            'action' => 'view',
+            'slug' => 'xx%2Fyy',
+            'pass' => [],
+            '_matchedRoute' => '/products/view/{slug}',
+        ];
+        $this->assertEquals($expected, $result);
+    }
+
     /**
      * Test getting the static path for a route.
      *

+ 6 - 3
tests/test_app/TestApp/Controller/DependenciesController.php

@@ -43,9 +43,12 @@ class DependenciesController extends Controller
         return $this->response->withStringBody(json_encode(compact('str')));
     }
 
-    public function requiredTyped(float $one, int $two, bool $three)
+    public function requiredTyped(float $one, int $two, bool $three, array $four)
     {
-        return $this->response->withStringBody(json_encode(compact('one', 'two', 'three'), JSON_PRESERVE_ZERO_FRACTION));
+        return $this->response->withStringBody(json_encode(
+            compact('one', 'two', 'three', 'four'),
+            JSON_PRESERVE_ZERO_FRACTION
+        ));
     }
 
     public function optionalTyped(float $one = 1.0, int $two = 2, bool $three = true)
@@ -53,7 +56,7 @@ class DependenciesController extends Controller
         return $this->response->withStringBody(json_encode(compact('one', 'two', 'three'), JSON_PRESERVE_ZERO_FRACTION));
     }
 
-    public function unsupportedTyped(array $one)
+    public function unsupportedTyped(iterable $one)
     {
         return $this->response->withStringBody(json_encode(compact('one')));
     }