Browse Source

Merge branch 'master' into 3.next

Mark Story 9 years ago
parent
commit
208a3552f1

+ 5 - 0
phpcs.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<ruleset name="CakePHP">
+    <rule ref=".\vendor\cakephp\cakephp-codesniffer\CakePHP\ruleset.xml"/>
+    <file>./src</file>
+</ruleset>

+ 2 - 2
src/Controller/Component/RequestHandlerComponent.php

@@ -310,8 +310,8 @@ class RequestHandlerComponent extends Component
 
         if (!empty($this->ext) && $isRecognized) {
             $this->renderAs($event->subject(), $this->ext);
-        } elseif (empty($this->ext) || in_array($this->ext, ['html', 'htm'])) {
-            $this->respondAs('html', ['charset' => Configure::read('App.encoding')]);
+        } else {
+            $this->response->charset(Configure::read('App.encoding'));
         }
 
         if ($this->_config['checkHttpCache'] &&

+ 7 - 2
src/Database/Expression/ValuesExpression.php

@@ -171,7 +171,13 @@ class ValuesExpression implements ExpressionInterface
         }
 
         $i = 0;
-        $defaults = array_fill_keys($this->_columns, null);
+        $columns = [];
+
+        // Remove identifier quoting so column names match keys.
+        foreach ($this->_columns as $col) {
+            $columns[] = trim($col, '`[]"');
+        }
+        $defaults = array_fill_keys($columns, null);
         $placeholders = [];
 
         $types = [];
@@ -203,7 +209,6 @@ class ValuesExpression implements ExpressionInterface
         if ($this->query()) {
             return ' ' . $this->query()->sql($generator);
         }
-
         return sprintf(' VALUES (%s)', implode('), (', $placeholders));
     }
 

+ 2 - 2
src/Database/Query.php

@@ -1320,11 +1320,11 @@ class Query implements ExpressionInterface, IteratorAggregate
         $this->_dirty();
         $this->_type = 'insert';
         $this->_parts['insert'][1] = $columns;
-
         if (!$this->_parts['values']) {
             $this->_parts['values'] = new ValuesExpression($columns, $this->typeMap()->types($types));
+        } else {
+            $this->_parts['values']->columns($columns);
         }
-
         return $this;
     }
 

+ 29 - 0
src/Filesystem/Folder.php

