2007-07-19 12:40:28 +02:00
|
|
|
<?php
|
|
|
|
/**
|
2009-10-11 02:07:13 +02:00
|
|
|
* A TinyMCE-powered WYSIWYG HTML editor field with image and link insertion and tracking capabilities. Editor fields
|
|
|
|
* are created from <textarea> tags, which are then converted with JavaScript.
|
|
|
|
*
|
2008-01-09 05:18:36 +01:00
|
|
|
* @package forms
|
|
|
|
* @subpackage fields-formattedinput
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
|
|
|
class HtmlEditorField extends TextareaField {
|
2012-01-06 12:01:33 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @var Boolean Use TinyMCE's GZIP compressor
|
|
|
|
*/
|
|
|
|
static $use_gzip = true;
|
2012-02-10 12:15:21 +01:00
|
|
|
|
2012-03-07 10:59:03 +01:00
|
|
|
protected $rows = null;
|
2009-06-15 07:17:48 +02:00
|
|
|
|
2009-05-18 02:19:47 +02:00
|
|
|
/**
|
2009-10-11 02:07:13 +02:00
|
|
|
* Includes the JavaScript neccesary for this field to work using the {@link Requirements} system.
|
2009-05-18 02:19:47 +02:00
|
|
|
*/
|
2009-10-11 02:07:13 +02:00
|
|
|
public static function include_js() {
|
2012-01-06 12:01:33 +01:00
|
|
|
require_once 'tinymce/tiny_mce_gzip.php';
|
|
|
|
|
|
|
|
$configObj = HtmlEditorConfig::get_active();
|
|
|
|
|
|
|
|
if(self::$use_gzip) {
|
|
|
|
$internalPlugins = array();
|
|
|
|
foreach($configObj->getPlugins() as $plugin => $path) if(!$path) $internalPlugins[] = $plugin;
|
|
|
|
$tag = TinyMCE_Compressor::renderTag(array(
|
|
|
|
'url' => THIRDPARTY_DIR . '/tinymce/tiny_mce_gzip.php',
|
|
|
|
'plugins' => implode(',', $internalPlugins),
|
|
|
|
'themes' => 'advanced',
|
|
|
|
'languages' => $configObj->getOption('language')
|
|
|
|
), true);
|
|
|
|
preg_match('/src="([^"]*)"/', $tag, $matches);
|
|
|
|
Requirements::javascript($matches[1]);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
Requirements::javascript(MCE_ROOT . 'tiny_mce_src.js');
|
|
|
|
}
|
|
|
|
|
|
|
|
Requirements::customScript($configObj->generateJS(), 'htmlEditorConfig');
|
2009-05-18 02:19:47 +02:00
|
|
|
}
|
2009-10-11 02:07:13 +02:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
/**
|
2009-10-11 02:07:13 +02:00
|
|
|
* @see TextareaField::__construct()
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
2012-01-02 17:44:23 +01:00
|
|
|
public function __construct($name, $title = null, $value = '') {
|
|
|
|
if(count(func_get_args()) > 3) Deprecation::notice('3.0', 'Use setRows() and setCols() instead of constructor arguments');
|
|
|
|
|
|
|
|
parent::__construct($name, $title, $value);
|
2009-10-11 02:07:13 +02:00
|
|
|
|
|
|
|
self::include_js();
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2009-10-11 02:07:13 +02:00
|
|
|
* @return string
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
|
|
|
function Field() {
|
2009-10-11 02:07:27 +02:00
|
|
|
// mark up broken links
|
|
|
|
$value = new SS_HTMLValue($this->value);
|
|
|
|
|
|
|
|
if($links = $value->getElementsByTagName('a')) foreach($links as $link) {
|
|
|
|
$matches = array();
|
|
|
|
|
|
|
|
if(preg_match('/\[sitetree_link id=([0-9]+)\]/i', $link->getAttribute('href'), $matches)) {
|
|
|
|
if(!DataObject::get_by_id('SiteTree', $matches[1])) {
|
|
|
|
$class = $link->getAttribute('class');
|
|
|
|
$link->setAttribute('class', ($class ? "$class ss-broken" : 'ss-broken'));
|
|
|
|
}
|
|
|
|
}
|
2012-03-01 11:24:58 +01:00
|
|
|
|
|
|
|
if(preg_match('/\[file_link id=([0-9]+)\]/i', $link->getAttribute('href'), $matches)) {
|
|
|
|
if(!DataObject::get_by_id('File', $matches[1])) {
|
|
|
|
$class = $link->getAttribute('class');
|
|
|
|
$link->setAttribute('class', ($class ? "$class ss-broken" : 'ss-broken'));
|
|
|
|
}
|
|
|
|
}
|
2009-10-11 02:07:27 +02:00
|
|
|
}
|
2012-03-01 11:24:58 +01:00
|
|
|
|
2009-10-11 02:07:13 +02:00
|
|
|
return $this->createTag (
|
|
|
|
'textarea',
|
2011-12-22 13:10:57 +01:00
|
|
|
$this->getAttributes(),
|
2009-10-11 02:07:27 +02:00
|
|
|
htmlentities($value->getContent(), ENT_COMPAT, 'UTF-8')
|
2009-10-11 02:07:13 +02:00
|
|
|
);
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2011-12-22 13:10:57 +01:00
|
|
|
|
|
|
|
function getAttributes() {
|
|
|
|
return array_merge(
|
|
|
|
parent::getAttributes(),
|
|
|
|
array(
|
|
|
|
'tinymce' => 'true',
|
|
|
|
'value' => null,
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
|
2009-10-11 02:07:13 +02:00
|
|
|
public function saveInto($record) {
|
2008-01-17 05:23:31 +01:00
|
|
|
if($record->escapeTypeForField($this->name) != 'xml') {
|
2009-10-11 02:07:13 +02:00
|
|
|
throw new Exception (
|
|
|
|
'HtmlEditorField->saveInto(): This field should save into a HTMLText or HTMLVarchar field.'
|
2008-01-17 05:22:13 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2009-02-02 00:49:53 +01:00
|
|
|
$linkedPages = array();
|
2009-10-11 02:07:13 +02:00
|
|
|
$linkedFiles = array();
|
2007-07-19 12:40:28 +02:00
|
|
|
|
2009-10-11 02:07:27 +02:00
|
|
|
$htmlValue = new SS_HTMLValue($this->value);
|
2009-10-11 02:07:13 +02:00
|
|
|
|
2011-03-23 02:32:38 +01:00
|
|
|
if(class_exists('SiteTree')) {
|
|
|
|
// Populate link tracking for internal links & links to asset files.
|
|
|
|
if($links = $htmlValue->getElementsByTagName('a')) foreach($links as $link) {
|
|
|
|
$href = Director::makeRelative($link->getAttribute('href'));
|
|
|
|
|
|
|
|
if($href) {
|
|
|
|
if(preg_match('/\[sitetree_link id=([0-9]+)\]/i', $href, $matches)) {
|
|
|
|
$ID = $matches[1];
|
|
|
|
|
|
|
|
// clear out any broken link classes
|
|
|
|
if($class = $link->getAttribute('class')) {
|
|
|
|
$link->setAttribute('class', preg_replace('/(^ss-broken|ss-broken$| ss-broken )/', null, $class));
|
|
|
|
}
|
|
|
|
|
|
|
|
$linkedPages[] = $ID;
|
|
|
|
if(!DataObject::get_by_id('SiteTree', $ID)) $record->HasBrokenLink = true;
|
2010-10-12 23:42:25 +02:00
|
|
|
|
2011-03-23 02:32:38 +01:00
|
|
|
} else if(substr($href, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR.'/') {
|
|
|
|
$candidateFile = File::find(Convert::raw2sql(urldecode($href)));
|
|
|
|
if($candidateFile) {
|
|
|
|
$linkedFiles[] = $candidateFile->ID;
|
|
|
|
} else {
|
|
|
|
$record->HasBrokenFile = true;
|
|
|
|
}
|
|
|
|
} else if($href == '' || $href[0] == '/') {
|
|
|
|
$record->HasBrokenLink = true;
|
2009-10-13 03:45:01 +02:00
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2007-11-07 22:39:57 +01:00
|
|
|
|
2009-10-11 02:07:13 +02:00
|
|
|
// Resample images, add default attributes and add to assets tracking.
|
2009-10-11 02:07:27 +02:00
|
|
|
if($images = $htmlValue->getElementsByTagName('img')) foreach($images as $img) {
|
|
|
|
// strip any ?r=n data from the src attribute
|
|
|
|
$img->setAttribute('src', preg_replace('/([^\?]*)\?r=[0-9]+$/i', '$1', $img->getAttribute('src')));
|
2010-10-04 06:29:00 +02:00
|
|
|
if(!$image = File::find($path = urldecode(Director::makeRelative($img->getAttribute('src'))))) {
|
2009-10-11 02:07:13 +02:00
|
|
|
if(substr($path, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR . '/') {
|
|
|
|
$record->HasBrokenFile = true;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
2009-10-11 02:07:13 +02:00
|
|
|
continue;
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
|
2009-10-11 02:07:13 +02:00
|
|
|
// Resample the images if the width & height have changed.
|
|
|
|
$width = $img->getAttribute('width');
|
|
|
|
$height = $img->getAttribute('height');
|
|
|
|
|
2010-04-12 04:45:35 +02:00
|
|
|
if($image){
|
|
|
|
if($width && $height && ($width != $image->getWidth() || $height != $image->getHeight())) {
|
|
|
|
//Make sure that the resized image actually returns an image:
|
|
|
|
$resized=$image->ResizedImage($width, $height);
|
|
|
|
if($resized)
|
|
|
|
$img->setAttribute('src', $resized->getRelativePath());
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
2009-10-11 02:07:13 +02:00
|
|
|
// Add default empty title & alt attributes.
|
|
|
|
if(!$img->getAttribute('alt')) $img->setAttribute('alt', '');
|
|
|
|
if(!$img->getAttribute('title')) $img->setAttribute('title', '');
|
|
|
|
|
2010-04-12 04:45:35 +02:00
|
|
|
//If the src attribute is not set, then we won't add this to the list:
|
|
|
|
if($img->getAttribute('src')){
|
|
|
|
// Add to the tracked files.
|
|
|
|
$linkedFiles[] = $image->ID;
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2009-10-11 02:07:13 +02:00
|
|
|
|
|
|
|
// Save file & link tracking data.
|
2011-03-23 02:32:38 +01:00
|
|
|
if(class_exists('SiteTree')) {
|
|
|
|
if($record->ID && $record->many_many('LinkTracking') && $tracker = $record->LinkTracking()) {
|
2009-11-22 06:16:38 +01:00
|
|
|
$tracker->removeByFilter(sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', $this->name, $record->ID));
|
2009-11-26 23:18:59 +01:00
|
|
|
|
2011-03-23 02:32:38 +01:00
|
|
|
if($linkedPages) foreach($linkedPages as $item) {
|
|
|
|
$SQL_fieldName = Convert::raw2sql($this->name);
|
|
|
|
DB::query("INSERT INTO \"SiteTree_LinkTracking\" (\"SiteTreeID\",\"ChildID\", \"FieldName\")
|
|
|
|
VALUES ($record->ID, $item, '$SQL_fieldName')");
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2009-11-22 06:16:38 +01:00
|
|
|
|
2011-03-23 02:32:38 +01:00
|
|
|
if($record->ID && $record->many_many('ImageTracking') && $tracker = $record->ImageTracking()) {
|
2011-10-29 06:23:53 +02:00
|
|
|
$tracker->where(sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', $this->name, $record->ID))->removeAll();
|
2011-03-23 02:32:38 +01:00
|
|
|
|
|
|
|
$fieldName = $this->name;
|
|
|
|
if($linkedFiles) foreach($linkedFiles as $item) {
|
|
|
|
$tracker->add($item, array('FieldName' => $this->name));
|
|
|
|
}
|
2009-10-11 02:07:13 +02:00
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
2009-10-11 02:07:27 +02:00
|
|
|
$record->{$this->name} = $htmlValue->getContent();
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2009-11-26 23:18:59 +01:00
|
|
|
|
2009-10-11 02:07:13 +02:00
|
|
|
/**
|
|
|
|
* @return HtmlEditorField_Readonly
|
|
|
|
*/
|
|
|
|
public function performReadonlyTransformation() {
|
|
|
|
$field = new HtmlEditorField_Readonly($this->name, $this->title, $this->value);
|
2007-07-19 12:40:28 +02:00
|
|
|
$field->setForm($this->form);
|
|
|
|
$field->dontEscape = true;
|
|
|
|
return $field;
|
|
|
|
}
|
2009-10-11 02:07:13 +02:00
|
|
|
|
2012-01-02 16:19:22 +01:00
|
|
|
public function performDisabledTransformation() {
|
|
|
|
return $this->performReadonlyTransformation();
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
|
2008-01-10 01:34:18 +01:00
|
|
|
/**
|
|
|
|
* Readonly version of an {@link HTMLEditorField}.
|
|
|
|
* @package forms
|
|
|
|
* @subpackage fields-formattedinput
|
|
|
|
*/
|
2009-10-11 02:07:13 +02:00
|
|
|
class HtmlEditorField_Readonly extends ReadonlyField {
|
2007-07-19 12:40:28 +02:00
|
|
|
function Field() {
|
|
|
|
$valforInput = $this->value ? Convert::raw2att($this->value) : "";
|
2007-11-07 05:17:58 +01:00
|
|
|
return "<span class=\"readonly typography\" id=\"" . $this->id() . "\">" . ( $this->value && $this->value != '<p></p>' ? $this->value : '<i>(not set)</i>' ) . "</span><input type=\"hidden\" name=\"".$this->name."\" value=\"".$valforInput."\" />";
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
function Type() {
|
2007-11-06 22:01:37 +01:00
|
|
|
return 'htmleditorfield readonly';
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-01-10 01:34:18 +01:00
|
|
|
/**
|
2012-02-09 17:17:39 +01:00
|
|
|
* Toolbar shared by all instances of {@link HTMLEditorField}, to avoid too much markup duplication.
|
|
|
|
* Needs to be inserted manually into the template in order to function - see {@link LeftAndMain->EditorToolbar()}.
|
|
|
|
*
|
2008-01-10 01:34:18 +01:00
|
|
|
* @package forms
|
|
|
|
* @subpackage fields-formattedinput
|
|
|
|
*/
|
2008-10-30 23:03:21 +01:00
|
|
|
class HtmlEditorField_Toolbar extends RequestHandler {
|
2012-02-08 21:32:25 +01:00
|
|
|
|
|
|
|
static $allowed_actions = array(
|
|
|
|
'LinkForm',
|
|
|
|
'MediaForm',
|
2012-02-09 17:17:39 +01:00
|
|
|
'viewfile'
|
2012-02-08 21:32:25 +01:00
|
|
|
);
|
|
|
|
|
2012-02-09 17:17:39 +01:00
|
|
|
/**
|
|
|
|
* @var string
|
|
|
|
*/
|
|
|
|
protected $templateViewFile = 'HtmlEditorField_viewfile';
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
protected $controller, $name;
|
|
|
|
|
|
|
|
function __construct($controller, $name) {
|
2008-11-05 02:59:27 +01:00
|
|
|
parent::__construct();
|
2012-02-08 21:32:25 +01:00
|
|
|
|
|
|
|
Requirements::javascript(SAPPHIRE_DIR . "/thirdparty/jquery/jquery.js");
|
|
|
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery-ui/jquery-ui.js');
|
|
|
|
Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/dist/jquery.entwine-dist.js');
|
|
|
|
Requirements::javascript(SAPPHIRE_ADMIN_DIR . '/javascript/ssui.core.js');
|
2010-04-12 23:54:41 +02:00
|
|
|
Requirements::javascript(SAPPHIRE_DIR ."/javascript/HtmlEditorField.js");
|
2012-02-08 21:32:25 +01:00
|
|
|
|
|
|
|
Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
|
2008-11-05 02:59:27 +01:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
$this->controller = $controller;
|
|
|
|
$this->name = $name;
|
|
|
|
}
|
2010-04-13 01:34:08 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Searches the SiteTree for display in the dropdown
|
|
|
|
*
|
|
|
|
* @return callback
|
|
|
|
*/
|
|
|
|
function siteTreeSearchCallback($sourceObject, $labelField, $search) {
|
|
|
|
return DataObject::get($sourceObject, "\"MenuTitle\" LIKE '%$search%' OR \"Title\" LIKE '%$search%'");
|
|
|
|
}
|
2007-07-19 12:40:28 +02:00
|
|
|
|
|
|
|
/**
|
2008-11-04 05:12:54 +01:00
|
|
|
* Return a {@link Form} instance allowing a user to
|
|
|
|
* add links in the TinyMCE content editor.
|
|
|
|
*
|
|
|
|
* @return Form
|
2007-07-19 12:40:28 +02:00
|
|
|
*/
|
|
|
|
function LinkForm() {
|
2010-04-13 01:34:08 +02:00
|
|
|
$siteTree = new TreeDropdownField('internal', _t('HtmlEditorField.PAGE', "Page"), 'SiteTree', 'ID', 'MenuTitle', true);
|
|
|
|
// mimic the SiteTree::getMenuTitle(), which is bypassed when the search is performed
|
|
|
|
$siteTree->setSearchFunction(array($this, 'siteTreeSearchCallback'));
|
|
|
|
|
2012-01-03 22:30:24 +01:00
|
|
|
$numericLabelTmpl = '<span class="step-label"><span class="flyout">%d</span><span class="arrow"></span><strong class="title">%s</strong></span>';
|
2007-07-19 12:40:28 +02:00
|
|
|
$form = new Form(
|
|
|
|
$this->controller,
|
2008-08-11 01:35:11 +02:00
|
|
|
"{$this->name}/LinkForm",
|
2011-05-11 09:51:54 +02:00
|
|
|
new FieldList(
|
2009-11-21 03:22:44 +01:00
|
|
|
new LiteralField(
|
|
|
|
'Heading',
|
|
|
|
sprintf('<h3>%s</h3>', _t('HtmlEditorField.LINK', 'Link'))
|
2007-07-19 12:40:28 +02:00
|
|
|
),
|
2009-11-21 03:22:44 +01:00
|
|
|
$contentComposite = new CompositeField(
|
|
|
|
new OptionsetField(
|
|
|
|
'LinkType',
|
2012-01-03 22:30:24 +01:00
|
|
|
sprintf($numericLabelTmpl, '1', _t('HtmlEditorField.LINKTO', 'Link to')),
|
2009-11-21 03:22:44 +01:00
|
|
|
array(
|
|
|
|
'internal' => _t('HtmlEditorField.LINKINTERNAL', 'Page on the site'),
|
|
|
|
'external' => _t('HtmlEditorField.LINKEXTERNAL', 'Another website'),
|
|
|
|
'anchor' => _t('HtmlEditorField.LINKANCHOR', 'Anchor on this page'),
|
|
|
|
'email' => _t('HtmlEditorField.LINKEMAIL', 'Email address'),
|
|
|
|
'file' => _t('HtmlEditorField.LINKFILE', 'Download a file'),
|
|
|
|
)
|
|
|
|
),
|
2012-01-03 22:30:24 +01:00
|
|
|
new LiteralField('Step2',
|
|
|
|
'<div class="step2">' . sprintf($numericLabelTmpl, '2', _t('HtmlEditorField.DETAILS', 'Details')) . '</div>'
|
|
|
|
),
|
2010-04-13 01:34:08 +02:00
|
|
|
$siteTree,
|
2009-11-21 03:22:44 +01:00
|
|
|
new TextField('external', _t('HtmlEditorField.URL', 'URL'), 'http://'),
|
|
|
|
new EmailField('email', _t('HtmlEditorField.EMAIL', 'Email address')),
|
2012-03-01 09:13:42 +01:00
|
|
|
new TreeDropdownField('file', _t('HtmlEditorField.FILE', 'File'), 'File', 'ID', 'Title', true),
|
2009-11-21 03:22:44 +01:00
|
|
|
new TextField('Anchor', _t('HtmlEditorField.ANCHORVALUE', 'Anchor')),
|
|
|
|
new TextField('Description', _t('HtmlEditorField.LINKDESCR', 'Link description')),
|
2010-10-15 05:25:51 +02:00
|
|
|
new CheckboxField('TargetBlank', _t('HtmlEditorField.LINKOPENNEWWIN', 'Open link in a new window?')),
|
|
|
|
new HiddenField('Locale', null, $this->controller->Locale)
|
2009-11-21 03:22:44 +01:00
|
|
|
)
|
2007-07-19 12:40:28 +02:00
|
|
|
),
|
2011-05-11 09:51:54 +02:00
|
|
|
new FieldList(
|
2012-02-17 00:35:10 +01:00
|
|
|
Object::create('ResetFormAction', 'remove', _t('HtmlEditorField.BUTTONREMOVELINK', 'Remove link'))
|
|
|
|
->addExtraClass('ss-ui-action-destructive')
|
|
|
|
->setUseButtonTag(true)
|
|
|
|
,
|
|
|
|
FormAction::create('insert', _t('HtmlEditorField.BUTTONINSERTLINK', 'Insert link'))
|
|
|
|
->addExtraClass('ss-ui-action-constructive')
|
|
|
|
->setAttribute('data-icon', 'accept')
|
|
|
|
->setUseButtonTag(true)
|
2007-07-19 12:40:28 +02:00
|
|
|
)
|
|
|
|
);
|
2008-11-04 05:12:54 +01:00
|
|
|
|
2009-11-21 03:22:44 +01:00
|
|
|
$contentComposite->addExtraClass('content');
|
|
|
|
|
2009-11-21 03:31:11 +01:00
|
|
|
$form->unsetValidator();
|
2007-07-19 12:40:28 +02:00
|
|
|
$form->loadDataFrom($this);
|
2012-01-03 17:38:11 +01:00
|
|
|
$form->addExtraClass('htmleditorfield-form htmleditorfield-linkform cms-dialog-content');
|
2008-11-04 05:12:54 +01:00
|
|
|
|
2010-05-25 06:56:02 +02:00
|
|
|
$this->extend('updateLinkForm', $form);
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
return $form;
|
|
|
|
}
|
|
|
|
|
2008-11-04 05:12:54 +01:00
|
|
|
/**
|
|
|
|
* Return a {@link Form} instance allowing a user to
|
2012-02-08 21:32:25 +01:00
|
|
|
* add images and flash objects to the TinyMCE content editor.
|
2008-11-04 05:12:54 +01:00
|
|
|
*
|
|
|
|
* @return Form
|
|
|
|
*/
|
2012-02-08 21:32:25 +01:00
|
|
|
function MediaForm() {
|
|
|
|
// TODO Handle through GridState within field - currently this state set too late to be useful here (during request handling)
|
|
|
|
$parentID = $this->controller->getRequest()->requestVar('ParentID');
|
|
|
|
|
|
|
|
$fileFieldConfig = GridFieldConfig::create();
|
|
|
|
$fileFieldConfig->addComponent(new GridFieldSortableHeader());
|
|
|
|
$fileFieldConfig->addComponent(new GridFieldFilter());
|
|
|
|
$fileFieldConfig->addComponent(new GridFieldDefaultColumns());
|
2012-02-09 17:17:39 +01:00
|
|
|
$fileFieldConfig->addComponent(new GridFieldPaginator(5));
|
|
|
|
$fileField = new GridField('Files', false, null, $fileFieldConfig);
|
2012-02-08 21:32:25 +01:00
|
|
|
$fileField->setList($this->getFiles($parentID));
|
|
|
|
$fileField->setAttribute('data-selectable', true);
|
|
|
|
$fileField->setAttribute('data-multiselect', true);
|
2012-02-09 17:17:39 +01:00
|
|
|
$fileField->setDisplayFields(array(
|
|
|
|
'CMSThumbnail' => false,
|
|
|
|
'Name' => _t('File.Name'),
|
|
|
|
));
|
2011-03-23 03:38:31 +01:00
|
|
|
|
2012-02-08 21:32:25 +01:00
|
|
|
$numericLabelTmpl = '<span class="step-label"><span class="flyout">%d</span><span class="arrow"></span><strong class="title">%s</strong></span>';
|
2011-05-11 09:51:54 +02:00
|
|
|
$fields = new FieldList(
|
2010-05-25 06:56:02 +02:00
|
|
|
new LiteralField(
|
|
|
|
'Heading',
|
|
|
|
sprintf('<h3>%s</h3>', _t('HtmlEditorField.IMAGE', 'Image'))
|
|
|
|
),
|
|
|
|
|
|
|
|
$contentComposite = new CompositeField(
|
2012-02-09 17:17:39 +01:00
|
|
|
new LiteralField('headerSelect', '<h4 class="field header-select">' . sprintf($numericLabelTmpl, '1', _t('HtmlEditorField.Find', 'Find')) . '</h4>'),
|
|
|
|
$selectComposite = new CompositeField(
|
|
|
|
new TreeDropdownField('ParentID', _t('HtmlEditorField.FOLDER', 'Folder'), 'Folder'),
|
|
|
|
$fileField
|
|
|
|
),
|
|
|
|
|
|
|
|
new LiteralField('headerEdit', '<h4 class="field header-edit">' . sprintf($numericLabelTmpl, '2', _t('HtmlEditorField.EditDetails', 'Edit details')) . '</h4>'),
|
|
|
|
$editComposite = new CompositeField(
|
|
|
|
new LiteralField('contentEdit', '<div class="content-edit"></div>')
|
|
|
|
)
|
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
)
|
|
|
|
);
|
2008-11-04 05:12:54 +01:00
|
|
|
|
2011-05-11 09:51:54 +02:00
|
|
|
$actions = new FieldList(
|
2012-02-17 00:35:10 +01:00
|
|
|
FormAction::create('insertimage', _t('HtmlEditorField.BUTTONINSERT', 'Insert'))
|
|
|
|
->addExtraClass('ss-ui-action-constructive')
|
|
|
|
->setAttribute('data-icon', 'accept')
|
|
|
|
->setUseButtonTag(true)
|
2010-05-25 06:56:02 +02:00
|
|
|
);
|
2012-01-03 09:38:33 +01:00
|
|
|
|
2010-05-25 06:56:02 +02:00
|
|
|
$form = new Form(
|
|
|
|
$this->controller,
|
2012-02-08 21:32:25 +01:00
|
|
|
"{$this->name}/MediaForm",
|
2010-05-25 06:56:02 +02:00
|
|
|
$fields,
|
|
|
|
$actions
|
|
|
|
);
|
|
|
|
|
2009-11-21 03:22:44 +01:00
|
|
|
$contentComposite->addExtraClass('content');
|
2012-02-09 17:17:39 +01:00
|
|
|
$selectComposite->addExtraClass('content-select');
|
2010-05-25 06:56:02 +02:00
|
|
|
|
2009-11-21 03:31:11 +01:00
|
|
|
$form->unsetValidator();
|
2009-06-28 03:17:20 +02:00
|
|
|
$form->disableSecurityToken();
|
2007-07-19 12:40:28 +02:00
|
|
|
$form->loadDataFrom($this);
|
2012-02-08 21:32:25 +01:00
|
|
|
$form->addExtraClass('htmleditorfield-form htmleditorfield-mediaform cms-dialog-content');
|
2012-02-09 17:17:39 +01:00
|
|
|
// TODO Re-enable once we remove $.metadata dependency which currently breaks the JS due to $.ui.widget
|
|
|
|
// $form->setAttribute('data-urlViewfile', $this->controller->Link($this->name));
|
|
|
|
|
|
|
|
// Allow other people to extend the fields being added to the imageform
|
|
|
|
$this->extend('updateMediaForm', $form);
|
2008-11-04 05:12:54 +01:00
|
|
|
|
2007-07-19 12:40:28 +02:00
|
|
|
return $form;
|
|
|
|
}
|
|
|
|
|
2012-02-09 17:17:39 +01:00
|
|
|
/**
|
|
|
|
* View of a single file, either on the filesystem or on the web.
|
|
|
|
*/
|
|
|
|
public function viewfile($request) {
|
|
|
|
// TODO Would be cleaner to consistently pass URL for both local and remote files,
|
|
|
|
// but GridField doesn't allow for this kind of metadata customization at the moment.
|
|
|
|
if($url = $request->getVar('FileURL')) {
|
|
|
|
if(Director::is_absolute_url($url)) {
|
|
|
|
$url = $url;
|
|
|
|
$file = null;
|
|
|
|
} else {
|
|
|
|
$url = Director::makeRelative($request->getVar('FileURL'));
|
2012-02-27 22:14:02 +01:00
|
|
|
$url = preg_replace('/_resampled/[^-]+-/', '', $url);
|
2012-02-09 17:17:39 +01:00
|
|
|
$file = DataList::create('File')->filter('Filename', $url)->first();
|
|
|
|
if(!$file) $file = new File(array('Title' => basename($url)));
|
|
|
|
}
|
|
|
|
} elseif($id = $request->getVar('ID')) {
|
|
|
|
$file = DataObject::get_by_id('File', $id);
|
|
|
|
$url = $file->RelativeLink();
|
|
|
|
} else {
|
|
|
|
throw new LogicException('Need either "ID" or "FileURL" parameter to identify the file');
|
|
|
|
}
|
|
|
|
|
|
|
|
// Instanciate file wrapper and get fields based on its type
|
|
|
|
if($file && $file->appCategory() == 'image') {
|
|
|
|
$fileWrapper = new HtmlEditorField_Image($url, $file);
|
|
|
|
} else {
|
|
|
|
$fileWrapper = new HtmlEditorField_File($url, $file);
|
|
|
|
}
|
|
|
|
$fields = $this->getFieldsForFile($url, $fileWrapper);
|
|
|
|
$this->extend('updateFieldsForFile', $fields, $url, $fileWrapper);
|
|
|
|
|
|
|
|
return $fileWrapper->customise(array(
|
|
|
|
'Fields' => $fields,
|
|
|
|
))->renderWith($this->templateViewFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Similar to {@link File->getCMSFields()}, but only returns fields
|
|
|
|
* for manipulating the instance of the file as inserted into the HTML content,
|
|
|
|
* not the "master record" in the database - hence there's no form or saving logic.
|
|
|
|
*
|
|
|
|
* @param String Relative or absolute URL to file
|
|
|
|
* @return FieldList
|
|
|
|
*/
|
|
|
|
protected function getFieldsForFile($url, $file) {
|
|
|
|
$fields = $this->extend('getFieldsForFile', $url, $file);
|
|
|
|
if(!$fields) {
|
|
|
|
if($file->Extension == 'swf') {
|
|
|
|
$fields = $this->getFieldsForFlash($url, $file);
|
|
|
|
} else {
|
|
|
|
$fields = $this->getFieldsForImage($url, $file);
|
|
|
|
}
|
|
|
|
$fields->push(new HiddenField('URL', false, $url));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->extend('updateFieldsForFile', $fields, $url, $file);
|
2011-03-23 03:38:31 +01:00
|
|
|
|
2012-02-09 17:17:39 +01:00
|
|
|
return $fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return FieldList
|
|
|
|
*/
|
|
|
|
protected function getFieldsForFlash($url, $file) {
|
|
|
|
$fields = new FieldList(
|
|
|
|
$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)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
$dimensionsField->addExtraClass('dimensions');
|
|
|
|
$widthField->setMaxLength(5);
|
|
|
|
$heightField->setMaxLength(5);
|
|
|
|
|
|
|
|
$this->extend('updateFieldsForFlash', $fields, $url, $file);
|
|
|
|
|
|
|
|
return $fields;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return FieldList
|
|
|
|
*/
|
|
|
|
protected function getFieldsForImage($url, $file) {
|
2012-02-28 18:07:55 +01:00
|
|
|
$formattedImage = $file->getFormattedImage('SetWidth', Image::$asset_preview_width);
|
|
|
|
$thumbnail = $formattedImage ? $formattedImage->URL : '';
|
|
|
|
$previewField = new LiteralField("ImageFull",
|
|
|
|
"<img id='thumbnailImage' class='thumbnail-preview' src='{$thumbnail}?r=" . rand(1,100000) . "' alt='{$file->Name}' />\n"
|
|
|
|
);
|
|
|
|
|
2012-02-09 17:17:39 +01:00
|
|
|
$fields = new FieldList(
|
2012-02-28 18:07:55 +01:00
|
|
|
$filePreview = FormField::create('CompositeField',
|
|
|
|
FormField::create('CompositeField',
|
|
|
|
$previewField
|
|
|
|
)->setName("FilePreviewImage")->addExtraClass('cms-file-info-preview'),
|
|
|
|
FormField::create('CompositeField',
|
|
|
|
FormField::create('CompositeField',
|
|
|
|
new ReadonlyField("FileType", _t('AssetTableField.TYPE','File type') . ':', $file->FileType),
|
|
|
|
new ReadonlyField("Size", _t('AssetTableField.SIZE','File size') . ':', $file->getSize()),
|
|
|
|
$urlField = new ReadonlyField('ClickableURL', _t('AssetTableField.URL','URL'),
|
|
|
|
sprintf('<a href="%s" target="_blank">%s</a>', $file->Link(), $file->RelativeLink())
|
|
|
|
),
|
|
|
|
new DateField_Disabled("Created", _t('AssetTableField.CREATED','First uploaded') . ':', $file->Created),
|
|
|
|
new DateField_Disabled("LastEdited", _t('AssetTableField.LASTEDIT','Last changed') . ':', $file->LastEdited)
|
|
|
|
)
|
|
|
|
)->setName("FilePreviewData")->addExtraClass('cms-file-info-data')
|
|
|
|
)->setName("FilePreview")->addExtraClass('cms-file-info'),
|
2012-02-09 17:17:39 +01:00
|
|
|
new TextField(
|
|
|
|
'AltText',
|
|
|
|
_t('HtmlEditorField.IMAGEALTTEXT', 'Alternative text (alt) - shown if image cannot be displayed'),
|
|
|
|
$file->Title,
|
|
|
|
80
|
|
|
|
),
|
|
|
|
new TextField(
|
|
|
|
'Title',
|
|
|
|
_t('HtmlEditorField.IMAGETITLE', 'Title text (tooltip) - for additional information about the image')
|
|
|
|
),
|
|
|
|
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)
|
|
|
|
)
|
|
|
|
);
|
2012-02-28 18:07:55 +01:00
|
|
|
$urlField->dontEscape = true;
|
2012-02-09 17:17:39 +01:00
|
|
|
$dimensionsField->addExtraClass('dimensions');
|
|
|
|
$widthField->setMaxLength(5);
|
|
|
|
$heightField->setMaxLength(5);
|
|
|
|
|
|
|
|
$this->extend('updateFieldsForImage', $fields, $url, $file);
|
|
|
|
|
|
|
|
return $fields;
|
2012-02-08 21:32:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Int
|
|
|
|
* @return DataList
|
|
|
|
*/
|
|
|
|
protected function getFiles($parentID = null) {
|
|
|
|
// TODO Use array('Filename:EndsWith' => $exts) once that's supported
|
2012-02-09 17:17:39 +01:00
|
|
|
$exts = $this->getAllowedExtensions();
|
2012-02-08 21:32:25 +01:00
|
|
|
$wheres = array();
|
|
|
|
foreach($exts as $ext) $wheres[] = '"Filename" LIKE \'%.' . $ext . '\'';
|
|
|
|
|
|
|
|
$files = DataList::create('File')->where(implode(' OR ', $wheres));
|
2009-11-21 03:31:11 +01:00
|
|
|
|
2012-02-08 21:32:25 +01:00
|
|
|
// Limit by folder (if required)
|
|
|
|
if($parentID) $files->filter('ParentID', $parentID);
|
2010-05-25 06:56:02 +02:00
|
|
|
|
2012-02-08 21:32:25 +01:00
|
|
|
return $files;
|
2007-07-19 12:40:28 +02:00
|
|
|
}
|
2012-02-09 17:17:39 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Array All extensions which can be handled by the different views.
|
|
|
|
*/
|
|
|
|
protected function getAllowedExtensions() {
|
|
|
|
$exts = array('jpg', 'gif', 'png', 'swf');
|
|
|
|
$this->extend('updateAllowedExtensions', $exts);
|
|
|
|
return $exts;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Encapsulation of a file which can either be a remote URL
|
|
|
|
* or a {@link File} on the local filesystem, exhibiting common properties
|
|
|
|
* such as file name or the URL.
|
|
|
|
*
|
|
|
|
* @todo Remove once core has support for remote files
|
|
|
|
*/
|
|
|
|
class HtmlEditorField_File extends ViewableData {
|
|
|
|
|
|
|
|
/** @var String */
|
|
|
|
protected $url;
|
|
|
|
|
|
|
|
/** @var File */
|
|
|
|
protected $file;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param String
|
|
|
|
* @param File
|
|
|
|
*/
|
|
|
|
function __construct($url, $file = null) {
|
|
|
|
$this->url = $url;
|
|
|
|
$this->file = $file;
|
2012-02-28 18:07:55 +01:00
|
|
|
$this->failover = $file;
|
2012-02-09 17:17:39 +01:00
|
|
|
|
|
|
|
parent::__construct();
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return File Might not be set (for remote files)
|
|
|
|
*/
|
|
|
|
function getFile() {
|
|
|
|
return $this->file;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getURL() {
|
|
|
|
return $this->url;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getName() {
|
|
|
|
return ($this->file) ? $this->file->Name : preg_replace('/\?.*/', '', basename($this->url));
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return String HTML
|
|
|
|
*/
|
|
|
|
function getPreview() {
|
|
|
|
$preview = $this->extend('getPreview');
|
|
|
|
if($preview) return $preview;
|
|
|
|
|
|
|
|
if($this->file) {
|
|
|
|
return $this->file->CMSThumbnail();
|
|
|
|
} else {
|
|
|
|
// Hack to use the framework's built-in thumbnail support without creating a local file representation
|
|
|
|
$tmpFile = new File(array('Name' => $this->Name, 'Filename' => $this->Name));
|
|
|
|
return $tmpFile->CMSThumbnail();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getExtension() {
|
|
|
|
return strtolower(($this->file) ? $this->file->Extension : pathinfo($this->Name, PATHINFO_EXTENSION));
|
|
|
|
}
|
|
|
|
|
|
|
|
function appCategory() {
|
|
|
|
if($this->file) {
|
|
|
|
return $this->file->appCategory();
|
|
|
|
} else {
|
|
|
|
// Hack to use the framework's built-in thumbnail support without creating a local file representation
|
|
|
|
$tmpFile = new File(array('Name' => $this->Name, 'Filename' => $this->Name));
|
|
|
|
return $tmpFile->appCategory();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
class HtmlEditorField_Image extends HtmlEditorField_File {
|
|
|
|
|
|
|
|
protected $width;
|
|
|
|
|
|
|
|
protected $height;
|
|
|
|
|
|
|
|
function __construct($url, $file = null) {
|
|
|
|
parent::__construct($url, $file);
|
|
|
|
|
|
|
|
// Get dimensions for remote file
|
|
|
|
$info = @getimagesize($url);
|
|
|
|
if($info) {
|
|
|
|
$this->width = $info[0];
|
|
|
|
$this->height = $info[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function getWidth() {
|
|
|
|
return ($this->file) ? $this->file->Width : $this->width;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getHeight() {
|
|
|
|
return ($this->file) ? $this->file->Height : $this->height;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPreview() {
|
|
|
|
return ($this->file) ? $this->file->CMSThumbnail() : sprintf('<img src="%s" />', $this->url);
|
|
|
|
}
|
|
|
|
|
2012-03-01 09:13:42 +01:00
|
|
|
}
|