]*)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 "";
- }
-
- /**
- * This function has been created to explicit the functionnality.
- */
- function setCSSClass($class){
- $this->extraClass = $class;
+ return $this->createTag (
+ 'textarea',
+ array (
+ 'class' => $this->extraClass(),
+ 'rows' => $this->rows,
+ 'cols' => $this->cols,
+ 'style' => "width: 97%; height: " . ($this->rows * 16) . "px", // Prevents horizontal scrollbars
+ 'tinymce' => 'true',
+ 'id' => $this->id(),
+ 'name' => $this->name
+ ),
+ htmlentities($this->value, ENT_COMPAT, 'UTF-8')
+ );
}
- function saveInto($record) {
+ public function saveInto($record) {
if($record->escapeTypeForField($this->name) != 'xml') {
- user_error("HTMLEditorField should save into an HTMLText or HTMLVarchar field.
- If you don't, your template won't display properly.
- This changed in version 2.2.2, so please update
- your database field '$this->name'",
- E_USER_WARNING
+ throw new Exception (
+ 'HtmlEditorField->saveInto(): This field should save into a HTMLText or HTMLVarchar field.'
);
}
- $content = $this->value;
+ $value = $this->value ? $this->value : '
';
+ $value = preg_replace('/src="([^\?]*)\?r=[0-9]+"/i', 'src="$1"', $value);
- $content = preg_replace('/mce_real_src="[^"]+"/i', "", $content);
-
- $content = eregi_replace('( ]* )width=([0-9]+)( [^>]*>|>)','\\1width="\\2"\\3', $content);
- $content = eregi_replace('( ]* )height=([0-9]+)( [^>]*>|>)','\\1height="\\2"\\3', $content);
- $content = eregi_replace('src="([^\?]*)\?r=[0-9]+"','src="\\1"', $content);
- $content = eregi_replace('mce_src="([^\?]*)\?r=[0-9]+"','mce_src="\\1"', $content);
-
- $content = preg_replace_callback('/( ]* )(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 = "$content
";
-
- $links = HTTP::getLinksIn($content);
$linkedPages = array();
+ $linkedFiles = array();
- if($links) foreach($links as $link) {
- $link = Director::makeRelative($link);
+ $document = new DOMDocument(null, 'UTF-8');
+ $document->strictErrorChecking = false;
+ $document->loadHTML($value);
+
+ // Populate link tracking for internal links & links to asset files.
+ if($links = $document->getElementsByTagName('a')) foreach($links as $link) {
+ $link = Director::makeRelative($link->getAttribute('href'));
- 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 == '' || $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;
- }
- }
- }
+ if(preg_match('/\[sitetree_link id=([0-9]+)\]/i', $link, $matches)) {
+ $ID = $matches[1];
- $fieldName = $this->name;
- if($record->ID && $record->hasMethod('LinkTracking') && $linkTracking = $record->LinkTracking()) {
- $linkTracking->removeByFilter("\"FieldName\" = '$fieldName' AND \"SiteTreeID\" = $record->ID");
-
- if(isset($linkedPages)) foreach($linkedPages as $item) {
- $linkTracking->add($item, array("FieldName" => $fieldName));
+ if($page = DataObject::get_by_id('SiteTree', $ID)) {
+ $linkedPages[] = $page->ID;
+ } else {
+ $record->HasBrokenLink = true;
+ }
+ } elseif($link[0] != '/' && $file = File::find($link)) {
+ $linkedFiles[] = $file->ID;
}
-
- // $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();
}
- // Sometimes clients will double-escape %20. Fix this up with this dirty hack
- $content = str_replace('%2520', '%20', $content);
+ // Resample images, add default attributes and add to assets tracking.
+ if($images = $document->getElementsByTagName('img')) foreach($images as $img) {
+ if(!$image = File::find($path = Director::makeRelative($img->getAttribute('src')))) {
+ if(substr($path, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR . '/') {
+ $record->HasBrokenFile = true;
+ }
+
+ continue;
+ }
- $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);
+ // Resample the images if the width & height have changed.
+ $width = $img->getAttribute('width');
+ $height = $img->getAttribute('height');
+
+ if($width && $height && ($width != $image->getWidth() || $height != $image->getHeight())) {
+ $img->setAttribute('src', $image->ResizedImage($width, $height)->getRelativePath());
+ }
+
+ // Add default empty title & alt attributes.
+ if(!$img->getAttribute('alt')) $img->setAttribute('alt', '');
+ if(!$img->getAttribute('title')) $img->setAttribute('title', '');
+
+ // Add to the tracked files.
+ $linkedFiles[] = $image->ID;
}
- $this->value = ereg_replace("(href=\"?)$base$old","\\1$new", $this->value);
- return $this->value;
+ // Save file & link tracking data.
+ if($record->ID && $record->many_many('LinkTracking') && $tracker = $record->LinkTracking()) {
+ $tracker->removeByFilter(sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', $this->name, $record->ID));
+
+ if($linkedPages) foreach($linkedPages as $item) {
+ $tracker->add($item, array('FieldName' => $this->name));
+ }
+ }
+
+ if($record->ID && $record->many_many('ImageTracking') && $tracker = $record->ImageTracking()) {
+ $tracker->removeByFilter(sprintf('"FieldName" = \'%s\' AND "SiteTreeID" = %d', $this->name, $record->ID));
+
+ if($linkedFiles) foreach($linkedFiles as $item) {
+ $tracker->add($item, array('FieldName' => $this->name));
+ }
+ }
+
+ $record->{$this->name} = substr(simplexml_import_dom($document)->body->asXML(), 6, -7);
}
- function performReadonlyTransformation() {
- $field = new HtmlEditorField_readonly($this->name, $this->title, $this->value);
+ /**
+ * @return HtmlEditorField_Readonly
+ */
+ public function performReadonlyTransformation() {
+ $field = new HtmlEditorField_Readonly($this->name, $this->title, $this->value);
$field->setForm($this->form);
$field->dontEscape = true;
return $field;
}
+
}
/**
@@ -205,7 +144,7 @@ class HtmlEditorField extends TextareaField {
* @package forms
* @subpackage fields-formattedinput
*/
-class HtmlEditorField_readonly extends ReadonlyField {
+class HtmlEditorField_Readonly extends ReadonlyField {
function Field() {
$valforInput = $this->value ? Convert::raw2att($this->value) : "";
return "id() . "\">" . ( $this->value && $this->value != '
' ? $this->value : '(not set) ' ) . " name."\" value=\"".$valforInput."\" />";
@@ -215,54 +154,6 @@ class HtmlEditorField_readonly extends ReadonlyField {
}
}
-/**
- * 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(urldecode($src));
-
- // If we have an image, insert the resampled one into the src attribute; otherwise, leave the img src alone.
- if($image && ($image instanceof Image) && ($image->getWidth() != $width) && ($image->getHeight() != $height)) {
- // If we have an image, generate the resized image.
- $resizedImage = $image->getFormattedImage('ResizedImage', $width, $height);
- if($resizedImage) $parts[$partSource['src="']] = $resizedImage->getRelativePath();
- }
-
- $parts[0] = "";
- $result = implode("", $parts);
-
- // Insert an empty alt tag if there isn't one
- if(strpos($result, "alt=") === false) {
- $result = substr_replace($result, ' alt="" />', -3);
- }
-
- // Insert an empty title tag if there isn't one (IE shows the alt as title if no title tag)
- if(strpos($result, "title=") === false) {
- $result = substr_replace($result, ' title="" />', -3);
- }
-
- return $result;
-}
-
/**
* External toolbar for the HtmlEditorField.
* This is used by the CMS
diff --git a/tests/forms/HtmlEditorFieldTest.php b/tests/forms/HtmlEditorFieldTest.php
new file mode 100644
index 000000000..7b73952f3
--- /dev/null
+++ b/tests/forms/HtmlEditorFieldTest.php
@@ -0,0 +1,127 @@
+setValue('Un-enclosed Content');
+ $editor->saveInto($sitetree);
+ $this->assertEquals('Un-enclosed Content
', $sitetree->Content, 'Un-enclosed content is put in p tags.');
+
+ $editor->setValue('Simple Content
');
+ $editor->saveInto($sitetree);
+ $this->assertEquals('Simple Content
', $sitetree->Content, 'Attributes are preserved.');
+
+ $editor->setValue('Unclosed Tag');
+ $editor->saveInto($sitetree);
+ $this->assertEquals('
Unclosed Tag
', $sitetree->Content, 'Unclosed tags are closed.');
+ }
+
+ public function testNullSaving() {
+ $sitetree = new SiteTree();
+ $editor = new HtmlEditorField('Content');
+
+ $editor->setValue(null);
+ $editor->saveInto($sitetree);
+ $this->assertEquals('
', $sitetree->Content, 'Doesn\'t choke on null values.');
+ }
+
+ public function testLinkTracking() {
+ $sitetree = $this->objFromFixture('SiteTree', 'home');
+ $editor = new HtmlEditorField('Content');
+
+ $aboutID = $this->idFromFixture('SiteTree', 'about');
+ $contactID = $this->idFromFixture('SiteTree', 'contact');
+
+ $editor->setValue("