Browse Source

Merge branch 'master' into 3.next

Mark Story 8 years ago
parent
commit
fe9bc8427d

+ 1 - 1
VERSION.txt

@@ -16,4 +16,4 @@
 // @license       https://opensource.org/licenses/mit-license.php MIT License
 // +--------------------------------------------------------------------------------------------+ //
 ////////////////////////////////////////////////////////////////////////////////////////////////////
-3.5.10
+3.5.11

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

@@ -268,7 +268,7 @@ class PaginatorComponent extends Component
     {
         $controller = $this->getController();
         $request = $controller->getRequest();
-        $paging = $this->_paginator->getPagingParams() + (array)$request->getParam('paging');
+        $paging = $this->_paginator->getPagingParams() + (array)$request->getParam('paging', []);
 
         $controller->setRequest($request->withParam('paging', $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());
     }
 

+ 7 - 3
src/Http/Session.php

@@ -108,12 +108,16 @@ class Session
             $sessionConfig['ini']['session.name'] = $sessionConfig['cookie'];
         }
 
-        // In PHP7.1.0+ session.save_handler can't be set to user by the user.
-        // https://github.com/php/php-src/blob/master/ext/session/session.c#L559
-        if (!empty($sessionConfig['handler']) && version_compare(PHP_VERSION, '7.1.0', '<=')) {
+        if (!empty($sessionConfig['handler'])) {
             $sessionConfig['ini']['session.save_handler'] = 'user';
         }
 
+        // In PHP7.2.0+ session.save_handler can't be set to user by the user.
+        // https://github.com/php/php-src/commit/a93a51c3bf4ea1638ce0adc4a899cb93531b9f0d
+        if (version_compare(PHP_VERSION, '7.2.0', '>=')) {
+            unset($sessionConfig['ini']['session.save_handler']);
+        }
+
         if (!isset($sessionConfig['ini']['session.cookie_httponly']) && ini_get('session.cookie_httponly') != 1) {
             $sessionConfig['ini']['session.cookie_httponly'] = 1;
         }

+ 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

+ 101 - 14
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;
             }
@@ -117,6 +140,8 @@ class AssetsTask extends Shell
      */
     protected function _process($plugins, $copy = false, $overwrite = false)
     {
+        $overwrite = (bool)$this->param('overwrite');
+
         foreach ($plugins as $plugin => $config) {
             $this->out();
             $this->out('For plugin: ' . $plugin);
@@ -129,19 +154,25 @@ class AssetsTask extends Shell
                 continue;
             }
 
-            if (file_exists($config['destDir'] . $config['link']) && !$overwrite) {
-                $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;
@@ -150,7 +181,7 @@ class AssetsTask extends Shell
 
             $this->_copyDirectory(
                 $config['srcPath'],
-                $config['destDir'] . $config['link']
+                $dest
             );
         }
 
@@ -159,6 +190,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
@@ -239,11 +323,14 @@ 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', [
-            'help' => 'Overwrite existing files.',
+            'help' => 'Overwrite existing symlink / folder / files.',
+            'default' => false,
             'boolean' => true
         ]);
 

+ 1 - 1
src/View/View.php

@@ -1285,7 +1285,7 @@ class View implements EventDispatcherInterface
         if (strlen($this->subDir)) {
             $subDir = $this->subDir . DIRECTORY_SEPARATOR;
             // Check if templatePath already terminates with subDir
-            if (strrpos($templatePath, $subDir) == strlen($templatePath) - strlen($subDir)) {
+            if ($templatePath != $subDir && substr($templatePath, -(strlen($subDir))) == $subDir) {
                 $subDir = '';
             }
         }

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

@@ -222,6 +222,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->controller->request->getParam('paging'));
+        $this->assertArrayNotHasKey(0, $this->controller->request->getParam('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

@@ -1394,4 +1394,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

@@ -6747,15 +6747,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);
     }
 
     /**

+ 116 - 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,16 +185,14 @@ 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');
@@ -235,6 +229,113 @@ class AssetsTaskTest extends TestCase
         $this->assertFileEquals($path . DS . 'root.js', $pluginPath . DS . 'root.js');
 
         $folder = new Folder($path);
+    }
+
+    /**
+     * 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();
     }
 }

+ 21 - 0
tests/TestCase/View/ViewTest.php

@@ -666,6 +666,27 @@ class ViewTest extends TestCase
     }
 
     /**
+     * Test getViewFileName applies subdirectories on equal length names
+     *
+     * @return void
+     */
+    public function testGetViewFileNameSubDirLength()
+    {
+        $viewOptions = [
+            'plugin' => null,
+            'name' => 'Jobs',
+            'viewPath' => 'Jobs',
+            'layoutPath' => 'json',
+        ];
+        $view = new TestView(null, null, null, $viewOptions);
+
+        $view->subDir = 'json';
+        $result = $view->getViewFileName('index');
+        $expected = TEST_APP . 'TestApp' . DS . 'Template' . DS . 'Jobs' . DS . 'json' . DS . 'index.ctp';
+        $this->assertPathEquals($expected, $result);
+    }
+
+    /**
      * Test getting layout filenames
      *
      * @return void

+ 2 - 0
tests/test_app/TestApp/Template/Jobs/json/index.ctp

@@ -0,0 +1,2 @@
+<?php
+// used to test subdir path additions.