diff --git a/src/Forms/HTMLEditor/TinyMCEConfig.php b/src/Forms/HTMLEditor/TinyMCEConfig.php index 4285aa310..e456b6f56 100644 --- a/src/Forms/HTMLEditor/TinyMCEConfig.php +++ b/src/Forms/HTMLEditor/TinyMCEConfig.php @@ -5,11 +5,13 @@ namespace SilverStripe\Forms\HTMLEditor; use SilverStripe\Core\Convert; use SilverStripe\Control\Controller; use SilverStripe\Control\Director; +use SilverStripe\Core\Manifest\ModuleLoader; use SilverStripe\i18n\i18n; use SilverStripe\View\Requirements; use SilverStripe\View\SSViewer; use SilverStripe\View\ThemeResourceLoader; use TinyMCE_Compressor; +use Exception; /** * Default configuration for HtmlEditor specific to tinymce @@ -189,7 +191,7 @@ class TinyMCEConfig extends HTMLEditorConfig * - themes * - skins * - * If left blank defaults to ADMIN_THIRDPARTY_DIR . '/tinymce' + * If left blank defaults to [admin dir]/tinyme * * @config * @var string @@ -555,8 +557,7 @@ class TinyMCEConfig extends HTMLEditorConfig // https://www.tinymce.com/docs/api/class/tinymce.editormanager/#baseURL $tinyMCEBaseURL = Controller::join_links( Director::absoluteBaseURL(), - TinyMCEConfig::config()->get('base_dir') - ?: ADMIN_THIRDPARTY_DIR . '/tinymce' + $this->getTinyMCEPath() ); $settings['baseURL'] = $tinyMCEBaseURL; @@ -617,7 +618,7 @@ class TinyMCEConfig extends HTMLEditorConfig $editor = array(); // Add standard editor.css - $editor[] = Director::absoluteURL(FRAMEWORK_ADMIN_DIR . '/client/dist/styles/editor.css'); + $editor[] = Director::absoluteURL(ltrim($this->getAdminPath() . '/client/dist/styles/editor.css', '/')); // Themed editor.css $themedEditor = ThemeResourceLoader::instance()->findThemedCSS('editor', SSViewer::get_themes()); @@ -640,18 +641,25 @@ class TinyMCEConfig extends HTMLEditorConfig // If gzip is disabled just return core script url $useGzip = HTMLEditorField::config()->get('use_gzip'); if (!$useGzip) { - return ADMIN_THIRDPARTY_DIR . '/tinymce/tinymce.min.js'; + return $this->getTinyMCEPath() . '/tinymce.min.js'; } // tinyMCE JS requirement - require_once ADMIN_THIRDPARTY_PATH . '/tinymce/tiny_mce_gzip.php'; + $gzipPath = BASE_PATH . '/' . $this->getTinyMCEPath() . '/tiny_mce_gzip.php'; + if (!file_exists($gzipPath)) { + throw new Exception("HTMLEditorField.use_gzip enabled, but file $gzipPath does not exist!"); + } + + require_once $gzipPath; + $tag = TinyMCE_Compressor::renderTag(array( - 'url' => ADMIN_THIRDPARTY_DIR . '/tinymce/tiny_mce_gzip.php', + 'url' => $this->getTinyMCEPath() . '/tiny_mce_gzip.php', 'plugins' => implode(',', $this->getInternalPlugins()), 'themes' => $this->getTheme(), 'languages' => $this->getOption('language') ), true); preg_match('/src="([^"]*)"/', $tag, $matches); + return html_entity_decode($matches[1]); } @@ -676,4 +684,45 @@ class TinyMCEConfig extends HTMLEditorConfig } return 'en'; } + + /** + * @return string|false + */ + public function getAdminPath() + { + $module = $this->getAdminModule(); + if ($module) { + return $module->getRelativePath(); + } + return false; + } + + /** + * @return string|false + */ + public function getTinyMCEPath() + { + $configDir = static::config()->get('base_dir'); + if ($configDir) { + return $configDir; + } + + if ($admin = $this->getAdminModule()) { + return $admin->getResourcePath('thirdparty/tinymce'); + } + + throw new Exception(sprintf( + 'If the silverstripe/admin module is not installed, + you must set the TinyMCE path in %s.base_dir', + __CLASS__ + )); + } + + /** + * @return \SilverStripe\Core\Manifest\Module + */ + protected function getAdminModule() + { + return ModuleLoader::instance()->getManifest()->getModule('silverstripe/admin'); + } } diff --git a/tests/php/Forms/HTMLEditor/HTMLEditorConfigTest.php b/tests/php/Forms/HTMLEditor/HTMLEditorConfigTest.php index cc734dcd2..fc1301996 100644 --- a/tests/php/Forms/HTMLEditor/HTMLEditorConfigTest.php +++ b/tests/php/Forms/HTMLEditor/HTMLEditorConfigTest.php @@ -2,16 +2,27 @@ namespace SilverStripe\Forms\Tests\HTMLEditor; +use SilverStripe\Control\Director; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Convert; +use SilverStripe\Core\Manifest\ModuleLoader; +use SilverStripe\Core\Manifest\ModuleManifest; use SilverStripe\Dev\SapphireTest; use SilverStripe\Forms\HTMLEditor\HTMLEditorField; use SilverStripe\Forms\HTMLEditor\TinyMCEConfig; use SilverStripe\Forms\HTMLEditor\HTMLEditorConfig; +use \Exception; class HTMLEditorConfigTest extends SapphireTest { + protected function setUp() + { + parent::setUp(); + + TinyMCEConfig::config()->set('base_dir', 'test/thirdparty/tinymce'); + } + public function testEnablePluginsByString() { $c = new TinyMCEConfig(); @@ -37,18 +48,18 @@ class HTMLEditorConfigTest extends SapphireTest public function testEnablePluginsByArrayWithPaths() { - Config::inst()->update('SilverStripe\\Control\\Director', 'alternate_base_url', 'http://mysite.com/subdir'); + Config::inst()->update(Director::class, 'alternate_base_url', 'http://mysite.com/subdir'); $c = new TinyMCEConfig(); $c->setTheme('modern'); $c->setOption('language', 'es'); $c->disablePlugins('table', 'emoticons', 'paste', 'code', 'link', 'importcss'); $c->enablePlugins( array( - 'plugin1' => 'mypath/plugin1.js', - 'plugin2' => '/anotherbase/mypath/plugin2.js', - 'plugin3' => 'https://www.google.com/plugin.js', - 'plugin4' => null, - 'plugin5' => null, + 'plugin1' => 'mypath/plugin1.js', + 'plugin2' => '/anotherbase/mypath/plugin2.js', + 'plugin3' => 'https://www.google.com/plugin.js', + 'plugin4' => null, + 'plugin5' => null, ) ); $attributes = $c->getAttributes(); @@ -80,24 +91,51 @@ class HTMLEditorConfigTest extends SapphireTest // Plugin specified with standard location $this->assertContains('plugin4', array_keys($plugins)); $this->assertEquals( - 'http://mysite.com/subdir/'.ADMIN_THIRDPARTY_DIR.'/tinymce/plugins/plugin4/plugin.min.js', + 'http://mysite.com/subdir/test/thirdparty/tinymce/plugins/plugin4/plugin.min.js', $plugins['plugin4'] ); // Check that internal plugins are extractable separately $this->assertEquals(['plugin4', 'plugin5'], $c->getInternalPlugins()); + } + + public function testPluginCompression() + { + $module = ModuleLoader::instance()->getManifest()->getModule('silverstripe/admin'); + if (!$module) { + $this->markTestSkipped('No silverstripe/admin module loaded'); + } + TinyMCEConfig::config()->remove('base_dir'); + Config::inst()->update(Director::class, 'alternate_base_url', 'http://mysite.com/subdir'); + $c = new TinyMCEConfig(); + $c->setTheme('modern'); + $c->setOption('language', 'es'); + $c->disablePlugins('table', 'emoticons', 'paste', 'code', 'link', 'importcss'); + $c->enablePlugins( + array( + 'plugin1' => 'mypath/plugin1.js', + 'plugin2' => '/anotherbase/mypath/plugin2.js', + 'plugin3' => 'https://www.google.com/plugin.js', + 'plugin4' => null, + 'plugin5' => null, + ) + ); + $attributes = $c->getAttributes(); + $config = Convert::json2array($attributes['data-config']); + $plugins = $config['external_plugins']; + $this->assertNotEmpty($plugins); // Test plugins included via gzip compresser HTMLEditorField::config()->update('use_gzip', true); $this->assertEquals( - ADMIN_THIRDPARTY_DIR . '/tinymce/tiny_mce_gzip.php?js=1&plugins=plugin4,plugin5&themes=modern&languages=es&diskcache=true&src=true', + 'silverstripe-admin/thirdparty/tinymce/tiny_mce_gzip.php?js=1&plugins=plugin4,plugin5&themes=modern&languages=es&diskcache=true&src=true', $c->getScriptURL() ); // If gzip is disabled only the core plugin is loaded HTMLEditorField::config()->remove('use_gzip'); $this->assertEquals( - ADMIN_THIRDPARTY_DIR . '/tinymce/tinymce.min.js', + 'silverstripe-admin/thirdparty/tinymce/tinymce.min.js', $c->getScriptURL() ); } @@ -149,4 +187,40 @@ class HTMLEditorConfigTest extends SapphireTest $this->assertNotEmpty($aAttributes['data-config']); $this->assertNotEmpty($cAttributes['data-config']); } + + public function testExceptionThrownWhenTinyMCEPathCannotBeComputed() + { + TinyMCEConfig::config()->remove('base_dir'); + ModuleLoader::instance()->pushManifest(new ModuleManifest( + dirname(__FILE__), + false + )); + $c = new TinyMCEConfig(); + + $this->setExpectedExceptionRegExp( + Exception::class, + '/module is not installed/' + ); + + $c->getScriptURL(); + + ModuleLoader::instance()->popManifest(); + } + + public function testExceptionThrownWhenTinyMCEGZipPathDoesntExist() + { + HTMLEditorField::config()->set('use_gzip', true); + $stub = $this->getMockBuilder(TinyMCEConfig::class) + ->setMethods(['getTinyMCEPath']) + ->getMock(); + $stub->method('getTinyMCEPath') + ->willReturn('fail'); + + $this->setExpectedExceptionRegExp( + Exception::class, + '/does not exist/' + ); + + $stub->getScriptURL(); + } } diff --git a/tests/php/Forms/HTMLEditor/HTMLEditorFieldTest.php b/tests/php/Forms/HTMLEditor/HTMLEditorFieldTest.php index 7135be6d5..df2c59fb1 100644 --- a/tests/php/Forms/HTMLEditor/HTMLEditorFieldTest.php +++ b/tests/php/Forms/HTMLEditor/HTMLEditorFieldTest.php @@ -21,6 +21,7 @@ use SilverStripe\Forms\HTMLReadonlyField; use SilverStripe\Forms\Tests\HTMLEditor\HTMLEditorFieldTest\DummyMediaFormFieldExtension; use SilverStripe\Forms\Tests\HTMLEditor\HTMLEditorFieldTest\TestObject; use SilverStripe\ORM\FieldType\DBHTMLText; +use SilverStripe\Forms\HTMLEditor\TinyMCEConfig; class HTMLEditorFieldTest extends FunctionalTest { @@ -77,6 +78,10 @@ class HTMLEditorFieldTest extends FunctionalTest public function testCasting() { + // Shim TinyMCE so silverstripe/admin doesn't have to be installed + TinyMCEConfig::config()->set('base_dir', 'test'); + HtmlEditorField::config()->set('use_gzip', false); + // Test special characters $inputText = "These are some unicodes: ä, ö, & ü"; $field = new HTMLEditorField("Test", "Test");