mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
FEATURE: Added SS_HTMLValue to provide a simple wrapper around DOMDocument to allow the manipulation of HTML snippets.
FEATURE: Re-instated broken link highlighting by manually checking all shortcodes in HtmlEditorField->Field(), and adding a class to broken ones. From: Andrew Short <andrewjshort@gmail.com> git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@88510 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
8546a6d194
commit
9b4d9993a4
@ -32,18 +32,32 @@ class HtmlEditorField extends TextareaField {
|
|||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
function Field() {
|
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 (
|
return $this->createTag (
|
||||||
'textarea',
|
'textarea',
|
||||||
array (
|
array (
|
||||||
'class' => $this->extraClass(),
|
'class' => $this->extraClass(),
|
||||||
'rows' => $this->rows,
|
'rows' => $this->rows,
|
||||||
'cols' => $this->cols,
|
'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',
|
'tinymce' => 'true',
|
||||||
'id' => $this->id(),
|
'id' => $this->id(),
|
||||||
'name' => $this->name
|
'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 : '<p></p>';
|
|
||||||
$value = preg_replace('/src="([^\?]*)\?r=[0-9]+"/i', 'src="$1"', $value);
|
|
||||||
|
|
||||||
$linkedPages = array();
|
$linkedPages = array();
|
||||||
$linkedFiles = array();
|
$linkedFiles = array();
|
||||||
|
|
||||||
$document = new DOMDocument(null, 'UTF-8');
|
$htmlValue = new SS_HTMLValue($this->value);
|
||||||
$document->strictErrorChecking = false;
|
|
||||||
$document->loadHTML($value);
|
|
||||||
|
|
||||||
// Populate link tracking for internal links & links to asset files.
|
// Populate link tracking for internal links & links to asset files.
|
||||||
if($links = $document->getElementsByTagName('a')) foreach($links as $link) {
|
if($links = $htmlValue->getElementsByTagName('a')) foreach($links as $link) {
|
||||||
$link = Director::makeRelative($link->getAttribute('href'));
|
$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];
|
$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)) {
|
if($page = DataObject::get_by_id('SiteTree', $ID)) {
|
||||||
$linkedPages[] = $page->ID;
|
$linkedPages[] = $page->ID;
|
||||||
} else {
|
} else {
|
||||||
$record->HasBrokenLink = true;
|
$record->HasBrokenLink = true;
|
||||||
}
|
}
|
||||||
} elseif($link[0] != '/' && $file = File::find($link)) {
|
} elseif($href[0] != '/' && $file = File::find($href)) {
|
||||||
$linkedFiles[] = $file->ID;
|
$linkedFiles[] = $file->ID;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resample images, add default attributes and add to assets tracking.
|
// 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(!$image = File::find($path = Director::makeRelative($img->getAttribute('src')))) {
|
||||||
if(substr($path, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR . '/') {
|
if(substr($path, 0, strlen(ASSETS_DIR) + 1) == ASSETS_DIR . '/') {
|
||||||
$record->HasBrokenFile = true;
|
$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();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
78
integration/HTMLValue.php
Executable file
78
integration/HTMLValue.php
Executable file
@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* This class acts as a wrapper around the built in DOMDocument class in order to use it to manage a HTML snippet,
|
||||||
|
* rather than a whole document, while still exposing the DOMDocument API.
|
||||||
|
*
|
||||||
|
* @package sapphire
|
||||||
|
* @subpackage integration
|
||||||
|
*/
|
||||||
|
class SS_HTMLValue extends ViewableData {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var DOMDocument
|
||||||
|
*/
|
||||||
|
protected $document;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $content
|
||||||
|
*/
|
||||||
|
public function __construct($content = null) {
|
||||||
|
$this->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*<body[^>]*>/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(
|
||||||
|
'<html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"></head>' .
|
||||||
|
"<body>$content</body></html>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
6
tests/forms/HtmlEditorFieldTest.php
Normal file → Executable file
6
tests/forms/HtmlEditorFieldTest.php
Normal file → Executable file
@ -13,10 +13,6 @@ class HtmlEditorFieldTest extends FunctionalTest {
|
|||||||
$sitetree = new SiteTree();
|
$sitetree = new SiteTree();
|
||||||
$editor = new HtmlEditorField('Content');
|
$editor = new HtmlEditorField('Content');
|
||||||
|
|
||||||
$editor->setValue('Un-enclosed Content');
|
|
||||||
$editor->saveInto($sitetree);
|
|
||||||
$this->assertEquals('<p>Un-enclosed Content</p>', $sitetree->Content, 'Un-enclosed content is put in p tags.');
|
|
||||||
|
|
||||||
$editor->setValue('<p class="foo">Simple Content</p>');
|
$editor->setValue('<p class="foo">Simple Content</p>');
|
||||||
$editor->saveInto($sitetree);
|
$editor->saveInto($sitetree);
|
||||||
$this->assertEquals('<p class="foo">Simple Content</p>', $sitetree->Content, 'Attributes are preserved.');
|
$this->assertEquals('<p class="foo">Simple Content</p>', $sitetree->Content, 'Attributes are preserved.');
|
||||||
@ -32,7 +28,7 @@ class HtmlEditorFieldTest extends FunctionalTest {
|
|||||||
|
|
||||||
$editor->setValue(null);
|
$editor->setValue(null);
|
||||||
$editor->saveInto($sitetree);
|
$editor->saveInto($sitetree);
|
||||||
$this->assertEquals('<p/>', $sitetree->Content, 'Doesn\'t choke on null values.');
|
$this->assertEquals('', $sitetree->Content, "Doesn't choke on empty/null values.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLinkTracking() {
|
public function testLinkTracking() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user