mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
393caf4287
FEATURE a little bit of german translation git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@43842 467b73ca-7a2a-4603-9d3b-597d59a354a9
450 lines
17 KiB
PHP
Executable File
450 lines
17 KiB
PHP
Executable File
<?php
|
|
|
|
/**
|
|
* A WYSIWYG editor field, powered by tinymce.
|
|
* tinymce editor fields are created from <textarea> tags which are then converted with javascript.
|
|
* The {@link Requirements} system is used to ensure that all necessary javascript is included.
|
|
*/
|
|
class HtmlEditorField extends TextareaField {
|
|
protected $rows;
|
|
|
|
/**
|
|
* Construct a new HtmlEditor field
|
|
*/
|
|
function __construct($name, $title = "", $rows = 15, $cols = 20, $value = "", $form = null) {
|
|
parent::__construct($name, $title, $rows, $cols, $value, $form);
|
|
$this->extraClass = 'typography';
|
|
}
|
|
|
|
|
|
/**
|
|
* Returns the a <textarea> field with tinymce="true" set on it
|
|
*/
|
|
function Field() {
|
|
Requirements::javascript(MCE_ROOT . "tiny_mce_src.js");
|
|
Requirements::javascript("jsparty/tiny_mce_improvements.js");
|
|
|
|
// Don't allow unclosed tags - they will break the whole application ;-)
|
|
$cleanVal = $this->value;
|
|
$lPos = strrpos($cleanVal,'<');
|
|
$rPos = strrpos($cleanVal,'>');
|
|
if(($lPos > $rPos) || ($rPos === false && $lPos !== false)) $cleanVal .= '>';
|
|
|
|
// Remove broken link classes, we'll add them now
|
|
$cleanVal = eregi_replace('class="([^"]*) *broken *( [^"]*)?"','class="\\1\\2"', $cleanVal);
|
|
|
|
// Mark up broken links
|
|
$links = HTTP::getLinksIn($cleanVal);
|
|
if($links) {
|
|
$links = array_unique($links);
|
|
foreach($links as $link) {
|
|
$originalLink = $link;
|
|
$link = Director::makeRelative($link);
|
|
$broken = false;
|
|
if(ereg('^([A-Za-z0-9_\-]+)/?(#.*)?$', $link, $parts)) {
|
|
if(!DataObject::get_one("SiteTree", "URLSegment = '$parts[1]'", false)) {
|
|
$broken = true;
|
|
// Prevents execution timeouts if a page has 50 identical broken links by only highlighting them once
|
|
$alreadyHighlighted[$parts[1]] = true;
|
|
}
|
|
} else if($link[0] == '/') {
|
|
$broken = true;
|
|
} else if(ereg('^assets/',$link)) {
|
|
if(!DataObject::get_one("File", "Filename = '$link'", false)) {
|
|
$broken = true;
|
|
}
|
|
}
|
|
|
|
// Add a class. Note that this might create multiple class attributes, which are stripped below
|
|
if($broken) $cleanVal = eregi_replace("(<a)([^>]*href=\"{$originalLink}[^\"]*\"[^>]*>)",'\\1 class="broken"\\2', $cleanVal);
|
|
}
|
|
}
|
|
|
|
// Combined multiple classes into a single class
|
|
while( eregi('(<a[^>]*)class="([^"]*)"([^>]*)class="([^"]*)"([^>]*>)', $cleanVal) )
|
|
$cleanVal = eregi_replace('(<a[^>]*)class="([^"]*)"([^>]*)class="([^"]*)"([^>]*>)','\\1class="\\2 \\4"\\3\\5', $cleanVal);
|
|
|
|
// We can't use htmlentities as that messes with unicode
|
|
$cleanVal = str_replace(array("&","<",">"),array("&","<",">"),$cleanVal);
|
|
// 97% instead of 100% to prevent horizontal scrollbars in IE7
|
|
$style = "width: 97%; height: " . ($this->rows * 16) . "px";
|
|
|
|
$class = "htmleditor";
|
|
$class = ($this->extraClass)?$class." ".$this->extraClass:$class;
|
|
|
|
return "<textarea class=\"$class\" rows=\"$this->rows\" cols=\"$this->cols\" style=\"$style\" tinymce=\"true\" id=\"" . $this->id() . "\" name=\"{$this->name}\">$cleanVal</textarea>";
|
|
}
|
|
|
|
/**
|
|
* This function has been created to explicit the functionnality.
|
|
*/
|
|
function setCSSClass($class){
|
|
$this->extraClass = $class;
|
|
}
|
|
|
|
function saveInto($record) {
|
|
$content = $this->value;
|
|
|
|
$content = preg_replace('/mce_real_src="[^"]+"/i', "", $content);
|
|
|
|
$content = eregi_replace('width=([0-9]+)','width="\\1"',$content);
|
|
$content = eregi_replace('height=([0-9]+)','height="\\1"',$content);
|
|
|
|
$content = preg_replace_callback('/(<img[^>]* )(width="|height="|src=")([^"]+)("[^>]* )(width="|height="|src=")([^"]+)("[^>]* )(width="|height="|src=")([^"]+)("[^>]*>)/i', "HtmlEditorField_dataValue_processImage", $content);
|
|
|
|
// If we don't have a containing block element, add a p tag.
|
|
if(!ereg("^[ \t\r\n]*<", $content)) $content = "<p>$content</p>";
|
|
|
|
$links = HTTP::getLinksIn($content);
|
|
|
|
if($links) foreach($links as $link) {
|
|
$link = Director::makeRelative($link);
|
|
|
|
if(preg_match( '/^([A-Za-z0-9_-]+)\/?(#.*)?$/', $link, $parts ) ) {
|
|
$candidatePage = DataObject::get_one("SiteTree", "URLSegment = '" . urldecode( $parts[1] ). "'", false);
|
|
if($candidatePage) {
|
|
$linkedPages[] = $candidatePage->ID;
|
|
// This caused bugs in the publication script
|
|
// $candidatePage->destroy();
|
|
} else {
|
|
$record->HasBrokenLink = 1;
|
|
}
|
|
|
|
} else if($link{0} == '/') {
|
|
$record->HasBrokenLink = 1;
|
|
|
|
} else if($candidateFile = DataObject::get_one("File", "Filename = '" . Convert::raw2sql(urldecode($link)) . "'", false)) {
|
|
$linkedFiles[] = $candidateFile->ID;
|
|
// $candidateFile->destroy();
|
|
}
|
|
}
|
|
|
|
$images = HTTP::getImagesIn($content);
|
|
|
|
if($images){
|
|
foreach($images as $image) {
|
|
|
|
$image = Director::makeRelative($image);
|
|
if(substr($image,0,7) == 'assets/') {
|
|
$candidateImage = DataObject::get_one("File", "Filename = '$image'");
|
|
if($candidateImage) $linkedFiles[] = $candidateImage->ID;
|
|
else $record->HasBrokenFile = 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
$fieldName = $this->name;
|
|
if($record->ID && $record->hasMethod('LinkTracking') && $linkTracking = $record->LinkTracking()) {
|
|
$linkTracking->removeByFilter("FieldName = '$fieldName'");
|
|
|
|
if(isset($linkedPages)) foreach($linkedPages as $item) {
|
|
$linkTracking->add($item, array("FieldName" => $fieldName));
|
|
}
|
|
|
|
// $linkTracking->destroy();
|
|
}
|
|
if($record->ID && $record->hasMethod('ImageTracking') && $imageTracking = $record->ImageTracking()) {
|
|
$imageTracking->removeByFilter("FieldName = '$fieldName'");
|
|
if(isset($linkedFiles)) foreach($linkedFiles as $item) {
|
|
$imageTracking->add($item, array("FieldName" => $fieldName));
|
|
}
|
|
// $imageTracking->destroy();
|
|
}
|
|
|
|
$record->$fieldName = $content;
|
|
}
|
|
|
|
function rewriteLink($old, $new) {
|
|
$bases[] = "";
|
|
$bases[] = Director::baseURL();
|
|
$bases[] = Director::absoluteBaseURL();
|
|
foreach($bases as $base) {
|
|
$this->value = ereg_replace("(href=\"?)$base$old","\\1$new", $this->value);
|
|
}
|
|
|
|
$this->value = ereg_replace("(href=\"?)$base$old","\\1$new", $this->value);
|
|
return $this->value;
|
|
}
|
|
|
|
function performReadonlyTransformation() {
|
|
$field = new HtmlEditorField_readonly($this->name, $this->title, $this->value);
|
|
$field->setForm($this->form);
|
|
$field->dontEscape = true;
|
|
return $field;
|
|
}
|
|
}
|
|
|
|
class HtmlEditorField_readonly extends ReadonlyField {
|
|
function Field() {
|
|
$valforInput = $this->value ? Convert::raw2att($this->value) : "";
|
|
return "<div class=\"readonly typography\" id=\"" . $this->id() . "\">$this->value</div><br /><input type=\"hidden\" name=\"".$this->name."\" value=\"".$valforInput."\" />";
|
|
}
|
|
function Type() {
|
|
return 'readonly';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Proccesses HTML images into the correct proportions from
|
|
* the regular expression evaluated on the save.
|
|
*/
|
|
function HtmlEditorField_dataValue_processImage($parts) {
|
|
|
|
// The info could be in any order
|
|
$info[$parts[2]] = $parts[3]; $partSource[$parts[2]] = 3;
|
|
$info[$parts[5]] = $parts[6]; $partSource[$parts[5]] = 6;
|
|
$info[$parts[8]] = $parts[9]; $partSource[$parts[8]] = 9;
|
|
$src = Director::makeRelative($info['src="']);
|
|
|
|
if(substr($src,0,10) == '../assets/') $src = substr($src,3);
|
|
|
|
$width = $info['width="'];
|
|
$height = $info['height="'];
|
|
|
|
if(!$width || !$height) {
|
|
user_error("Can't find width/height in $text", E_USER_ERROR);
|
|
}
|
|
|
|
// find the image inserted from the HTML editor
|
|
$image = Image::find($src);
|
|
|
|
if($image) {
|
|
// If we have an image, generate the resized image.
|
|
$resizedImage = $image->getFormattedImage("ResizedImage",$width, $height);
|
|
$parts[$partSource['src="']] = $resizedImage->getRelativePath() ;
|
|
} else {
|
|
$parts[$partSource['src="']] = "";
|
|
}
|
|
|
|
$parts[0] = "";
|
|
$result = implode("", $parts);
|
|
return $result;
|
|
}
|
|
|
|
|
|
class HtmlEditorField_Toolbar extends ViewableData {
|
|
protected $controller, $name;
|
|
|
|
function __construct($controller, $name) {
|
|
$this->controller = $controller;
|
|
$this->name = $name;
|
|
}
|
|
|
|
function Buttons() {
|
|
return new DataObjectSet(
|
|
new HtmlEditorField_button("Bold","bold",_t('HtmlEditorField.BUTTONBOLD', "Bold (Ctrl+B)")),
|
|
new HtmlEditorField_button("Italic","italic",_t('HtmlEditorField.BUTTONITALIC', "Italic (Ctrl+I)")),
|
|
new HtmlEditorField_button("Underline","underline", _t('HtmlEditorField.BUTTONUNDERLINE', "Underline (Ctrl+U)")),
|
|
new HtmlEditorField_button("Strikethrough","strikethrough", _t('HtmlEditorField.BUTTONSTRIKE', "strikethrough")),
|
|
new HtmlEditorField_separator(),
|
|
new HtmlEditorField_button("JustifyLeft","justifyleft", _t('HtmlEditorField.BUTTONALIGNLEFT', "Align left")),
|
|
new HtmlEditorField_button("JustifyCenter","justifycenter", _t('HtmlEditorField.BUTTONALIGNCENTER', "Align center")),
|
|
new HtmlEditorField_button("JustifyRight","justifyright",_t('HtmlEditorField.BUTTONALIGNRIGHT',"Align right")),
|
|
new HtmlEditorField_button("JustifyFull","justifyfull",_t('HtmlEditorField.BUTTONALIGNJUSTIFY',"Justify")),
|
|
|
|
/*new HtmlEditorField_dropdown("mceSetCSSClass", "styleSelect", array(
|
|
"mceContentBody" => "mceContentBody",
|
|
)),*/
|
|
new HtmlEditorField_dropdown("FormatBlock", "formatSelect", array(
|
|
"<p>" => _t('HtmlEditorField.FORMATP', "Paragraph", PR_MEDIUM, '<p> tag'),
|
|
"<address>" => _t('HtmlEditorField.FORMATADDR', "Address", PR_MEDIUM, '<address> tag'),
|
|
"<pre>" => "Preformatted",_t('HtmlEditorField.FORMATADDR', "Address", PR_MEDIUM, '<address> tag'),
|
|
"<h1>" => _t('HtmlEditorField.FORMATH1', "Heading 1", PR_MEDIUM, '<h1> tag'),
|
|
"<h2>" => _t('HtmlEditorField.FORMATH2', "Heading 2", PR_MEDIUM, '<h2> tag'),
|
|
"<h3>" => _t('HtmlEditorField.FORMATH3', "Heading 3", PR_MEDIUM, '<h3> tag'),
|
|
"<h4>" => _t('HtmlEditorField.FORMATH4', "Heading 4", PR_MEDIUM, '<h4> tag'),
|
|
"<h5>" => _t('HtmlEditorField.FORMATH5', "Heading 5", PR_MEDIUM, '<h5> tag'),
|
|
"<h6>" => _t('HtmlEditorField.FORMATH6', "Heading 6", PR_MEDIUM, '<h6> tag'),
|
|
)),
|
|
new HtmlEditorField_separator(),
|
|
new HtmlEditorField_button("InsertUnorderedList","bullist",_t('HtmlEditorField.BULLETLIST', "Bullet-point list")),
|
|
new HtmlEditorField_button("InsertOrderedList","numlist",_t('HtmlEditorField.OL', "Numbered list")),
|
|
new HtmlEditorField_button("Outdent","outdent",_t('HtmlEditorField.OUTDENT', "Decrease outdent")),
|
|
new HtmlEditorField_button("Indent","indent",_t('HtmlEditorField.INDENT', "Increase indent")),
|
|
new HtmlEditorField_button("inserthorizontalrule","hr",_t('HtmlEditorField.HR', "Insert horizontal line")),
|
|
new HtmlEditorField_button("mceCharMap","charmap",_t('HtmlEditorField.CHARMAP', "Insert symbol")),
|
|
|
|
new HtmlEditorField_break(),
|
|
|
|
new HtmlEditorField_button("Undo","undo",_t('HtmlEditorField.UNDO', "Undo (Ctrl+Z)")),
|
|
new HtmlEditorField_button("Redo","redo",_t('HtmlEditorField.REDO', "Redo (Ctrl+Y)")),
|
|
new HtmlEditorField_separator(),
|
|
new HtmlEditorField_button("Cut","cut",_t('HtmlEditorField.CUT', "Cut (Ctrl+X)")),
|
|
new HtmlEditorField_button("Copy","copy",_t('HtmlEditorField.COPY', "Copy (Ctrl+C)")),
|
|
new HtmlEditorField_button("Paste","paste",_t('HtmlEditorField.PASTE', "Paste (Ctrl+V)")),
|
|
new HtmlEditorField_separator(),
|
|
|
|
new HtmlEditorField_button("ssImage","image",_t('HtmlEditorField.IMAGE', "Insert image")),
|
|
new HtmlEditorField_button("ssFlash","flash:flash",_t('HtmlEditorField.FLASH', "Insert flash")),
|
|
|
|
new HtmlEditorField_button("ssLink","link",_t('HtmlEditorField.LINK', "Insert/edit link for highlighted text")),
|
|
new HtmlEditorField_button("unlink","unlink",_t('HtmlEditorField.UNLINK', "Remove link")),
|
|
new HtmlEditorField_button("mceInsertAnchor","anchor",_t('HtmlEditorField.ANCHOR', "Insert/edit anchor")),
|
|
new HtmlEditorField_separator(),
|
|
|
|
new HtmlEditorField_button("mceCodeEditor","code",_t('HtmlEditorField.EDITCODE', "Edit HTML Code")),
|
|
|
|
// We don't need this because tinymce is good at auto-tidying
|
|
// new HtmlEditorField_button("mceCleanup","cleanup","Clean up code"),
|
|
|
|
|
|
new HtmlEditorField_button("mceToggleVisualAid","visualaid",_t('HtmlEditorField.VISUALAID', "Show/hide guidelines")),
|
|
|
|
new HtmlEditorField_separator(),
|
|
|
|
new HtmlEditorField_button("mceInsertTable","table:table",_t('HtmlEditorField.INSERTTABLE', "Insert table")),
|
|
new HtmlEditorField_button("mceTableInsertRowBefore","table:table_insert_row_before",_t('HtmlEditorField.INSERTROWBEF', "Insert row before")),
|
|
new HtmlEditorField_button("mceTableInsertRowAfter","table:table_insert_row_after",_t('HtmlEditorField.INSERTROWAFTER', "Insert row after")),
|
|
new HtmlEditorField_button("mceTableDeleteRow","table:table_delete_row",_t('HtmlEditorField.DELETEROW', "Delete row")),
|
|
new HtmlEditorField_button("mceTableInsertColBefore","table:table_insert_col_before",_t('HtmlEditorField.INSERTCOLBEF', "Insert column before")),
|
|
new HtmlEditorField_button("mceTableInsertColAfter","table:table_insert_col_after",_t('HtmlEditorField.INSERTCOLAFTER', "Insert column after")),
|
|
new HtmlEditorField_button("mceTableDeleteCol","table:table_delete_col",_t('HtmlEditorField.DELETECOL', "Delete column"))
|
|
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the form which the Link button returns.
|
|
* The link functions below are shown and hidden via javascript
|
|
*/
|
|
function LinkForm() {
|
|
$form = new Form(
|
|
$this->controller,
|
|
"{$this->name}.LinkForm",
|
|
new FieldSet(
|
|
new OptionsetField("LinkType", _t('HtmlEditorField.LINKTO', "Link to"),
|
|
array(
|
|
"internal" => _t('HtmlEditorField.LINKINTERNAL',"Page on the site"),
|
|
"external" => _t('HtmlEditorField.LINKEXTERNAL',"Another website"),
|
|
"email" => _t('HtmlEditorField.LINKEMAIL', "Email address"),
|
|
"file" => _t('HtmlEditorField.LINKFILE', "Download a file"),
|
|
)
|
|
),
|
|
new TreeDropdownField("internal", _t('HtmlEditorField.PAGE', "Page"), "SiteTree", "URLSegment"),
|
|
new TextField("external", _t('HtmlEditorField.URL', "URL")),
|
|
new EmailField("email", _t('HtmlEditorField.EMAIL', "Email address")),
|
|
new TreeDropdownField("file",_t('HtmlEditorField.FILE', "File"),"File", "Filename"),
|
|
new TextField("Description", _t('HtmlEditorField.LINKDESCR', "Link description")),
|
|
new CheckboxField("TargetBlank", _t('HtmlEditorField.LINKOPENNEWWIN', "Open link in a new window?"))
|
|
),
|
|
new FieldSet(
|
|
new FormAction("insert", _t('HtmlEditorField.BUTTONINSERTLINK', "Insert link")),
|
|
new FormAction("remove", _t('HtmlEditorField.BUTTONREMOVELINK', "Remove link")),
|
|
new FormAction("cancel", _t('HtmlEditorField.BUTTONCANCEL', "Cancel"))
|
|
)
|
|
);
|
|
$form->loadDataFrom($this);
|
|
return $form;
|
|
}
|
|
|
|
function ImageForm() {
|
|
$form = new Form($this->controller, "{$this->name}.ImageForm",
|
|
new FieldSet(
|
|
new TreeDropdownField("FolderID", _t('HtmlEditorField.FOLDER', "Folder"), "Folder"),
|
|
new ThumbnailStripField("Image", "FolderID", "getimages"),
|
|
new TextField("AltText", _t('HtmlEditorField.ALTTEXT', "Description"), "", 80),
|
|
new DropdownField("CSSClass", _t('HtmlEditorField.CSSCLASS', "Alignment / style"), array(
|
|
"left" => _t('HtmlEditorField.CSSCLASSLEFT', "On the left, with text wrapping around."),
|
|
"right" => _t('HtmlEditorField.CSSCLASSRIGHT', "On the right, with text wrapping around."),
|
|
"center" => _t('HtmlEditorField.CSSCLASSCENTER', "Centred, on its own."),
|
|
)),
|
|
new FieldGroup(_t('HtmlEditorField.IMAGEDIMENSIONS', "Dimensions"),
|
|
new TextField("Width", "", "", 5),
|
|
new TextField("Height", "x", "", 5)
|
|
)
|
|
),
|
|
new FieldSet(
|
|
/*
|
|
new FormAction("insertimage", "Insert image"),
|
|
new FormAction("cancel", "Cancel")
|
|
*/
|
|
)
|
|
);
|
|
$form->loadDataFrom($this);
|
|
return $form;
|
|
}
|
|
|
|
function FlashForm() {
|
|
$form = new Form($this->controller, "{$this->name}.FlashForm",
|
|
new FieldSet(
|
|
new TreeDropdownField("FolderID", _t('HtmlEditorField.FOLDER'), "Folder"),
|
|
new ThumbnailStripField("Flash", "FolderID", "getflash"),
|
|
new TextField('Width', _t('HtmlEditorField.IMAGEWIDTHPX', "Width (px)")),
|
|
new TextField('Height', _t('HtmlEditorField.IMAGEHEIGHTPX', "Height (px)"))
|
|
),
|
|
new FieldSet()
|
|
);
|
|
$form->loadDataFrom($this);
|
|
return $form;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* These controls are used when manually constructing a toolbar, as we do in the CMS
|
|
*/
|
|
class HtmlEditorField_control extends ViewableData {
|
|
protected $command;
|
|
|
|
function Type() { return substr($this->class,strrpos($this->class,'_')+1); }
|
|
function Command() { return $this->command; }
|
|
}
|
|
|
|
class HtmlEditorField_button extends HtmlEditorField_control {
|
|
function __construct($command, $icon, $title = null) {
|
|
$this->title = $title ? $title : $command;
|
|
$this->command = $command;
|
|
$this->icon = $icon;
|
|
parent::__construct();
|
|
}
|
|
function Command() {
|
|
return $this->command;
|
|
}
|
|
function Icon() {
|
|
if(strpos($this->icon,'/') !== false) {
|
|
return $this->icon;
|
|
|
|
} else if(strpos($this->icon,':') !== false) {
|
|
list($plugin,$icon) = explode(':', $this->icon, 2);
|
|
return MCE_ROOT . 'plugins/' . $plugin . '/images/' . $icon . '.gif';
|
|
|
|
} else {
|
|
return MCE_ROOT . 'themes/advanced/images/' . $this->icon . '.gif';
|
|
}
|
|
}
|
|
function Title() {
|
|
return $this->title;
|
|
}
|
|
function IDSegment() {
|
|
return $this->icon;
|
|
}
|
|
}
|
|
class HtmlEditorField_separator extends HtmlEditorField_control {
|
|
}
|
|
class HtmlEditorField_dropdown extends HtmlEditorField_control {
|
|
protected $options, $idSegment;
|
|
|
|
function __construct($command, $idSegment, $options) {
|
|
$this->command = $command;
|
|
$this->options = $options;
|
|
$this->idSegment = $idSegment;
|
|
parent::__construct();
|
|
}
|
|
|
|
function Options() {
|
|
$options = '';
|
|
foreach($this->options as $k => $v) {
|
|
$k = Convert::raw2att($k);
|
|
$v = Convert::raw2xml($v);
|
|
$options .= "<option value=\"$k\">$v</option>\n";
|
|
}
|
|
return $options;
|
|
}
|
|
function IDSegment() {
|
|
return $this->idSegment;
|
|
}
|
|
}
|
|
class HtmlEditorField_break extends HtmlEditorField_control {
|
|
}
|
|
|
|
?>
|