diff --git a/_config.php b/_config.php index 3fd281b7e..fae926994 100644 --- a/_config.php +++ b/_config.php @@ -49,6 +49,8 @@ Object::useCustomClass('Datetime', 'SS_Datetime', true); */ define('MCE_ROOT', 'sapphire/thirdparty/tinymce/'); +ShortcodeParser::get('default')->register('file_link', array('File', 'link_shortcode_handler')); + /** * The secret key that needs to be sent along with pings to /Email_BounceHandler * @@ -81,4 +83,4 @@ SS_Cache::pick_backend('aggregatestore', 'aggregate', 1000); Deprecation::notification_version('3.0.0'); // TODO Remove once new ManifestBuilder with submodule support is in place -require_once('admin/_config.php'); \ No newline at end of file +require_once('admin/_config.php'); diff --git a/filesystem/File.php b/filesystem/File.php index 53473b44a..0546ab92d 100644 --- a/filesystem/File.php +++ b/filesystem/File.php @@ -137,7 +137,44 @@ class File extends DataObject { * @var array */ protected static $cache_file_fields = null; - + + /** + * Replace "[file_link id=n]" shortcode with an anchor tag or link to the file. + * @param $arguments array Arguments to the shortcode + * @param $content string Content of the returned link (optional) + * @param $parser object Specify a parser to parse the content (see {@link ShortCodeParser}) + * @return string anchor HTML tag if content argument given, otherwise file path link + */ + public static function link_shortcode_handler($arguments, $content = null, $parser = null) { + if(!isset($arguments['id']) || !is_numeric($arguments['id'])) return; + + if ( + !($record = DataObject::get_by_id('File', $arguments['id'])) // Get the file by ID. + && !($record = DataObject::get_one('ErrorPage', '"ErrorCode" = \'404\'')) // Link to 404 page directly. + ) { + return; // There were no suitable matches at all. + } + + // build the HTML tag + if($content) { + // build some useful meta-data (file type and size) as data attributes + $attrs = ' '; + if($record instanceof File) { + foreach(array( + 'class' => 'file', + 'data-type' => $record->getExtension(), + 'data-size' => $record->getSize() + ) as $name => $value) { + $attrs .= sprintf('%s="%s" ', $name, $value); + } + } + + return sprintf('%s', $record->Link(), rtrim($attrs), $parser->parse($content)); + } else { + return $record->Link(); + } + } + /** * Find a File object by the given filename. * @@ -167,10 +204,10 @@ class File extends DataObject { return Director::baseURL() . $this->RelativeLink(); } - function RelativeLink(){ + function RelativeLink() { return $this->Filename; } - + /** * @deprecated 3.0 Use getTreeTitle() */ diff --git a/forms/HtmlEditorField.php b/forms/HtmlEditorField.php index 7f697bfc2..6a206e10a 100644 --- a/forms/HtmlEditorField.php +++ b/forms/HtmlEditorField.php @@ -307,7 +307,7 @@ class HtmlEditorField_Toolbar extends RequestHandler { $siteTree, new TextField('external', _t('HtmlEditorField.URL', 'URL'), 'http://'), new EmailField('email', _t('HtmlEditorField.EMAIL', 'Email address')), - new TreeDropdownField('file', _t('HtmlEditorField.FILE', 'File'), 'File', 'Filename', 'Title', true), + new TreeDropdownField('file', _t('HtmlEditorField.FILE', 'File'), 'File', 'ID', 'Title', true), new TextField('Anchor', _t('HtmlEditorField.ANCHORVALUE', 'Anchor')), new TextField('Description', _t('HtmlEditorField.LINKDESCR', 'Link description')), new CheckboxField('TargetBlank', _t('HtmlEditorField.LINKOPENNEWWIN', 'Open link in a new window?')), @@ -663,4 +663,4 @@ class HtmlEditorField_Image extends HtmlEditorField_File { return ($this->file) ? $this->file->CMSThumbnail() : sprintf('', $this->url); } -} \ No newline at end of file +} diff --git a/javascript/HtmlEditorField.js b/javascript/HtmlEditorField.js index f44cf55dc..e71286490 100644 --- a/javascript/HtmlEditorField.js +++ b/javascript/HtmlEditorField.js @@ -379,7 +379,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; break; case 'file': - href = this.find(':input[name=file]').val(); + href = '[file_link id=' + this.find(':input[name=file]').val() + ']'; target = '_blank'; break; @@ -532,7 +532,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; email: RegExp.$1, Description: title }; - } else if(href.match(/^(assets\/.*)$/)) { + } else if(href.match(/^(assets\/.*)$/) || href.match(/^\[file_link\s*(?:%20)?id=([0-9]+)\]?(#.*)?$/)) { return { LinkType: 'file', file: RegExp.$1, @@ -565,7 +565,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE; LinkType: 'internal' }; } - } + } }); $('form.htmleditorfield-linkform input[name=LinkType]').entwine({ @@ -967,4 +967,4 @@ function sapphiremce_cleanup(type, value) { } return value; -} \ No newline at end of file +} diff --git a/tests/filesystem/FileTest.php b/tests/filesystem/FileTest.php index feb8f8d7f..8c1e36b30 100644 --- a/tests/filesystem/FileTest.php +++ b/tests/filesystem/FileTest.php @@ -8,7 +8,39 @@ class FileTest extends SapphireTest { static $fixture_file = 'FileTest.yml'; protected $extraDataObjects = array('FileTest_MyCustomFile'); - + + public function testLinkShortcodeHandler() { + $testFile = $this->objFromFixture('File', 'asdf'); + $errorPage = $this->objFromFixture('ErrorPage', '404'); + + $parser = new ShortcodeParser(); + $parser->register('file_link', array('File', 'link_shortcode_handler')); + + $fileShortcode = sprintf('[file_link id=%d]', $testFile->ID); + $fileEnclosed = sprintf('[file_link id=%d]Example Content[/file_link]', $testFile->ID); + + $fileShortcodeExpected = $testFile->Link(); + $fileEnclosedExpected = sprintf('Example Content', $testFile->Link()); + + $this->assertEquals($fileShortcodeExpected, $parser->parse($fileShortcode), 'Test that simple linking works.'); + $this->assertEquals($fileEnclosedExpected, $parser->parse($fileEnclosed), 'Test enclosed content is linked.'); + + $testFile->delete(); + + $fileShortcode = '[file_link id="-1"]'; + $fileEnclosed = '[file_link id="-1"]Example Content[/file_link]'; + + $fileShortcodeExpected = $errorPage->Link(); + $fileEnclosedExpected = sprintf('Example Content', $errorPage->Link()); + + $this->assertEquals($fileShortcodeExpected, $parser->parse($fileShortcode), 'Test link to 404 page if no suitable matches.'); + $this->assertEquals($fileEnclosedExpected, $parser->parse($fileEnclosed)); + + $this->assertEquals('', $parser->parse('[file_link]'), 'Test that invalid ID attributes are not parsed.'); + $this->assertEquals('', $parser->parse('[file_link id="text"]')); + $this->assertEquals('', $parser->parse('[file_link]Example Content[/file_link]')); + } + function testCreateWithFilenameWithSubfolder() { // Note: We can't use fixtures/setUp() for this, as we want to create the db record manually. // Creating the folder is necessary to avoid having "Filename" overwritten by setName()/setRelativePath(), @@ -373,4 +405,4 @@ class FileTest extends SapphireTest { class FileTest_MyCustomFile extends File implements TestOnly { -} \ No newline at end of file +} diff --git a/tests/filesystem/FileTest.yml b/tests/filesystem/FileTest.yml index 5835c3608..63e8df2c5 100644 --- a/tests/filesystem/FileTest.yml +++ b/tests/filesystem/FileTest.yml @@ -27,4 +27,8 @@ File: file1-folder1: Filename: assets/FileTest-folder1/File1.txt Name: File1.txt - ParentID: =>Folder.folder1 \ No newline at end of file + ParentID: =>Folder.folder1 +ErrorPage: + 404: + Title: Page not Found + ErrorCode: 404