Browse Source

Merge branch 'master' of github.com:cakephp/cakephp

Mark Story 8 years ago
parent
commit
c0231dca43

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

@@ -270,7 +270,7 @@ class PaginatorComponent extends Component
 
         $request->addParams([
             'paging' => $this->_paginator->getPagingParams()
-                + (array)$request->getParam('paging')
+                + $request->getParam('paging', [])
         ]);
     }
 

+ 1 - 1
src/Filesystem/Folder.php

@@ -345,7 +345,7 @@ class Folder
      */
     public static function isRegisteredStreamWrapper($path)
     {
-        return preg_match('/^[A-Z]+(?=:\/\/)/i', $path, $matches) &&
+        return preg_match('/^[^:\/\/]+?(?=:\/\/)/i', $path, $matches) &&
             in_array($matches[0], stream_get_wrappers());
     }
 

+ 28 - 0
src/ORM/Exception/PersistenceFailedException.php

@@ -45,10 +45,38 @@ class PersistenceFailedException extends Exception
     public function __construct(EntityInterface $entity, $message, $code = null, $previous = null)
     {
         $this->_entity = $entity;
+        if (is_array($message)) {
+            $errors = [];
+            foreach ($entity->getErrors() as $field => $error) {
+                $errors[] = $field . ': "' . $this->buildError($error) . '"';
+            }
+            if ($errors) {
+                $message[] = implode(', ', $errors);
+                $this->_messageTemplate = 'Entity %s failure (%s).';
+            }
+        }
         parent::__construct($message, $code, $previous);
     }
 
     /**
+     * @param string|array $error Error message.
+     * @return string
+     */
+    protected function buildError($error)
+    {
+        if (!is_array($error)) {
+            return $error;
+        }
+
+        $errors = [];
+        foreach ($error as $key => $message) {
+            $errors[] = is_int($key) ? $message : $key;
+        }
+
+        return implode(', ', $errors);
+    }
+
+    /**
      * Get the passed in entity
      *
      * @return \Cake\Datasource\EntityInterface

+ 104 - 13
src/Shell/Task/AssetsTask.php

@@ -53,6 +53,30 @@ class AssetsTask extends Shell
     }
 
     /**
+     * Remove plugin assets from app's webroot.
+     *
+     * @param string|null $name Name of plugin for which to remove assets.
+     *   If null all plugins will be processed.
+     * @return void
+     * @since 3.5.12
+     */
+    public function remove($name = null)
+    {
+        $plugins = $this->_list($name);
+
+        foreach ($plugins as $plugin => $config) {
+            $this->out();
+            $this->out('For plugin: ' . $plugin);
+            $this->hr();
+
+            $this->_remove($config);
+        }
+
+        $this->out();
+        $this->out('Done');
+    }
+
+    /**
      * Get list of plugins to process. Plugins without a webroot directory are skipped.
      *
      * @param string|null $name Name of plugin for which to symlink assets.
@@ -77,11 +101,10 @@ class AssetsTask extends Shell
         foreach ($pluginsList as $plugin) {
             $path = Plugin::path($plugin) . 'webroot';
             if (!is_dir($path)) {
-                $this->out('', 1, Shell::VERBOSE);
-                $this->out(
+                $this->verbose('', 1);
+                $this->verbose(
                     sprintf('Skipping plugin %s. It does not have webroot folder.', $plugin),
-                    2,
-                    Shell::VERBOSE
+                    2
                 );
                 continue;
             }
@@ -116,6 +139,8 @@ class AssetsTask extends Shell
      */
     protected function _process($plugins, $copy = false)
     {
+        $overwrite = (bool)$this->param('overwrite');
+
         foreach ($plugins as $plugin => $config) {
             $this->out();
             $this->out('For plugin: ' . $plugin);
@@ -128,19 +153,25 @@ class AssetsTask extends Shell
                 continue;
             }
 
-            if (file_exists($config['destDir'] . $config['link'])) {
-                $this->out(
-                    $config['destDir'] . $config['link'] . ' already exists',
-                    1,
-                    Shell::VERBOSE
-                );
-                continue;
+            $dest = $config['destDir'] . $config['link'];
+
+            if (file_exists($dest)) {
+                if ($overwrite && !$this->_remove($config)) {
+                    continue;
+                } elseif (!$overwrite) {
+                    $this->verbose(
+                        $dest . ' already exists',
+                        1
+                    );
+
+                    continue;
+                }
             }
 
             if (!$copy) {
                 $result = $this->_createSymlink(
                     $config['srcPath'],
-                    $config['destDir'] . $config['link']
+                    $dest
                 );
                 if ($result) {
                     continue;
@@ -149,7 +180,7 @@ class AssetsTask extends Shell
 
             $this->_copyDirectory(
                 $config['srcPath'],
-                $config['destDir'] . $config['link']
+                $dest
             );
         }
 
@@ -158,6 +189,59 @@ class AssetsTask extends Shell
     }
 
     /**
+     * Remove folder/symlink.
+     *
+     * @param array $config Plugin config.
+     * @return bool
+     */
+    protected function _remove($config)
+    {
+        if ($config['namespaced'] && !is_dir($config['destDir'])) {
+            $this->verbose(
+                $config['destDir'] . $config['link'] . ' does not exist',
+                1
+            );
+
+            return false;
+        }
+
+        $dest = $config['destDir'] . $config['link'];
+
+        if (!file_exists($dest)) {
+            $this->verbose(
+                $dest . ' does not exist',
+                1
+            );
+
+            return false;
+        }
+
+        if (is_link($dest)) {
+            // @codingStandardsIgnoreLine
+            if (@unlink($dest)) {
+                $this->out('Unlinked ' . $dest);
+
+                return true;
+            } else {
+                $this->err('Failed to unlink  ' . $dest);
+
+                return false;
+            }
+        }
+
+        $folder = new Folder($dest);
+        if ($folder->delete()) {
+            $this->out('Deleted ' . $dest);
+
+            return true;
+        } else {
+            $this->err('Failed to delete ' . $dest);
+
+            return false;
+        }
+    }
+
+    /**
      * Create directory
      *
      * @param string $dir Directory name
@@ -238,9 +322,16 @@ class AssetsTask extends Shell
             'help' => 'Symlink (copy as fallback) plugin assets to app\'s webroot.'
         ])->addSubcommand('copy', [
             'help' => 'Copy plugin assets to app\'s webroot.'
+        ])->addSubcommand('remove', [
+            'help' => 'Remove plugin assets from app\'s webroot.'
         ])->addArgument('name', [
             'help' => 'A specific plugin you want to symlink assets for.',
             'optional' => true,
+        ])
+        ->addOption('overwrite', [
+            'boolean' => true,
+            'default' => false,
+            'help' => 'Overwrite existing symlink / folder.'
         ]);
 
         return $parser;

+ 23 - 0
tests/TestCase/Controller/Component/PaginatorComponentTest.php

@@ -224,6 +224,29 @@ class PaginatorComponentTest extends TestCase
     }
 
     /**
+     * testRequestParamsSetting
+     *
+     * @return void
+     * @see https://github.com/cakephp/cakephp/issues/11655
+     */
+    public function testRequestParamsSetting()
+    {
+        $this->loadFixtures('Posts');
+
+        $settings = [
+            'PaginatorPosts' => [
+                'limit' => 10,
+            ]
+        ];
+
+        $table = TableRegistry::get('PaginatorPosts');
+
+        $this->Paginator->paginate($table, $settings);
+        $this->assertArrayHasKey('PaginatorPosts', $this->request->params['paging']);
+        $this->assertArrayNotHasKey(0, $this->request->params['paging']);
+    }
+
+    /**
      * Test that special paginate types are called and that the type param doesn't leak out into defaults or options.
      *
      * @return void

+ 19 - 0
tests/TestCase/Filesystem/FolderTest.php

@@ -1391,4 +1391,23 @@ class FolderTest extends TestCase
 
         $this->assertSame(['a.txt', 'b.txt', 'c.txt'], $results);
     }
+
+    /**
+     * testIsRegisteredStreamWrapper
+     *
+     * @return void
+     */
+    public function testIsRegisteredStreamWrapper()
+    {
+        foreach (stream_get_wrappers() as $wrapper) {
+            $this->assertTrue(Folder::isRegisteredStreamWrapper($wrapper . "://path/to/file"));
+            $this->assertFalse(Folder::isRegisteredStreamWrapper("bad." . $wrapper . "://path/to/file"));
+        }
+
+        $wrapper = 'unit.test1-';
+        $this->assertFalse(Folder::isRegisteredStreamWrapper($wrapper . "://path/to/file"));
+        stream_wrapper_register($wrapper, self::class);
+        $this->assertTrue(Folder::isRegisteredStreamWrapper($wrapper . "://path/to/file"));
+        stream_wrapper_unregister($wrapper);
+    }
 }

+ 20 - 2
tests/TestCase/ORM/TableTest.php

@@ -6507,15 +6507,33 @@ class TableTest extends TestCase
     {
         $this->expectException(\Cake\ORM\Exception\PersistenceFailedException::class);
         $this->expectExceptionMessage('Entity save failure.');
+
         $entity = new Entity([
             'foo' => 'bar'
         ]);
         $table = TableRegistry::get('users');
 
         $table->saveOrFail($entity);
+    }
 
-        $row = $table->find('all')->where(['foo' => 'bar'])->toArray();
-        $this->assertSame([], $row->toArray());
+    /**
+     * Tests that saveOrFail displays useful messages on output, especially in tests for CLI.
+     *
+     * @return void
+     */
+    public function testSaveOrFailErrorDisplay()
+    {
+        $this->expectException(\Cake\ORM\Exception\PersistenceFailedException::class);
+        $this->expectExceptionMessage('Entity save failure (field: "Some message", multiple: "one, two")');
+
+        $entity = new Entity([
+            'foo' => 'bar'
+        ]);
+        $entity->setError('field', 'Some message');
+        $entity->setError('multiple', ['one' => 'One', 'two' => 'Two']);
+        $table = TableRegistry::get('users');
+
+        $table->saveOrFail($entity);
     }
 
     /**

+ 117 - 15
tests/TestCase/Shell/Task/AssetsTaskTest.php

@@ -73,23 +73,21 @@ class AssetsTaskTest extends TestCase
         $this->Task->symlink();
 
         $path = WWW_ROOT . 'test_plugin';
-        $link = new \SplFileInfo($path);
         $this->assertFileExists($path . DS . 'root.js');
         if (DS === '\\') {
-            $this->assertTrue($link->isDir());
+            $this->assertDirectoryExists($path);
             $folder = new Folder($path);
             $folder->delete();
         } else {
-            $this->assertTrue($link->isLink());
+            $this->assertTrue(is_link($path));
             unlink($path);
         }
 
         $path = WWW_ROOT . 'company' . DS . 'test_plugin_three';
-        $link = new \SplFileInfo($path);
         // If "company" directory exists beforehand "test_plugin_three" would
         // be a link. But if the directory is created by the shell itself
         // symlinking fails and the assets folder is copied as fallback.
-        $this->assertTrue($link->isDir());
+        $this->assertDirectoryExists($path);
         $this->assertFileExists($path . DS . 'css' . DS . 'company.css');
         $folder = new Folder(WWW_ROOT . 'company');
         $folder->delete();
@@ -108,11 +106,10 @@ class AssetsTaskTest extends TestCase
 
         $this->Task->symlink();
         $path = WWW_ROOT . 'company' . DS . 'test_plugin_three';
-        $link = new \SplFileInfo($path);
         if (DS === '\\') {
-            $this->assertTrue($link->isDir());
+            $this->assertDirectoryExits($path);
         } else {
-            $this->assertTrue($link->isLink());
+            $this->assertTrue(is_link($path));
         }
         $this->assertFileExists($path . DS . 'css' . DS . 'company.css');
         $folder = new Folder(WWW_ROOT . 'company');
@@ -171,9 +168,8 @@ class AssetsTaskTest extends TestCase
         unlink($path);
 
         $path = WWW_ROOT . 'company' . DS . 'test_plugin_three';
-        $link = new \SplFileInfo($path);
-        $this->assertFalse($link->isDir());
-        $this->assertFalse($link->isLink());
+        $this->assertDirectoryNotExists($path);
+        $this->assertFalse(is_link($path));
     }
 
     /**
@@ -189,19 +185,125 @@ class AssetsTaskTest extends TestCase
         $this->Task->copy();
 
         $path = WWW_ROOT . 'test_plugin';
-        $dir = new \SplFileInfo($path);
-        $this->assertTrue($dir->isDir());
+        $this->assertDirectoryExists($path);
         $this->assertFileExists($path . DS . 'root.js');
 
         $folder = new Folder($path);
         $folder->delete();
 
         $path = WWW_ROOT . 'company' . DS . 'test_plugin_three';
-        $link = new \SplFileInfo($path);
-        $this->assertTrue($link->isDir());
+        $this->assertDirectoryExists($path);
         $this->assertFileExists($path . DS . 'css' . DS . 'company.css');
 
         $folder = new Folder(WWW_ROOT . 'company');
         $folder->delete();
     }
+
+    /**
+     * testRemoveSymlink method
+     *
+     * @return void
+     */
+    public function testRemoveSymlink()
+    {
+        if (DS === '\\') {
+            $this->markTestSkipped(
+                "Can't test symlink removal on windows."
+            );
+        }
+
+        Plugin::load('TestPlugin');
+        Plugin::load('Company/TestPluginThree');
+
+        mkdir(WWW_ROOT . 'company');
+
+        $this->Task->symlink();
+
+        $this->assertTrue(is_link(WWW_ROOT . 'test_plugin'));
+
+        $path = WWW_ROOT . 'company' . DS . 'test_plugin_three';
+        $this->assertTrue(is_link($path));
+
+        $this->Task->remove();
+
+        $this->assertFalse(is_link(WWW_ROOT . 'test_plugin'));
+        $this->assertFalse(is_link($path));
+        $this->assertDirectoryExists(WWW_ROOT . 'company', 'Ensure namespace folder isn\'t removed');
+
+        rmdir(WWW_ROOT . 'company');
+    }
+
+    /**
+     * testRemoveFolder method
+     *
+     * @return void
+     */
+    public function testRemoveFolder()
+    {
+        Plugin::load('TestPlugin');
+        Plugin::load('Company/TestPluginThree');
+
+        $this->Task->copy();
+
+        $this->assertTrue(is_dir(WWW_ROOT . 'test_plugin'));
+
+        $this->assertTrue(is_dir(WWW_ROOT . 'company' . DS . 'test_plugin_three'));
+
+        $this->Task->remove();
+
+        $this->assertDirectoryNotExists(WWW_ROOT . 'test_plugin');
+        $this->assertDirectoryNotExists(WWW_ROOT . 'company' . DS . 'test_plugin_three');
+        $this->assertDirectoryExists(WWW_ROOT . 'company', 'Ensure namespace folder isn\'t removed');
+
+        rmdir(WWW_ROOT . 'company');
+    }
+
+    /**
+     * testOverwrite
+     *
+     * @return void
+     */
+    public function testOverwrite()
+    {
+        Plugin::load('TestPlugin');
+        Plugin::load('Company/TestPluginThree');
+
+        $path = WWW_ROOT . 'test_plugin';
+
+        mkdir($path);
+        $filectime = filectime($path);
+
+        sleep(1);
+        $this->Task->params['overwrite'] = true;
+        $this->Task->symlink('TestPlugin');
+        if (DS === '\\') {
+            $this->assertDirectoryExists($path);
+        } else {
+            $this->assertTrue(is_link($path));
+        }
+
+        $newfilectime = filectime($path);
+        $this->assertTrue($newfilectime !== $filectime);
+
+        if (DS === '\\') {
+            $folder = new Folder($path);
+            $folder->delete();
+        } else {
+            unlink($path);
+        }
+
+        $path = WWW_ROOT . 'company' . DS . 'test_plugin_three';
+        mkdir($path, 0777, true);
+        $filectime = filectime($path);
+
+        sleep(1);
+        $this->Task->params['overwrite'] = true;
+        $this->Task->copy('Company/TestPluginThree');
+
+        $newfilectime = filectime($path);
+        $this->assertTrue($newfilectime > $filectime);
+
+        $folder = new Folder(WWW_ROOT . 'company');
+        $folder->delete();
+    }
 }