Browse Source

Merge branch 'master' into 3.next

Mark Story 8 years ago
parent
commit
d4010d4bd9

+ 0 - 3
.travis.yml

@@ -37,9 +37,6 @@ matrix:
     - php: 7.0
       env: PHPCS=1 DEFAULT=0
 
-    - php: 7.0
-      env: PHPSTAN=1 DEFAULT=0
-
     - php: 7.1
       env: PHPSTAN=1 DEFAULT=0
 

+ 1 - 1
VERSION.txt

@@ -16,4 +16,4 @@
 // @license       https://opensource.org/licenses/mit-license.php MIT License
 // +--------------------------------------------------------------------------------------------+ //
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-3.6.0-dev
+3.5.7

+ 10 - 10
composer.json

@@ -46,7 +46,7 @@
     },
     "autoload": {
         "psr-4": {
-            "Cake\\": "src"
+            "Cake\\": "src/"
         },
         "files": [
             "src/Core/functions.php",
@@ -57,15 +57,15 @@
     },
     "autoload-dev": {
         "psr-4": {
-            "Cake\\PHPStan\\": "tests/PHPStan",
-            "Cake\\Test\\": "tests",
-            "TestApp\\": "tests/test_app/TestApp",
-            "TestPlugin\\": "tests/test_app/Plugin/TestPlugin/src",
-            "TestPlugin\\Test\\": "tests/test_app/Plugin/TestPlugin/tests",
-            "TestPluginTwo\\": "tests/test_app/Plugin/TestPluginTwo/src",
-            "Company\\TestPluginThree\\": "tests/test_app/Plugin/Company/TestPluginThree/src",
-            "Company\\TestPluginThree\\Test\\": "tests/test_app/Plugin/Company/TestPluginThree/tests",
-            "PluginJs\\": "tests/test_app/Plugin/PluginJs/src"
+            "Cake\\PHPStan\\": "tests/PHPStan/",
+            "Cake\\Test\\": "tests/",
+            "TestApp\\": "tests/test_app/TestApp/",
+            "TestPlugin\\": "tests/test_app/Plugin/TestPlugin/src/",
+            "TestPlugin\\Test\\": "tests/test_app/Plugin/TestPlugin/tests/",
+            "TestPluginTwo\\": "tests/test_app/Plugin/TestPluginTwo/src/",
+            "Company\\TestPluginThree\\": "tests/test_app/Plugin/Company/TestPluginThree/src/",
+            "Company\\TestPluginThree\\Test\\": "tests/test_app/Plugin/Company/TestPluginThree/tests/",
+            "PluginJs\\": "tests/test_app/Plugin/PluginJs/src/"
         }
     },
     "replace": {

+ 2 - 2
phpunit.xml.dist

@@ -17,13 +17,13 @@
     </php>
 
     <testsuites>
-        <testsuite name="CakePHP Test Suite">
+        <testsuite name="cakephp">
             <directory>./tests/TestCase/</directory>
             <!-- Excludes are required in order to let DatabaseSuite decorate the tests -->
             <exclude>./tests/TestCase/Database/</exclude>
             <exclude>./tests/TestCase/ORM/</exclude>
         </testsuite>
-        <testsuite name="Database Test Suite">
+        <testsuite name="database">
             <file>./tests/TestCase/DatabaseSuite.php</file>
         </testsuite>
     </testsuites>

+ 38 - 22
src/Database/Type.php

@@ -29,7 +29,7 @@ class Type implements TypeInterface
      * identifier is used as key and a complete namespaced class name as value
      * representing the class that will do actual type conversions.
      *
-     * @var array
+     * @var string[]|\Cake\Database\Type[]
      */
     protected static $_types = [
         'tinyinteger' => 'Cake\Database\Type\IntegerType',
@@ -53,7 +53,7 @@ class Type implements TypeInterface
 
     /**
      * List of basic type mappings, used to avoid having to instantiate a class
-     * for doing conversion on these
+     * for doing conversion on these.
      *
      * @var array
      * @deprecated 3.1 All types will now use a specific class
@@ -68,9 +68,9 @@ class Type implements TypeInterface
     ];
 
     /**
-     * Contains a map of type object instances to be reused if needed
+     * Contains a map of type object instances to be reused if needed.
      *
-     * @var array
+     * @var \Cake\Database\Type[]
      */
     protected static $_builtTypes = [];
 
@@ -92,7 +92,7 @@ class Type implements TypeInterface
     }
 
     /**
-     * Returns a Type object capable of converting a type identified by $name
+     * Returns a Type object capable of converting a type identified by name.
      *
      * @param string $name type identifier
      * @throws \InvalidArgumentException If type identifier is unknown
@@ -114,14 +114,14 @@ class Type implements TypeInterface
     }
 
     /**
-     * Returns an arrays with all the mapped type objects, indexed by name
+     * Returns an arrays with all the mapped type objects, indexed by name.
      *
      * @return array
      */
     public static function buildAll()
     {
         $result = [];
-        foreach (self::$_types as $name => $type) {
+        foreach (static::$_types as $name => $type) {
             $result[$name] = isset(static::$_builtTypes[$name]) ? static::$_builtTypes[$name] : static::build($name);
         }
 
@@ -145,27 +145,30 @@ class Type implements TypeInterface
      * If called with no arguments it will return current types map array
      * If $className is omitted it will return mapped class for $type
      *
-     * @param string|array|\Cake\Database\Type|null $type if string name of type to map, if array list of arrays to be mapped
-     * @param string|null $className The classname to register.
-     * @return array|string|null if $type is null then array with current map, if $className is null string
+     * Deprecated: The usage of $type as \Cake\Database\Type[] is deprecated. Please always use string[] if you pass an array
+     * as first argument.
+     *
+     * @param string|string[]|\Cake\Database\Type[]|null $type If string name of type to map, if array list of arrays to be mapped
+     * @param string|\Cake\Database\Type|null $className The classname or object instance of it to register.
+     * @return array|string|null If $type is null then array with current map, if $className is null string
      * configured class name for give $type, null otherwise
      */
     public static function map($type = null, $className = null)
     {
         if ($type === null) {
-            return self::$_types;
+            return static::$_types;
         }
         if (is_array($type)) {
-            self::$_types = $type;
+            static::$_types = $type;
 
             return null;
         }
         if ($className === null) {
-            return isset(self::$_types[$type]) ? self::$_types[$type] : null;
+            return isset(static::$_types[$type]) ? static::$_types[$type] : null;
         }
 
-        self::$_types[$type] = $className;
-        unset(self::$_builtTypes[$type]);
+        static::$_types[$type] = $className;
+        unset(static::$_builtTypes[$type]);
     }
 
     /**
@@ -175,8 +178,8 @@ class Type implements TypeInterface
      */
     public static function clear()
     {
-        self::$_types = [];
-        self::$_builtTypes = [];
+        static::$_types = [];
+        static::$_builtTypes = [];
     }
 
     /**
@@ -206,8 +209,8 @@ class Type implements TypeInterface
     /**
      * Casts given value from a database type to PHP equivalent
      *
-     * @param mixed $value value to be converted to PHP equivalent
-     * @param \Cake\Database\Driver $driver object from which database preferences and configuration will be extracted
+     * @param mixed $value Value to be converted to PHP equivalent
+     * @param \Cake\Database\Driver $driver Object from which database preferences and configuration will be extracted
      * @return mixed
      */
     public function toPHP($value, Driver $driver)
@@ -219,7 +222,7 @@ class Type implements TypeInterface
      * Checks whether this type is a basic one and can be converted using a callback
      * If it is, returns converted value
      *
-     * @param mixed $value value to be converted to PHP equivalent
+     * @param mixed $value Value to be converted to PHP equivalent
      * @return mixed
      * @deprecated 3.1 All types should now be a specific class
      */
@@ -229,8 +232,8 @@ class Type implements TypeInterface
         if ($value === null) {
             return null;
         }
-        if (!empty(self::$_basicTypes[$this->_name])) {
-            $typeInfo = self::$_basicTypes[$this->_name];
+        if (!empty(static::$_basicTypes[$this->_name])) {
+            $typeInfo = static::$_basicTypes[$this->_name];
             if (isset($typeInfo['callback'])) {
                 return $typeInfo['callback']($value);
             }
@@ -304,4 +307,17 @@ class Type implements TypeInterface
     {
         return $this->_basicTypeCast($value);
     }
+
+    /**
+     * Returns an array that can be used to describe the internal state of this
+     * object.
+     *
+     * @return array
+     */
+    public function __debugInfo()
+    {
+        return [
+            'name' => $this->_name,
+        ];
+    }
 }

+ 1 - 1
src/Http/Response.php

@@ -865,7 +865,7 @@ class Response implements ResponseInterface
         }
 
         // Compatibility with closure/streaming responses
-        if (is_callable($content)) {
+        if (!is_string($content) && is_callable($content)) {
             $this->stream = new CallbackStream($content);
         } else {
             $this->_createStream();

+ 1 - 1
src/Http/ServerRequest.php

@@ -586,7 +586,7 @@ class ServerRequest implements ArrayAccess, ServerRequestInterface
         if (!empty($ref) && !empty($base)) {
             if ($local && strpos($ref, $base) === 0) {
                 $ref = substr($ref, strlen($base));
-                if (!strlen($ref)) {
+                if (!strlen($ref) || strpos($ref, '//') === 0) {
                     $ref = '/';
                 }
                 if ($ref[0] !== '/') {

+ 4 - 3
src/I18n/Parser/PoFileParser.php

@@ -14,6 +14,8 @@
  */
 namespace Cake\I18n\Parser;
 
+use Cake\I18n\Translator;
+
 /**
  * Parses file in PO format
  *
@@ -23,7 +25,6 @@ namespace Cake\I18n\Parser;
  */
 class PoFileParser
 {
-
     /**
      * Parses portable object (PO) format.
      *
@@ -166,9 +167,9 @@ class PoFileParser
             $key = stripcslashes($item['ids']['plural']);
 
             if ($context !== null) {
-                $messages[$key]['_context'][$context] = $plurals;
+                $messages[Translator::PLURAL_PREFIX . $key]['_context'][$context] = $plurals;
             } else {
-                $messages[$key]['_context'][''] = $plurals;
+                $messages[Translator::PLURAL_PREFIX . $key]['_context'][''] = $plurals;
             }
         }
     }

+ 14 - 1
src/I18n/Translator.php

@@ -22,6 +22,8 @@ use Aura\Intl\Translator as BaseTranslator;
 class Translator extends BaseTranslator
 {
 
+    const PLURAL_PREFIX = 'p:';
+
     /**
      * Translates the message formatting any placeholders
      *
@@ -33,7 +35,18 @@ class Translator extends BaseTranslator
      */
     public function translate($key, array $tokensValues = [])
     {
-        $message = $this->getMessage($key);
+        if (isset($tokensValues['_count'])) {
+            $message = $this->getMessage(static::PLURAL_PREFIX . $key);
+            if (!$message) {
+                $message = $this->getMessage($key);
+            }
+        } else {
+            $message = $this->getMessage($key);
+            if (!$message) {
+                $message = $this->getMessage(static::PLURAL_PREFIX . $key);
+            }
+        }
+
         if (!$message) {
             // Fallback to the message key
             $message = $key;

+ 17 - 5
src/ORM/BehaviorRegistry.php

@@ -84,19 +84,31 @@ class BehaviorRegistry extends ObjectRegistry implements EventDispatcherInterfac
     /**
      * Resolve a behavior classname.
      *
-     * Part of the template method for Cake\Core\ObjectRegistry::load()
-     *
      * @param string $class Partial classname to resolve.
-     * @return string|false Either the correct classname or false.
+     * @return string|null Either the correct classname or null.
+     * @since 3.5.7
      */
-    protected function _resolveClassName($class)
+    public static function className($class)
     {
         $result = App::className($class, 'Model/Behavior', 'Behavior');
         if (!$result) {
             $result = App::className($class, 'ORM/Behavior', 'Behavior');
         }
 
-        return $result;
+        return $result ?: null;
+    }
+
+    /**
+     * Resolve a behavior classname.
+     *
+     * Part of the template method for Cake\Core\ObjectRegistry::load()
+     *
+     * @param string $class Partial classname to resolve.
+     * @return string|false Either the correct classname or false.
+     */
+    protected function _resolveClassName($class)
+    {
+        return static::className($class) ?: false;
     }
 
     /**

+ 10 - 2
src/View/Helper/FormHelper.php

@@ -1745,6 +1745,7 @@ class FormHelper extends Helper
      * ### Options:
      *
      * - `escape` - HTML entity encode the $title of the button. Defaults to false.
+     * - `confirm` - Confirm message to show. Form execution will only continue if confirmed then.
      *
      * @param string $title The button's caption. Not automatically HTML encoded
      * @param array $options Array of options and HTML attributes.
@@ -1753,9 +1754,15 @@ class FormHelper extends Helper
      */
     public function button($title, array $options = [])
     {
-        $options += ['type' => 'submit', 'escape' => false, 'secure' => false];
+        $options += ['type' => 'submit', 'escape' => false, 'secure' => false, 'confirm' => null];
         $options['text'] = $title;
 
+        $confirmMessage = $options['confirm'];
+        unset($options['confirm']);
+        if ($confirmMessage) {
+            $options['onclick'] = $this->_confirm($confirmMessage, 'return true;', 'return false;', $options);
+        }
+
         return $this->widget('button', $options);
     }
 
@@ -1772,6 +1779,7 @@ class FormHelper extends Helper
      *   HTTP/1.1 DELETE (or others) request. Defaults to 'post'.
      * - `form` - Array with any option that FormHelper::create() can take
      * - Other options is the same of button method.
+     * - `confirm` - Confirm message to show. Form execution will only continue if confirmed then.
      *
      * @param string $title The button's caption. Not automatically HTML encoded
      * @param string|array $url URL as string or array
@@ -1819,7 +1827,7 @@ class FormHelper extends Helper
      * - `data` - Array with key/value to pass in input hidden
      * - `method` - Request method to use. Set to 'delete' to simulate
      *   HTTP/1.1 DELETE request. Defaults to 'post'.
-     * - `confirm` - Confirm message to show.
+     * - `confirm` - Confirm message to show. Form execution will only continue if confirmed then.
      * - `block` - Set to true to append form to view block "postLink" or provide
      *   custom block name.
      * - Other options are the same of HtmlHelper::link() method.

+ 38 - 3
tests/TestCase/Database/TypeTest.php

@@ -15,6 +15,9 @@
 namespace Cake\Test\TestCase\Database;
 
 use Cake\Database\Type;
+use Cake\Database\Type\BoolType;
+use Cake\Database\Type\IntegerType;
+use Cake\Database\Type\UuidType;
 use Cake\TestSuite\TestCase;
 use PDO;
 use TestApp\Database\Type\BarType;
@@ -149,7 +152,7 @@ class TypeTest extends TestCase
     public function testReMapAndBuild()
     {
         $fooType = FooType::class;
-        $map = Type::map('foo', $fooType);
+        Type::map('foo', $fooType);
         $type = Type::build('foo');
         $this->assertInstanceOf($fooType, $type);
 
@@ -160,6 +163,23 @@ class TypeTest extends TestCase
     }
 
     /**
+     * Tests new types can be registered and built as objects
+     *
+     * @return void
+     */
+    public function testMapAndBuildWithObjects()
+    {
+        $map = Type::map();
+        Type::clear();
+
+        $uuidType = new UuidType('uuid');
+        Type::map('uuid', $uuidType);
+
+        $this->assertSame($uuidType, Type::build('uuid'));
+        Type::map($map);
+    }
+
+    /**
      * Tests clear function in conjunction with map
      *
      * @return void
@@ -174,9 +194,11 @@ class TypeTest extends TestCase
 
         $this->assertEmpty(Type::map());
         Type::map($map);
-        $this->assertEquals($map, Type::map());
+        $newMap = Type::map();
 
-        $this->assertNotSame($type, Type::build('float'));
+        $this->assertEquals(array_keys($map), array_keys($newMap));
+        $this->assertEquals($map['integer'], $newMap['integer']);
+        $this->assertEquals($type, Type::build('float'));
     }
 
     /**
@@ -251,4 +273,17 @@ class TypeTest extends TestCase
         Type::set('random', $instance);
         $this->assertSame($instance, Type::build('random'));
     }
+
+    /**
+     * @return void
+     */
+    public function testDebugInfo()
+    {
+        $type = new Type('foo');
+        $result = $type->__debugInfo();
+        $expected = [
+            'name' => 'foo',
+        ];
+        $this->assertEquals($expected, $result);
+    }
 }

+ 17 - 0
tests/TestCase/Http/ResponseTest.php

@@ -585,6 +585,23 @@ class ResponseTest extends TestCase
     }
 
     /**
+     * Tests that callable strings are not triggered
+     *
+     * @return void
+     */
+    public function testSendWithCallableStringBody()
+    {
+        $response = $this->getMockBuilder('Cake\Http\Response')
+            ->setMethods(['_sendHeader'])
+            ->getMock();
+        $response->body('phpversion');
+
+        ob_start();
+        $response->send();
+        $this->assertEquals('phpversion', ob_get_clean());
+    }
+
+    /**
      * Tests the disableCache method
      *
      * @group deprecated

+ 7 - 0
tests/TestCase/Http/ServerRequestTest.php

@@ -733,6 +733,9 @@ class ServerRequestTest extends TestCase
         $this->assertSame('http://cakephp.org', $result);
 
         $request = $request->withEnv('HTTP_REFERER', '');
+        $result = $request->referer(true);
+        $this->assertSame('/', $result);
+
         $result = $request->referer();
         $this->assertSame('/', $result);
 
@@ -740,6 +743,10 @@ class ServerRequestTest extends TestCase
         $result = $request->referer(true);
         $this->assertSame('/some/path', $result);
 
+        $request = $request->withEnv('HTTP_REFERER', Configure::read('App.fullBaseUrl') . '///cakephp.org/');
+        $result = $request->referer(true);
+        $this->assertSame('/', $result); // Avoid returning scheme-relative URLs.
+
         $request = $request->withEnv('HTTP_REFERER', Configure::read('App.fullBaseUrl') . '/0');
         $result = $request->referer(true);
         $this->assertSame('/0', $result);

+ 34 - 9
tests/TestCase/I18n/Parser/PoFileParserTest.php

@@ -65,8 +65,8 @@ class PoFileParserTest extends TestCase
         $expected = [
             'Plural Rule 1' => [
                 '_context' => [
-                    '' => 'Plural Rule 1 (translated)',
-                ],
+                    '' => 'Plural Rule 1 (translated)'
+                ]
             ],
             '%d = 1' => [
                 '_context' => [
@@ -74,7 +74,7 @@ class PoFileParserTest extends TestCase
                     'Another Context' => '%d = 1 (translated)'
                 ]
             ],
-            '%d = 0 or > 1' => [
+            'p:%d = 0 or > 1' => [
                 '_context' => [
                     'Another Context' => [
                         0 => '%d = 1 (translated)',
@@ -84,10 +84,10 @@ class PoFileParserTest extends TestCase
             ],
             '%-5d = 1' => [
                 '_context' => [
-                    '' => '%-5d = 1 (translated)',
-                ],
+                    '' => '%-5d = 1 (translated)'
+                ]
             ],
-            '%-5d = 0 or > 1' => [
+            'p:%-5d = 0 or > 1' => [
                 '_context' => [
                     '' => [
                         0 => '%-5d = 1 (translated)',
@@ -95,9 +95,9 @@ class PoFileParserTest extends TestCase
                         2 => '',
                         3 => '',
                         4 => '%-5d = 0 or > 1 (translated)'
-                    ],
-                ],
-            ],
+                    ]
+                ]
+            ]
         ];
         $this->assertEquals($expected, $messages);
     }
@@ -189,4 +189,29 @@ class PoFileParserTest extends TestCase
         $this->assertSame('Titel mit anderem Kontext', __x('another_context', 'title'));
         $this->assertSame('Titel ohne Kontext', __('title'));
     }
+
+    /**
+     * Test parsing plurals
+     *
+     * @return void
+     */
+    public function testPlurals()
+    {
+        $parser = new PoFileParser();
+        $file = APP . 'Locale' . DS . 'de' . DS . 'wa.po';
+        $messages = $parser->parse($file);
+
+        I18n::translator('default', 'de_DE', function () use ($messages) {
+            $package = new Package('default');
+            $package->setMessages($messages);
+
+            return $package;
+        });
+
+        // Check translated messages
+        I18n::locale('de_DE');
+        $this->assertEquals('Standorte', __d('wa', 'Locations'));
+        I18n::locale('en_EN');
+        $this->assertEquals('Locations', __d('wa', 'Locations'));
+    }
 }

+ 20 - 0
tests/TestCase/ORM/BehaviorRegistryTest.php

@@ -52,6 +52,26 @@ class BehaviorRegistryTest extends TestCase
     }
 
     /**
+     * Test classname resolution.
+     *
+     * @return void
+     */
+    public function testClassName()
+    {
+        Plugin::load('TestPlugin');
+
+        $expected = 'Cake\ORM\Behavior\TranslateBehavior';
+        $result = BehaviorRegistry::className('Translate');
+        $this->assertSame($expected, $result);
+
+        $expected = 'TestPlugin\Model\Behavior\PersisterOneBehavior';
+        $result = BehaviorRegistry::className('TestPlugin.PersisterOne');
+        $this->assertSame($expected, $result);
+
+        $this->assertNull(BehaviorRegistry::className('NonExistent'));
+    }
+
+    /**
      * Test loading behaviors.
      *
      * @return void

+ 12 - 0
tests/TestCase/View/Helper/FormHelperTest.php

@@ -7327,6 +7327,18 @@ class FormHelperTest extends TestCase
     }
 
     /**
+     * Test generation of a form button with confirm message.
+     *
+     * @return void
+     */
+    public function testButtonWithConfirm()
+    {
+        $result = $this->Form->button('Hi', ['confirm' => 'Confirm me!']);
+        $expected = ['button' => ['type' => 'submit', 'onclick' => 'if (confirm(&quot;Confirm me!&quot;)) { return true; } return false;'], 'Hi', '/button'];
+        $this->assertHtml($expected, $result);
+    }
+
+    /**
      * testPostButton method
      *
      * @return void