ソースを参照

Merge branch 'master' into 3.next

Also fix the stupid mistake I made in ValuesExpression.
Mark Story 10 年 前
コミット
a71cc748e2

+ 15 - 0
src/Console/ShellDispatcher.php

@@ -210,6 +210,10 @@ class ShellDispatcher
             $this->help();
             return true;
         }
+        if (in_array($shell, ['version', '--version'])) {
+            $this->version();
+            return true;
+        }
 
         $Shell = $this->findShell($shell);
 
@@ -367,4 +371,15 @@ class ShellDispatcher
         $this->args = array_merge(['command_list'], $this->args);
         $this->dispatch();
     }
+
+    /**
+     * Prints the currently installed version of CakePHP. Performs an internal dispatch to the CommandList Shell
+     *
+     * @return void
+     */
+    public function version()
+    {
+        $this->args = array_merge(['command_list', '--version'], $this->args);
+        $this->dispatch();
+    }
 }

+ 1 - 1
src/Database/Expression/ValuesExpression.php

@@ -190,7 +190,7 @@ class ValuesExpression implements ExpressionInterface
             $row += $defaults;
             $rowPlaceholders = [];
 
-            foreach ($this->_columns as $column) {
+            foreach ($columns as $column) {
                 $value = $row[$column];
 
                 if ($value instanceof ExpressionInterface) {

+ 1 - 1
src/Database/Query.php

@@ -1027,7 +1027,7 @@ class Query implements ExpressionInterface, IteratorAggregate
     }
 
     /**
-     * Add an ORDER BY clause with an ASC direction.
+     * Add an ORDER BY clause with a DESC direction.
      *
      * This method allows you to set complex expressions
      * as order conditions unlike order()

+ 24 - 1
src/Database/Schema/SqlserverSchema.php

@@ -161,12 +161,35 @@ class SqlserverSchema extends BaseSchema
 
         $field += [
             'null' => $row['null'] === '1' ? true : false,
-            'default' => $row['default'],
+            'default' => $this->_defaultValue($row['default']),
         ];
         $table->addColumn($row['name'], $field);
     }
 
     /**
+     * Manipulate the default value.
+     *
+     * Sqlite includes quotes and bared NULLs in default values.
+     * We need to remove those.
+     *
+     * @param string|null $default The default value.
+     * @return string|null
+     */
+    protected function _defaultValue($default)
+    {
+        if ($default === 'NULL') {
+            return null;
+        }
+
+        // Remove quotes
+        if (preg_match("/^'(.*)'$/", $default, $matches)) {
+            return str_replace("''", "'", $matches[1]);
+        }
+
+        return $default;
+    }
+
+    /**
      * {@inheritDoc}
      */
     public function describeIndexSql($tableName, $config)

+ 2 - 1
src/Mailer/Email.php

@@ -22,6 +22,7 @@ use Cake\Filesystem\File;
 use Cake\Log\Log;
 use Cake\Network\Http\FormData\Part;
 use Cake\Utility\Hash;
+use Cake\Utility\Security;
 use Cake\Utility\Text;
 use Cake\View\ViewVarsTrait;
 use Closure;
@@ -1635,7 +1636,7 @@ class Email implements JsonSerializable, Serializable
     protected function _createBoundary()
     {
         if (!empty($this->_attachments) || $this->_emailFormat === 'both') {
-            $this->_boundary = md5(uniqid(time()));
+            $this->_boundary = md5(Security::randomBytes(16));
         }
     }
 

+ 1 - 1
src/ORM/Association/BelongsToMany.php

@@ -894,7 +894,7 @@ class BelongsToMany extends Association
 
         $belongsTo = $this->junction()->association($this->target()->alias());
         $conditions = $belongsTo->_joinCondition([
-            'foreignKey' => $this->foreignKey()
+            'foreignKey' => $this->targetForeignKey()
         ]);
         $conditions += $this->junctionConditions();
         return $this->_appendJunctionJoin($query, $conditions);

+ 2 - 1
src/ORM/Marshaller.php

@@ -227,7 +227,8 @@ class Marshaller
 
         $tableName = $this->_table->alias();
         if (isset($data[$tableName])) {
-            $data = $data[$tableName];
+            $data += $data[$tableName];
+            unset($data[$tableName]);
         }
 
         $data = new ArrayObject($data);

+ 4 - 1
src/ORM/Rule/IsUnique.php

@@ -74,6 +74,9 @@ class IsUnique
     /**
      * Add a model alias to all the keys in a set of conditions.
      *
+     * Null values will be omitted from the generated conditions,
+     * as SQL UNIQUE indexes treat `NULL != NULL`
+     *
      * @param string $alias The alias to add.
      * @param array $conditions The conditions to alias.
      * @return array
@@ -82,7 +85,7 @@ class IsUnique
     {
         $aliased = [];
         foreach ($conditions as $key => $value) {
-            $aliased["$alias.$key IS"] = $value;
+            $aliased["$alias.$key"] = $value;
         }
         return $aliased;
     }

+ 13 - 4
src/Shell/CommandListShell.php

@@ -16,6 +16,7 @@ namespace Cake\Shell;
 
 use Cake\Console\ConsoleOutput;
 use Cake\Console\Shell;
+use Cake\Core\Configure;
 use Cake\Core\Plugin;
 use Cake\Utility\Inflector;
 use SimpleXmlElement;
@@ -41,7 +42,7 @@ class CommandListShell extends Shell
      */
     public function startup()
     {
-        if (empty($this->params['xml'])) {
+        if (!$this->param('xml') && !$this->param('version')) {
             parent::startup();
         }
     }
@@ -53,7 +54,7 @@ class CommandListShell extends Shell
      */
     public function main()
     {
-        if (empty($this->params['xml'])) {
+        if (!$this->param('xml') && !$this->param('version')) {
             $this->out("<info>Current Paths:</info>", 2);
             $this->out("* app:  " . APP_DIR);
             $this->out("* root: " . rtrim(ROOT, DIRECTORY_SEPARATOR));
@@ -63,12 +64,17 @@ class CommandListShell extends Shell
             $this->out("<info>Available Shells:</info>", 2);
         }
 
+        if ($this->param('version')) {
+            $this->out(Configure::version());
+            return;
+        }
+
         $shellList = $this->Command->getShellList();
-        if (empty($shellList)) {
+        if (!$shellList) {
             return;
         }
 
-        if (empty($this->params['xml'])) {
+        if (!$this->param('xml')) {
             $this->_asText($shellList);
         } else {
             $this->_asXml($shellList);
@@ -136,6 +142,9 @@ class CommandListShell extends Shell
         )->addOption('xml', [
             'help' => 'Get the listing as XML.',
             'boolean' => true
+        ])->addOption('version', [
+            'help' => 'Prints the currently installed version of CakePHP.',
+            'boolean' => true
         ]);
 
         return $parser;

+ 23 - 8
src/Template/Error/missing_action.ctp

@@ -26,18 +26,33 @@ if (!empty($prefix)) {
     $prefixNs = '\\' . $prefix;
     $prefix .= DIRECTORY_SEPARATOR;
 }
+
+// Controller MissingAction support
+if (isset($controller)) {
+    $baseClass = $namespace . '\Controller\AppController';
+    $extends = 'AppController';
+    $type = 'Controller';
+    $class = $controller;
+}
+// Mailer MissingActionException support
+if (isset($mailer)) {
+    $baseClass = 'Cake\Mailer\Mailer';
+    $type = $extends = 'Mailer';
+    $class = $mailer;
+}
+
 if (empty($plugin)) {
-    $path = APP_DIR . DIRECTORY_SEPARATOR . 'Controller' . DIRECTORY_SEPARATOR . $prefix . h($controller) . '.php' ;
+    $path = APP_DIR . DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . $prefix . h($class) . '.php' ;
 } else {
-    $path = Plugin::classPath($plugin) . 'Controller' . DIRECTORY_SEPARATOR . $prefix . h($controller) . '.php';
+    $path = Plugin::classPath($plugin) . $type . DIRECTORY_SEPARATOR . $prefix . h($class) . '.php';
 }
 
 $this->layout = 'dev_error';
 
-$this->assign('title', sprintf('Missing Method in %s', h($controller)));
+$this->assign('title', sprintf('Missing Method in %s', h($class)));
 $this->assign(
     'subheading',
-    sprintf('The action <em>%s</em> is not defined in <em>%s</em>', h($action), h($controller))
+    sprintf('The action <em>%s</em> is not defined in <em>%s</em>', h($action), h($class))
 );
 $this->assign('templateName', 'missing_action.ctp');
 
@@ -45,17 +60,17 @@ $this->start('file');
 ?>
 <p class="error">
     <strong>Error: </strong>
-    <?= sprintf('Create <em>%s::%s()</em> in file: %s.', h($controller),  h($action), $path); ?>
+    <?= sprintf('Create <em>%s::%s()</em> in file: %s.', h($class),  h($action), $path); ?>
 </p>
 
 <?php
 $code = <<<PHP
 <?php
-namespace {$namespace}\Controller{$prefixNs};
+namespace {$namespace}\\{$type}{$prefixNs};
 
-use {$namespace}\Controller\AppController;
+use {$baseClass};
 
-class {$controller} extends AppController
+class {$class} extends {$extends}
 {
 
     public function {$action}()

+ 7 - 0
src/Template/Error/missing_template.ctp

@@ -19,10 +19,17 @@ $this->layout = 'dev_error';
 $this->assign('title', 'Missing Template');
 $this->assign('templateName', 'missing_template.ctp');
 
+$isEmail = strpos($file, 'Email/') === 0;
+
 $this->start('subheading');
 ?>
+<?php if ($isEmail): ?>
+    <strong>Error: </strong>
+    <?= sprintf('The template %s</em> was not found.', h($file)); ?>
+<?php else: ?>
     <strong>Error: </strong>
     <?= sprintf('The view for <em>%sController::%s()</em> was not found.', h(Inflector::camelize($this->request->controller)), h($this->request->action)); ?>
+<?php endif ?>
 <?php $this->end() ?>
 
 <?php $this->start('file') ?>

+ 1 - 0
src/View/Helper/FormHelper.php

@@ -530,6 +530,7 @@ class FormHelper extends Helper
         ) {
             $out .= $this->secure($this->fields, $secureAttributes);
             $this->fields = [];
+            $this->_unlockedFields = [];
         }
         $out .= $this->formatTemplate('formEnd', []);
 

+ 40 - 0
tests/TestCase/Console/ShellDispatcherTest.php

@@ -341,4 +341,44 @@ class ShellDispatcherTest extends TestCase
         $this->assertNull($this->dispatcher->shiftArgs());
         $this->assertSame([], $this->dispatcher->args);
     }
+
+    /**
+     * Test how `bin/cake --help` works.
+     *
+     * @return void
+     */
+    public function testHelpOption()
+    {
+        $mockShell = $this->getMock('Cake\Shell\CommandListShell', ['main', 'initialize', 'startup']);
+        $mockShell->expects($this->once())
+            ->method('main');
+
+        $dispatcher = $this->getMock('Cake\Console\ShellDispatcher', ['findShell', '_stop']);
+        $dispatcher->expects($this->once())
+            ->method('findShell')
+            ->with('command_list')
+            ->will($this->returnValue($mockShell));
+        $dispatcher->args = ['--help'];
+        $dispatcher->dispatch();
+    }
+
+    /**
+     * Test how `bin/cake --version` works.
+     *
+     * @return void
+     */
+    public function testVersionOption()
+    {
+        $mockShell = $this->getMock('Cake\Shell\CommandListShell', ['main', 'initialize', 'startup']);
+        $mockShell->expects($this->once())
+            ->method('main');
+
+        $dispatcher = $this->getMock('Cake\Console\ShellDispatcher', ['findShell', '_stop']);
+        $dispatcher->expects($this->once())
+            ->method('findShell')
+            ->with('command_list')
+            ->will($this->returnValue($mockShell));
+        $dispatcher->args = ['--version'];
+        $dispatcher->dispatch();
+    }
 }

+ 30 - 0
tests/TestCase/Database/Schema/SqlserverSchemaTest.php

@@ -68,6 +68,9 @@ author_id INTEGER NOT NULL,
 published BIT DEFAULT 0,
 views SMALLINT DEFAULT 0,
 created DATETIME,
+field1 VARCHAR(10) DEFAULT NULL,
+field2 VARCHAR(10) DEFAULT 'NULL',
+field3 VARCHAR(10) DEFAULT 'O''hare',
 CONSTRAINT [content_idx] UNIQUE ([title], [body]),
 CONSTRAINT [author_idx] FOREIGN KEY ([author_id]) REFERENCES [schema_authors] ([id]) ON DELETE CASCADE ON UPDATE CASCADE
 )
@@ -345,6 +348,33 @@ SQL;
                 'precision' => null,
                 'comment' => null,
             ],
+            'field1' => [
+                'type' => 'string',
+                'null' => true,
+                'default' => null,
+                'length' => 10,
+                'precision' => null,
+                'fixed' => null,
+                'comment' => null,
+            ],
+            'field2' => [
+                'type' => 'string',
+                'null' => true,
+                'default' => 'NULL',
+                'length' => 10,
+                'precision' => null,
+                'fixed' => null,
+                'comment' => null,
+            ],
+            'field3' => [
+                'type' => 'string',
+                'null' => true,
+                'default' => 'O\'hare',
+                'length' => 10,
+                'precision' => null,
+                'fixed' => null,
+                'comment' => null,
+            ],
         ];
         $this->assertEquals(['id'], $result->primaryKey());
         foreach ($expected as $field => $definition) {

+ 14 - 0
tests/TestCase/Error/ExceptionRendererTest.php

@@ -27,6 +27,7 @@ use Cake\Datasource\Exception\MissingDatasourceException;
 use Cake\Error\ExceptionRenderer;
 use Cake\Event\Event;
 use Cake\Event\EventManager;
+use Cake\Mailer\Exception\MissingActionException as MissingMailerActionException;
 use Cake\Network\Exception\InternalErrorException;
 use Cake\Network\Exception\MethodNotAllowedException;
 use Cake\Network\Exception\NotFoundException;
@@ -584,6 +585,19 @@ class ExceptionRendererTest extends TestCase
                 500
             ],
             [
+                new MissingMailerActionException([
+                    'mailer' => 'UserMailer',
+                    'action' => 'welcome',
+                    'prefix' => '',
+                    'plugin' => '',
+                ]),
+                [
+                    '/Missing Method in UserMailer/',
+                    '/<em>UserMailer::welcome\(\)<\/em>/'
+                ],
+                404
+            ],
+            [
                 new Exception('boom'),
                 [
                     '/Internal Error/'

+ 1 - 1
tests/TestCase/Mailer/EmailTest.php

@@ -2083,7 +2083,7 @@ class EmailTest extends TestCase
         $this->assertNotEmpty($result);
 
         $result = $this->CakeEmail->getBoundary();
-        $this->assertNotEmpty($result);
+        $this->assertRegExp('/^[0-9a-f]{32}$/', $result);
     }
 
     /**

+ 2 - 0
tests/TestCase/ORM/Association/BelongsToManyTest.php

@@ -1022,6 +1022,7 @@ class BelongsToManyTest extends TestCase
         $query = $table->Tags->find();
         $result = $query->toArray();
         $this->assertCount(1, $result);
+        $this->assertEquals(1, $result[0]->id);
     }
 
     /**
@@ -1045,6 +1046,7 @@ class BelongsToManyTest extends TestCase
         $query = $table->Tags->find();
         $result = $query->toArray();
         $this->assertCount(1, $result);
+        $this->assertEquals(1, $result[0]->id);
     }
 
     /**

+ 3 - 1
tests/TestCase/ORM/MarshallerTest.php

@@ -361,6 +361,7 @@ class MarshallerTest extends TestCase
     public function testOneWithAdditionalName()
     {
         $data = [
+            'title' => 'Original Title',
             'Articles' => [
                 'title' => 'My title',
                 'body' => 'My content',
@@ -377,7 +378,8 @@ class MarshallerTest extends TestCase
         $this->assertInstanceOf('Cake\ORM\Entity', $result);
         $this->assertTrue($result->dirty(), 'Should be a dirty entity.');
         $this->assertTrue($result->isNew(), 'Should be new');
-        $this->assertEquals($data['Articles']['title'], $result->title);
+        $this->assertFalse($result->has('Articles'), 'No prefixed field.');
+        $this->assertEquals($data['title'], $result->title, 'Data from prefix should be merged.');
         $this->assertEquals($data['Articles']['user']['username'], $result->user->username);
     }
 

+ 4 - 5
tests/TestCase/ORM/RulesCheckerIntegrationTest.php

@@ -431,7 +431,7 @@ class RulesCheckerIntegrationTest extends TestCase
     }
 
     /**
-     * Tests isUnique with multiple fields and a nulled field.
+     * Tests isUnique with multiple fields emulates SQL UNIQUE keys
      *
      * @group save
      * @return void
@@ -448,13 +448,12 @@ class RulesCheckerIntegrationTest extends TestCase
 
         $this->assertSame($entity, $table->save($entity));
 
-        // Make a duplicate
+        // Make a matching record
         $entity = new Entity([
             'author_id' => null,
-            'title' => 'First Article'
+            'title' => 'New Article'
         ]);
-        $this->assertFalse($table->save($entity));
-        $this->assertEquals(['title' => ['_isUnique' => 'Nope']], $entity->errors());
+        $this->assertSame($entity, $table->save($entity));
     }
 
     /**

+ 17 - 0
tests/TestCase/Shell/CommandListShellTest.php

@@ -15,6 +15,7 @@
 namespace Cake\Test\TestCase\Shell;
 
 use Cake\Console\ConsoleIo;
+use Cake\Core\Configure;
 use Cake\Core\Plugin;
 use Cake\TestSuite\Stub\ConsoleOutput;
 use Cake\TestSuite\TestCase;
@@ -131,4 +132,20 @@ class CommandListShellTest extends TestCase
         $find = '<shell name="welcome" call_as="TestPluginTwo.welcome" provider="TestPluginTwo" help="TestPluginTwo.welcome -h"';
         $this->assertContains($find, $output);
     }
+
+    /**
+     * test that main prints the cakephp's version.
+     *
+     * @return void
+     */
+    public function testMainVersion()
+    {
+        $this->Shell->params['version'] = true;
+        $this->Shell->main();
+        $output = $this->out->messages();
+        $output = implode("\n", $output);
+
+        $expected = Configure::version();
+        $this->assertEquals($expected, $output);
+    }
 }

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

@@ -2281,6 +2281,29 @@ class FormHelperTest extends TestCase
     }
 
     /**
+     * test reset unlockFields, when create new form.
+     *
+     * @return void
+     */
+    public function testResetUnlockFields()
+    {
+        $this->Form->request['_Token'] = [
+            'key' => 'testKey',
+            'unlockedFields' => []
+        ];
+
+        $this->Form->unlockField('Contact.id');
+        $this->Form->create('Contact');
+        $this->Form->hidden('Contact.id', ['value' => 1]);
+        $this->assertEmpty($this->Form->fields, 'Field should be unlocked');
+        $this->Form->end();
+
+        $this->Form->create('Contact');
+        $this->Form->hidden('Contact.id', ['value' => 1]);
+        $this->assertEquals(1, $this->Form->fields['Contact.id'], 'Hidden input should be secured.');
+    }
+
+    /**
      * Test that only the path + query elements of a form's URL show up in their hash.
      *
      * @return void