PageRenderTime 28ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/model/fieldtypes/Text.php

http://github.com/silverstripe/sapphire
PHP | 353 lines | 178 code | 55 blank | 120 comment | 54 complexity | 1979f92f08a9e8e7019c64426e2838e6 MD5 | raw file
Possible License(s): BSD-3-Clause, MIT, CC-BY-3.0, GPL-2.0, AGPL-1.0, LGPL-2.1
  1. <?php
  2. /**
  3. * Represents a variable-length string of up to 2 megabytes, designed to store raw text
  4. *
  5. * Example definition via {@link DataObject::$db}:
  6. * <code>
  7. * static $db = array(
  8. * "MyDescription" => "Text",
  9. * );
  10. * </code>
  11. *
  12. * @see HTMLText
  13. * @see HTMLVarchar
  14. * @see Varchar
  15. *
  16. * @package sapphire
  17. * @subpackage model
  18. */
  19. class Text extends StringField {
  20. static $casting = array(
  21. "AbsoluteLinks" => "Text",
  22. "BigSummary" => "Text",
  23. "ContextSummary" => "Text",
  24. "FirstParagraph" => "Text",
  25. "FirstSentence" => "Text",
  26. "LimitCharacters" => "Text",
  27. "LimitSentences" => "Text",
  28. "Summary" => "Text",
  29. 'EscapeXML' => 'Text',
  30. 'LimitWordCount' => 'Text',
  31. 'LimitWordCountXML' => 'HTMLText',
  32. 'NoHTML' => 'Text',
  33. );
  34. /**
  35. * (non-PHPdoc)
  36. * @see DBField::requireField()
  37. */
  38. function requireField() {
  39. $parts=Array('datatype'=>'mediumtext', 'character set'=>'utf8', 'collate'=>'utf8_general_ci', 'arrayValue'=>$this->arrayValue);
  40. $values=Array('type'=>'text', 'parts'=>$parts);
  41. DB::requireField($this->tableName, $this->name, $values, $this->default);
  42. }
  43. /**
  44. * Limit this field's content by a number of words.
  45. * CAUTION: This is not XML safe. Please use
  46. * {@link LimitWordCountXML()} instead.
  47. *
  48. * @param int $numWords Number of words to limit by
  49. * @param string $add Ellipsis to add to the end of truncated string
  50. * @return string
  51. */
  52. function LimitWordCount($numWords = 26, $add = '...') {
  53. $this->value = trim(Convert::xml2raw($this->value));
  54. $ret = explode(' ', $this->value, $numWords + 1);
  55. if(count($ret) <= $numWords - 1) {
  56. $ret = $this->value;
  57. } else {
  58. array_pop($ret);
  59. $ret = implode(' ', $ret) . $add;
  60. }
  61. return $ret;
  62. }
  63. /**
  64. * Return the value of the field stripped of html tags
  65. * @return string
  66. */
  67. function NoHTML() {
  68. return strip_tags($this->value);
  69. }
  70. /**
  71. * Return the value of the field with XML tags escaped.
  72. *
  73. * @deprecated 3.0 Use DBField->XML() instead.
  74. * @return string
  75. */
  76. function EscapeXML() {
  77. Deprecation::notice('3.0', 'Use DBField->XML() instead.');
  78. return str_replace(array('&','<','>','"'), array('&amp;','&lt;','&gt;','&quot;'), $this->value);
  79. }
  80. /**
  81. * Return the value of the field with relative links converted to absolute urls.
  82. * @return string
  83. */
  84. function AbsoluteLinks() {
  85. return HTTP::absoluteURLs($this->value);
  86. }
  87. /**
  88. * Limit the number of words of the current field's
  89. * content. This is XML safe, so characters like &
  90. * are converted to &amp;
  91. *
  92. * @param int $numWords Number of words to limit by
  93. * @param string $add Ellipsis to add to the end of truncated string
  94. * @return string
  95. */
  96. function LimitWordCountXML($numWords = 26, $add = '...') {
  97. $ret = $this->LimitWordCount($numWords, $add);
  98. return Convert::raw2xml($ret);
  99. }
  100. /**
  101. * Limit sentences, can be controlled by passing an integer.
  102. * @param int $sentCount The amount of sentences you want.
  103. */
  104. function LimitSentences($sentCount = 2) {
  105. if(!is_numeric($sentCount)) user_error("Text::LimitSentence() expects one numeric argument", E_USER_NOTICE);
  106. $output = array();
  107. $data = trim(Convert::xml2raw($this->value));
  108. $sentences = explode('.', $data);
  109. if ($sentCount == 0) return '';
  110. for($i = 0; $i < $sentCount; $i++) {
  111. if(isset($sentences[$i])) {
  112. $sentence = trim($sentences[$i]);
  113. if(!empty($sentence)) $output[] .= $sentence;
  114. }
  115. }
  116. return count($output)==0 ? '' : implode($output, '. ') . '.';
  117. }
  118. /**
  119. * Caution: Not XML/HTML-safe - does not respect closing tags.
  120. */
  121. function FirstSentence() {
  122. $data = Convert::xml2raw( $this->value );
  123. if( !$data ) return "";
  124. $sentences = explode( '.', $data );
  125. if( count( $sentences ) )
  126. return $sentences[0] . '.';
  127. else
  128. return $this->Summary(20);
  129. }
  130. /**
  131. * Caution: Not XML/HTML-safe - does not respect closing tags.
  132. */
  133. function Summary($maxWords = 50) {
  134. // get first sentence?
  135. // this needs to be more robust
  136. $value = Convert::xml2raw( $this->value /*, true*/ );
  137. if(!$value) return '';
  138. // grab the first paragraph, or, failing that, the whole content
  139. if(strpos($value, "\n\n")) $value = substr($value, 0, strpos($value, "\n\n"));
  140. $sentences = explode('.', $value);
  141. $count = count(explode(' ', $sentences[0]));
  142. // if the first sentence is too long, show only the first $maxWords words
  143. if($count > $maxWords) {
  144. return implode( ' ', array_slice(explode( ' ', $sentences[0] ), 0, $maxWords)) . '...';
  145. }
  146. // add each sentence while there are enough words to do so
  147. $result = '';
  148. do {
  149. $result .= trim(array_shift( $sentences )).'.';
  150. if(count($sentences) > 0) {
  151. $count += count(explode(' ', $sentences[0]));
  152. }
  153. // Ensure that we don't trim half way through a tag or a link
  154. $brokenLink = (
  155. substr_count($result,'<') != substr_count($result,'>')) ||
  156. (substr_count($result,'<a') != substr_count($result,'</a')
  157. );
  158. } while(($count < $maxWords || $brokenLink) && $sentences && trim( $sentences[0]));
  159. if(preg_match('/<a[^>]*>/', $result) && !preg_match( '/<\/a>/', $result)) $result .= '</a>';
  160. return Convert::raw2xml($result);
  161. }
  162. /**
  163. * Performs the same function as the big summary, but doesnt trim new paragraphs off data.
  164. * Caution: Not XML/HTML-safe - does not respect closing tags.
  165. */
  166. function BigSummary($maxWords = 50, $plain = 1) {
  167. $result = "";
  168. // get first sentence?
  169. // this needs to be more robust
  170. if($plain) $data = Convert::xml2raw($this->value, true);
  171. if(!$data) return "";
  172. $sentences = explode('.', $data);
  173. $count = count(explode(' ', $sentences[0]));
  174. // if the first sentence is too long, show only the first $maxWords words
  175. if($count > $maxWords) {
  176. return implode(' ', array_slice(explode( ' ', $sentences[0] ), 0, $maxWords)) . '...';
  177. }
  178. // add each sentence while there are enough words to do so
  179. do {
  180. $result .= trim(array_shift($sentences));
  181. if($sentences) {
  182. $result .= '. ';
  183. $count += count(explode(' ', $sentences[0]));
  184. }
  185. // Ensure that we don't trim half way through a tag or a link
  186. $brokenLink = (
  187. substr_count($result,'<') != substr_count($result,'>')) ||
  188. (substr_count($result,'<a') != substr_count($result,'</a')
  189. );
  190. } while(($count < $maxWords || $brokenLink) && $sentences && trim($sentences[0]));
  191. if(preg_match( '/<a[^>]*>/', $result) && !preg_match( '/<\/a>/', $result)) {
  192. $result .= '</a>';
  193. }
  194. return $result;
  195. }
  196. /**
  197. * Caution: Not XML/HTML-safe - does not respect closing tags.
  198. */
  199. function FirstParagraph($plain = 1) {
  200. // get first sentence?
  201. // this needs to be more robust
  202. if($plain && $plain != 'html') {
  203. $data = Convert::xml2raw($this->value, true);
  204. if(!$data) return "";
  205. // grab the first paragraph, or, failing that, the whole content
  206. $pos = strpos($data, "\n\n");
  207. if($pos) $data = substr($data, 0, $pos);
  208. return $data;
  209. } else {
  210. if(strpos($this->value, "</p>") === false) return $this->value;
  211. $data = substr($this->value, 0, strpos($this->value, "</p>") + 4);
  212. if(strlen($data) < 20 && strpos($this->value, "</p>", strlen($data))) {
  213. $data = substr($this->value, 0, strpos( $this->value, "</p>", strlen($data)) + 4 );
  214. }
  215. return $data;
  216. }
  217. }
  218. /**
  219. * Perform context searching to give some context to searches, optionally
  220. * highlighting the search term.
  221. *
  222. * @param int $characters Number of characters in the summary
  223. * @param boolean $string Supplied string ("keywords")
  224. * @param boolean $striphtml Strip HTML?
  225. * @param boolean $highlight Add a highlight <span> element around search query?
  226. * @param String prefix text
  227. * @param String suffix
  228. *
  229. * @return string
  230. */
  231. function ContextSummary($characters = 500, $string = false, $striphtml = true, $highlight = true, $prefix = "... ", $suffix = "...") {
  232. if(!$string) $string = $_REQUEST['Search']; // Use the default "Search" request variable (from SearchForm)
  233. // Remove HTML tags so we don't have to deal with matching tags
  234. $text = $striphtml ? $this->NoHTML() : $this->value;
  235. // Find the search string
  236. $position = (int) stripos($text, $string);
  237. // We want to search string to be in the middle of our block to give it some context
  238. $position = max(0, $position - ($characters / 2));
  239. if($position > 0) {
  240. // We don't want to start mid-word
  241. $position = max((int) strrpos(substr($text, 0, $position), ' '), (int) strrpos(substr($text, 0, $position), "\n"));
  242. }
  243. $summary = substr($text, $position, $characters);
  244. $stringPieces = explode(' ', $string);
  245. if($highlight) {
  246. // Add a span around all key words from the search term as well
  247. if($stringPieces) {
  248. foreach($stringPieces as $stringPiece) {
  249. if(strlen($stringPiece) > 2) {
  250. $summary = str_ireplace($stringPiece, "<span class=\"highlight\">$stringPiece</span>", $summary);
  251. }
  252. }
  253. }
  254. }
  255. $summary = trim($summary);
  256. if($position > 0) $summary = $prefix . $summary;
  257. if(strlen($this->value) > ($characters + $position)) $summary = $summary . $suffix;
  258. return $summary;
  259. }
  260. /**
  261. * Allows a sub-class of TextParser to be rendered.
  262. *
  263. * @see TextParser for implementation details.
  264. * @return string
  265. */
  266. function Parse($parser = "TextParser") {
  267. if($parser == "TextParser" || is_subclass_of($parser, "TextParser")) {
  268. $obj = new $parser($this->value);
  269. return $obj->parse();
  270. } else {
  271. // Fallback to using raw2xml and show a warning
  272. // TODO Don't kill script execution, we can continue without losing complete control of the app
  273. user_error("Couldn't find an appropriate TextParser sub-class to create (Looked for '$parser'). Make sure it sub-classes TextParser and that you've done ?flush=1.", E_USER_WARNING);
  274. return Convert::raw2xml($this->value);
  275. }
  276. }
  277. /**
  278. * (non-PHPdoc)
  279. * @see DBField::scaffoldFormField()
  280. */
  281. public function scaffoldFormField($title = null, $params = null) {
  282. if(!$this->nullifyEmpty) {
  283. // Allow the user to select if it's null instead of automatically assuming empty string is
  284. return new NullableField(new TextareaField($this->name, $title));
  285. } else {
  286. // Automatically determine null (empty string)
  287. return new TextareaField($this->name, $title);
  288. }
  289. }
  290. /**
  291. * (non-PHPdoc)
  292. * @see DBField::scaffoldSearchField()
  293. */
  294. public function scaffoldSearchField($title = null, $params = null) {
  295. return new TextField($this->name, $title);
  296. }
  297. }
  298. ?>