API / BUG Fix DBField summary methods

Cleanup DBField subclasses
Fixes #2929
Fixes #1381
Fixes #5547
Fixes #1751
This commit is contained in:
Damian Mooyman 2016-06-17 18:49:23 +12:00
parent 5c9044a007
commit b7ac5c564d
28 changed files with 840 additions and 604 deletions

View File

@ -36,10 +36,10 @@ class DBDate extends DBField {
/**
* @config
* @see DBDateTime::nice_format
* @see Time::nice_format
* @see DBTime::nice_format
*/
private static $nice_format = 'd/m/Y';
public function setValue($value, $record = null, $markChanged = true) {
if($value === false || $value === null || (is_string($value) && !strlen($value))) {
// don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be
@ -80,44 +80,56 @@ class DBDate extends DBField {
/**
* Returns the date in the format specified by the config value nice_format, or dd/mm/yy by default
*/
*
* @return string
*/
public function Nice() {
if($this->value) return $this->Format($this->config()->nice_format);
return $this->Format($this->config()->nice_format);
}
/**
* Returns the date in US format: “01/18/2006
*
* @return string
*/
public function NiceUS() {
if($this->value) return $this->Format('m/d/Y');
return $this->Format('m/d/Y');
}
/**
* Returns the year from the given date
*
* @return string
*/
public function Year() {
if($this->value) return $this->Format('Y');
return $this->Format('Y');
}
/**
* Returns the Full day, of the given date.
*
* @return string
*/
public function Day(){
if($this->value) return $this->Format('l');
return $this->Format('l');
}
/**
* Returns a full textual representation of a month, such as January.
*
* @return string
*/
public function Month() {
if($this->value) return $this->Format('F');
return $this->Format('F');
}
/**
* Returns the short version of the month such as Jan
*
* @return string
*/
public function ShortMonth() {
if($this->value) return $this->Format('M');
return $this->Format('M');
}
/**
@ -126,25 +138,27 @@ class DBDate extends DBField {
* @return string
*/
public function DayOfMonth($includeOrdinal = false) {
if($this->value) {
$format = 'j';
if ($includeOrdinal) $format .= 'S';
return $this->Format($format);
}
}
/**
* Returns the date in the format 24 December 2006
*
* @return string
*/
public function Long() {
if($this->value) return $this->Format('j F Y');
return $this->Format('j F Y');
}
/**
* Returns the date in the format 24 Dec 2006
*
* @return string
*/
public function Full() {
if($this->value) return $this->Format('j M Y');
return $this->Format('j M Y');
}
/**
@ -158,6 +172,7 @@ class DBDate extends DBField {
$date = new DateTime($this->value);
return $date->format($format);
}
return null;
}
/**
@ -173,6 +188,7 @@ class DBDate extends DBField {
if($this->value) {
return strftime($formattingString, strtotime($this->value));
}
return null;
}
/**
@ -217,12 +233,28 @@ class DBDate extends DBField {
else return "$d1 - $d2 $m1 $y1";
}
/**
* Return string in RFC822 format
*
* @return string
*/
public function Rfc822() {
if($this->value) return date('r', strtotime($this->value));
if($this->value) {
return date('r', strtotime($this->value));
}
return null;
}
/**
* Return date in RFC2822 format
*
* @return string
*/
public function Rfc2822() {
if($this->value) return date('Y-m-d H:i:s', strtotime($this->value));
if($this->value) {
return date('Y-m-d H:i:s', strtotime($this->value));
}
return null;
}
public function Rfc3339() {
@ -249,7 +281,9 @@ class DBDate extends DBField {
* @return String
*/
public function Ago($includeSeconds = true, $significance = 2) {
if($this->value) {
if(!$this->value) {
return null;
}
$time = DBDatetime::now()->Format('U');
if(strtotime($this->value) == $time || $time > strtotime($this->value)) {
return _t(
@ -267,7 +301,6 @@ class DBDate extends DBField {
);
}
}
}
/**
* @param boolean $includeSeconds Show seconds, or just round to "less than a minute".
@ -304,7 +337,9 @@ class DBDate extends DBField {
* @return string The resulting formatted period
*/
public function TimeDiffIn($format) {
if(!$this->value) return false;
if(!$this->value) {
return null;
}
$time = DBDatetime::now()->Format('U');
$ago = abs($time - strtotime($this->value));
@ -333,6 +368,9 @@ class DBDate extends DBField {
case "years":
$span = round($ago/86400/365);
return ($span != 1) ? "{$span} "._t("Date.YEARS", "years") : "{$span} "._t("Date.YEAR", "year");
default:
throw new \InvalidArgumentException("Invalid format $format");
}
}

View File

@ -5,6 +5,7 @@ namespace SilverStripe\ORM\FieldType;
use Convert;
use Exception;
use DatetimeField;
use InvalidArgumentException;
use Zend_Date;
use TemplateGlobalProvider;
use DateTime;
@ -75,42 +76,47 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider {
/**
* Returns the date and time in the format specified by the config value nice_format, or 'd/m/Y g:ia'
* by default (e.g. '31/01/2014 2:23pm').
*
* @return string Formatted date and time.
*/
public function Nice() {
if($this->value) return $this->Format($this->config()->nice_format);
return $this->Format($this->config()->nice_format);
}
/**
* Returns the date and time (in 24-hour format) using the format string 'd/m/Y H:i' e.g. '28/02/2014 13:32'.
*
* @return string Formatted date and time.
*/
public function Nice24() {
if($this->value) return $this->Format('d/m/Y H:i');
return $this->Format('d/m/Y H:i');
}
/**
* Returns the date using the format string 'd/m/Y' e.g. '28/02/2014'.
*
* @return string Formatted date.
*/
public function Date() {
if($this->value) return $this->Format('d/m/Y');
return $this->Format('d/m/Y');
}
/**
* Returns the time in 12-hour format using the format string 'g:ia' e.g. '1:32pm'.
*
* @return string Formatted time.
*/
public function Time() {
if($this->value) return $this->Format('g:ia');
return $this->Format('g:ia');
}
/**
* Returns the time in 24-hour format using the format string 'H:i' e.g. '13:32'.
*
* @return string Formatted time.
*/
public function Time24() {
if($this->value) return $this->Format('H:i');
return $this->Format('H:i');
}
/**
@ -149,7 +155,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider {
* @return string Formatted date and time.
*/
public function URLDatetime() {
if($this->value) return $this->Format('Y-m-d%20H:i:s');
return $this->Format('Y-m-d%20H:i:s');
}
public function scaffoldFormField($title = null, $params = null) {
@ -187,7 +193,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider {
if(self::$mock_now) {
return self::$mock_now;
} else {
return DBField::create_field('SilverStripe\ORM\FieldType\DBDatetime', date('Y-m-d H:i:s'));
return DBField::create_field('Datetime', date('Y-m-d H:i:s'));
}
}
@ -203,9 +209,9 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider {
if($datetime instanceof DBDatetime) {
self::$mock_now = $datetime;
} elseif(is_string($datetime)) {
self::$mock_now = DBField::create_field('SilverStripe\ORM\FieldType\DBDatetime', $datetime);
self::$mock_now = DBField::create_field('Datetime', $datetime);
} else {
throw new Exception('DBDatetime::set_mock_now(): Wrong format: ' . $datetime);
throw new InvalidArgumentException('DBDatetime::set_mock_now(): Wrong format: ' . $datetime);
}
}
@ -219,7 +225,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider {
public static function get_template_global_variables() {
return array(
'Now' => array('method' => 'now', 'casting' => 'SilverStripe\ORM\FieldType\DBDatetime'),
'Now' => array('method' => 'now', 'casting' => 'Datetime'),
);
}
}

View File

@ -51,10 +51,8 @@ class DBEnum extends DBString {
* </code>
*
* @param string $name
* @param string|array $enum A string containing a comma separated list of options or an
* array of Vals.
* @param string $default The default option, which is either NULL or one of the
* items in the enumeration.
* @param string|array $enum A string containing a comma separated list of options or an array of Vals.
* @param string $default The default option, which is either NULL or one of the items in the enumeration.
*/
public function __construct($name = null, $enum = NULL, $default = NULL) {
if($enum) {
@ -107,10 +105,14 @@ class DBEnum extends DBString {
/**
* Return a dropdown field suitable for editing this field.
*
* @param string $title
* @param string $name
* @param bool $hasEmpty
* @param string $value
* @param string $emptyString
* @return DropdownField
*/
public function formField($title = null, $name = null, $hasEmpty = false, $value = "", $form = null,
$emptyString = null) {
public function formField($title = null, $name = null, $hasEmpty = false, $value = "", $emptyString = null) {
if(!$title) {
$title = $this->getName();
@ -119,7 +121,7 @@ class DBEnum extends DBString {
$name = $this->getName();
}
$field = new DropdownField($name, $title, $this->enumValues(false), $value, $form);
$field = new DropdownField($name, $title, $this->enumValues(false), $value);
if($hasEmpty) {
$field->setEmptyString($emptyString);
}
@ -127,10 +129,7 @@ class DBEnum extends DBString {
return $field;
}
/**
* @return DropdownField
*/
public function scaffoldFormField($title = null, $params = null) {
public function scaffoldFormField($title = null) {
return $this->formField($title);
}
@ -141,7 +140,7 @@ class DBEnum extends DBString {
*/
public function scaffoldSearchField($title = null) {
$anyText = _t('Enum.ANY', 'Any');
return $this->formField($title, null, true, $anyText, null, "($anyText)");
return $this->formField($title, null, true, $anyText, "($anyText)");
}
/**

View File

@ -4,8 +4,8 @@ namespace SilverStripe\ORM\FieldType;
use FormField;
use SearchFilter;
use SilverStripe\ORM\Connect\SS_Query;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Queries\SQLSelect;
use ViewableData;
use Convert;
use Object;
@ -256,7 +256,7 @@ abstract class DBField extends ViewableData {
* gets you the default representations
* of all columns.
*
* @param SS_Query $query
* @param SQLSelect $query
*/
public function addToQuery(&$query) {
@ -288,7 +288,8 @@ abstract class DBField extends ViewableData {
* @return string
*/
public function forTemplate() {
return Convert::raw2xml($this->getValue());
// Default to XML encoding
return $this->XML();
}
/**
@ -369,7 +370,7 @@ abstract class DBField extends ViewableData {
*
* @return string
*/
public function XML(){
public function XML() {
return Convert::raw2xml($this->RAW());
}
@ -379,7 +380,7 @@ abstract class DBField extends ViewableData {
* @return string
*/
public function CDATA() {
return $this->forTemplate();
return $this->XML();
}
/**
@ -402,7 +403,7 @@ abstract class DBField extends ViewableData {
if(empty($fieldName)) {
throw new \BadMethodCallException("DBField::saveInto() Called on a nameless '" . get_class($this) . "' object");
}
$dataObject->$fieldName = $this->value;
$dataObject->$fieldName = $this->value;
}
/**
@ -440,7 +441,6 @@ abstract class DBField extends ViewableData {
* search filters (note: parameter hack now in place to pass in the required full path - using $this->name
* won't work)
*
* @param string|bool $name
* @param string $name Override name of this field
* @return SearchFilter
*/

View File

@ -4,11 +4,9 @@ namespace SilverStripe\ORM\FieldType;
use Injector;
use HTTP;
use ShortcodeParser;
use DOMDocument;
use HTMLEditorField;
use ShortcodeParser;
use TextField;
use Exception;
/**
* Represents a large text field that contains HTML content.
@ -32,20 +30,10 @@ class DBHTMLText extends DBText {
private static $casting = array(
"AbsoluteLinks" => "HTMLFragment",
// DBText summary methods - override to HTMLFragment
"BigSummary" => "HTMLFragment",
"ContextSummary" => "HTMLFragment", // Same as DBText
"FirstParagraph" => "HTMLFragment",
"FirstSentence" => "HTMLFragment",
"LimitSentences" => "HTMLFragment",
"Summary" => "HTMLFragment",
// DBString conversion / summary methods - override to HTMLFragment
"LimitCharacters" => "HTMLFragment",
"LimitCharactersToClosestWord" => "HTMLFragment",
"LimitWordCount" => "HTMLFragment",
// DBString conversion / summary methods
// Not overridden, but returns HTML instead of plain text.
"LowerCase" => "HTMLFragment",
"UpperCase" => "HTMLFragment",
"NoHTML" => "Text", // Actually stays same as DBString cast
);
/**
@ -134,141 +122,6 @@ class DBHTMLText extends DBText {
return parent::setOptions($options);
}
public function LimitSentences($maxSentences = 2)
{
// @todo
return parent::LimitSentences($maxSentences);
}
public function LimitWordCount($numWords = 26, $add = '...')
{
// @todo
return parent::LimitWordCount($numWords, $add);
}
public function LimitCharacters($limit = 20, $add = '...')
{
// @todo
return parent::LimitCharacters($limit, $add);
}
public function LimitCharactersToClosestWord($limit = 20, $add = '...')
{
// @todo
return parent::LimitCharactersToClosestWord($limit, $add);
}
public function BigSummary($maxWords = 50)
{
// @todo
return parent::BigSummary($maxWords); // TODO: Change the autogenerated stub
}
/**
* Create a summary of the content. This will be some section of the first paragraph, limited by
* $maxWords. All internal tags are stripped out - the return value is a string
*
* This is sort of the HTML aware equivilent to Text#Summary, although the logic for summarising is not exactly
* the same
*
* @param int $maxWords Maximum number of words to return - may return less, but never more. Pass -1 for no limit
* @param int $flex Number of words to search through when looking for a nice cut point
* @param string $add What to add to the end of the summary if we cut at a less-than-ideal cut point
* @return string A nice(ish) summary with no html tags (but possibly still some html entities)
*
* @see framework/core/model/fieldtypes/Text#Summary($maxWords)
*/
public function Summary($maxWords = 50, $flex = 15, $add = '...') {
$str = false;
/* First we need the text of the first paragraph, without tags. Try using SimpleXML first */
if (class_exists('SimpleXMLElement')) {
$doc = new DOMDocument();
// Catch warnings thrown by loadHTML and turn them into a failure boolean rather than a SilverStripe error
set_error_handler(create_function('$no, $str', 'throw new Exception("HTML Parse Error: ".$str);'), E_ALL);
// Nonbreaking spaces get converted into weird characters, so strip them
$value = str_replace('&nbsp;', ' ', $this->RAW());
try {
$res = $doc->loadHTML('<meta content="text/html; charset=utf-8" http-equiv="Content-type"/>' . $value);
}
catch (Exception $e) { $res = false; }
restore_error_handler();
if ($res) {
$xml = simplexml_import_dom($doc);
$res = $xml->xpath('//p');
if (!empty($res)) $str = strip_tags($res[0]->asXML());
}
}
/* If that failed, most likely the passed HTML is broken. use a simple regex + a custom more brutal strip_tags.
* We don't use strip_tags because that does very badly on broken HTML */
if (!$str) {
/* See if we can pull a paragraph out*/
// Strip out any images in case there's one at the beginning. Not doing this will return a blank paragraph
$str = preg_replace('{^\s*(<.+?>)*<img[^>]*>}', '', $this->value);
if (preg_match('{<p(\s[^<>]*)?>(.*[A-Za-z]+.*)</p>}', $str, $matches)) $str = $matches[2];
/* If _that_ failed, just use the whole text */
if (!$str) $str = $this->value;
/* Now pull out all the html-alike stuff */
/* Take out anything that is obviously a tag */
$str = preg_replace('{</?[a-zA-Z]+[^<>]*>}', '', $str);
/* Strip out any left over looking bits. Textual < or > should already be encoded to &lt; or &gt; */
$str = preg_replace('{</|<|>}', '', $str);
}
/* Now split into words. If we are under the maxWords limit, just return the whole string (re-implode for
* whitespace normalization) */
$words = preg_split('/\s+/', $str);
if ($maxWords == -1 || count($words) <= $maxWords) return implode(' ', $words);
/* Otherwise work backwards for a looking for a sentence ending (we try to avoid abbreviations, but aren't
* very good at it) */
for ($i = $maxWords; $i >= $maxWords - $flex && $i >= 0; $i--) {
if (preg_match('/\.$/', $words[$i]) && !preg_match('/(Dr|Mr|Mrs|Ms|Miss|Sr|Jr|No)\.$/i', $words[$i])) {
return implode(' ', array_slice($words, 0, $i+1));
}
}
// If we didn't find a sentence ending quickly enough, just cut at the maxWords point and add '...' to the end
return implode(' ', array_slice($words, 0, $maxWords)) . $add;
}
public function FirstParagraph() {
// @todo implement
return parent::FirstParagraph();
}
/**
* Returns the first sentence from the first paragraph. If it can't figure out what the first paragraph is (or
* there isn't one), it returns the same as Summary()
*
* This is the HTML aware equivilent to Text#FirstSentence
*
* @see framework/core/model/fieldtypes/Text#FirstSentence()
*/
public function FirstSentence() {
/* Use summary's html processing logic to get the first paragraph */
$paragraph = $this->Summary(-1);
/* Then look for the first sentence ending. We could probably use a nice regex, but for now this will do */
$words = preg_split('/\s+/', $paragraph);
foreach ($words as $i => $word) {
if (preg_match('/(!|\?|\.)$/', $word) && !preg_match('/(Dr|Mr|Mrs|Ms|Miss|Sr|Jr|No)\.$/i', $word)) {
return implode(' ', array_slice($words, 0, $i+1));
}
}
/* If we didn't find a sentence ending, use the summary. We re-call rather than using paragraph so that
* Summary will limit the result this time */
return $this->Summary();
}
public function RAW() {
if ($this->processShortcodes) {
return ShortcodeParser::get_active()->parse($this->value);
@ -287,6 +140,7 @@ class DBHTMLText extends DBText {
}
public function forTemplate() {
// Suppress XML encoding for DBHtmlText
return $this->RAW();
}
@ -364,23 +218,34 @@ class DBHTMLText extends DBText {
return true;
}
public function scaffoldFormField($title = null, $params = null) {
public function scaffoldFormField($title = null) {
return new HTMLEditorField($this->name, $title);
}
public function scaffoldSearchField($title = null, $params = null) {
public function scaffoldSearchField($title = null) {
return new TextField($this->name, $title);
}
/**
* Get plain-text version
*
* @return string
*/
public function NoHTML()
{
public function Plain() {
// Preserve line breaks
$text = preg_replace('/\<br(\s*)?\/?\>/i', "\n", $this->RAW());
// Convert back to plain text
return \Convert::xml2raw(strip_tags($text));
// Convert paragraph breaks to multi-lines
$text = preg_replace('/\<\/p\>/i', "\n\n", $text);
// Strip out HTML tags
$text = strip_tags($text);
// Implode >3 consecutive linebreaks into 2
$text = preg_replace('~(\R){2,}~', "\n\n", $text);
// Decode HTML entities back to plain text
return trim(\Convert::xml2raw($text));
}
}

View File

@ -17,6 +17,13 @@ class DBHTMLVarchar extends DBVarchar {
private static $escape_type = 'xml';
private static $casting = array(
// DBString conversion / summary methods
// Not overridden, but returns HTML instead of plain text.
"LowerCase" => "HTMLFragment",
"UpperCase" => "HTMLFragment",
);
/**
* Enable shortcode parsing on this field
*
@ -69,6 +76,7 @@ class DBHTMLVarchar extends DBVarchar {
}
public function forTemplate() {
// Suppress XML encoding for DBHtmlText
return $this->RAW();
}
@ -88,15 +96,53 @@ class DBHTMLVarchar extends DBVarchar {
public function CDATA() {
return sprintf(
'<![CDATA[%s]]>',
str_replace(']]>', ']]]]><![CDATA[>', $this->forTemplate())
str_replace(']]>', ']]]]><![CDATA[>', $this->RAW())
);
}
public function exists() {
return parent::exists() && $this->RAW() != '<p></p>';
/**
* Get plain-text version.
*
* Note: unlike DBHTMLText, this doesn't respect line breaks / paragraphs
*
* @return string
*/
public function Plain() {
// Strip out HTML
$text = strip_tags($this->RAW());
// Convert back to plain text
return trim(\Convert::xml2raw($text));
}
public function scaffoldFormField($title = null, $params = null) {
/**
* Returns true if the field has meaningful content.
* Excludes null content like <h1></h1>, <p></p> ,etc
*
* @return boolean
*/
public function exists() {
// If it's blank, it's blank
if(!parent::exists()) {
return false;
}
// If it's got a content tag
if(preg_match('/<(img|embed|object|iframe|meta|source|link)[^>]*>/i', $this->RAW())) {
return true;
}
// If it's just one or two tags on its own (and not the above) it's empty.
// This might be <p></p> or <h1></h1> or whatever.
if(preg_match('/^[\\s]*(<[^>]+>[\\s]*){1,2}$/', $this->RAW())) {
return false;
}
// Otherwise its content is genuine content
return true;
}
public function scaffoldFormField($title = null) {
return new HTMLEditorField($this->name, $title, 1);
}

View File

@ -63,6 +63,7 @@ class DBMoney extends DBComposite {
}
/**
* @param array $options
* @return string
*/
public function Nice($options = array()) {

View File

@ -54,14 +54,20 @@ class DBMultiEnum extends DBEnum {
/**
* Return a {@link CheckboxSetField} suitable for editing this field
*
* @param string $title
* @param string $name
* @param bool $hasEmpty
* @param string $value
* @param string $emptyString
* @return CheckboxSetField
*/
public function formField($title = null, $name = null, $hasEmpty = false, $value = "", $form = null,
$emptyString = null) {
public function formField($title = null, $name = null, $hasEmpty = false, $value = "", $emptyString = null) {
if(!$title) $title = $this->name;
if(!$name) $name = $this->name;
$field = new CheckboxSetField($name, $title, $this->enumValues($hasEmpty), $value, $form);
$field = new CheckboxSetField($name, $title, $this->enumValues($hasEmpty), $value);
return $field;
}

View File

@ -80,5 +80,6 @@ class DBPolymorphicForeignKey extends DBComposite {
if($id && $class && is_subclass_of($class, 'SilverStripe\ORM\DataObject')) {
return DataObject::get_by_id($class, $id);
}
return null;
}
}

View File

@ -5,7 +5,6 @@ namespace SilverStripe\ORM\FieldType;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\DataObject;
/**
* A special type Int field used for primary keys.
*

View File

@ -2,8 +2,6 @@
namespace SilverStripe\ORM\FieldType;
use Convert;
/**
* An abstract base class for the string field types (i.e. Varchar and Text)
*
@ -23,10 +21,10 @@ abstract class DBString extends DBField {
private static $casting = array(
"LimitCharacters" => "Text",
"LimitCharactersToClosestWord" => "Text",
'LimitWordCount' => 'Text',
"LimitWordCount" => "Text",
"LowerCase" => "Text",
"UpperCase" => "Text",
'NoHTML' => 'Text',
"Plain" => "Text",
);
/**
@ -129,10 +127,6 @@ abstract class DBString extends DBField {
|| (!$this->getNullifyEmpty() && $value === ''); // Remove this stupid exemption in 4.0
}
/**
* (non-PHPdoc)
* @see core/model/fieldtypes/DBField#prepValueForDB($value)
*/
public function prepValueForDB($value) {
if(!$this->nullifyEmpty && $value === '') {
return $value;
@ -158,17 +152,11 @@ abstract class DBString extends DBField {
* @return string
*/
public function LimitCharacters($limit = 20, $add = '...') {
$value = trim($this->RAW());
if($this->stat('escape_type') == 'xml') {
$value = strip_tags($value);
$value = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
$value = (mb_strlen($value) > $limit) ? mb_substr($value, 0, $limit) . $add : $value;
// Avoid encoding all multibyte characters as HTML entities by using htmlspecialchars().
$value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
} else {
$value = (mb_strlen($value) > $limit) ? mb_substr($value, 0, $limit) . $add : $value;
$value = $this->Plain();
if(mb_strlen($value) <= $limit) {
return $value;
}
return $value;
return mb_substr($value, 0, $limit) . $add;
}
/**
@ -178,27 +166,26 @@ abstract class DBString extends DBField {
*
* @param int $limit Number of characters to limit by
* @param string $add Ellipsis to add to the end of truncated string
* @return string
* @return string Plain text value with limited characters
*/
public function LimitCharactersToClosestWord($limit = 20, $add = '...') {
// Strip HTML tags if they exist in the field
$value = strip_tags($this->RAW());
// Safely convert to plain text
$value = $this->Plain();
// Determine if value exceeds limit before limiting characters
$exceedsLimit = mb_strlen($value) > $limit;
// Limit to character limit
$value = DBField::create_field(get_class($this), $value)->LimitCharacters($limit, '');
// If value exceeds limit, strip punctuation off the end to the last space and apply ellipsis
if($exceedsLimit) {
$value = html_entity_decode($value, ENT_COMPAT, 'UTF-8');
$value = rtrim(mb_substr($value, 0, mb_strrpos($value, " ")), "/[\.,-\/#!$%\^&\*;:{}=\-_`~()]\s") . $add;
$value = htmlspecialchars($value, ENT_COMPAT, 'UTF-8');
if(mb_strlen($value) <= $limit) {
return $value;
}
// Limit to character limit
$value = mb_substr($value, 0, $limit);
// If value exceeds limit, strip punctuation off the end to the last space and apply ellipsis
$value = preg_replace(
'/[^\w_]+$/',
'',
mb_substr($value, 0, mb_strrpos($value, " "))
) . $add;
return $value;
}
@ -211,23 +198,21 @@ abstract class DBString extends DBField {
* @return string
*/
public function LimitWordCount($numWords = 26, $add = '...') {
$value = trim(Convert::xml2raw($this->RAW()));
$ret = explode(' ', $value, $numWords + 1);
if(count($ret) <= $numWords - 1) {
$ret = $value;
} else {
array_pop($ret);
$ret = implode(' ', $ret) . $add;
$value = $this->Plain();
$words = explode(' ', $value);
if(count($words) <= $numWords) {
return $value;
}
return $ret;
// Limit
$words = array_slice($words, 0, $numWords);
return implode(' ', $words) . $add;
}
/**
* Converts the current value for this StringField to lowercase.
*
* @return string
* @return string Text with lowercase (HTML for some subclasses)
*/
public function LowerCase() {
return mb_strtolower($this->RAW());
@ -236,7 +221,7 @@ abstract class DBString extends DBField {
/**
* Converts the current value for this StringField to uppercase.
*
* @return string
* @return string Text with uppercase (HTML for some subclasses)
*/
public function UpperCase() {
return mb_strtoupper($this->RAW());
@ -247,7 +232,7 @@ abstract class DBString extends DBField {
*
* @return string Plain text
*/
public function NoHTML() {
return $this->RAW();
public function Plain() {
return trim($this->RAW());
}
}

View File

@ -32,7 +32,7 @@ class DBText extends DBString {
private static $casting = array(
"BigSummary" => "Text",
"ContextSummary" => "HTMLText", // Always returns HTML as it contains formatting and highlighting
"ContextSummary" => "HTMLFragment", // Always returns HTML as it contains formatting and highlighting
"FirstParagraph" => "Text",
"FirstSentence" => "Text",
"LimitSentences" => "Text",
@ -72,9 +72,9 @@ class DBText extends DBString {
public function LimitSentences($maxSentences = 2) {
if(!is_numeric($maxSentences)) {
throw new InvalidArgumentException("Text::LimitSentence() expects one numeric argument");
}
}
$value = $this->NoHTML();
$value = $this->Plain();
if( !$value ) {
return "";
}
@ -114,40 +114,32 @@ class DBText extends DBString {
* Builds a basic summary, up to a maximum number of words
*
* @param int $maxWords
* @param int $maxParagraphs Optional paragraph limit
* @param string $add
* @return string
*/
public function Summary($maxWords = 50, $maxParagraphs = 1) {
public function Summary($maxWords = 50, $add = '...') {
// Get plain-text version
$value = $this->NoHTML();
$value = $this->Plain();
if(!$value) {
return '';
}
// Set max paragraphs
if($maxParagraphs) {
// Split on >2 linebreaks
$paragraphs = preg_split('#\n{2,}#', $value);
if(count($paragraphs) > $maxParagraphs) {
$paragraphs = array_slice($paragraphs, 0, $maxParagraphs);
}
$value = implode("\n\n", $paragraphs);
}
// Find sentences
$sentences = explode('.', $value);
// Split on sentences (don't remove period)
$sentences = array_filter(array_map(function($str) {
return trim($str);
}, preg_split('@(?<=\.)@', $value)));
$wordCount = count(preg_split('#\s+#', $sentences[0]));
// if the first sentence is too long, show only the first $maxWords words
if($wordCount > $maxWords) {
return implode( ' ', array_slice(explode( ' ', $sentences[0] ), 0, $maxWords)) . '...';
return implode( ' ', array_slice(explode( ' ', $sentences[0] ), 0, $maxWords)) . $add;
}
// add each sentence while there are enough words to do so
$result = '';
do {
// Add next sentence
$result .= ' ' . trim(array_shift( $sentences )).'.';
$result .= ' ' . array_shift( $sentences );
// If more sentences to process, count number of words
if($sentences) {
@ -158,31 +150,21 @@ class DBText extends DBString {
return trim($result);
}
/**
* Performs the same function as the big summary, but doesn't trim new paragraphs off data.
*
* @param int $maxWords
* @return string
*/
public function BigSummary($maxWords = 50) {
return $this->Summary($maxWords, 0);
}
/**
* Get first paragraph
*
* @return string
*/
public function FirstParagraph() {
$value = $this->NoHTML();
$value = $this->Plain();
if(empty($value)) {
return '';
}
}
// Split paragraphs and return first
$paragraphs = preg_split('#\n{2,}#', $value);
return reset($paragraphs);
}
}
/**
* Perform context searching to give some context to searches, optionally
@ -205,7 +187,7 @@ class DBText extends DBString {
}
// Get raw text value, but XML encode it (as we'll be merging with HTML tags soon)
$text = nl2br(Convert::raw2xml($this->NoHTML()));
$text = nl2br(Convert::raw2xml($this->Plain()));
$keywords = Convert::raw2xml($keywords);
// Find the search string
@ -230,9 +212,10 @@ class DBText extends DBString {
if($stringPieces) {
foreach($stringPieces as $stringPiece) {
if(strlen($stringPiece) > 2) {
$summary = str_ireplace(
$stringPiece,
"<span class=\"highlight\">$stringPiece</span>",
// Maintain case of original string
$summary = preg_replace(
'/' . preg_quote($stringPiece, '/') . '/i',
'<span class="highlight">$0</span>',
$summary
);
}
@ -245,7 +228,7 @@ class DBText extends DBString {
if($position > 0) {
$summary = $prefix . $summary;
}
if(strlen($this->value) > ($characters + $position)) {
if(strlen($text) > ($characters + $position)) {
$summary = $summary . $suffix;
}
@ -267,14 +250,10 @@ class DBText extends DBString {
/** @var TextParser $obj */
$obj = \Injector::inst()->createWithArgs($parser, [$this->forTemplate()]);
return $obj->parse();
return $obj->parse();
}
/**
* (non-PHPdoc)
* @see DBField::scaffoldFormField()
*/
public function scaffoldFormField($title = null, $params = null) {
public function scaffoldFormField($title = null) {
if(!$this->nullifyEmpty) {
// Allow the user to select if it's null instead of automatically assuming empty string is
return new NullableField(new TextareaField($this->name, $title));
@ -284,11 +263,7 @@ class DBText extends DBString {
}
}
/**
* (non-PHPdoc)
* @see DBField::scaffoldSearchField()
*/
public function scaffoldSearchField($title = null, $params = null) {
public function scaffoldSearchField($title = null) {
return new TextField($this->name, $title);
}
}

View File

@ -47,7 +47,7 @@ class DBTime extends DBField {
* @return string
*/
public function Nice() {
if($this->value) return $this->Format($this->config()->nice_format);
return $this->Format($this->config()->nice_format);
}
/**
@ -57,7 +57,7 @@ class DBTime extends DBField {
* @return string Time in 24 hour format
*/
public function Nice24() {
if($this->value) return date('H:i', strtotime($this->value));
return $this->Format('H:i');
}
/**
@ -67,7 +67,10 @@ class DBTime extends DBField {
* @return string The date in the requested format
*/
public function Format($format) {
if($this->value) return date($format, strtotime($this->value));
if($this->value) {
return date($format, strtotime($this->value));
}
return null;
}
public function TwelveHour( $parts ) {

View File

@ -78,21 +78,29 @@ class DBVarchar extends DBString {
/**
* Return the first letter of the string followed by a .
*
* @return string
*/
public function Initial() {
if($this->exists()) {
$value = $this->RAW();
return $value[0] . '.';
}
return null;
}
/**
* Ensure that the given value is an absolute URL.
*
* @return string
*/
public function URL() {
$value = $this->RAW();
if(preg_match('#^[a-zA-Z]+://#', $value)) return $value;
else return "http://" . $value;
if(preg_match('#^[a-zA-Z]+://#', $value)) {
return $value;
} else {
return "http://" . $value;
}
}
/**
@ -103,11 +111,7 @@ class DBVarchar extends DBString {
return str_replace("\n", '\par ', $this->RAW());
}
/**
* (non-PHPdoc)
* @see DBField::scaffoldFormField()
*/
public function scaffoldFormField($title = null, $params = null) {
public function scaffoldFormField($title = null) {
if(!$this->nullifyEmpty) {
// Allow the user to select if it's null instead of automatically assuming empty string is
return new NullableField(new TextField($this->name, $title));

View File

@ -40,9 +40,13 @@ class DBYear extends DBField {
* @param int|bool $end end date to count down to
* @return array
*/
private function getDefaultOptions($start=false, $end=false) {
if (!$start) $start = (int)date('Y');
if (!$end) $end = 1900;
private function getDefaultOptions($start = null, $end = null) {
if (!$start) {
$start = (int)date('Y');
}
if (!$end) {
$end = 1900;
}
$years = array();
for($i=$start;$i>=$end;$i--) {
$years[$i] = $i;

View File

@ -311,7 +311,14 @@ class RSSFeed_Entry extends ViewableData {
* @return DBField Returns the description of the entry.
*/
public function Description() {
return $this->rssField($this->descriptionField);
$description = $this->rssField($this->descriptionField);
// HTML fields need links re-written
if($description instanceof DBHTMLText) {
return $description->obj('AbsoluteLinks');
}
return $description;
}
/**

View File

@ -191,6 +191,8 @@ class Convert {
* Convert XML to raw text.
* @uses html2raw()
* @todo Currently &#xxx; entries are stripped; they should be converted
* @param mixed $val
* @return array|string
*/
public static function xml2raw($val) {
if(is_array($val)) {
@ -234,6 +236,7 @@ class Convert {
* false by default.
* @param boolean $disableExternals Disables the loading of external entities. false by default.
* @return array
* @throws Exception
*/
public static function xml2array($val, $disableDoctypes = false, $disableExternals = false) {
// Check doctype
@ -242,6 +245,7 @@ class Convert {
}
// Disable external entity loading
$oldVal = null;
if($disableExternals) $oldVal = libxml_disable_entity_loader($disableExternals);
try {
$xml = new SimpleXMLElement($val);
@ -263,7 +267,7 @@ class Convert {
* @return mixed
*/
protected static function recursiveXMLToArray($xml) {
if(is_object($xml) && get_class($xml) == 'SimpleXMLElement') {
if($xml instanceof SimpleXMLElement) {
$attributes = $xml->attributes();
foreach($attributes as $k => $v) {
if($v) $a[$k] = (string) $v;

View File

@ -145,3 +145,16 @@ html. To ensure that the correct encoding is used for that field in a template,
`$Field` by itself to allow the casting helper to determine the best encoding itself.
</div>
## Cast summary methods
Certain subclasses of DBField also have additional summary or manipulations methods, each of
which can be chained in order to perform more complicated manipulations.
For instance, The following class methods can be used in templates for the below types:
Text / HTMLText methods:
* `$Plain` Will convert any HTML to plain text version. For example, could be used for plain-text
version of emails.
* `$LimitSentences(<num>)` Will limit to the first `<num>` sentences in the content. If called on
HTML content this will have all HTML stripped and converted to plain text.

View File

@ -50,6 +50,7 @@
html fields as `HTMLText(['whitelist=meta,link'])`, or use a `ShortcodeHTMLText` as
a shorthand substitute.
* `FormField->dontEscape` has been removed. Escaping is now managed on a class by class basis.
* `DBString->LimitWordCountXML` removed. Use `LimitWordCount` for XML safe version.
## New API
@ -104,6 +105,11 @@
* `FormAction::setValidationExempt` can be used to turn on or off form validation for individual actions
* `DataObject.table_name` config can now be used to customise the database table for any record.
* `DataObjectSchema` class added to assist with mapping between classes and tables.
* Changes to `DBString` formatting:
* `NoHTML` is renamed to `Plain`
* `LimitWordCountXML` is removed. Use `LimitWordCount` instead.
* `BigSummary` is removed. Use `Summary` instead.
* Most limit methods on `DBHTMLText` now plain text rather than attempt to manipulate the underlying HTML.
* `FormField::Title` and `FormField::RightTitle` are now cast as plain text by default (but can be overridden).
### Front-end build tooling for CMS interface
@ -174,7 +180,7 @@ admin/font/ => admin/client/dist/font/
* History.js
* `debugmethods` querystring argument has been removed from debugging.
* The following ClassInfo methods are now deprecated:
* `ClassInfo::baseDataClass` - Use `DataObject::getSchema()->baseDataClass()` instead.
* `ClassInfo::table_for_object_field` - Use `DataObject::getSchema()->tableForField()` instead

View File

@ -1,6 +1,8 @@
<?php
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBField;
/**
* Adds a "level up" link to a GridField table, which is useful when viewing
* hierarchical data. Requires the managed record to have a "getParent()"
@ -60,7 +62,7 @@ class GridFieldLevelup extends Object implements GridField_HTMLProvider {
foreach($attrs as $k => $v) $attrsStr .= " $k=\"" . Convert::raw2att($v) . "\"";
$forTemplate = new ArrayData(array(
'UpLink' => sprintf('<a%s></a>', $attrsStr)
'UpLink' => DBField::create_field('HTMLFragment', sprintf('<a%s></a>', $attrsStr))
));
return array(

View File

@ -105,7 +105,7 @@ class BBCodeParser extends TextParser {
);
}
public function useable_tagsHTML(){
public function useable_tagsHTML() {
$useabletags = "<ul class='bbcodeExamples'>";
foreach($this->usable_tags()->toArray() as $tag){
$useabletags = $useabletags."<li><span>".$tag->Example."</span></li>";
@ -120,20 +120,13 @@ class BBCodeParser extends TextParser {
* @return DBField
*/
public function parse() {
$this->content = str_replace(array('&', '<', '>'), array('&amp;', '&lt;', '&gt;'), $this->content);
// Convert content to plain text
$this->content = DBField::create_field('HTMLFragment', $this->content)->Plain();
$p = new SSHTMLBBCodeParser();
$this->content = $p->qparse($this->content);
unset($p);
$this->content = "<p>".$this->content."</p>";
$this->content = preg_replace('/(<p[^>]*>)\s+/i', '\\1', $this->content);
$this->content = preg_replace('/\s+(<\/p[^>]*>)/i', '\\1', $this->content);
$this->content = preg_replace("/\n\s*\n/", "</p><p>", $this->content);
$this->content = str_replace("\n", "<br />", $this->content);
if($this->config()->allow_smilies) {
$smilies = array(
'#(?<!\w):D(?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/grin.gif'> ", // :D

View File

@ -1,4 +1,6 @@
<?php
use SilverStripe\Model\FieldType\DBField;
/**
* Parses text in a variety of ways.
*

View File

@ -10,7 +10,7 @@
<item>
<title>$Title.XML</title>
<link>$AbsoluteLink.XML</link>
<% if $Description %><description>$Description.AbsoluteLinks.CDATA</description><% end_if %>
<% if $Description %><description>$Description.CDATA</description><% end_if %>
<% if $Date %><pubDate>$Date.Rfc822</pubDate>
<% else %><pubDate>$Created.Rfc822</pubDate><% end_if %>
<% if $Author %><dc:creator>$Author.XML</dc:creator><% end_if %>

View File

@ -225,12 +225,19 @@ class DBFieldTest extends SapphireTest {
$value = '<p>üåäö&amp;ÜÅÄÖ</p>';
foreach ($htmlFields as $stringField) {
$stringField = DBField::create_field($stringField, $value);
$this->assertEquals('üåäö&amp;ÜÅÄ...', $stringField->LimitCharacters(8));
$stringObj = DBField::create_field($stringField, $value);
// Converted to plain text
$this->assertEquals('üåäö&ÜÅÄ...', $stringObj->LimitCharacters(8));
// But which will be safely cast in templates
$this->assertEquals('üåäö&amp;ÜÅÄ...', $stringObj->obj('LimitCharacters', [8])->forTemplate());
}
$this->assertEquals('ÅÄÖ', DBField::create_field('Text', 'åäö')->UpperCase());
$this->assertEquals('åäö', DBField::create_field('Text', 'ÅÄÖ')->LowerCase());
$this->assertEquals('<P>ÅÄÖ</P>', DBField::create_field('HTMLFragment', '<p>åäö</p>')->UpperCase());
$this->assertEquals('<p>åäö</p>', DBField::create_field('HTMLFragment', '<p>ÅÄÖ</p>')->LowerCase());
}
}

View File

@ -12,142 +12,385 @@ use SilverStripe\ORM\FieldType\DBField;
*/
class DBHTMLTextTest extends SapphireTest {
public function setUp() {
parent::setUp();
// Set test handler
ShortcodeParser::get('htmltest')
->register('test_shortcode', array('DBHTMLTextTest_Shortcode', 'handle_shortcode'));
ShortcodeParser::set_active('htmltest');
}
public function tearDown() {
ShortcodeParser::set_active('default');
parent::tearDown();
}
/**
* Test {@link HTMLText->LimitCharacters()}
* Test {@link Text->LimitCharacters()}
*/
public function testLimitCharacters() {
$cases = array(
'The little brown fox jumped over the lazy cow.' => 'The little brown fox...',
'<p>This is some text in a paragraph.</p>' => 'This is some text in...',
'This text contains &amp; in it' => 'This text contains &amp;...'
);
foreach($cases as $originalValue => $expectedValue) {
$textObj = new DBHTMLText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->LimitCharacters());
}
public function providerLimitCharacters()
{
// HTML characters are stripped safely
return [
['The little brown fox jumped over the lazy cow.', 'The little brown fox...'],
['<p>Short &amp; Sweet</p>', 'Short &amp; Sweet'],
['This text contains &amp; in it', 'This text contains &amp;...'],
];
}
public function testSummaryBasics() {
$cases = array(
'<h1>Should not take header</h1><p>Should take paragraph</p>' => 'Should take paragraph',
'<p>Should strip <b>tags, but leave</b> text</p>' => 'Should strip tags, but leave text',
'<p>Unclosed tags <br>should not phase it</p>' => 'Unclosed tags should not phase it',
'<p>Second paragraph</p><p>should not cause errors or appear in output</p>' => 'Second paragraph',
'<img src="hello" /><p>Second paragraph</p><p>should not cause errors or appear in output</p>'
=> 'Second paragraph',
' <img src="hello" /><p>Second paragraph</p><p>should not cause errors or appear in output</p>'
=> 'Second paragraph',
'<p><img src="remove me">example <img src="include me">text words hello<img src="hello"></p>'
=> 'example text words hello',
);
foreach($cases as $originalValue => $expectedValue) {
$textObj = new DBHTMLText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->Summary());
}
/**
* Test {@link DBHTMLText->LimitCharacters()}
* @dataProvider providerLimitCharacters
* @param string $originalValue
* @param string $expectedValue
*/
public function testLimitCharacters($originalValue, $expectedValue) {
$textObj = DBField::create_field('HTMLFragment', $originalValue);
$result = $textObj->obj('LimitCharacters')->forTemplate();
$this->assertEquals($expectedValue, $result);
}
public function testSummaryLimits() {
$cases = array(
'<p>A long paragraph should be cut off if limit is set</p>' => 'A long paragraph should be...',
'<p>No matter <i>how many <b>tags</b></i> are in it</p>' => 'No matter how many tags...',
'<p>A sentence is. nicer than hard limits</p>' => 'A sentence is.',
'<p>But not. If it\'s too short</p>' => 'But not. If it\'s too...'
);
/**
* @return array
*/
public function providerLimitCharactersToClosestWord()
{
// HTML is converted safely to plain text
return [
// Standard words limited, ellipsis added if truncated
['<p>Lorem ipsum dolor sit amet</p>', 24, 'Lorem ipsum dolor sit...'],
foreach($cases as $originalValue => $expectedValue) {
$textObj = new DBHTMLText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->Summary(5, 3, '...'));
}
// Complete words less than the character limit don't get truncated, ellipsis not added
['<p>Lorem ipsum</p>', 24, 'Lorem ipsum'],
['<p>Lorem</p>', 24, 'Lorem'],
['', 24, ''], // No words produces nothing!
// Special characters are encoded safely
['Nice &amp; Easy', 24, 'Nice &amp; Easy'],
// HTML is safely converted to plain text
['<p>Lorem ipsum dolor sit amet</p>', 24, 'Lorem ipsum dolor sit...'],
['<p><span>Lorem ipsum dolor sit amet</span></p>', 24, 'Lorem ipsum dolor sit...'],
['<p>Lorem ipsum</p>', 24, 'Lorem ipsum'],
['Lorem &amp; ipsum dolor sit amet', 24, 'Lorem &amp; ipsum dolor sit...']
];
}
/**
* Test {@link DBHTMLText->LimitCharactersToClosestWord()}
* @dataProvider providerLimitCharactersToClosestWord
*
* @param string $originalValue Raw string input
* @param int $limit
* @param string $expectedValue Expected template value
*/
public function testLimitCharactersToClosestWord($originalValue, $limit, $expectedValue) {
$textObj = DBField::create_field('HTMLFragment', $originalValue);
$result = $textObj->obj('LimitCharactersToClosestWord', [$limit])->forTemplate();
$this->assertEquals($expectedValue, $result);
}
public function providerSummary()
{
return [
[
'<p>Should strip <b>tags, but leave</b> text</p>',
50,
'Should strip tags, but leave text',
],
[
// Line breaks are preserved
'<p>Unclosed tags <br>should not phase it</p>',
50,
"Unclosed tags <br />\nshould not phase it",
],
[
// Paragraphs converted to linebreak
'<p>Second paragraph</p><p>should not cause errors or appear in output</p>',
50,
"Second paragraph<br />\n<br />\nshould not cause errors or appear in output",
],
[
'<img src="hello" /><p>Second paragraph</p><p>should not cause errors or appear in output</p>',
50,
"Second paragraph<br />\n<br />\nshould not cause errors or appear in output",
],
[
' <img src="hello" /><p>Second paragraph</p><p>should not cause errors or appear in output</p>',
50,
"Second paragraph<br />\n<br />\nshould not cause errors or appear in output",
],
[
'<p><img src="remove me">example <img src="include me">text words hello<img src="hello"></p>',
50,
'example text words hello',
],
// Shorter limits
[
'<p>A long paragraph should be cut off if limit is set</p>',
5,
'A long paragraph should be...',
],
[
'<p>No matter <i>how many <b>tags</b></i> are in it</p>',
5,
'No matter how many tags...',
],
[
'<p>A sentence is. nicer than hard limits</p>',
5,
'A sentence is.',
],
];
}
/**
* @dataProvider providerSummary
* @param string $originalValue
* @param int $limit
* @param string $expectedValue
*/
public function testSummary($originalValue, $limit, $expectedValue) {
$textObj = DBField::create_field('HTMLFragment', $originalValue);
$result = $textObj->obj('Summary', [$limit])->forTemplate();
$this->assertEquals($expectedValue, $result);
}
public function testSummaryEndings() {
$cases = array(
'...', ' -> more', ''
'...',
' -> more',
''
);
$orig = '<p>Cut it off, cut it off</p>';
$match = 'Cut it off, cut';
foreach($cases as $add) {
$textObj = new DBHTMLText();
$textObj->setValue($orig);
$this->assertEquals($match.$add, $textObj->Summary(4, 0, $add));
$textObj = DBField::create_field('HTMLFragment', $orig);
$result = $textObj->obj('Summary', [4, $add])->forTemplate();
$this->assertEquals($match.Convert::raw2xml($add), $result);
}
}
public function testSummaryFlexTooBigShouldNotCauseError() {
$orig = '<p>Cut it off, cut it off</p>';
$match = 'Cut it off, cut';
$textObj = new DBHTMLText();
$textObj->setValue($orig);
$this->assertEquals($match, $textObj->Summary(4, 10, ''));
public function providerFirstSentence()
{
return [
// Same behaviour as DBTextTest
['', ''],
['First sentence.', 'First sentence.'],
['First sentence. Second sentence', 'First sentence.'],
['First sentence? Second sentence', 'First sentence?'],
['First sentence! Second sentence', 'First sentence!'],
// DBHTHLText strips HTML first
['<br />First sentence.', 'First sentence.'],
['<p>First sentence. Second sentence. Third sentence</p>', 'First sentence.'],
];
}
public function testSummaryInvalidHTML() {
$cases = array(
'It\'s got a <p<> tag, but<p junk true>This doesn\'t <a id="boo">make</b class="wa"> < ><any< sense</p>'
=> 'This doesn\'t make any',
'This doesn\'t <a style="much horray= true>even</b> < ><have< a <i>p tag' => 'This doesn\'t even have'
);
foreach($cases as $orig => $match) {
$textObj = new DBHTMLText();
$textObj->setValue($orig);
$this->assertEquals($match, $textObj->Summary(4, 0, ''));
}
/**
* @dataProvider providerFirstSentence
* @param string $originalValue
* @param string $expectedValue
*/
public function testFirstSentence($originalValue, $expectedValue) {
$textObj = DBField::create_field('HTMLFragment', $originalValue);
$result = $textObj->obj('FirstSentence')->forTemplate();
$this->assertEquals($expectedValue, $result);
}
public function testFirstSentence() {
$many = str_repeat('many ', 100);
$cases = array(
'<h1>should ignore</h1><p>First sentence. Second sentence.</p>' => 'First sentence.',
'<h1>should ignore</h1><p>First Mr. sentence. Second sentence.</p>' => 'First Mr. sentence.',
"<h1>should ignore</h1><p>Sentence with {$many}words. Second sentence.</p>"
=> "Sentence with {$many}words.",
'<p>This classic picture book features a repetitive format that lends itself to audience interaction.'.
'&nbsp; Illustrator Eric Carle submitted new, bolder artwork for the 25th anniversary edition.</p>'
=> 'This classic picture book features a repetitive format that lends itself to audience interaction.'
);
public function providerToPlain()
{
return [
[
'<p><img />Lots of <strong>HTML <i>nested</i></strong> tags',
'Lots of HTML nested tags',
],
[
'<p>Multi</p><p>Paragraph<br>Also has multilines.</p>',
"Multi\n\nParagraph\nAlso has multilines.",
],
[
'<p>Collapses</p><p></p><p>Excessive<br/><br /><br>Newlines</p>',
"Collapses\n\nExcessive\n\nNewlines",
]
];
}
foreach($cases as $orig => $match) {
$textObj = new DBHTMLText();
$textObj->setValue($orig);
$this->assertEquals($match, $textObj->FirstSentence());
}
/**
* @dataProvider providerToPlain
* @param string $html
* @param string $plain
*/
public function testToPlain($html, $plain) {
/** @var DBHTMLText $textObj */
$textObj = DBField::create_field('HTMLFragment', $html);
$this->assertEquals($plain, $textObj->Plain());
}
/**
* each test is in the format input, charactere limit, highlight, expected output
*
* @return array
*/
public function providerContextSummary()
{
return [
[
'This is some text. It is a test',
20,
'test',
'... text. It is a <span class="highlight">test</span>'
],
[
// Retains case of original string
'This is some test text. Test test what if you have multiple keywords.',
50,
'some test',
'This is <span class="highlight">some</span> <span class="highlight">test</span> text.'
. ' <span class="highlight">Test</span> <span class="highlight">test</span> what if you have...'
],
[
'Here is some text &amp; HTML included',
20,
'html',
'... text &amp; <span class="highlight">HTML</span> inc...'
],
[
'A dog ate a cat while looking at a Foobar',
100,
'a',
// test that it does not highlight too much (eg every a)
'A dog ate a cat while looking at a Foobar',
],
[
'A dog ate a cat while looking at a Foobar',
100,
'ate',
// it should highlight 3 letters or more.
'A dog <span class="highlight">ate</span> a cat while looking at a Foobar',
],
// HTML Content is plain-textified, and incorrect tags removed
[
'<p>A dog ate a cat while <span class="highlight">looking</span> at a Foobar</p>',
100,
'ate',
// it should highlight 3 letters or more.
'A dog <span class="highlight">ate</span> a cat while looking at a Foobar',
]
];
}
/**
* @dataProvider providerContextSummary
* @param string $originalValue Input
* @param int $limit Numer of characters
* @param string $keywords Keywords to highlight
* @param string $expectedValue Expected output (XML encoded safely)
*/
public function testContextSummary($originalValue, $limit, $keywords, $expectedValue)
{
$text = DBField::create_field('HTMLFragment', $originalValue);
$result = $text->obj('ContextSummary', [$limit, $keywords])->forTemplate();
// it should highlight 3 letters or more.
$this->assertEquals($expectedValue, $result);
}
public function testRAW() {
$data = DBField::create_field('HTMLText', 'This &amp; This');
$this->assertEquals($data->RAW(), 'This &amp; This');
$data = DBField::create_field('HTMLFragment', 'This &amp; This');
$this->assertEquals('This &amp; This', $data->RAW());
$data = DBField::create_field('HTMLText', 'This & This');
$this->assertEquals($data->RAW(), 'This & This');
$data = DBField::create_field('HTMLFragment', 'This & This');
$this->assertEquals('This & This', $data->RAW());
}
public function testXML() {
$data = DBField::create_field('HTMLText', 'This & This');
$this->assertEquals($data->XML(), 'This &amp; This');
$data = DBField::create_field('HTMLFragment', 'This & This');
$this->assertEquals('This &amp; This', $data->XML());
$data = DBField::create_field('HTMLFragment', 'This &amp; This');
$this->assertEquals('This &amp;amp; This', $data->XML());
}
public function testHTML() {
$data = DBField::create_field('HTMLText', 'This & This');
$this->assertEquals($data->HTML(), 'This &amp; This');
$data = DBField::create_field('HTMLFragment', 'This & This');
$this->assertEquals('This &amp; This', $data->HTML());
$data = DBField::create_field('HTMLFragment', 'This &amp; This');
$this->assertEquals('This &amp;amp; This', $data->HTML());
}
public function testJS() {
$data = DBField::create_field('HTMLText', '"this is a test"');
$this->assertEquals($data->JS(), '\"this is a test\"');
$data = DBField::create_field('HTMLText', '"this is &amp; test"');
$this->assertEquals('\"this is \x26amp; test\"', $data->JS());
}
public function testATT() {
$data = DBField::create_field('HTMLText', '"this is a test"');
$this->assertEquals($data->ATT(), '&quot;this is a test&quot;');
// HTML Fragment
$data = DBField::create_field('HTMLFragment', '"this is a test"');
$this->assertEquals('&quot;this is a test&quot;', $data->ATT());
// HTML Text (passes shortcodes + tidy)
$data = DBField::create_field('HTMLText', '&quot;');
$this->assertEquals('&quot;', $data->ATT());
}
public function testShortcodesProcessed()
{
/** @var DBHTMLText $obj */
$obj = DBField::create_field(
'HTMLText',
'<p>Some content <strong>[test_shortcode]</strong> with shortcode</p>'
);
// Basic DBField methods process shortcodes
$this->assertEquals(
'Some content shortcode content with shortcode',
$obj->Plain()
);
$this->assertEquals(
'<p>Some content <strong>shortcode content</strong> with shortcode</p>',
$obj->RAW()
);
$this->assertEquals(
'&lt;p&gt;Some content &lt;strong&gt;shortcode content&lt;/strong&gt; with shortcode&lt;/p&gt;',
$obj->XML()
);
$this->assertEquals(
'&lt;p&gt;Some content &lt;strong&gt;shortcode content&lt;/strong&gt; with shortcode&lt;/p&gt;',
$obj->HTML()
);
// Test summary methods
$this->assertEquals(
'Some content shortcode...',
$obj->Summary(3)
);
$this->assertEquals(
'Some content shortcode content with shortcode',
$obj->LimitSentences(1)
);
$this->assertEquals(
'Some content shortco...',
$obj->LimitCharacters(20)
);
}
public function testParse() {
// Test parse
/** @var DBHTMLText $obj */
$obj = DBField::create_field(
'HTMLText',
'<p>[b]Some content[/b] [test_shortcode] with shortcode</p>'
);
// BBCode strips HTML and applies own formatting
$this->assertEquals(
'<strong>Some content</strong> shortcode content with shortcode',
$obj->Parse('BBCodeParser')->forTemplate()
);
}
function testExists() {
@ -322,3 +565,15 @@ class DBHTMLTextTest extends SapphireTest {
ShortcodeParser::set_active('default');
}
}
class DBHTMLTextTest_Shortcode implements ShortcodeHandler, TestOnly {
public static function get_shortcodes()
{
return 'test';
}
public static function handle_shortcode($arguments, $content, $parser, $shortcode, $extra = array())
{
return 'shortcode content';
}
}

View File

@ -7,6 +7,8 @@ use SilverStripe\ORM\FieldType\DBField;
/**
* Tests parsing and summary methods on DBText
*
* @package framework
* @subpackage tests
*/
@ -15,211 +17,224 @@ class DBTextTest extends SapphireTest {
/**
* Test {@link Text->LimitCharacters()}
*/
public function testLimitCharacters() {
$cases = array(
'The little brown fox jumped over the lazy cow.' => 'The little brown fox...',
'<p>This is some text in a paragraph.</p>' => '<p>This is some text...'
);
public function providerLimitCharacters()
{
// Plain text values always encoded safely
// HTML stored in non-html fields is treated literally.
return [
['The little brown fox jumped over the lazy cow.', 'The little brown fox...'],
['<p>Short & Sweet</p>', '&lt;p&gt;Short &amp; Sweet&lt;/p&gt;'],
['This text contains &amp; in it', 'This text contains &amp;...'],
];
}
foreach($cases as $originalValue => $expectedValue) {
$textObj = new DBText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->LimitCharacters());
}
/**
* Test {@link Text->LimitCharacters()}
* @dataProvider providerLimitCharacters
* @param string $originalValue
* @param string $expectedValue
*/
public function testLimitCharacters($originalValue, $expectedValue) {
$textObj = DBField::create_field('Text', $originalValue);
$result = $textObj->obj('LimitCharacters')->forTemplate();
$this->assertEquals($expectedValue, $result);
}
/**
* @return array
*/
public function providerLimitCharactersToClosestWord()
{
return [
// Standard words limited, ellipsis added if truncated
['Lorem ipsum dolor sit amet', 24, 'Lorem ipsum dolor sit...'],
// Complete words less than the character limit don't get truncated, ellipsis not added
['Lorem ipsum', 24, 'Lorem ipsum'],
['Lorem', 24, 'Lorem'],
['', 24, ''], // No words produces nothing!
// Special characters are encoded safely
['Nice & Easy', 24, 'Nice &amp; Easy'],
// HTML stored in non-html fields is treated literally.
// If storing HTML you should use DBHTMLText instead
['<p>Lorem ipsum dolor sit amet</p>', 24, '&lt;p&gt;Lorem ipsum dolor...'],
['<p><span>Lorem ipsum dolor sit amet</span></p>', 24, '&lt;p&gt;&lt;span&gt;Lorem ipsum...'],
['<p>Lorem ipsum</p>', 24, '&lt;p&gt;Lorem ipsum&lt;/p&gt;'],
['Lorem &amp; ipsum dolor sit amet', 24, 'Lorem &amp;amp; ipsum dolor...']
];
}
/**
* Test {@link Text->LimitCharactersToClosestWord()}
* @dataProvider providerLimitCharactersToClosestWord
*
* @param string $originalValue Raw string input
* @param int $limit
* @param string $expectedValue Expected template value
*/
public function testLimitCharactersToClosestWord() {
$cases = array(
/* Standard words limited, ellipsis added if truncated */
'Lorem ipsum dolor sit amet' => 'Lorem ipsum dolor sit...',
/* Complete words less than the character limit don't get truncated, ellipsis not added */
'Lorem ipsum' => 'Lorem ipsum',
'Lorem' => 'Lorem',
'' => '', // No words produces nothing!
/* HTML tags get stripped out, leaving the raw text */
'<p>Lorem ipsum dolor sit amet</p>' => 'Lorem ipsum dolor sit...',
'<p><span>Lorem ipsum dolor sit amet</span></p>' => 'Lorem ipsum dolor sit...',
'<p>Lorem ipsum</p>' => 'Lorem ipsum',
/* HTML entities are treated as a single character */
'Lorem &amp; ipsum dolor sit amet' => 'Lorem &amp; ipsum dolor...'
);
foreach($cases as $originalValue => $expectedValue) {
$textObj = new DBText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->LimitCharactersToClosestWord(24));
}
public function testLimitCharactersToClosestWord($originalValue, $limit, $expectedValue) {
$textObj = DBField::create_field('Text', $originalValue);
$result = $textObj->obj('LimitCharactersToClosestWord', [$limit])->forTemplate();
$this->assertEquals($expectedValue, $result);
}
/**
* Test {@link Text->LimitWordCount()}
*/
public function testLimitWordCount() {
$cases = array(
/* Standard words limited, ellipsis added if truncated */
'The little brown fox jumped over the lazy cow.' => 'The little brown...',
' This text has white space around the ends ' => 'This text has...',
public function providerLimitWordCount() {
return [
// Standard words limited, ellipsis added if truncated
['The little brown fox jumped over the lazy cow.', 3, 'The little brown...'],
[' This text has white space around the ends ', 3, 'This text has...'],
/* Words less than the limt word count don't get truncated, ellipsis not added */
'Two words' => 'Two words', // Two words shouldn't have an ellipsis
'One' => 'One', // Neither should one word
'' => '', // No words produces nothing!
// Words less than the limt word count don't get truncated, ellipsis not added
['Two words', 3, 'Two words'], // Two words shouldn't have an ellipsis
['These three words', 3, 'These three words'], // Three words shouldn't have an ellipsis
['One', 3, 'One'], // Neither should one word
['', 3, ''], // No words produces nothing!
/* HTML tags get stripped out, leaving the raw text */
'<p>Text inside a paragraph tag should also work</p>' => 'Text inside a...',
'<p><span>Text nested inside another tag should also work</span></p>' => 'Text nested inside...',
'<p>Two words</p>' => 'Two words'
);
// Text with special characters
['Nice & Easy', 3, 'Nice &amp; Easy'],
['One & Two & Three', 3, 'One &amp; Two...'],
foreach($cases as $originalValue => $expectedValue) {
$textObj = new DBText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->LimitWordCount(3));
}
// HTML stored in non-html fields is treated literally.
// If storing HTML you should use DBHTMLText instead
['<p>Text inside a paragraph tag should also work</p>', 3, '&lt;p&gt;Text inside a...'],
['<p>Two words</p>', 3, '&lt;p&gt;Two words&lt;/p&gt;'],
];
}
/**
* Test {@link Text->LimitWordCountXML()}
* Test {@link DBText->LimitWordCount()}
* @dataProvider providerLimitWordCount
*
* @param string $originalValue Raw string input
* @param int $limit Number of words
* @param string $expectedValue Expected template value
*/
public function testLimitWordCountXML() {
$cases = array(
'<p>Stuff & stuff</p>' => 'Stuff &amp;...',
"Stuff\nBlah Blah Blah" => "Stuff\nBlah Blah...",
"Stuff<Blah Blah" => "Stuff&lt;Blah Blah",
"Stuff>Blah Blah" => "Stuff&gt;Blah Blah"
);
foreach($cases as $originalValue => $expectedValue) {
$textObj = new DBText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->LimitWordCountXML(3));
}
public function testLimitWordCount($originalValue, $limit, $expectedValue) {
$textObj = DBField::create_field('Text', $originalValue);
$result = $textObj->obj('LimitWordCount', [$limit])->forTemplate();
$this->assertEquals($expectedValue, $result);
}
/**
* Test {@link Text->LimitSentences()}
*/
public function testLimitSentences() {
$cases = array(
'' => '',
'First sentence.' => 'First sentence.',
'First sentence. Second sentence' => 'First sentence. Second sentence.',
'<p>First sentence.</p>' => 'First sentence.',
'<p>First sentence. Second sentence. Third sentence</p>' => 'First sentence. Second sentence.',
'<p>First sentence. <em>Second sentence</em>. Third sentence</p>' => 'First sentence. Second sentence.',
'<p>First sentence. <em class="dummyClass">Second sentence</em>. Third sentence</p>'
=> 'First sentence. Second sentence.'
);
public function providerLimitSentences()
{
return [
['', 2, ''],
['First sentence.', 2, 'First sentence.'],
['First sentence. Second sentence.', 2, 'First sentence. Second sentence.'],
foreach($cases as $originalValue => $expectedValue) {
$textObj = new DBText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->LimitSentences(2));
}
}
public function testFirstSentance() {
$cases = array(
'' => '',
'First sentence.' => 'First sentence.',
'First sentence. Second sentence' => 'First sentence.',
'First sentence? Second sentence' => 'First sentence?',
'First sentence! Second sentence' => 'First sentence!',
'<p>First sentence.</p>' => 'First sentence.',
'<p>First sentence. Second sentence. Third sentence</p>' => 'First sentence.',
'<p>First sentence. <em>Second sentence</em>. Third sentence</p>' => 'First sentence.',
'<p>First sentence. <em class="dummyClass">Second sentence</em>. Third sentence</p>'
=> 'First sentence.'
);
foreach($cases as $originalValue => $expectedValue) {
$textObj = new DBText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->FirstSentence());
}
// HTML stored in non-html fields is treated literally.
// If storing HTML you should use DBHTMLText instead
['<p>First sentence.</p>', 2, '&lt;p&gt;First sentence.&lt;/p&gt;'],
['<p>First sentence. Second sentence. Third sentence</p>', 2, '&lt;p&gt;First sentence. Second sentence.'],
];
}
/**
* Test {@link Text->BigSummary()}
*/
public function testBigSummaryPlain() {
$cases = array(
'<p>This text has multiple sentences. Big Summary uses this to split sentences up.</p>'
=> 'This text has multiple...',
'This text does not have multiple sentences' => 'This text does not...',
'Very short' => 'Very short',
'' => ''
);
* Test {@link DBText->LimitSentences()}
*
* @dataProvider providerLimitSentences
* @param string $originalValue
* @param int $limit Number of sentences
* @param string $expectedValue Expected template value
*/
public function testLimitSentences($originalValue, $limit, $expectedValue) {
$textObj = DBField::create_field('Text', $originalValue);
$result = $textObj->obj('LimitSentences', [$limit])->forTemplate();
$this->assertEquals($expectedValue, $result);
}
foreach($cases as $originalValue => $expectedValue) {
$textObj = DBField::create_field('Text', $originalValue);
$this->assertEquals($expectedValue, $textObj->BigSummary(4, true));
}
public function providerFirstSentence()
{
return [
['', ''],
['First sentence.', 'First sentence.'],
['First sentence. Second sentence', 'First sentence.'],
['First sentence? Second sentence', 'First sentence?'],
['First sentence! Second sentence', 'First sentence!'],
// HTML stored in non-html fields is treated literally.
// If storing HTML you should use DBHTMLText instead
['<br />First sentence.', '&lt;br /&gt;First sentence.'],
['<p>First sentence. Second sentence. Third sentence</p>', '&lt;p&gt;First sentence.'],
];
}
/**
* Test {@link Text->BigSummary()}
*/
public function testBigSummary() {
$cases = array(
'<strong>This</strong> text has multiple sentences. Big Summary uses this to split sentences up.</p>'
=> '<strong>This</strong> text has multiple...',
'This text does not have multiple sentences' => 'This text does not...',
'Very short' => 'Very short',
'' => ''
);
foreach($cases as $originalValue => $expectedValue) {
$textObj = DBField::create_field('Text', $originalValue);
$this->assertEquals($expectedValue, $textObj->BigSummary(4, false));
}
* @dataProvider providerFirstSentence
* @param string $originalValue
* @param string $expectedValue
*/
public function testFirstSentence($originalValue, $expectedValue) {
$textObj = DBField::create_field('Text', $originalValue);
$result = $textObj->obj('FirstSentence')->forTemplate();
$this->assertEquals($expectedValue, $result);
}
public function testContextSummary() {
$testString1 = '<p>This is some text. It is a test</p>';
$testKeywords1 = 'test';
$testString2 = '<p>This is some test text. Test test what if you have multiple keywords.</p>';
$testKeywords2 = 'some test';
$testString3 = '<p>A dog ate a cat while looking at a Foobar</p>';
$testKeyword3 = 'a';
$testKeyword3a = 'ate';
$textObj = DBField::create_field('Text', $testString1, 'Text');
$this->assertEquals(
'... text. It is a <span class="highlight">test</span>...',
$textObj->ContextSummary(20, $testKeywords1)
);
$textObj->setValue($testString2);
$this->assertEquals(
'This is <span class="highlight">some</span> <span class="highlight">test</span> text.'
. ' <span class="highlight">test</span> <span class="highlight">test</span> what if you have...',
$textObj->ContextSummary(50, $testKeywords2)
);
$textObj->setValue($testString3);
// test that it does not highlight too much (eg every a)
$this->assertEquals(
'A dog ate a cat while looking at a Foobar',
$textObj->ContextSummary(100, $testKeyword3)
);
/**
* each test is in the format input, charactere limit, highlight, expected output
*
* @return array
*/
public function providerContextSummary()
{
return [
[
'This is some text. It is a test',
20,
'test',
'... text. It is a <span class="highlight">test</span>'
],
[
// Retains case of original string
'This is some test text. Test test what if you have multiple keywords.',
50,
'some test',
'This is <span class="highlight">some</span> <span class="highlight">test</span> text.'
. ' <span class="highlight">Test</span> <span class="highlight">test</span> what if you have...'
],
[
'Here is some text & HTML included',
20,
'html',
'... text &amp; <span class="highlight">HTML</span> inc...'
],
[
'A dog ate a cat while looking at a Foobar',
100,
'a',
// test that it does not highlight too much (eg every a)
'A dog ate a cat while looking at a Foobar',
],
[
'A dog ate a cat while looking at a Foobar',
100,
'ate',
// it should highlight 3 letters or more.
'A dog <span class="highlight">ate</span> a cat while looking at a Foobar',
]
];
}
/**
* @dataProvider providerContextSummary
* @param string $originalValue Input
* @param int $limit Numer of characters
* @param string $keywords Keywords to highlight
* @param string $expectedValue Expected output (XML encoded safely)
*/
public function testContextSummary($originalValue, $limit, $keywords, $expectedValue)
{
$text = DBField::create_field('Text', $originalValue);
$result = $text->obj('ContextSummary', [$limit, $keywords])->forTemplate();
// it should highlight 3 letters or more.
$this->assertEquals(
'A dog <span class="highlight">ate</span> a cat while looking at a Foobar',
$textObj->ContextSummary(100, $testKeyword3a)
);
$this->assertEquals($expectedValue, $result);
}
public function testRAW() {

View File

@ -159,7 +159,7 @@ class DatabaseTest extends SapphireTest {
}
public function testTransactions() {
$conn = DB::getConn();
$conn = DB::get_conn();
if(!$conn->supportsTransactions()) {
$this->markTestSkipped("DB Doesn't support transactions");
return;

View File

@ -24,8 +24,8 @@ class ViewableDataTest extends SapphireTest {
$this->assertEquals($htmlString, $htmlField->obj('ATT')->forTemplate());
$this->assertEquals($textString, $htmlField->obj('RAW')->forTemplate());
$this->assertEquals('\"', $htmlField->obj('JS')->forTemplate());
$this->assertEquals($textString, $htmlField->obj('HTML')->forTemplate());
$this->assertEquals($textString, $htmlField->obj('XML')->forTemplate());
$this->assertEquals($htmlString, $htmlField->obj('HTML')->forTemplate());
$this->assertEquals($htmlString, $htmlField->obj('XML')->forTemplate());
$textField = DBField::create_field('Text', $textString);
$this->assertEquals($htmlString, $textField->forTemplate());