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