Skip to content

Commit

Permalink
FEATURE: Allow adding media to the CMS using oEmbed
Browse files Browse the repository at this point in the history
  • Loading branch information
simonwelsh authored and mateusz committed May 24, 2012
1 parent b325fea commit bb29ff3
Show file tree
Hide file tree
Showing 12 changed files with 496 additions and 25 deletions.
1 change: 1 addition & 0 deletions _config.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
define('MCE_ROOT', FRAMEWORK_DIR . '/thirdparty/tinymce/');

ShortcodeParser::get('default')->register('file_link', array('File', 'link_shortcode_handler'));
ShortcodeParser::get('default')->register('embed', array('Oembed', 'handle_shortcode'));

/**
* The secret key that needs to be sent along with pings to /Email_BounceHandler
Expand Down
22 changes: 22 additions & 0 deletions _config/Oembed.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: Oembed
---
Oembed:
providers:
'http://*.youtube.com/watch*':
'http://www.youtube.com/oembed/'
'http://*.flickr.com/*':
'http://www.flickr.com/services/oembed/'
'http://*.viddler.com/*':
'http://lab.viddler.com/services/oembed/'
'http://*.revision3.com/*':
'http://revision3.com/api/oembed/'
'http://*.hulu.com/watch/*':
'http://www.hulu.com/api/oembed.json'
'http://*.vimeo.com/*':
'http://www.vimeo.com/api/oembed.json'
'https://twitter.com/*':
'https://api.twitter.com/1/statuses/oembed.json'
'http://twitter.com/*':
'https://api.twitter.com/1/statuses/oembed.json'
autodiscover:
true
2 changes: 1 addition & 1 deletion admin/_config.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
HtmlEditorConfig::get('cms')->enablePlugins(array('ssbuttons' => sprintf('../../../%s/tinymce_ssbuttons/editor_plugin_src.js', THIRDPARTY_DIR)));

HtmlEditorConfig::get('cms')->insertButtonsBefore('formatselect', 'styleselect');
HtmlEditorConfig::get('cms')->addButtonsToLine(2, 'ssimage', 'ssflash', 'sslink', 'unlink', 'anchor', 'separator','code', 'fullscreen', 'separator');
HtmlEditorConfig::get('cms')->addButtonsToLine(2, 'ssmedia', 'ssflash', 'sslink', 'unlink', 'anchor', 'separator','code', 'fullscreen', 'separator');

HtmlEditorConfig::get('cms')->removeButtons('tablecontrols');
HtmlEditorConfig::get('cms')->addButtonsToLine(3, 'tablecontrols');
Expand Down
6 changes: 3 additions & 3 deletions docs/en/topics/rich-text-editing.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,8 @@ so this is considered advanced usage of the field.
:::php
// File: mysite/_config.php
HtmlEditorConfig::get('cms')->disablePlugins('ssbuttons');
HtmlEditorConfig::get('cms')->removeButtons('sslink', 'ssimage');
HtmlEditorConfig::get('cms')->addButtonsToLine(2, 'link', 'image');
HtmlEditorConfig::get('cms')->removeButtons('sslink', 'ssmedia');
HtmlEditorConfig::get('cms')->addButtonsToLine(2, 'link', 'media');

### Developing a wrapper to use a different WYSIWYG editors with HTMLEditorField

Expand All @@ -126,4 +126,4 @@ or start your own configuration.

## Related

* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)
* [Howto: Extend the CMS Interface](../howto/extend-cms-interface)
150 changes: 140 additions & 10 deletions forms/HtmlEditorField.php
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,13 @@ function MediaForm() {
$fromCMS->addExtraClass('content');
$selectComposite->addExtraClass('content-select');

$fromWeb = new CompositeField(
new LiteralField('headerURL', '<h4 class="field header-url">' . sprintf($numericLabelTmpl, '1', _t('HtmlEditorField.ADDURL', 'Add URL')) . '</h4>'),
$remoteURL = new TextField('RemoteURL', ''),
new LiteralField('addURLImage', '<img class="field add-url" src="' . CMS_DIR . '/images/add.gif" width="16" height="16" />')
);
$remoteURL->addExtraClass('remoteurl');

Requirements::css(FRAMEWORK_DIR . '/css/AssetUploadField.css');
$computerUploadField = Object::create('UploadField', 'AssetUploadField', '');
$computerUploadField->setConfig('previewMaxWidth', 40);
Expand All @@ -401,11 +408,15 @@ function MediaForm() {
$computerUploadField->setFolderName(Upload::$uploads_folder);

$tabSet = new TabSet(
"MediaFormInsertImageTabs",
"MediaFormInsertMediaTabs",
new Tab(
_t('HtmlEditorField.FROMCOMPUTER','From your computer'),
$computerUploadField
),
new Tab(
_t('HtmlEditorField.FROMWEB', 'From the web'),
$fromWeb
),
new Tab(
_t('HtmlEditorField.FROMCMS','From the CMS'),
$fromCMS
Expand All @@ -423,19 +434,19 @@ function MediaForm() {
$fields = new FieldList(
new LiteralField(
'Heading',
sprintf('<h3 class="htmleditorfield-mediaform-heading insert">%s</h3>', _t('HtmlEditorField.INSERTIMAGE', 'Insert Image')).
sprintf('<h3 class="htmleditorfield-mediaform-heading update">%s</h3>', _t('HtmlEditorField.UpdateIMAGE', 'Update Image'))
sprintf('<h3 class="htmleditorfield-mediaform-heading insert">%s</h3>', _t('HtmlEditorField.INSERTMEDIA', 'Insert Media')).
sprintf('<h3 class="htmleditorfield-mediaform-heading update">%s</h3>', _t('HtmlEditorField.UpdateMEDIA', 'Update Media'))
),
$allFields
);

$actions = new FieldList(
FormAction::create('insertimage', _t('HtmlEditorField.BUTTONINSERT', 'Insert'))
->addExtraClass('ss-ui-action-constructive image-insert')
FormAction::create('insertmedia', _t('HtmlEditorField.BUTTONINSERT', 'Insert'))
->addExtraClass('ss-ui-action-constructive media-insert')
->setAttribute('data-icon', 'accept')
->setUseButtonTag(true),
FormAction::create('insertimage', _t('HtmlEditorField.BUTTONUpdate', 'Update'))
->addExtraClass('ss-ui-action-constructive image-update')
FormAction::create('insertmedia', _t('HtmlEditorField.BUTTONUpdate', 'Update'))
->addExtraClass('ss-ui-action-constructive media-update')
->setAttribute('data-icon', 'accept')
->setUseButtonTag(true)
);
Expand Down Expand Up @@ -493,6 +504,8 @@ public function viewfile($request) {
// Instanciate file wrapper and get fields based on its type
if($file && $file->appCategory() == 'image') {
$fileWrapper = new HtmlEditorField_Image($url, $file);
} elseif(!Director::is_site_url($url)) {
$fileWrapper = new HtmlEditorField_Embed($url, $file);
} else {
$fileWrapper = new HtmlEditorField_File($url, $file);
}
Expand All @@ -516,7 +529,9 @@ public function viewfile($request) {
protected function getFieldsForFile($url, $file) {
$fields = $this->extend('getFieldsForFile', $url, $file);
if(!$fields) {
if($file->Extension == 'swf') {
if($file instanceof HtmlEditorField_Embed) {
$fields = $this->getFieldsForOembed($url, $file);
} elseif($file->Extension == 'swf') {
$fields = $this->getFieldsForFlash($url, $file);
} else {
$fields = $this->getFieldsForImage($url, $file);
Expand All @@ -529,6 +544,75 @@ protected function getFieldsForFile($url, $file) {
return $fields;
}

/**
* @return FieldList
*/
protected function getFieldsForOembed($url, $file) {
if(isset($file->Oembed->thumbnail_url)) {
$thumbnailURL = $file->Oembed->thumbnail_url;
} elseif($file->Type == 'photo') {
$thumbnailURL = $file->Oembed->url;
} else {
$thumbnailURL = $url;
}

$previewField = new LiteralField("ImageFull",
"<img id='thumbnailImage' class='thumbnail-preview' src='{$thumbnailURL}?r=" . rand(1,100000) . "' alt='{$file->Name}' />\n"
);

$fields = new FieldList(
$filePreview = CompositeField::create(
CompositeField::create(
$previewField
)->setName("FilePreviewImage")->addExtraClass('cms-file-info-preview'),
CompositeField::create(
CompositeField::create(
new ReadonlyField("FileType", _t('AssetTableField.TYPE','File type') . ':', $file->Type),
$urlField = new ReadonlyField('ClickableURL', _t('AssetTableField.URL','URL'),
sprintf('<a href="%s" target="_blank">%s</a>', $url, $url)
)
)
)->setName("FilePreviewData")->addExtraClass('cms-file-info-data')
)->setName("FilePreview")->addExtraClass('cms-file-info'),
new TextField('CaptionText', _t('HtmlEditorField.CAPTIONTEXT', 'Caption text')),
new DropdownField(
'CSSClass',
_t('HtmlEditorField.CSSCLASS', 'Alignment / style'),
array(
'left' => _t('HtmlEditorField.CSSCLASSLEFT', 'On the left, with text wrapping around.'),
'leftAlone' => _t('HtmlEditorField.CSSCLASSLEFTALONE', 'On the left, on its own.'),
'right' => _t('HtmlEditorField.CSSCLASSRIGHT', 'On the right, with text wrapping around.'),
'center' => _t('HtmlEditorField.CSSCLASSCENTER', 'Centered, on its own.'),
)
),
$dimensionsField = new FieldGroup(_t('HtmlEditorField.IMAGEDIMENSIONS', 'Dimensions'),
$widthField = new TextField('Width', _t('HtmlEditorField.IMAGEWIDTHPX', 'Width'), $file->Width),
$heightField = new TextField('Height', " x " . _t('HtmlEditorField.IMAGEHEIGHTPX', 'Height'), $file->Height)
)
);
$urlField->dontEscape = true;
$dimensionsField->addExtraClass('dimensions');
$widthField->setMaxLength(5);
$heightField->setMaxLength(5);

if($file->Type == 'photo') {
$filePreview->FieldList()->insertBefore(new TextField(
'AltText',
_t('HtmlEditorField.IMAGEALTTEXT', 'Alternative text (alt) - shown if image cannot be displayed'),
$file->Title,
80
), 'CaptionText');
$filePreview->FieldList()->insertBefore(new TextField(
'Title',
_t('HtmlEditorField.IMAGETITLE', 'Title text (tooltip) - for additional information about the image')
), 'CaptionText');
}

$this->extend('updateFieldsForImage', $fields, $url, $file);

return $fields;
}

