diff --git a/forms/HtmlEditorField.php b/forms/HtmlEditorField.php index 369a3a193..c4994efa1 100755 --- a/forms/HtmlEditorField.php +++ b/forms/HtmlEditorField.php @@ -32,18 +32,32 @@ class HtmlEditorField extends TextareaField { * @return string */ function Field() { + // 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')); + } + } + } + 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 + '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') + htmlentities($value->getContent(), ENT_COMPAT, 'UTF-8') ); } @@ -54,35 +68,38 @@ class HtmlEditorField extends TextareaField { ); } - $value = $this->value ? $this->value : '

'; - $value = preg_replace('/src="([^\?]*)\?r=[0-9]+"/i', 'src="$1"', $value); - $linkedPages = array(); $linkedFiles = array(); - $document = new DOMDocument(null, 'UTF-8'); - $document->strictErrorChecking = false; - $document->loadHTML($value); + $htmlValue = new SS_HTMLValue($this->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($links = $htmlValue->getElementsByTagName('a')) foreach($links as $link) { + $href = Director::makeRelative($link->getAttribute('href')); - if(preg_match('/\[sitetree_link id=([0-9]+)\]/i', $link, $matches)) { + 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)); + } + if($page = DataObject::get_by_id('SiteTree', $ID)) { $linkedPages[] = $page->ID; } else { $record->HasBrokenLink = true; } - } elseif($link[0] != '/' && $file = File::find($link)) { + } elseif($href[0] != '/' && $file = File::find($href)) { $linkedFiles[] = $file->ID; } } // Resample images, add default attributes and add to assets tracking. - if($images = $document->getElementsByTagName('img')) foreach($images as $img) { + 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'))); + if(!$image = File::find($path = Director::makeRelative($img->getAttribute('src')))) { if(substr($path, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR . '/') { $record->HasBrokenFile = true; @@ -124,7 +141,7 @@ class HtmlEditorField extends TextareaField { } } - $record->{$this->name} = substr(simplexml_import_dom($document)->body->asXML(), 6, -7); + $record->{$this->name} = $htmlValue->getContent(); } /** diff --git a/integration/HTMLValue.php b/integration/HTMLValue.php new file mode 100755 index 000000000..bc26c66d4 --- /dev/null +++ b/integration/HTMLValue.php @@ -0,0 +1,78 @@ +document = new DOMDocument('1.0', 'UTF-8'); + $this->document->scrictErrorChecking = false; + + $this->setContent($content); + + parent::__construct(); + } + + /** + * @return string + */ + public function getContent() { + // strip the body tags from the output (which are automatically added by DOMDocument) + return preg_replace ( + array ( + '/^\s*]*>/i', + '/<\/body[^>]*>\s*$/i' + ), + null, + $this->getDocument()->saveXML($this->getDocument()->documentElement->lastChild) + ); + } + + /** + * @param string $content + * @return bool + */ + public function setContent($content) { + return $this->getDocument()->loadHTML( + '' . + "$content" + ); + } + + /** + * @return DOMDocument + */ + public function getDocument() { + return $this->document; + } + + /** + * A simple convenience wrapper around DOMDocument::getElementsByTagName(). + * + * @param string $name + * @return DOMNodeList + */ + public function getElementsByTagName($name) { + return $this->getDocument()->getElementsByTagName($name); + } + + /** + * @see HTMLValue::getContent() + */ + public function forTemplate() { + return $this->getContent(); + } + +} \ No newline at end of file diff --git a/tests/forms/HtmlEditorFieldTest.php b/tests/forms/HtmlEditorFieldTest.php old mode 100644 new mode 100755 index 7b73952f3..ce95b4b3d --- a/tests/forms/HtmlEditorFieldTest.php +++ b/tests/forms/HtmlEditorFieldTest.php @@ -13,10 +13,6 @@ class HtmlEditorFieldTest extends FunctionalTest { $sitetree = new SiteTree(); $editor = new HtmlEditorField('Content'); - $editor->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.'); @@ -32,7 +28,7 @@ class HtmlEditorFieldTest extends FunctionalTest { $editor->setValue(null); $editor->saveInto($sitetree); - $this->assertEquals('

', $sitetree->Content, 'Doesn\'t choke on null values.'); + $this->assertEquals('', $sitetree->Content, "Doesn't choke on empty/null values."); } public function testLinkTracking() {