silverstripe-framework/ORM/FieldType/DBText.php

270 lines
7.1 KiB
PHP

<?php
namespace SilverStripe\ORM\FieldType;
use Convert;
use NullableField;
use TextareaField;
use TextField;
use Config;
use SilverStripe\ORM\DB;
use InvalidArgumentException;
use TextParser;
/**
* Represents a variable-length string of up to 2 megabytes, designed to store raw text
*
* Example definition via {@link DataObject::$db}:
* <code>
* static $db = array(
* "MyDescription" => "Text",
* );
* </code>
*
* @see DBHTMLText
* @see DBHTMLVarchar
* @see Varchar
*
* @package framework
* @subpackage orm
*/
class DBText extends DBString {
private static $casting = array(
"BigSummary" => "Text",
"ContextSummary" => "HTMLFragment", // Always returns HTML as it contains formatting and highlighting
"FirstParagraph" => "Text",
"FirstSentence" => "Text",
"LimitSentences" => "Text",
"Summary" => "Text",
);
/**
* (non-PHPdoc)
* @see DBField::requireField()
*/
public function requireField() {
$charset = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'charset');
$collation = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'collation');
$parts = [
'datatype' => 'mediumtext',
'character set' => $charset,
'collate' => $collation,
'default' => $this->defaultVal,
'arrayValue' => $this->arrayValue
];
$values = [
'type' => 'text',
'parts' => $parts
];
DB::require_field($this->tableName, $this->name, $values);
}
/**
* Limit sentences, can be controlled by passing an integer.
*
* @param int $maxSentences The amount of sentences you want.
* @return string
*/
public function LimitSentences($maxSentences = 2) {
if(!is_numeric($maxSentences)) {
throw new InvalidArgumentException("Text::LimitSentence() expects one numeric argument");
}
$value = $this->Plain();
if( !$value ) {
return "";
}
// Do a word-search
$words = preg_split('/\s+/', $value);
$sentences = 0;
foreach ($words as $i => $word) {
if (preg_match('/(!|\?|\.)$/', $word) && !preg_match('/(Dr|Mr|Mrs|Ms|Miss|Sr|Jr|No)\.$/i', $word)) {
$sentences++;
if($sentences >= $maxSentences) {
return implode(' ', array_slice($words, 0, $i + 1));
}
}
}
// Failing to find the number of sentences requested, fallback to a logical default
if($maxSentences > 1) {
return $value;
} else {
// If searching for a single sentence (and there are none) just do a text summary
return $this->Summary(20);
}
}
/**
* Return the first string that finishes with a period (.) in this text.
*
* @return string
*/
public function FirstSentence() {
return $this->LimitSentences(1);
}
/**
* Builds a basic summary, up to a maximum number of words
*
* @param int $maxWords
* @param string $add
* @return string
*/
public function Summary($maxWords = 50, $add = '...') {
// Get plain-text version
$value = $this->Plain();
if(!$value) {
return '';
}
// 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)) . $add;
}
// add each sentence while there are enough words to do so
$result = '';
do {
// Add next sentence
$result .= ' ' . array_shift( $sentences );
// If more sentences to process, count number of words
if($sentences) {
$wordCount += count(preg_split('#\s+#', $sentences[0]));
}
} while($wordCount < $maxWords && $sentences && trim( $sentences[0]));
return trim($result);
}
/**
* Get first paragraph
*
* @return string
*/
public function FirstParagraph() {
$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
* highlighting the search term.
*
* @param int $characters Number of characters in the summary
* @param string $keywords Supplied string ("keywords"). Will fall back to 'Search' querystring arg.
* @param bool $highlight Add a highlight <span> element around search query?
* @param string $prefix Prefix text
* @param string $suffix Suffix text
* @return string HTML string with context
*/
public function ContextSummary(
$characters = 500, $keywords = null, $highlight = true, $prefix = "... ", $suffix = "..."
) {
if(!$keywords) {
// Use the default "Search" request variable (from SearchForm)
$keywords = isset($_REQUEST['Search']) ? $_REQUEST['Search'] : '';
}
// Get raw text value, but XML encode it (as we'll be merging with HTML tags soon)
$text = nl2br(Convert::raw2xml($this->Plain()));
$keywords = Convert::raw2xml($keywords);
// Find the search string
$position = (int) stripos($text, $keywords);
// We want to search string to be in the middle of our block to give it some context
$position = max(0, $position - ($characters / 2));
if($position > 0) {
// We don't want to start mid-word
$position = max(
(int) strrpos(substr($text, 0, $position), ' '),
(int) strrpos(substr($text, 0, $position), "\n")
);
}
$summary = substr($text, $position, $characters);
$stringPieces = explode(' ', $keywords);
if($highlight) {
// Add a span around all key words from the search term as well
if($stringPieces) {
foreach($stringPieces as $stringPiece) {
if(strlen($stringPiece) > 2) {
// Maintain case of original string
$summary = preg_replace(
'/' . preg_quote($stringPiece, '/') . '/i',
'<span class="highlight">$0</span>',
$summary
);
}
}
}
}
$summary = trim($summary);
// Add leading / trailing '...' if trimmed on either end
if($position > 0) {
$summary = $prefix . $summary;
}
if(strlen($text) > ($characters + $position)) {
$summary = $summary . $suffix;
}
return $summary;
}
/**
* Allows a sub-class of TextParser to be rendered.
*
* @see TextParser for implementation details.
* @param string $parser Class name of parser (Must extend {@see TextParser})
* @return DBField Parsed value in the appropriate type
*/
public function Parse($parser) {
$reflection = new \ReflectionClass($parser);
if($reflection->isAbstract() || !$reflection->isSubclassOf('TextParser')) {
throw new InvalidArgumentException("Invalid parser {$parser}");
}
/** @var TextParser $obj */
$obj = \Injector::inst()->createWithArgs($parser, [$this->forTemplate()]);
return $obj->parse();
}
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));
} else {
// Automatically determine null (empty string)
return new TextareaField($this->name, $title);
}
}
public function scaffoldSearchField($title = null) {
return new TextField($this->name, $title);
}
}