/**
* @return FieldList
*/
Expand All @@ -552,8 +636,8 @@ protected function getFieldsForFlash($url, $file) {
* @return FieldList
*/
protected function getFieldsForImage($url, $file) {
if($file instanceof Image) {
$formattedImage = $file->FormattedImage('SetWidth', Image::$asset_preview_width);
if($file->File instanceof Image) {
$formattedImage = $file->File->generateFormattedImage('SetWidth', Image::$asset_preview_width);
$thumbnailURL = $formattedImage ? $formattedImage->URL : $url;
} else {
$thumbnailURL = $url;
Expand Down Expand Up @@ -719,6 +803,52 @@ function appCategory() {

}

class HtmlEditorField_Embed extends HtmlEditorField_File {
protected $oembed;

public function __construct($url, $file = null) {
parent::__construct($url, $file);
$this->oembed = Oembed::get_oembed_from_url($url);
if(!$this->oembed) {
return Controller::curr()->httpError(404, 'The URL ' . $url . ' could not be turned into a media resource.');
}
}

public function getWidth() {
return $this->oembed->Width;
}

public function getHeight() {
return $this->oembed->Height;
}

public function getPreview() {
if(isset($this->oembed->thumbnail_url)) {
return sprintf('<img src="%s" />', $this->oembed->thumbnail_url);
}
}

public function getName() {
if(isset($this->oembed->title)) {
return $this->oembed->title;
} else {
return parent::getName();
}
}

public function getType() {
return $this->oembed->type;
}

public function getOembed() {
return $this->oembed;
}

public function appCategory() {
return 'embed';
}
}

class HtmlEditorField_Image extends HtmlEditorField_File {

protected $width;
Expand Down
75 changes: 72 additions & 3 deletions javascript/HtmlEditorField.js
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,7 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
var self = this, ed = this.getEditor(), node = $(ed.getSelectedNode());
// TODO Depends on managed mime type
if(node.is('img')) {
this.showFileView(node.attr('src'), function() {
this.showFileView(node.data('url') || node.attr('src'), function() {
$(this).updateFromNode(node);
self.toggleCloseButton();
self.redraw();
Expand Down Expand Up @@ -711,9 +711,9 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;

var updateExisting = Boolean(this.find('.ss-htmleditorfield-file').length);
this.find('.htmleditorfield-mediaform-heading.insert')[updateExisting ? 'hide' : 'show']();
this.find('.Actions .image-insert')[updateExisting ? 'hide' : 'show']();
this.find('.Actions .media-insert')[updateExisting ? 'hide' : 'show']();
this.find('.htmleditorfield-mediaform-heading.update')[updateExisting ? 'show' : 'hide']();
this.find('.Actions .image-update')[updateExisting ? 'show' : 'hide']();
this.find('.Actions .media-update')[updateExisting ? 'show' : 'hide']();
},
resetFields: function() {
var ed = this.getEditor(), node = $(ed.getSelectedNode());
Expand Down Expand Up @@ -796,6 +796,21 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;

});

/**
* Show the second step after adding a URL
*/
$('form.htmleditorfield-form.htmleditorfield-mediaform img.add-url').entwine({
onclick: function(e) {
var form = this.closest('form');

var urlField = this.closest('.CompositeField').find('input.remoteurl');

form.showFileView(urlField.val());
form.redraw();
}

});

/**
* Represents a single selected file, together with a set of form fields to edit its properties.
* Overload this based on the media type to determine how the HTML should be created.
Expand Down Expand Up @@ -985,6 +1000,60 @@ ss.editorWrappers['default'] = ss.editorWrappers.tinyMCE;
}
});


/**
* Insert an oembed object tag into the content.
* Requires the 'media' plugin for serialization of tags into <img> placeholders.
*/
$('form.htmleditorfield-mediaform .ss-htmleditorfield-file.embed').entwine({
getAttributes: function() {
var width = this.find(':input[name=Width]').val(),
height = this.find(':input[name=Height]').val();
return {
'src' : this.find('.thumbnail-preview').attr('src'),
'width' : width ? parseInt(width, 10) : null,
'height' : height ? parseInt(height, 10) : null,
'class' : this.find(':input[name=CSSClass]').val()
};
},
getExtraData: function() {
var width = this.find(':input[name=Width]').val(),
height = this.find(':input[name=Height]').val();
return {
'CaptionText': this.find(':input[name=CaptionText]').val(),
'Url': this.find(':input[name=URL]').val(),
'thumbnail': this.find('.thumbnail-preview').attr('src'),
'width' : width ? parseInt(width, 10) : null,
'height' : height ? parseInt(height, 10) : null,
'cssclass': this.find(':input[name=CSSClass]').val()
};
},
getHTML: function() {
var el,
attrs = this.getAttributes(),
extraData = this.getExtraData(),
// imgEl = $('<img id="_ss_tmp_img" />');
imgEl = $('<img />').attr(attrs).addClass('ss-htmleditorfield-file embed');

$.each(extraData, function (key, value) {
imgEl.attr('data-' + key, value)
});

if(extraData.CaptionText) {
el = $('<div style="width: ' + attrs['width'] + 'px;" class="captionImage ' + attrs['class'] + '"><p class="caption">' + extraData.CaptionText + '</p></div>').prepend(imgEl);
} else {
el = imgEl;
}
return $('<div />').append(el).html(); // Little hack to get outerHTML string
},
updateFromNode: function(node) {
this.find(':input[name=Width]').val(node.width());
this.find(':input[name=Height]').val(node.height());
this.find(':input[name=Title]').val(node.attr('title'));
this.find(':input[name=CSSClass]').val(node.data('cssclass'));
}
});

$('form.htmleditorfield-mediaform .ss-htmleditorfield-file .dimensions :input').entwine({
OrigVal: null,
onmatch: function () {
Expand Down
2 changes: 1 addition & 1 deletion lang/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -255,7 +255,7 @@ en:
IMAGEHEIGHTPX: Height
IMAGETITLE: 'Title text (tooltip) - for additional information about the image'
IMAGEWIDTHPX: Width
INSERTIMAGE: 'Insert Image'
INSERTMEDIA: 'Insert Media'
LINK: 'Insert Link'
LINKANCHOR: 'Anchor on this page'
LINKDESCR: 'Link description'
Expand Down
Loading

0 comments on commit bb29ff3

Please sign in to comment.