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 * @config
* @see DBDateTime::nice_format * @see DBDateTime::nice_format
* @see Time::nice_format * @see DBTime::nice_format
*/ */
private static $nice_format = 'd/m/Y'; private static $nice_format = 'd/m/Y';
public function setValue($value, $record = null, $markChanged = true) { public function setValue($value, $record = null, $markChanged = true) {
if($value === false || $value === null || (is_string($value) && !strlen($value))) { 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 // 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 * Returns the date in the format specified by the config value nice_format, or dd/mm/yy by default
*/ *
* @return string
*/
public function Nice() { 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 * Returns the date in US format: “01/18/2006
*
* @return string
*/ */
public function NiceUS() { 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 * Returns the year from the given date
*
* @return string
*/ */
public function Year() { public function Year() {
if($this->value) return $this->Format('Y'); return $this->Format('Y');
} }
/** /**
* Returns the Full day, of the given date. * Returns the Full day, of the given date.
*
* @return string
*/ */
public function Day(){ 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. * Returns a full textual representation of a month, such as January.
*
* @return string
*/ */
public function Month() { public function Month() {
if($this->value) return $this->Format('F'); return $this->Format('F');
} }
/** /**
* Returns the short version of the month such as Jan * Returns the short version of the month such as Jan
*
* @return string
*/ */
public function ShortMonth() { public function ShortMonth() {
if($this->value) return $this->Format('M'); return $this->Format('M');
} }
/** /**
@ -126,25 +138,27 @@ class DBDate extends DBField {
* @return string * @return string
*/ */
public function DayOfMonth($includeOrdinal = false) { public function DayOfMonth($includeOrdinal = false) {
if($this->value) {
$format = 'j'; $format = 'j';
if ($includeOrdinal) $format .= 'S'; if ($includeOrdinal) $format .= 'S';
return $this->Format($format); return $this->Format($format);
} }
}
/** /**
* Returns the date in the format 24 December 2006 * Returns the date in the format 24 December 2006
*
* @return string
*/ */
public function Long() { 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 * Returns the date in the format 24 Dec 2006
*
* @return string
*/ */
public function Full() { 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); $date = new DateTime($this->value);
return $date->format($format); return $date->format($format);
} }
return null;
} }
/** /**
@ -173,6 +188,7 @@ class DBDate extends DBField {
if($this->value) { if($this->value) {
return strftime($formattingString, strtotime($this->value)); return strftime($formattingString, strtotime($this->value));
} }
return null;
} }
/** /**
@ -217,12 +233,28 @@ class DBDate extends DBField {
else return "$d1 - $d2 $m1 $y1"; else return "$d1 - $d2 $m1 $y1";
} }
/**
* Return string in RFC822 format
*
* @return string
*/
public function Rfc822() { 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() { 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() { public function Rfc3339() {
@ -249,7 +281,9 @@ class DBDate extends DBField {
* @return String * @return String
*/ */
public function Ago($includeSeconds = true, $significance = 2) { public function Ago($includeSeconds = true, $significance = 2) {
if($this->value) { if(!$this->value) {
return null;
}
$time = DBDatetime::now()->Format('U'); $time = DBDatetime::now()->Format('U');
if(strtotime($this->value) == $time || $time > strtotime($this->value)) { if(strtotime($this->value) == $time || $time > strtotime($this->value)) {
return _t( return _t(
@ -267,7 +301,6 @@ class DBDate extends DBField {
); );
} }
} }
}
/** /**
* @param boolean $includeSeconds Show seconds, or just round to "less than a minute". * @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 * @return string The resulting formatted period
*/ */
public function TimeDiffIn($format) { public function TimeDiffIn($format) {
if(!$this->value) return false; if(!$this->value) {
return null;
}
$time = DBDatetime::now()->Format('U'); $time = DBDatetime::now()->Format('U');
$ago = abs($time - strtotime($this->value)); $ago = abs($time - strtotime($this->value));
@ -333,6 +368,9 @@ class DBDate extends DBField {
case "years": case "years":
$span = round($ago/86400/365); $span = round($ago/86400/365);
return ($span != 1) ? "{$span} "._t("Date.YEARS", "years") : "{$span} "._t("Date.YEAR", "year"); 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 Convert;
use Exception; use Exception;
use DatetimeField; use DatetimeField;
use InvalidArgumentException;
use Zend_Date; use Zend_Date;
use TemplateGlobalProvider; use TemplateGlobalProvider;
use DateTime; 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' * 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'). * by default (e.g. '31/01/2014 2:23pm').
*
* @return string Formatted date and time. * @return string Formatted date and time.
*/ */
public function Nice() { 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'. * 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. * @return string Formatted date and time.
*/ */
public function Nice24() { 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'. * Returns the date using the format string 'd/m/Y' e.g. '28/02/2014'.
*
* @return string Formatted date. * @return string Formatted date.
*/ */
public function 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'. * Returns the time in 12-hour format using the format string 'g:ia' e.g. '1:32pm'.
*
* @return string Formatted time. * @return string Formatted time.
*/ */
public function 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'. * Returns the time in 24-hour format using the format string 'H:i' e.g. '13:32'.
*
* @return string Formatted time. * @return string Formatted time.
*/ */
public function Time24() { 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. * @return string Formatted date and time.
*/ */
public function URLDatetime() { 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) { public function scaffoldFormField($title = null, $params = null) {
@ -187,7 +193,7 @@ class DBDatetime extends DBDate implements TemplateGlobalProvider {
if(self::$mock_now) { if(self::$mock_now) {
return self::$mock_now; return self::$mock_now;
} else { } 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) { if($datetime instanceof DBDatetime) {
self::$mock_now = $datetime; self::$mock_now = $datetime;
} elseif(is_string($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 { } 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() { public static function get_template_global_variables() {
return array( 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> * </code>
* *
* @param string $name * @param string $name
* @param string|array $enum A string containing a comma separated list of options or an * @param string|array $enum A string containing a comma separated list of options or an array of Vals.
* array of Vals. * @param string $default The default option, which is either NULL or one of the items in the enumeration.
* @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) { public function __construct($name = null, $enum = NULL, $default = NULL) {
if($enum) { if($enum) {
@ -107,10 +105,14 @@ class DBEnum extends DBString {
/** /**
* Return a dropdown field suitable for editing this field. * 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 * @return DropdownField
*/ */
public function formField($title = null, $name = null, $hasEmpty = false, $value = "", $form = null, public function formField($title = null, $name = null, $hasEmpty = false, $value = "", $emptyString = null) {
$emptyString = null) {
if(!$title) { if(!$title) {
$title = $this->getName(); $title = $this->getName();
@ -119,7 +121,7 @@ class DBEnum extends DBString {
$name = $this->getName(); $name = $this->getName();
} }
$field = new DropdownField($name, $title, $this->enumValues(false), $value, $form); $field = new DropdownField($name, $title, $this->enumValues(false), $value);
if($hasEmpty) { if($hasEmpty) {
$field->setEmptyString($emptyString); $field->setEmptyString($emptyString);
} }
@ -127,10 +129,7 @@ class DBEnum extends DBString {
return $field; return $field;
} }
/** public function scaffoldFormField($title = null) {
* @return DropdownField
*/
public function scaffoldFormField($title = null, $params = null) {
return $this->formField($title); return $this->formField($title);
} }
@ -141,7 +140,7 @@ class DBEnum extends DBString {
*/ */
public function scaffoldSearchField($title = null) { public function scaffoldSearchField($title = null) {
$anyText = _t('Enum.ANY', 'Any'); $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 FormField;
use SearchFilter; use SearchFilter;
use SilverStripe\ORM\Connect\SS_Query;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Queries\SQLSelect;
use ViewableData; use ViewableData;
use Convert; use Convert;
use Object; use Object;
@ -256,7 +256,7 @@ abstract class DBField extends ViewableData {
* gets you the default representations * gets you the default representations
* of all columns. * of all columns.
* *
* @param SS_Query $query * @param SQLSelect $query
*/ */
public function addToQuery(&$query) { public function addToQuery(&$query) {
@ -288,7 +288,8 @@ abstract class DBField extends ViewableData {
* @return string * @return string
*/ */
public function forTemplate() { 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 * @return string
*/ */
public function XML(){ public function XML() {
return Convert::raw2xml($this->RAW()); return Convert::raw2xml($this->RAW());
} }
@ -379,7 +380,7 @@ abstract class DBField extends ViewableData {
* @return string * @return string
*/ */
public function CDATA() { public function CDATA() {
return $this->forTemplate(); return $this->XML();
} }
/** /**
@ -402,7 +403,7 @@ abstract class DBField extends ViewableData {
if(empty($fieldName)) { if(empty($fieldName)) {
throw new \BadMethodCallException("DBField::saveInto() Called on a nameless '" . get_class($this) . "' object"); 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 * search filters (note: parameter hack now in place to pass in the required full path - using $this->name
* won't work) * won't work)
* *
* @param string|bool $name
* @param string $name Override name of this field * @param string $name Override name of this field
* @return SearchFilter * @return SearchFilter
*/ */

View File

@ -4,11 +4,9 @@ namespace SilverStripe\ORM\FieldType;
use Injector; use Injector;
use HTTP; use HTTP;
use ShortcodeParser;
use DOMDocument;
use HTMLEditorField; use HTMLEditorField;
use ShortcodeParser;
use TextField; use TextField;
use Exception;
/** /**
* Represents a large text field that contains HTML content. * Represents a large text field that contains HTML content.
@ -32,20 +30,10 @@ class DBHTMLText extends DBText {
private static $casting = array( private static $casting = array(
"AbsoluteLinks" => "HTMLFragment", "AbsoluteLinks" => "HTMLFragment",
// DBText summary methods - override to HTMLFragment // DBString conversion / summary methods
"BigSummary" => "HTMLFragment", // Not overridden, but returns HTML instead of plain text.
"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",
"LowerCase" => "HTMLFragment", "LowerCase" => "HTMLFragment",
"UpperCase" => "HTMLFragment", "UpperCase" => "HTMLFragment",
"NoHTML" => "Text", // Actually stays same as DBString cast
); );
/** /**
@ -134,141 +122,6 @@ class DBHTMLText extends DBText {
return parent::setOptions($options); 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() { public function RAW() {
if ($this->processShortcodes) { if ($this->processShortcodes) {
return ShortcodeParser::get_active()->parse($this->value); return ShortcodeParser::get_active()->parse($this->value);
@ -287,6 +140,7 @@ class DBHTMLText extends DBText {
} }
public function forTemplate() { public function forTemplate() {
// Suppress XML encoding for DBHtmlText
return $this->RAW(); return $this->RAW();
} }
@ -364,23 +218,34 @@ class DBHTMLText extends DBText {
return true; return true;
} }
public function scaffoldFormField($title = null, $params = null) { public function scaffoldFormField($title = null) {
return new HTMLEditorField($this->name, $title); return new HTMLEditorField($this->name, $title);
} }
public function scaffoldSearchField($title = null, $params = null) { public function scaffoldSearchField($title = null) {
return new TextField($this->name, $title); return new TextField($this->name, $title);
} }
/** /**
* Get plain-text version
*
* @return string * @return string
*/ */
public function NoHTML() public function Plain() {
{
// Preserve line breaks // Preserve line breaks
$text = preg_replace('/\<br(\s*)?\/?\>/i', "\n", $this->RAW()); $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 $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 * Enable shortcode parsing on this field
* *
@ -69,6 +76,7 @@ class DBHTMLVarchar extends DBVarchar {
} }
public function forTemplate() { public function forTemplate() {
// Suppress XML encoding for DBHtmlText
return $this->RAW(); return $this->RAW();
} }
@ -88,15 +96,53 @@ class DBHTMLVarchar extends DBVarchar {
public function CDATA() { public function CDATA() {
return sprintf( return sprintf(
'<![CDATA[%s]]>', '<![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); return new HTMLEditorField($this->name, $title, 1);
} }

View File

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

View File

@ -54,14 +54,20 @@ class DBMultiEnum extends DBEnum {
/** /**
* Return a {@link CheckboxSetField} suitable for editing this field * 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, public function formField($title = null, $name = null, $hasEmpty = false, $value = "", $emptyString = null) {
$emptyString = null) {
if(!$title) $title = $this->name; if(!$title) $title = $this->name;
if(!$name) $name = $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; return $field;
} }

View File

@ -80,5 +80,6 @@ class DBPolymorphicForeignKey extends DBComposite {
if($id && $class && is_subclass_of($class, 'SilverStripe\ORM\DataObject')) { if($id && $class && is_subclass_of($class, 'SilverStripe\ORM\DataObject')) {
return DataObject::get_by_id($class, $id); 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\DB;
use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObject;
/** /**
* A special type Int field used for primary keys. * A special type Int field used for primary keys.
* *

View File

@ -2,8 +2,6 @@
namespace SilverStripe\ORM\FieldType; namespace SilverStripe\ORM\FieldType;
use Convert;
/** /**
* An abstract base class for the string field types (i.e. Varchar and Text) * 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( private static $casting = array(
"LimitCharacters" => "Text", "LimitCharacters" => "Text",
"LimitCharactersToClosestWord" => "Text", "LimitCharactersToClosestWord" => "Text",
'LimitWordCount' => 'Text', "LimitWordCount" => "Text",
"LowerCase" => "Text", "LowerCase" => "Text",
"UpperCase" => "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 || (!$this->getNullifyEmpty() && $value === ''); // Remove this stupid exemption in 4.0
} }
/**
* (non-PHPdoc)
* @see core/model/fieldtypes/DBField#prepValueForDB($value)
*/
public function prepValueForDB($value) { public function prepValueForDB($value) {
if(!$this->nullifyEmpty && $value === '') { if(!$this->nullifyEmpty && $value === '') {
return $value; return $value;
@ -158,17 +152,11 @@ abstract class DBString extends DBField {
* @return string * @return string
*/ */
public function LimitCharacters($limit = 20, $add = '...') { public function LimitCharacters($limit = 20, $add = '...') {
$value = trim($this->RAW()); $value = $this->Plain();
if($this->stat('escape_type') == 'xml') { if(mb_strlen($value) <= $limit) {
$value = strip_tags($value); return $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;
} }
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 int $limit Number of characters to limit by
* @param string $add Ellipsis to add to the end of truncated string * @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 = '...') { public function LimitCharactersToClosestWord($limit = 20, $add = '...') {
// Strip HTML tags if they exist in the field // Safely convert to plain text
$value = strip_tags($this->RAW()); $value = $this->Plain();
// Determine if value exceeds limit before limiting characters // Determine if value exceeds limit before limiting characters
$exceedsLimit = mb_strlen($value) > $limit; if(mb_strlen($value) <= $limit) {
return $value;
// 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');
} }
// 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; return $value;
} }
@ -211,23 +198,21 @@ abstract class DBString extends DBField {
* @return string * @return string
*/ */
public function LimitWordCount($numWords = 26, $add = '...') { public function LimitWordCount($numWords = 26, $add = '...') {
$value = trim(Convert::xml2raw($this->RAW())); $value = $this->Plain();
$ret = explode(' ', $value, $numWords + 1); $words = explode(' ', $value);
if(count($words) <= $numWords) {
if(count($ret) <= $numWords - 1) { return $value;
$ret = $value;
} else {
array_pop($ret);
$ret = implode(' ', $ret) . $add;
} }
return $ret; // Limit
$words = array_slice($words, 0, $numWords);
return implode(' ', $words) . $add;
} }
/** /**
* Converts the current value for this StringField to lowercase. * Converts the current value for this StringField to lowercase.
* *
* @return string * @return string Text with lowercase (HTML for some subclasses)
*/ */
public function LowerCase() { public function LowerCase() {
return mb_strtolower($this->RAW()); return mb_strtolower($this->RAW());
@ -236,7 +221,7 @@ abstract class DBString extends DBField {
/** /**
* Converts the current value for this StringField to uppercase. * Converts the current value for this StringField to uppercase.
* *
* @return string * @return string Text with uppercase (HTML for some subclasses)
*/ */
public function UpperCase() { public function UpperCase() {
return mb_strtoupper($this->RAW()); return mb_strtoupper($this->RAW());
@ -247,7 +232,7 @@ abstract class DBString extends DBField {
* *
* @return string Plain text * @return string Plain text
*/ */
public function NoHTML() { public function Plain() {
return $this->RAW(); return trim($this->RAW());
} }
} }

View File

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

View File

@ -47,7 +47,7 @@ class DBTime extends DBField {
* @return string * @return string
*/ */
public function Nice() { 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 * @return string Time in 24 hour format
*/ */
public function Nice24() { 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 * @return string The date in the requested format
*/ */
public function Format($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 ) { 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 the first letter of the string followed by a .
*
* @return string
*/ */
public function Initial() { public function Initial() {
if($this->exists()) { if($this->exists()) {
$value = $this->RAW(); $value = $this->RAW();
return $value[0] . '.'; return $value[0] . '.';
} }
return null;
} }
/** /**
* Ensure that the given value is an absolute URL. * Ensure that the given value is an absolute URL.
*
* @return string
*/ */
public function URL() { public function URL() {
$value = $this->RAW(); $value = $this->RAW();
if(preg_match('#^[a-zA-Z]+://#', $value)) return $value; if(preg_match('#^[a-zA-Z]+://#', $value)) {
else return "http://" . $value; return $value;
} else {
return "http://" . $value;
}
} }
/** /**
@ -103,11 +111,7 @@ class DBVarchar extends DBString {
return str_replace("\n", '\par ', $this->RAW()); return str_replace("\n", '\par ', $this->RAW());
} }
/** public function scaffoldFormField($title = null) {
* (non-PHPdoc)
* @see DBField::scaffoldFormField()
*/
public function scaffoldFormField($title = null, $params = null) {
if(!$this->nullifyEmpty) { if(!$this->nullifyEmpty) {
// Allow the user to select if it's null instead of automatically assuming empty string is // Allow the user to select if it's null instead of automatically assuming empty string is
return new NullableField(new TextField($this->name, $title)); 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 * @param int|bool $end end date to count down to
* @return array * @return array
*/ */
private function getDefaultOptions($start=false, $end=false) { private function getDefaultOptions($start = null, $end = null) {
if (!$start) $start = (int)date('Y'); if (!$start) {
if (!$end) $end = 1900; $start = (int)date('Y');
}
if (!$end) {
$end = 1900;
}
$years = array(); $years = array();
for($i=$start;$i>=$end;$i--) { for($i=$start;$i>=$end;$i--) {
$years[$i] = $i; $years[$i] = $i;

View File

@ -311,7 +311,14 @@ class RSSFeed_Entry extends ViewableData {
* @return DBField Returns the description of the entry. * @return DBField Returns the description of the entry.
*/ */
public function Description() { 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. * Convert XML to raw text.
* @uses html2raw() * @uses html2raw()
* @todo Currently &#xxx; entries are stripped; they should be converted * @todo Currently &#xxx; entries are stripped; they should be converted
* @param mixed $val
* @return array|string
*/ */
public static function xml2raw($val) { public static function xml2raw($val) {
if(is_array($val)) { if(is_array($val)) {
@ -234,6 +236,7 @@ class Convert {
* false by default. * false by default.
* @param boolean $disableExternals Disables the loading of external entities. false by default. * @param boolean $disableExternals Disables the loading of external entities. false by default.
* @return array * @return array
* @throws Exception
*/ */
public static function xml2array($val, $disableDoctypes = false, $disableExternals = false) { public static function xml2array($val, $disableDoctypes = false, $disableExternals = false) {
// Check doctype // Check doctype
@ -242,6 +245,7 @@ class Convert {
} }
// Disable external entity loading // Disable external entity loading
$oldVal = null;
if($disableExternals) $oldVal = libxml_disable_entity_loader($disableExternals); if($disableExternals) $oldVal = libxml_disable_entity_loader($disableExternals);
try { try {
$xml = new SimpleXMLElement($val); $xml = new SimpleXMLElement($val);
@ -263,7 +267,7 @@ class Convert {
* @return mixed * @return mixed
*/ */
protected static function recursiveXMLToArray($xml) { protected static function recursiveXMLToArray($xml) {
if(is_object($xml) && get_class($xml) == 'SimpleXMLElement') { if($xml instanceof SimpleXMLElement) {
$attributes = $xml->attributes(); $attributes = $xml->attributes();
foreach($attributes as $k => $v) { foreach($attributes as $k => $v) {
if($v) $a[$k] = (string) $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. `$Field` by itself to allow the casting helper to determine the best encoding itself.
</div> </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 html fields as `HTMLText(['whitelist=meta,link'])`, or use a `ShortcodeHTMLText` as
a shorthand substitute. a shorthand substitute.
* `FormField->dontEscape` has been removed. Escaping is now managed on a class by class basis. * `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 ## New API
@ -104,6 +105,11 @@
* `FormAction::setValidationExempt` can be used to turn on or off form validation for individual actions * `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. * `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. * `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). * `FormField::Title` and `FormField::RightTitle` are now cast as plain text by default (but can be overridden).
### Front-end build tooling for CMS interface ### Front-end build tooling for CMS interface
@ -174,7 +180,7 @@ admin/font/ => admin/client/dist/font/
* History.js * History.js
* `debugmethods` querystring argument has been removed from debugging. * `debugmethods` querystring argument has been removed from debugging.
* The following ClassInfo methods are now deprecated: * The following ClassInfo methods are now deprecated:
* `ClassInfo::baseDataClass` - Use `DataObject::getSchema()->baseDataClass()` instead. * `ClassInfo::baseDataClass` - Use `DataObject::getSchema()->baseDataClass()` instead.
* `ClassInfo::table_for_object_field` - Use `DataObject::getSchema()->tableForField()` instead * `ClassInfo::table_for_object_field` - Use `DataObject::getSchema()->tableForField()` instead

View File

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

View File

@ -105,7 +105,7 @@ class BBCodeParser extends TextParser {
); );
} }
public function useable_tagsHTML(){ public function useable_tagsHTML() {
$useabletags = "<ul class='bbcodeExamples'>"; $useabletags = "<ul class='bbcodeExamples'>";
foreach($this->usable_tags()->toArray() as $tag){ foreach($this->usable_tags()->toArray() as $tag){
$useabletags = $useabletags."<li><span>".$tag->Example."</span></li>"; $useabletags = $useabletags."<li><span>".$tag->Example."</span></li>";
@ -120,20 +120,13 @@ class BBCodeParser extends TextParser {
* @return DBField * @return DBField
*/ */
public function parse() { 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(); $p = new SSHTMLBBCodeParser();
$this->content = $p->qparse($this->content); $this->content = $p->qparse($this->content);
unset($p); 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) { if($this->config()->allow_smilies) {
$smilies = array( $smilies = array(
'#(?<!\w):D(?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/grin.gif'> ", // :D '#(?<!\w):D(?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/grin.gif'> ", // :D

View File

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

View File

@ -10,7 +10,7 @@
<item> <item>
<title>$Title.XML</title> <title>$Title.XML</title>
<link>$AbsoluteLink.XML</link> <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> <% if $Date %><pubDate>$Date.Rfc822</pubDate>
<% else %><pubDate>$Created.Rfc822</pubDate><% end_if %> <% else %><pubDate>$Created.Rfc822</pubDate><% end_if %>
<% if $Author %><dc:creator>$Author.XML</dc:creator><% 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>'; $value = '<p>üåäö&amp;ÜÅÄÖ</p>';
foreach ($htmlFields as $stringField) { foreach ($htmlFields as $stringField) {
$stringField = DBField::create_field($stringField, $value); $stringObj = DBField::create_field($stringField, $value);
$this->assertEquals('üåäö&amp;ÜÅÄ...', $stringField->LimitCharacters(8));
// 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', 'åäö')->UpperCase());
$this->assertEquals('åäö', DBField::create_field('Text', 'ÅÄÖ')->LowerCase()); $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 { 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() { public function providerLimitCharacters()
$cases = array( {
'The little brown fox jumped over the lazy cow.' => 'The little brown fox...', // HTML characters are stripped safely
'<p>This is some text in a paragraph.</p>' => 'This is some text in...', return [
'This text contains &amp; in it' => 'This text contains &amp;...' ['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;...'],
foreach($cases as $originalValue => $expectedValue) { ];
$textObj = new DBHTMLText('Test');
$textObj->setValue($originalValue);
$this->assertEquals($expectedValue, $textObj->LimitCharacters());
}
} }
public function testSummaryBasics() { /**
$cases = array( * Test {@link DBHTMLText->LimitCharacters()}
'<h1>Should not take header</h1><p>Should take paragraph</p>' => 'Should take paragraph', * @dataProvider providerLimitCharacters
'<p>Should strip <b>tags, but leave</b> text</p>' => 'Should strip tags, but leave text', * @param string $originalValue
'<p>Unclosed tags <br>should not phase it</p>' => 'Unclosed tags should not phase it', * @param string $expectedValue
'<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>' public function testLimitCharacters($originalValue, $expectedValue) {
=> 'Second paragraph', $textObj = DBField::create_field('HTMLFragment', $originalValue);
' <img src="hello" /><p>Second paragraph</p><p>should not cause errors or appear in output</p>' $result = $textObj->obj('LimitCharacters')->forTemplate();
=> 'Second paragraph', $this->assertEquals($expectedValue, $result);
'<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());
}
} }
public function testSummaryLimits() { /**
$cases = array( * @return 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...', public function providerLimitCharactersToClosestWord()
'<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...' // 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) { // Complete words less than the character limit don't get truncated, ellipsis not added
$textObj = new DBHTMLText('Test'); ['<p>Lorem ipsum</p>', 24, 'Lorem ipsum'],
$textObj->setValue($originalValue); ['<p>Lorem</p>', 24, 'Lorem'],
$this->assertEquals($expectedValue, $textObj->Summary(5, 3, '...')); ['', 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() { public function testSummaryEndings() {
$cases = array( $cases = array(
'...', ' -> more', '' '...',
' -> more',
''
); );
$orig = '<p>Cut it off, cut it off</p>'; $orig = '<p>Cut it off, cut it off</p>';
$match = 'Cut it off, cut'; $match = 'Cut it off, cut';
foreach($cases as $add) { foreach($cases as $add) {
$textObj = new DBHTMLText(); $textObj = DBField::create_field('HTMLFragment', $orig);
$textObj->setValue($orig); $result = $textObj->obj('Summary', [4, $add])->forTemplate();
$this->assertEquals($match.$add, $textObj->Summary(4, 0, $add)); $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); public function providerFirstSentence()
$this->assertEquals($match, $textObj->Summary(4, 10, '')); {
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( * @dataProvider providerFirstSentence
'It\'s got a <p<> tag, but<p junk true>This doesn\'t <a id="boo">make</b class="wa"> < ><any< sense</p>' * @param string $originalValue
=> 'This doesn\'t make any', * @param string $expectedValue
'This doesn\'t <a style="much horray= true>even</b> < ><have< a <i>p tag' => 'This doesn\'t even have' */
); public function testFirstSentence($originalValue, $expectedValue) {
$textObj = DBField::create_field('HTMLFragment', $originalValue);
foreach($cases as $orig => $match) { $result = $textObj->obj('FirstSentence')->forTemplate();
$textObj = new DBHTMLText(); $this->assertEquals($expectedValue, $result);
$textObj->setValue($orig);
$this->assertEquals($match, $textObj->Summary(4, 0, ''));
}
} }
public function testFirstSentence() { public function providerToPlain()
$many = str_repeat('many ', 100); {
$cases = array( return [
'<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.', '<p><img />Lots of <strong>HTML <i>nested</i></strong> tags',
"<h1>should ignore</h1><p>Sentence with {$many}words. Second sentence.</p>" 'Lots of HTML nested tags',
=> "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>' '<p>Multi</p><p>Paragraph<br>Also has multilines.</p>',
=> 'This classic picture book features a repetitive format that lends itself to audience interaction.' "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(); * @dataProvider providerToPlain
$textObj->setValue($orig); * @param string $html
$this->assertEquals($match, $textObj->FirstSentence()); * @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() { public function testRAW() {
$data = DBField::create_field('HTMLText', 'This &amp; This'); $data = DBField::create_field('HTMLFragment', 'This &amp; This');
$this->assertEquals($data->RAW(), 'This &amp; This'); $this->assertEquals('This &amp; This', $data->RAW());
$data = DBField::create_field('HTMLText', 'This & This'); $data = DBField::create_field('HTMLFragment', 'This & This');
$this->assertEquals($data->RAW(), 'This & This'); $this->assertEquals('This & This', $data->RAW());
} }
public function testXML() { public function testXML() {
$data = DBField::create_field('HTMLText', 'This & This'); $data = DBField::create_field('HTMLFragment', 'This & This');
$this->assertEquals($data->XML(), 'This &amp; 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() { public function testHTML() {
$data = DBField::create_field('HTMLText', 'This & This'); $data = DBField::create_field('HTMLFragment', 'This & This');
$this->assertEquals($data->HTML(), 'This &amp; 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() { public function testJS() {
$data = DBField::create_field('HTMLText', '"this is a test"'); $data = DBField::create_field('HTMLText', '"this is &amp; test"');
$this->assertEquals($data->JS(), '\"this is a test\"'); $this->assertEquals('\"this is \x26amp; test\"', $data->JS());
} }
public function testATT() { public function testATT() {
$data = DBField::create_field('HTMLText', '"this is a test"'); // HTML Fragment
$this->assertEquals($data->ATT(), '&quot;this is a test&quot;'); $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() { function testExists() {
@ -322,3 +565,15 @@ class DBHTMLTextTest extends SapphireTest {
ShortcodeParser::set_active('default'); 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 * @package framework
* @subpackage tests * @subpackage tests
*/ */
@ -15,211 +17,224 @@ class DBTextTest extends SapphireTest {
/** /**
* Test {@link Text->LimitCharacters()} * Test {@link Text->LimitCharacters()}
*/ */
public function testLimitCharacters() { public function providerLimitCharacters()
$cases = array( {
'The little brown fox jumped over the lazy cow.' => 'The little brown fox...', // Plain text values always encoded safely
'<p>This is some text in a paragraph.</p>' => '<p>This is some text...' // 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'); * Test {@link Text->LimitCharacters()}
$textObj->setValue($originalValue); * @dataProvider providerLimitCharacters
$this->assertEquals($expectedValue, $textObj->LimitCharacters()); * @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()} * Test {@link Text->LimitCharactersToClosestWord()}
* @dataProvider providerLimitCharactersToClosestWord
*
* @param string $originalValue Raw string input
* @param int $limit
* @param string $expectedValue Expected template value
*/ */
public function testLimitCharactersToClosestWord() { public function testLimitCharactersToClosestWord($originalValue, $limit, $expectedValue) {
$cases = array( $textObj = DBField::create_field('Text', $originalValue);
/* Standard words limited, ellipsis added if truncated */ $result = $textObj->obj('LimitCharactersToClosestWord', [$limit])->forTemplate();
'Lorem ipsum dolor sit amet' => 'Lorem ipsum dolor sit...', $this->assertEquals($expectedValue, $result);
/* 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));
}
} }
/** /**
* Test {@link Text->LimitWordCount()} * Test {@link Text->LimitWordCount()}
*/ */
public function testLimitWordCount() { public function providerLimitWordCount() {
$cases = array( return [
/* Standard words limited, ellipsis added if truncated */ // Standard words limited, ellipsis added if truncated
'The little brown fox jumped over the lazy cow.' => 'The little brown...', ['The little brown fox jumped over the lazy cow.', 3, 'The little brown...'],
' This text has white space around the ends ' => 'This text has...', [' 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 */ // 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 ['Two words', 3, 'Two words'], // Two words shouldn't have an ellipsis
'One' => 'One', // Neither should one word ['These three words', 3, 'These three words'], // Three words shouldn't have an ellipsis
'' => '', // No words produces nothing! ['One', 3, 'One'], // Neither should one word
['', 3, ''], // No words produces nothing!
/* HTML tags get stripped out, leaving the raw text */ // Text with special characters
'<p>Text inside a paragraph tag should also work</p>' => 'Text inside a...', ['Nice & Easy', 3, 'Nice &amp; Easy'],
'<p><span>Text nested inside another tag should also work</span></p>' => 'Text nested inside...', ['One & Two & Three', 3, 'One &amp; Two...'],
'<p>Two words</p>' => 'Two words'
);
foreach($cases as $originalValue => $expectedValue) { // HTML stored in non-html fields is treated literally.
$textObj = new DBText('Test'); // If storing HTML you should use DBHTMLText instead
$textObj->setValue($originalValue); ['<p>Text inside a paragraph tag should also work</p>', 3, '&lt;p&gt;Text inside a...'],
$this->assertEquals($expectedValue, $textObj->LimitWordCount(3)); ['<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() { public function testLimitWordCount($originalValue, $limit, $expectedValue) {
$cases = array( $textObj = DBField::create_field('Text', $originalValue);
'<p>Stuff & stuff</p>' => 'Stuff &amp;...', $result = $textObj->obj('LimitWordCount', [$limit])->forTemplate();
"Stuff\nBlah Blah Blah" => "Stuff\nBlah Blah...", $this->assertEquals($expectedValue, $result);
"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));
}
} }
/** /**
* Test {@link Text->LimitSentences()}
*/ */
public function testLimitSentences() { public function providerLimitSentences()
$cases = array( {
'' => '', return [
'First sentence.' => 'First sentence.', ['', 2, ''],
'First sentence. Second sentence' => 'First sentence. Second sentence.', ['First sentence.', 2, 'First sentence.'],
'<p>First sentence.</p>' => 'First sentence.', ['First sentence. Second sentence.', 2, 'First sentence. Second 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.'
);
foreach($cases as $originalValue => $expectedValue) { // HTML stored in non-html fields is treated literally.
$textObj = new DBText('Test'); // If storing HTML you should use DBHTMLText instead
$textObj->setValue($originalValue); ['<p>First sentence.</p>', 2, '&lt;p&gt;First sentence.&lt;/p&gt;'],
$this->assertEquals($expectedValue, $textObj->LimitSentences(2)); ['<p>First sentence. Second sentence. Third sentence</p>', 2, '&lt;p&gt;First sentence. Second sentence.'],
} ];
}
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());
}
} }
/** /**
* Test {@link Text->BigSummary()} * Test {@link DBText->LimitSentences()}
*/ *
public function testBigSummaryPlain() { * @dataProvider providerLimitSentences
$cases = array( * @param string $originalValue
'<p>This text has multiple sentences. Big Summary uses this to split sentences up.</p>' * @param int $limit Number of sentences
=> 'This text has multiple...', * @param string $expectedValue Expected template value
'This text does not have multiple sentences' => 'This text does not...', */
'Very short' => 'Very short', 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) { public function providerFirstSentence()
$textObj = DBField::create_field('Text', $originalValue); {
$this->assertEquals($expectedValue, $textObj->BigSummary(4, true)); 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()} * @dataProvider providerFirstSentence
*/ * @param string $originalValue
public function testBigSummary() { * @param string $expectedValue
$cases = array( */
'<strong>This</strong> text has multiple sentences. Big Summary uses this to split sentences up.</p>' public function testFirstSentence($originalValue, $expectedValue) {
=> '<strong>This</strong> text has multiple...', $textObj = DBField::create_field('Text', $originalValue);
'This text does not have multiple sentences' => 'This text does not...', $result = $textObj->obj('FirstSentence')->forTemplate();
'Very short' => 'Very short', $this->assertEquals($expectedValue, $result);
'' => ''
);
foreach($cases as $originalValue => $expectedValue) {
$textObj = DBField::create_field('Text', $originalValue);
$this->assertEquals($expectedValue, $textObj->BigSummary(4, false));
}
} }
public function testContextSummary() { /**
$testString1 = '<p>This is some text. It is a test</p>'; * each test is in the format input, charactere limit, highlight, expected output
$testKeywords1 = 'test'; *
* @return array
$testString2 = '<p>This is some test text. Test test what if you have multiple keywords.</p>'; */
$testKeywords2 = 'some test'; public function providerContextSummary()
{
$testString3 = '<p>A dog ate a cat while looking at a Foobar</p>'; return [
$testKeyword3 = 'a'; [
$testKeyword3a = 'ate'; 'This is some text. It is a test',
20,
$textObj = DBField::create_field('Text', $testString1, 'Text'); 'test',
'... text. It is a <span class="highlight">test</span>'
$this->assertEquals( ],
'... text. It is a <span class="highlight">test</span>...', [
$textObj->ContextSummary(20, $testKeywords1) // Retains case of original string
); 'This is some test text. Test test what if you have multiple keywords.',
50,
$textObj->setValue($testString2); 'some test',
'This is <span class="highlight">some</span> <span class="highlight">test</span> text.'
$this->assertEquals( . ' <span class="highlight">Test</span> <span class="highlight">test</span> what if you have...'
'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) 'Here is some text & HTML included',
); 20,
'html',
$textObj->setValue($testString3); '... text &amp; <span class="highlight">HTML</span> inc...'
],
// test that it does not highlight too much (eg every a) [
$this->assertEquals( 'A dog ate a cat while looking at a Foobar',
'A dog ate a cat while looking at a Foobar', 100,
$textObj->ContextSummary(100, $testKeyword3) '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. // it should highlight 3 letters or more.
$this->assertEquals( $this->assertEquals($expectedValue, $result);
'A dog <span class="highlight">ate</span> a cat while looking at a Foobar',
$textObj->ContextSummary(100, $testKeyword3a)
);
} }
public function testRAW() { public function testRAW() {

View File

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

View File

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