@@ -484,6 +484,35 @@ class Folder
     }
 
     /**
+     * Returns an array of subdirectories for the provided or current path.
+     *
+     * @param string|null $path The directory path to get subdirectories for.
+     * @param bool $fullPath Whether to return the full path or only the directory name.
+     * @return array Array of subdirectories for the provided or current path.
+     */
+    public function subdirectories($path = null, $fullPath = true)
+    {
+        if (!$path) {
+            $path = $this->path;
+        }
+        $subdirectories = [];
+
+        try {
+            $iterator = new DirectoryIterator($path);
+        } catch (Exception $e) {
+            return [];
+        }
+
+        foreach ($iterator as $item) {
+            if (!$item->isDir() || $item->isDot()) {
+                continue;
+            }
+            $subdirectories[] = $fullPath ? $item->getRealPath() : $item->getFilename();
+        }
+        return $subdirectories;
+    }
+
+    /**
      * Returns an array of nested directories and files in each directory
      *
      * @param string|null $path the directory path to build the tree from

+ 7 - 5
src/Mailer/Email.php

@@ -1013,14 +1013,16 @@ class Email implements JsonSerializable, Serializable
         $className = App::className($config['className'], 'Mailer/Transport', 'Transport');
         if (!$className) {
             $className = App::className($config['className'], 'Network/Email', 'Transport');
-            trigger_error(
-                'Transports in "Network/Email" are deprecated, use "Mailer/Transport" instead.',
-                E_USER_DEPRECATED
-            );
+            if ($className) {
+                trigger_error(
+                    'Transports in "Network/Email" are deprecated, use "Mailer/Transport" instead.',
+                    E_USER_DEPRECATED
+                );
+            }
         }
 
         if (!$className) {
-            throw new InvalidArgumentException(sprintf('Transport class "%s" not found.', $name));
+            throw new InvalidArgumentException(sprintf('Transport class "%s" not found.', $config['className']));
         } elseif (!method_exists($className, 'send')) {
             throw new InvalidArgumentException(sprintf('The "%s" does not have a send() method.', $className));
         }

+ 4 - 5
src/Network/Request.php

@@ -336,11 +336,10 @@ class Request implements ArrayAccess
         if (isset($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '://') === false) {
             $uri = $_SERVER['REQUEST_URI'];
         } elseif (isset($_SERVER['REQUEST_URI'])) {
-            $qPosition = strpos($_SERVER['REQUEST_URI'], '?');
-            if ($qPosition !== false && strpos($_SERVER['REQUEST_URI'], '://') > $qPosition) {
-                $uri = $_SERVER['REQUEST_URI'];
-            } else {
-                $uri = substr($_SERVER['REQUEST_URI'], strlen(Configure::read('App.fullBaseUrl')));
+            $uri = $_SERVER['REQUEST_URI'];
+            $fullBaseUrl = Configure::read('App.fullBaseUrl');
+            if (strpos($uri, $fullBaseUrl) === 0) {
+                $uri = substr($_SERVER['REQUEST_URI'], strlen($fullBaseUrl));
             }
         } elseif (isset($_SERVER['PHP_SELF'], $_SERVER['SCRIPT_NAME'])) {
             $uri = str_replace($_SERVER['SCRIPT_NAME'], '', $_SERVER['PHP_SELF']);

+ 1 - 1
src/ORM/Rule/ExistsIn.php

@@ -92,7 +92,7 @@ class ExistsIn
             $source = $source->source();
         }
 
-        if (!$entity->extract($this->_fields, true) && !$entity->isNew()) {
+        if (!$entity->extract($this->_fields, true)) {
             return true;
         }
 

+ 17 - 1
src/Utility/Security.php

@@ -121,13 +121,29 @@ class Security
             'Falling back to an insecure random source.',
             E_USER_WARNING
         );
+        return static::insecureRandomBytes($length);
+    }
+
+    /**
+     * Like randomBytes() above, but not cryptographically secure.
+     *
+     * @param int $length The number of bytes you want.
+     * @return string Random bytes in binary.
+     * @see \Cake\Utility\Security::randomBytes()
+     */
+    public static function insecureRandomBytes($length)
+    {
+        $length *= 2;
+
         $bytes = '';
         $byteLength = 0;
         while ($byteLength < $length) {
             $bytes .= static::hash(Text::uuid() . uniqid(mt_rand(), true), 'sha512', true);
             $byteLength = strlen($bytes);
         }
-        return substr($bytes, 0, $length);
+        $bytes = substr($bytes, 0, $length);
+
+        return pack('H*', $bytes);
     }
 
     /**

+ 13 - 0
tests/TestCase/Controller/Component/RequestHandlerComponentTest.php

@@ -1189,4 +1189,17 @@ class RequestHandlerComponentTest extends TestCase
         $this->assertArrayHasKey('json', $inputs);
         $this->assertCount(1, $inputs);
     }
+
+    /**
+     * test beforeRender() doesn't override response type set in controller action
+     *
+     * @return void
+     */
+    public function testBeforeRender()
+    {
+        $this->Controller->set_response_type();
+        $event = new Event('Controller.beforeRender', $this->Controller);
+        $this->RequestHandler->beforeRender($event);
+        $this->assertEquals('text/plain', $this->Controller->response->type());
+    }
 }

+ 25 - 0
tests/TestCase/Database/QueryTest.php

@@ -2803,6 +2803,31 @@ class QueryTest extends TestCase
     }
 
     /**
+     * Test insert overwrites values
+     *
+     * @return void
+     */
+    public function testInsertOverwritesValues()
+    {
+        $this->loadFixtures('Articles');
+        $query = new Query($this->connection);
+        $query->insert(['title', 'body'])
+            ->insert(['title'])
+            ->into('articles')
+            ->values([
+                'title' => 'mark',
+            ]);
+
+        $result = $query->sql();
+        $this->assertQuotedQuery(
+            'INSERT INTO <articles> \(<title>\) (OUTPUT INSERTED\.\* )?' .
+            'VALUES \(:c0\)',
+            $result,
+            !$this->autoQuote
+        );
+    }
+
+    /**
      * Test inserting a single row.
      *
      * @return void

+ 38 - 1
tests/TestCase/Filesystem/FolderTest.php

@@ -428,6 +428,43 @@ class FolderTest extends TestCase
     }
 
     /**
+     * testFolderSubdirectories method
+     *
+     * @return void
+     */
+    public function testFolderSubdirectories()
+    {
+        $path = CAKE . 'Network';
+        $folder = new Folder($path);
+
+        $expected = [
+            $path . DS . 'Exception',
+            $path . DS . 'Http',
+            $path . DS . 'Session'
+        ];
+        $result = $folder->subdirectories();
+        $this->assertSame([], array_diff($expected, $result));
+        $result = $folder->subdirectories($path);
+        $this->assertSame([], array_diff($expected, $result));
+
+        $expected = [
+            'Exception',
+            'Http',
+            'Session'
+        ];
+        $result = $folder->subdirectories(null, false);
+        $this->assertSame([], array_diff($expected, $result));
+        $result = $folder->subdirectories($path, false);
+        $this->assertSame([], array_diff($expected, $result));
+
+        $expected = [];
+        $result = $folder->subdirectories('NonExistantPath');
+        $this->assertSame([], array_diff($expected, $result));
+        $result = $folder->subdirectories($path . DS . 'Exception');
+        $this->assertSame([], array_diff($expected, $result));
+    }
+
+    /**
      * testFolderTree method
      *
      * @return void
@@ -1235,7 +1272,7 @@ class FolderTest extends TestCase
         $Folder = new Folder($path);
         $Folder->delete();
     }
-    
+
     public function testMoveWithoutRecursive()
     {
         extract($this->_setupFilesystem());

+ 18 - 0
tests/TestCase/Mailer/EmailTest.php

@@ -113,6 +113,9 @@ class EmailTest extends TestCase
         $this->transports = [
             'debug' => [
                 'className' => 'Debug'
+            ],
+            'badClassName' => [
+                'className' => 'TestFalse'
             ]
         ];
         Email::configTransport($this->transports);
@@ -129,6 +132,7 @@ class EmailTest extends TestCase
         Log::drop('email');
         Email::drop('test');
         Email::dropTransport('debug');
+        Email::dropTransport('badClassName');
         Email::dropTransport('test_smtp');
     }
 
@@ -367,6 +371,20 @@ class EmailTest extends TestCase
     }
 
     /**
+     * Tests not found transport class name exception
+     *
+     * @return void
+     *
+     * @expectedException \InvalidArgumentException
+     * @expectedExceptionMessage Transport class "TestFalse" not found.
+     */
+    public function testClassNameException()
+    {
+        $email = new Email();
+        $email->transport('badClassName');
+    }
+
+    /**
      * Tests that it is possible to unset the email pattern and make use of filter_var() instead.
      *
      * @return void

+ 14 - 0
tests/TestCase/Network/RequestTest.php

@@ -195,6 +195,20 @@ class RequestTest extends TestCase
     }
 
     /**
+     * Test that URL in path is handled correctly.
+     */
+    public function testUrlInPath()
+    {
+        $_SERVER['REQUEST_URI'] = '/jump/http://cakephp.org';
+        $request = Request::createFromGlobals();
+        $this->assertEquals('jump/http://cakephp.org', $request->url);
+
+        $_SERVER['REQUEST_URI'] = Configure::read('App.fullBaseUrl') . '/jump/http://cakephp.org';
+        $request = Request::createFromGlobals();
+        $this->assertEquals('jump/http://cakephp.org', $request->url);
+    }
+
+    /**
      * Test addParams() method
      *
      * @return void

+ 10 - 8
tests/TestCase/ORM/RulesCheckerIntegrationTest.php

@@ -15,6 +15,7 @@
 namespace Cake\Test\TestCase\ORM;
 
 use Cake\ORM\Entity;
+use Cake\ORM\RulesChecker;
 use Cake\ORM\TableRegistry;
 use Cake\TestSuite\TestCase;
 
@@ -502,7 +503,7 @@ class RulesCheckerIntegrationTest extends TestCase
     /**
      * ExistsIn uses the schema to verify that nullable fields are ok.
      *
-     * @return void
+     * @return
      */
     public function testExistsInNullValue()
     {
@@ -521,16 +522,18 @@ class RulesCheckerIntegrationTest extends TestCase
     }
 
     /**
-     * Test ExistsIn on not dirty field in new Entity
+     * Test ExistsIn on a new entity that doesn't have the field populated.
+     *
+     * This use case is important for saving records and their
+     * associated belongsTo records in one pass.
      *
      * @return void
      */
-    public function testExistsInNotNullValue()
+    public function testExistsInNotNullValueNewEntity()
     {
         $entity = new Entity([
             'name' => 'A Category',
         ]);
-
         $table = TableRegistry::get('Categories');
         $table->belongsTo('Categories', [
             'foreignKey' => 'parent_id',
@@ -538,15 +541,14 @@ class RulesCheckerIntegrationTest extends TestCase
         ]);
         $rules = $table->rulesChecker();
         $rules->add($rules->existsIn('parent_id', 'Categories'));
-
-        $this->assertFalse($table->save($entity));
-        $this->assertEquals(['_existsIn' => 'This value does not exist'], $entity->errors('parent_id'));
+        $this->assertTrue($table->checkRules($entity, RulesChecker::CREATE));
+        $this->assertEmpty($entity->errors('parent_id'));
     }
 
     /**
      * Tests exists in uses the bindingKey of the association
      *
-     * @return void
+     * @return
      */
     public function testExistsInWithBindingKey()
     {

+ 19 - 1
tests/TestCase/Utility/SecurityTest.php

@@ -294,7 +294,7 @@ class SecurityTest extends TestCase
     }
 
     /**
-     * Test the random method.
+     * Test the randomBytes method.
      *
      * @return void
      */
@@ -305,5 +305,23 @@ class SecurityTest extends TestCase
 
         $value = Security::randomBytes(64);
         $this->assertSame(64, strlen($value));
+
+        $this->assertRegExp('/[^0-9a-f]/', $value, 'should return a binary string');
+    }
+
+    /**
+     * Test the insecureRandomBytes method
+     *
+     * @return void
+     */
+    public function testInsecureRandomBytes()
+    {
+        $value = Security::insecureRandomBytes(16);
+        $this->assertSame(16, strlen($value));
+
+        $value = Security::insecureRandomBytes(64);
+        $this->assertSame(64, strlen($value));
+
+        $this->assertRegExp('/[^0-9a-f]/', $value, 'should return a binary string');
     }
 }

+ 11 - 0
tests/test_app/TestApp/Controller/RequestHandlerTestController.php

@@ -59,4 +59,15 @@ class RequestHandlerTestController extends Controller
         $this->viewBuilder()->layout('ajax2');
         $this->destination();
     }
+
+    /**
+     * test method for testing that response type set in action doesn't get
+     * overridden by RequestHandlerComponent::beforeRender()
+     *
+     * @return void
+     */
+    public function set_response_type()
+    {
+        $this->response->type('txt');
+    }
 }