diff --git a/ORM/Connect/DBSchemaManager.php b/ORM/Connect/DBSchemaManager.php
index bc390e1efc4..7fb1fbfeded 100644
--- a/ORM/Connect/DBSchemaManager.php
+++ b/ORM/Connect/DBSchemaManager.php
@@ -7,6 +7,9 @@
use Object;
use Director;
use SilverStripe\ORM\FieldType\DBPrimaryKey;
+use SilverStripe\ORM\FieldType\DBField;
+use SilverStripe\ORM\FieldType\DBPrimaryKey;
+
/**
* Represents and handles all schema management for a database
@@ -110,8 +113,7 @@ public function preparedQuery($sql, $parameters, $errorLevel = E_USER_ERROR) {
/**
* Initiates a schema update within a single callback
*
- * @var callable $callback
- * @throws Exception
+ * @param callable $callback
*/
public function schemaUpdate($callback) {
// Begin schema update
@@ -153,15 +155,10 @@ public function schemaUpdate($callback) {
break;
}
}
- } catch(Exception $ex) {
- $error = $ex;
- }
- // finally {
+ } finally {
$this->schemaUpdateTransaction = null;
$this->schemaIsUpdating = false;
- // }
-
- if($error) throw $error;
+ }
}
/**
@@ -303,7 +300,7 @@ protected function transInitTable($table) {
* - array('fields' => array('A','B','C'), 'type' => 'index/unique/fulltext'): This gives you full
* control over the index.
* @param boolean $hasAutoIncPK A flag indicating that the primary key on this table is an autoincrement type
- * @param string|array $options SQL statement to append to the CREATE TABLE call.
+ * @param array $options Create table options (ENGINE, etc.)
* @param array|bool $extensions List of extensions
*/
public function requireTable($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK = true,
@@ -314,7 +311,7 @@ public function requireTable($table, $fieldSchema = null, $indexSchema = null, $
$this->alterationMessage("Table $table: created", "created");
} else {
if (Config::inst()->get('SilverStripe\ORM\Connect\DBSchemaManager', 'check_and_repair_on_build')) {
- $this->checkAndRepairTable($table, $options);
+ $this->checkAndRepairTable($table);
}
// Check if options changed
@@ -353,12 +350,14 @@ public function requireTable($table, $fieldSchema = null, $indexSchema = null, $
$fieldSpec = substr($fieldSpec, 0, $pos);
}
+ /** @var DBField $fieldObj */
$fieldObj = Object::create_from_string($fieldSpec, $fieldName);
- $fieldObj->arrayValue = $arrayValue;
+ $fieldObj->setArrayValue($arrayValue);
$fieldObj->setTable($table);
if($fieldObj instanceof DBPrimaryKey) {
+ /** @var DBPrimaryKey $fieldObj */
$fieldObj->setAutoIncrement($hasAutoIncPK);
}
@@ -383,7 +382,7 @@ public function dontRequireTable($table) {
$suffix = '';
while (isset($this->tableList[strtolower("_obsolete_{$table}$suffix")])) {
$suffix = $suffix
- ? ($suffix + 1)
+ ? ((int)$suffix + 1)
: 2;
}
$this->renameTable($table, "_obsolete_{$table}$suffix");
@@ -414,6 +413,8 @@ public function requireIndex($table, $index, $spec) {
$specString = $this->convertIndexSpec($spec);
// Check existing index
+ $oldSpecString = null;
+ $indexKey = null;
if (!$newTable) {
$indexKey = $this->indexKey($table, $index, $spec);
$indexList = $this->indexList($table);
@@ -502,6 +503,7 @@ protected function determineIndexType($spec) {
* Converts an array or string index spec into a universally useful array
*
* @see convertIndexSpec() for approximate inverse
+ * @param string $name Index name
* @param string|array $spec
* @return array The resulting spec array with the required fields name, type, and value
*/
@@ -544,7 +546,9 @@ protected function parseIndexSpec($name, $spec) {
*/
protected function convertIndexSpec($indexSpec) {
// Return already converted spec
- if (!is_array($indexSpec)) return $indexSpec;
+ if (!is_array($indexSpec)) {
+ return $indexSpec;
+ }
// Combine elements into standard string format
return "{$indexSpec['type']} ({$indexSpec['value']})";
@@ -647,7 +651,7 @@ public function requireField($table, $field, $spec) {
$new = preg_split("/'\s*,\s*'/", $newStr);
$oldStr = preg_replace("/(^$enumtype\s*\(')|('$\).*)/i", "", $fieldValue);
- $old = preg_split("/'\s*,\s*'/", $newStr);
+ $old = preg_split("/'\s*,\s*'/", $oldStr);
$holder = array();
foreach ($old as $check) {
@@ -690,7 +694,7 @@ public function dontRequireField($table, $fieldName) {
$suffix = '';
while (isset($fieldList[strtolower("_obsolete_{$fieldName}$suffix")])) {
$suffix = $suffix
- ? ($suffix + 1)
+ ? ((int)$suffix + 1)
: 2;
}
$this->renameField($table, $fieldName, "_obsolete_{$fieldName}$suffix");
@@ -942,10 +946,10 @@ abstract public function fieldList($table);
* This allows the cached values for a table's field list to be erased.
* If $tablename is empty, then the whole cache is erased.
*
- * @param string|bool $tableName
+ * @param string $tableName
* @return boolean
*/
- public function clearCachedFieldlist($tableName = false) {
+ public function clearCachedFieldlist($tableName = null) {
return true;
}
diff --git a/ORM/DB.php b/ORM/DB.php
index 137d9648b7b..963df4c5685 100644
--- a/ORM/DB.php
+++ b/ORM/DB.php
@@ -65,14 +65,6 @@ public static function set_conn(SS_Database $connection, $name = 'default') {
self::$connections[$name] = $connection;
}
- /**
- * @deprecated since version 4.0 Use DB::set_conn instead
- */
- public static function setConn(SS_Database $connection, $name = 'default') {
- Deprecation::notice('4.0', 'Use DB::set_conn instead');
- self::set_conn($connection, $name);
- }
-
/**
* Get the global database connection.
*
@@ -103,7 +95,10 @@ public static function getConn($name = 'default') {
*/
public static function get_schema($name = 'default') {
$connection = self::get_conn($name);
- if($connection) return $connection->getSchemaManager();
+ if($connection) {
+ return $connection->getSchemaManager();
+ }
+ return null;
}
/**
@@ -134,7 +129,10 @@ public static function build_sql(SQLExpression $expression, &$parameters, $name
*/
public static function get_connector($name = 'default') {
$connection = self::get_conn($name);
- if($connection) return $connection->getConnector();
+ if($connection) {
+ return $connection->getConnector();
+ }
+ return null;
}
/**
@@ -273,13 +271,6 @@ public static function connection_attempted() {
return self::$connection_attempted;
}
- /**
- * @deprecated since version 4.0 DB::getConnect was never implemented and is obsolete
- */
- public static function getConnect($parameters) {
- Deprecation::notice('4.0', 'DB::getConnect was never implemented and is obsolete');
- }
-
/**
* Execute the given SQL query.
* @param string $sql The SQL query to execute
@@ -371,7 +362,7 @@ public static function prepared_query($sql, $parameters, $errorLevel = E_USER_ER
*/
public static function manipulate($manipulation) {
self::$lastQuery = $manipulation;
- return self::get_conn()->manipulate($manipulation);
+ self::get_conn()->manipulate($manipulation);
}
/**
@@ -384,14 +375,6 @@ public static function get_generated_id($table) {
return self::get_conn()->getGeneratedID($table);
}
- /**
- * @deprecated since version 4.0 Use DB::get_generated_id instead
- */
- public static function getGeneratedID($table) {
- Deprecation::notice('4.0', 'Use DB::get_generated_id instead');
- return self::get_generated_id($table);
- }
-
/**
* Check if the connection to the database is active.
*
@@ -401,14 +384,6 @@ public static function is_active() {
return ($conn = self::get_conn()) && $conn->isActive();
}
- /**
- * @deprecated since version 4.0 Use DB::is_active instead
- */
- public static function isActive() {
- Deprecation::notice('4.0', 'Use DB::is_active instead');
- return self::is_active();
- }
-
/**
* Create the database and connect to it. This can be called if the
* initial database connection is not successful because the database
@@ -421,14 +396,6 @@ public static function create_database($database) {
return self::get_conn()->selectDatabase($database, true);
}
- /**
- * @deprecated since version 4.0 Use DB::create_database instead
- */
- public static function createDatabase($connect, $username, $password, $database) {
- Deprecation::notice('4.0', 'Use DB::create_database instead');
- return self::create_database($database);
- }
-
/**
* Create a new table.
* @param string $table The name of the table
@@ -438,7 +405,7 @@ public static function createDatabase($connect, $username, $password, $database)
* - 'MSSQLDatabase'/'MySQLDatabase'/'PostgreSQLDatabase' - database-specific options such as "engine"
* for MySQL.
* - 'temporary' - If true, then a temporary table will be created
- * @param array $advancedOptions
+ * @param array $advancedOptions Advanced creation options
* @return string The table name generated. This may be different from the table name, for example with
* temporary tables.
*/
@@ -448,14 +415,6 @@ public static function create_table($table, $fields = null, $indexes = null, $op
return self::get_schema()->createTable($table, $fields, $indexes, $options, $advancedOptions);
}
- /**
- * @deprecated since version 4.0 Use DB::create_table instead
- */
- public static function createTable($table, $fields = null, $indexes = null, $options = null) {
- Deprecation::notice('4.0', 'Use DB::create_table instead');
- return self::create_table($table, $fields, $indexes, $options);
- }
-
/**
* Create a new field on a table.
* @param string $table Name of the table.
@@ -466,14 +425,6 @@ public static function create_field($table, $field, $spec) {
return self::get_schema()->createField($table, $field, $spec);
}
- /**
- * @deprecated since version 4.0 Use DB::create_field instead
- */
- public static function createField($table, $field, $spec) {
- Deprecation::notice('4.0', 'Use DB::create_field instead');
- return self::create_field($table, $field, $spec);
- }
-
/**
* Generate the following table in the database, modifying whatever already exists
* as necessary.
@@ -492,18 +443,7 @@ public static function createField($table, $field, $spec) {
public static function require_table($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK = true,
$options = null, $extensions = null
) {
- return self::get_schema()->requireTable($table, $fieldSchema, $indexSchema, $hasAutoIncPK, $options,
- $extensions);
- }
-
- /**
- * @deprecated since version 4.0 Use DB::require_table instead
- */
- public static function requireTable($table, $fieldSchema = null, $indexSchema = null, $hasAutoIncPK = true,
- $options = null, $extensions = null
- ) {
- Deprecation::notice('4.0', 'Use DB::require_table instead');
- return self::require_table($table, $fieldSchema, $indexSchema, $hasAutoIncPK, $options, $extensions);
+ self::get_schema()->requireTable($table, $fieldSchema, $indexSchema, $hasAutoIncPK, $options, $extensions);
}
/**
@@ -514,15 +454,7 @@ public static function requireTable($table, $fieldSchema = null, $indexSchema =
* @param string $spec The field specification.
*/
public static function require_field($table, $field, $spec) {
- return self::get_schema()->requireField($table, $field, $spec);
- }
-
- /**
- * @deprecated since version 4.0 Use DB::require_field instead
- */
- public static function requireField($table, $field, $spec) {
- Deprecation::notice('4.0', 'Use DB::require_field instead');
- return self::require_field($table, $field, $spec);
+ self::get_schema()->requireField($table, $field, $spec);
}
/**
@@ -536,14 +468,6 @@ public static function require_index($table, $index, $spec) {
self::get_schema()->requireIndex($table, $index, $spec);
}
- /**
- * @deprecated since version 4.0 Use DB::require_index instead
- */
- public static function requireIndex($table, $index, $spec) {
- Deprecation::notice('4.0', 'Use DB::require_index instead');
- self::require_index($table, $index, $spec);
- }
-
/**
* If the given table exists, move it out of the way by renaming it to _obsolete_(tablename).
*
@@ -553,14 +477,6 @@ public static function dont_require_table($table) {
self::get_schema()->dontRequireTable($table);
}
- /**
- * @deprecated since version 4.0 Use DB::dont_require_table instead
- */
- public static function dontRequireTable($table) {
- Deprecation::notice('4.0', 'Use DB::dont_require_table instead');
- self::dont_require_table($table);
- }
-
/**
* See {@link SS_Database->dontRequireField()}.
*
@@ -571,14 +487,6 @@ public static function dont_require_field($table, $fieldName) {
self::get_schema()->dontRequireField($table, $fieldName);
}
- /**
- * @deprecated since version 4.0 Use DB::dont_require_field instead
- */
- public static function dontRequireField($table, $fieldName) {
- Deprecation::notice('4.0', 'Use DB::dont_require_field instead');
- self::dont_require_field($table, $fieldName);
- }
-
/**
* Checks a table's integrity and repairs it if necessary.
*
@@ -589,14 +497,6 @@ public static function check_and_repair_table($table) {
return self::get_schema()->checkAndRepairTable($table);
}
- /**
- * @deprecated since version 4.0 Use DB::check_and_repair_table instead
- */
- public static function checkAndRepairTable($table) {
- Deprecation::notice('4.0', 'Use DB::check_and_repair_table instead');
- self::check_and_repair_table($table);
- }
-
/**
* Return the number of rows affected by the previous operation.
*
@@ -606,14 +506,6 @@ public static function affected_rows() {
return self::get_conn()->affectedRows();
}
- /**
- * @deprecated since version 4.0 Use DB::affected_rows instead
- */
- public static function affectedRows() {
- Deprecation::notice('4.0', 'Use DB::affected_rows instead');
- return self::affected_rows();
- }
-
/**
* Returns a list of all tables in the database.
* The table names will be in lower case.
@@ -624,14 +516,6 @@ public static function table_list() {
return self::get_schema()->tableList();
}
- /**
- * @deprecated since version 4.0 Use DB::table_list instead
- */
- public static function tableList() {
- Deprecation::notice('4.0', 'Use DB::table_list instead');
- return self::table_list();
- }
-
/**
* Get a list of all the fields for the given table.
* Returns a map of field name => field spec.
@@ -643,14 +527,6 @@ public static function field_list($table) {
return self::get_schema()->fieldList($table);
}
- /**
- * @deprecated since version 4.0 Use DB::field_list instead
- */
- public static function fieldList($table) {
- Deprecation::notice('4.0', 'Use DB::field_list instead');
- return self::field_list($table);
- }
-
/**
* Enable supression of database messages.
*/
diff --git a/ORM/DataObject.php b/ORM/DataObject.php
index f6e4423cd1b..1cbe4b803bb 100644
--- a/ORM/DataObject.php
+++ b/ORM/DataObject.php
@@ -2598,9 +2598,10 @@ public function isChanged($fieldName = null, $changeLevel = self::CHANGE_STRICT)
*
* @param string $fieldName Name of the field
* @param mixed $val New field value
- * @return DataObject $this
+ * @return $this
*/
public function setField($fieldName, $val) {
+ $this->objCacheClear();
//if it's a has_one component, destroy the cache
if (substr($fieldName, -2) == 'ID') {
unset($this->components[substr($fieldName, 0, -2)]);
diff --git a/ORM/FieldType/DBBoolean.php b/ORM/FieldType/DBBoolean.php
index 077b1ea628f..406204ed9bc 100644
--- a/ORM/FieldType/DBBoolean.php
+++ b/ORM/FieldType/DBBoolean.php
@@ -42,9 +42,6 @@ public function NiceAsBoolean() {
return ($this->value) ? 'true' : 'false';
}
- /**
- * Saves this field to the given data object.
- */
public function saveInto($dataObject) {
$fieldName = $this->name;
if($fieldName) {
diff --git a/ORM/FieldType/DBComposite.php b/ORM/FieldType/DBComposite.php
index ecb9584d20b..e4a0199a45b 100644
--- a/ORM/FieldType/DBComposite.php
+++ b/ORM/FieldType/DBComposite.php
@@ -225,11 +225,14 @@ public function hasField($field) {
* @param string $field
* @param mixed $value
* @param bool $markChanged
+ * @return $this
*/
public function setField($field, $value, $markChanged = true) {
+ $this->objCacheClear();
+
// Skip non-db fields
if(!$this->hasField($field)) {
- return;
+ return $this;
}
// Set changed
@@ -246,6 +249,7 @@ public function setField($field, $value, $markChanged = true) {
// Set local record
$this->record[$field] = $value;
+ return $this;
}
/**
diff --git a/ORM/FieldType/DBDecimal.php b/ORM/FieldType/DBDecimal.php
index 79476e4948f..1f89bdb1470 100644
--- a/ORM/FieldType/DBDecimal.php
+++ b/ORM/FieldType/DBDecimal.php
@@ -63,9 +63,6 @@ public function requireField() {
DB::require_field($this->tableName, $this->name, $values);
}
- /**
- * @param DataObject $dataObject
- */
public function saveInto($dataObject) {
$fieldName = $this->name;
diff --git a/ORM/FieldType/DBField.php b/ORM/FieldType/DBField.php
index 43ff6e8884a..fe400324919 100644
--- a/ORM/FieldType/DBField.php
+++ b/ORM/FieldType/DBField.php
@@ -48,12 +48,32 @@
*/
abstract class DBField extends ViewableData {
+ /**
+ * Raw value of this field
+ *
+ * @var mixed
+ */
protected $value;
+ /**
+ * Table this field belongs to
+ *
+ * @var string
+ */
protected $tableName;
+ /**
+ * Name of this field
+ *
+ * @var string
+ */
protected $name;
+ /**
+ * Used for generating DB schema. {@see DBSchemaManager}
+ *
+ * @var array
+ */
protected $arrayValue;
/**
@@ -72,6 +92,19 @@ abstract class DBField extends ViewableData {
*/
private static $default_search_filter_class = 'PartialMatchFilter';
+ private static $casting = array(
+ 'ATT' => 'HTMLFragment',
+ 'CDATA' => 'HTMLFragment',
+ 'HTML' => 'HTMLFragment',
+ 'HTMLATT' => 'HTMLFragment',
+ 'JS' => 'HTMLFragment',
+ 'RAW' => 'HTMLFragment',
+ 'RAWURLATT' => 'HTMLFragment',
+ 'URLATT' => 'HTMLFragment',
+ 'XML' => 'HTMLFragment',
+ 'ProcessedRAW' => 'HTMLFragment',
+ );
+
/**
* @var $default mixed Default-value in the database.
* Might be overridden on DataObject-level, but still useful for setting defaults on
@@ -97,6 +130,7 @@ public function __construct($name = null) {
* @return DBField
*/
public static function create_field($className, $value, $name = null, $object = null) {
+ /** @var DBField $dbField */
$dbField = Object::create($className, $name, $object);
$dbField->setValue($value, null, false);
@@ -249,52 +283,105 @@ public function getTable() {
}
/**
+ * Determine 'default' casting for this field.
+ *
* @return string
*/
public function forTemplate() {
- return $this->XML();
+ return Convert::raw2xml($this->getValue());
}
+ /**
+ * Gets the value appropriate for a HTML attribute string
+ *
+ * @return string
+ */
public function HTMLATT() {
return Convert::raw2htmlatt($this->RAW());
}
+ /**
+ * urlencode this string
+ *
+ * @return string
+ */
public function URLATT() {
return urlencode($this->RAW());
}
+ /**
+ * rawurlencode this string
+ *
+ * @return string
+ */
public function RAWURLATT() {
return rawurlencode($this->RAW());
}
+ /**
+ * Gets the value appropriate for a HTML attribute string
+ *
+ * @return string
+ */
public function ATT() {
return Convert::raw2att($this->RAW());
}
+ /**
+ * Gets the raw value for this field.
+ * Note: Skips processors implemented via forTemplate()
+ *
+ * @return mixed
+ */
public function RAW() {
- return $this->value;
+ return $this->getValue();
}
+ /**
+ * Gets javascript string literal value
+ *
+ * @return string
+ */
public function JS() {
return Convert::raw2js($this->RAW());
}
/**
* Return JSON encoded value
+ *
* @return string
*/
public function JSON() {
return Convert::raw2json($this->RAW());
}
+ /**
+ * Alias for {@see XML()}
+ *
+ * @return string
+ */
public function HTML(){
- return Convert::raw2xml($this->RAW());
+ return $this->XML();
}
+ /**
+ * XML encode this value
+ *
+ * @return string
+ */
public function XML(){
return Convert::raw2xml($this->RAW());
}
+ /**
+ * Safely escape for XML string
+ *
+ * @return string
+ */
+ public function CDATA() {
+ return $this->forTemplate();
+ }
+
/**
* Returns the value to be set in the database to blank this field.
* Usually it's a choice between null, 0, and ''
@@ -307,14 +394,15 @@ public function nullValue() {
/**
* Saves this field to the given data object.
+ *
+ * @param DataObject $dataObject
*/
public function saveInto($dataObject) {
$fieldName = $this->name;
- if($fieldName) {
- $dataObject->$fieldName = $this->value;
- } else {
- user_error("DBField::saveInto() Called on a nameless '" . get_class($this) . "' object", E_USER_ERROR);
+ if(empty($fieldName)) {
+ throw new \BadMethodCallException("DBField::saveInto() Called on a nameless '" . get_class($this) . "' object");
}
+ $dataObject->$fieldName = $this->value;
}
/**
@@ -353,9 +441,10 @@ public function scaffoldSearchField($title = null) {
* won't work)
*
* @param string|bool $name
+ * @param string $name Override name of this field
* @return SearchFilter
*/
- public function defaultSearchFilter($name = false) {
+ public function defaultSearchFilter($name = null) {
$name = ($name) ? $name : $this->name;
$filterClass = $this->stat('default_search_filter_class');
return new $filterClass($name);
@@ -377,6 +466,22 @@ public function debug() {
}
public function __toString() {
- return $this->forTemplate();
+ return (string)$this->forTemplate();
+ }
+
+ /**
+ * @return array
+ */
+ public function getArrayValue() {
+ return $this->arrayValue;
+ }
+
+ /**
+ * @param array $value
+ * @return $this
+ */
+ public function setArrayValue($value) {
+ $this->arrayValue = $value;
+ return $this;
}
}
diff --git a/ORM/FieldType/DBHTMLText.php b/ORM/FieldType/DBHTMLText.php
index 73efceb1f71..43ad94eb785 100644
--- a/ORM/FieldType/DBHTMLText.php
+++ b/ORM/FieldType/DBHTMLText.php
@@ -14,6 +14,12 @@
* Represents a large text field that contains HTML content.
* This behaves similarly to {@link Text}, but the template processor won't escape any HTML content within it.
*
+ * Options can be specified in a $db config via one of the following:
+ * - "HTMLFragment(['shortcodes=true', 'whitelist=meta,link'])"
+ * - "HTMLFragment('whitelist=meta,link')"
+ * - "HTMLFragment(['shortcodes=true'])". "HTMLText" is also a synonym for this.
+ * - "HTMLFragment('shortcodes=true')"
+ *
* @see HTMLVarchar
* @see Text
* @see Varchar
@@ -25,34 +31,78 @@ class DBHTMLText extends DBText {
private static $escape_type = 'xml';
private static $casting = array(
- "AbsoluteLinks" => "HTMLText",
- "BigSummary" => "HTMLText",
- "ContextSummary" => "HTMLText",
- "FirstParagraph" => "HTMLText",
- "FirstSentence" => "HTMLText",
- "LimitCharacters" => "HTMLText",
- "LimitSentences" => "HTMLText",
- "Lower" => "HTMLText",
- "LowerCase" => "HTMLText",
- "Summary" => "HTMLText",
- "Upper" => "HTMLText",
- "UpperCase" => "HTMLText",
- 'EscapeXML' => 'HTMLText',
- 'LimitWordCount' => 'HTMLText',
- 'LimitWordCountXML' => 'HTMLText',
- 'NoHTML' => 'Text',
+ "AbsoluteLinks" => "HTMLFragment",
+ // DBText summary methods - override to HTMLFragment
+ "BigSummary" => "HTMLFragment",
+ "ContextSummary" => "HTMLFragment", // Same as DBText
+ "FirstParagraph" => "HTMLFragment",
+ "FirstSentence" => "HTMLFragment",
+ "LimitSentences" => "HTMLFragment",
+ "Summary" => "HTMLFragment",
+ // DBString conversion / summary methods - override to HTMLFragment
+ "LimitCharacters" => "HTMLFragment",
+ "LimitCharactersToClosestWord" => "HTMLFragment",
+ "LimitWordCount" => "HTMLFragment",
+ "LowerCase" => "HTMLFragment",
+ "UpperCase" => "HTMLFragment",
+ "NoHTML" => "Text", // Actually stays same as DBString cast
);
- protected $processShortcodes = true;
+ /**
+ * Enable shortcode parsing on this field
+ *
+ * @var bool
+ */
+ protected $processShortcodes = false;
- protected $whitelist = false;
+ /**
+ * Check if shortcodes are enabled
+ *
+ * @return bool
+ */
+ public function getProcessShortcodes() {
+ return $this->processShortcodes;
+ }
- public function __construct($name = null, $options = array()) {
- if(is_string($options)) {
- $options = array('whitelist' => $options);
- }
+ /**
+ * Set shortcodes on or off by default
+ *
+ * @param bool $process
+ * @return $this
+ */
+ public function setProcessShortcodes($process) {
+ $this->processShortcodes = (bool)$process;
+ return $this;
+ }
- return parent::__construct($name, $options);
+ /**
+ * List of html properties to whitelist
+ *
+ * @var array
+ */
+ protected $whitelist = [];
+
+ /**
+ * List of html properties to whitelist
+ *
+ * @return array
+ */
+ public function getWhitelist() {
+ return $this->whitelist;
+ }
+
+ /**
+ * Set list of html properties to whitelist
+ *
+ * @param array $whitelist
+ * @return $this
+ */
+ public function setWhitelist($whitelist) {
+ if(!is_array($whitelist)) {
+ $whitelist = preg_split('/\s*,\s*/', $whitelist);
+ }
+ $this->whitelist = $whitelist;
+ return $this;
}
/**
@@ -69,24 +119,52 @@ public function __construct($name = null, $options = array()) {
* Text nodes outside of HTML tags are filtered out by default, but may be included by adding
* the text() directive. E.g. 'link,meta,text()' will allow only and text at
* the root level.
+ *
+ * @return $this
*/
public function setOptions(array $options = array()) {
- parent::setOptions($options);
-
if(array_key_exists("shortcodes", $options)) {
- $this->processShortcodes = !!$options["shortcodes"];
+ $this->setProcessShortcodes(!!$options["shortcodes"]);
}
if(array_key_exists("whitelist", $options)) {
- if(is_array($options['whitelist'])) {
- $this->whitelist = $options['whitelist'];
- }
- else {
- $this->whitelist = preg_split('/,\s*/', $options['whitelist']);
- }
+ $this->setWhitelist($options['whitelist']);
}
+
+ 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
@@ -161,6 +239,11 @@ public function Summary($maxWords = 50, $flex = 15, $add = '...') {
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()
@@ -207,6 +290,18 @@ public function forTemplate() {
return $this->RAW();
}
+ /**
+ * Safely escape for XML string
+ *
+ * @return string
+ */
+ public function CDATA() {
+ return sprintf(
+ '',
+ str_replace(']]>', ']]]]>', $this->RAW())
+ );
+ }
+
public function prepValueForDB($value) {
return parent::prepValueForDB($this->whitelistContent($value));
}
@@ -277,6 +372,15 @@ public function scaffoldSearchField($title = null, $params = null) {
return new TextField($this->name, $title);
}
-}
-
+ /**
+ * @return string
+ */
+ public function NoHTML()
+ {
+ // Preserve line breaks
+ $text = preg_replace('/\ /i', "\n", $this->RAW());
+ // Convert back to plain text
+ return \Convert::xml2raw(strip_tags($text));
+ }
+}
diff --git a/ORM/FieldType/DBHTMLVarchar.php b/ORM/FieldType/DBHTMLVarchar.php
index f01f357ee2a..c8affc3c196 100644
--- a/ORM/FieldType/DBHTMLVarchar.php
+++ b/ORM/FieldType/DBHTMLVarchar.php
@@ -17,14 +17,55 @@ class DBHTMLVarchar extends DBVarchar {
private static $escape_type = 'xml';
- protected $processShortcodes = true;
+ /**
+ * Enable shortcode parsing on this field
+ *
+ * @var bool
+ */
+ protected $processShortcodes = false;
- public function setOptions(array $options = array()) {
- parent::setOptions($options);
+ /**
+ * Check if shortcodes are enabled
+ *
+ * @return bool
+ */
+ public function getProcessShortcodes() {
+ return $this->processShortcodes;
+ }
+ /**
+ * Set shortcodes on or off by default
+ *
+ * @param bool $process
+ * @return $this
+ */
+ public function setProcessShortcodes($process) {
+ $this->processShortcodes = (bool)$process;
+ return $this;
+ }
+ /**
+ * @param array $options
+ *
+ * Options accepted in addition to those provided by Text:
+ *
+ * - shortcodes: If true, shortcodes will be turned into the appropriate HTML.
+ * If false, shortcodes will not be processed.
+ *
+ * - whitelist: If provided, a comma-separated list of elements that will be allowed to be stored
+ * (be careful on relying on this for XSS protection - some seemingly-safe elements allow
+ * attributes that can be exploited, for instance )
+ * Text nodes outside of HTML tags are filtered out by default, but may be included by adding
+ * the text() directive. E.g. 'link,meta,text()' will allow only and text at
+ * the root level.
+ *
+ * @return $this
+ */
+ public function setOptions(array $options = array()) {
if(array_key_exists("shortcodes", $options)) {
- $this->processShortcodes = !!$options["shortcodes"];
+ $this->setProcessShortcodes(!!$options["shortcodes"]);
}
+
+ return parent::setOptions($options);
}
public function forTemplate() {
@@ -34,11 +75,21 @@ public function forTemplate() {
public function RAW() {
if ($this->processShortcodes) {
return ShortcodeParser::get_active()->parse($this->value);
- }
- else {
+ } else {
return $this->value;
}
+ }
+ /**
+ * Safely escape for XML string
+ *
+ * @return string
+ */
+ public function CDATA() {
+ return sprintf(
+ '',
+ str_replace(']]>', ']]]]>', $this->forTemplate())
+ );
}
public function exists() {
@@ -53,4 +104,15 @@ public function scaffoldSearchField($title = null) {
return new TextField($this->name, $title);
}
+ /**
+ * @return string
+ */
+ public function NoHTML()
+ {
+ // Preserve line breaks
+ $text = preg_replace('/\ /i', "\n", $this->RAW());
+ // Convert back to plain text
+ return \Convert::xml2raw(strip_tags($text));
+ }
+
}
diff --git a/ORM/FieldType/DBInt.php b/ORM/FieldType/DBInt.php
index 30f43ae67cd..423a1f84905 100644
--- a/ORM/FieldType/DBInt.php
+++ b/ORM/FieldType/DBInt.php
@@ -29,14 +29,14 @@ public function Formatted() {
}
public function requireField() {
- $parts=Array(
- 'datatype'=>'int',
- 'precision'=>11,
- 'null'=>'not null',
- 'default'=>$this->defaultVal,
- 'arrayValue'=>$this->arrayValue);
-
- $values=Array('type'=>'int', 'parts'=>$parts);
+ $parts = [
+ 'datatype' => 'int',
+ 'precision' => 11,
+ 'null' => 'not null',
+ 'default' => $this->defaultVal,
+ 'arrayValue' => $this->arrayValue
+ ];
+ $values = ['type' => 'int', 'parts' => $parts];
DB::require_field($this->tableName, $this->name, $values);
}
diff --git a/ORM/FieldType/DBString.php b/ORM/FieldType/DBString.php
index a28e1fcfff4..dda71107aac 100644
--- a/ORM/FieldType/DBString.php
+++ b/ORM/FieldType/DBString.php
@@ -24,7 +24,6 @@ abstract class DBString extends DBField {
"LimitCharacters" => "Text",
"LimitCharactersToClosestWord" => "Text",
'LimitWordCount' => 'Text',
- 'LimitWordCountXML' => 'HTMLText',
"LowerCase" => "Text",
"UpperCase" => "Text",
'NoHTML' => 'Text',
@@ -33,34 +32,70 @@ abstract class DBString extends DBField {
/**
* Construct a string type field with a set of optional parameters.
*
- * @param $name string The name of the field
- * @param $options array An array of options e.g. array('nullifyEmpty'=>false). See
+ * @param string $name string The name of the field
+ * @param array $options array An array of options e.g. array('nullifyEmpty'=>false). See
* {@link StringField::setOptions()} for information on the available options
*/
public function __construct($name = null, $options = array()) {
- // Workaround: The singleton pattern calls this constructor with true/1 as the second parameter, so we
- // must ignore it
- if(is_array($options)){
+ $options = $this->parseConstructorOptions($options);
+ if($options) {
$this->setOptions($options);
}
parent::__construct($name);
}
+ /**
+ * Parses the "options" parameter passed to the constructor. This could be a
+ * string value, or an array of options. Config specification might also
+ * encode "key=value" pairs in non-associative strings.
+ *
+ * @param mixed $options
+ * @return array The list of parsed options, or empty if there are none
+ */
+ protected function parseConstructorOptions($options) {
+ if(is_string($options)) {
+ $options = [$options];
+ }
+ if(!is_array($options)) {
+ return [];
+ }
+ $parsed = [];
+ foreach($options as $option => $value) {
+ // Workaround for inability for config args to support associative arrays
+ if(is_numeric($option) && strpos($value, '=') !== false) {
+ list($option, $value) = explode('=', $value);
+ $option = trim($option);
+ $value = trim($value);
+ }
+ // Convert bool values
+ if(strcasecmp($value, 'true') === 0) {
+ $value = true;
+ } elseif(strcasecmp($value, 'false') === 0) {
+ $value = false;
+ }
+ $parsed[$option] = $value;
+ }
+ return $parsed;
+ }
+
/**
* Update the optional parameters for this field.
- * @param array $options array of options
+ *
+ * @param array $options Array of options
* The options allowed are:
*
"nullifyEmpty"
* This is a boolean flag.
* True (the default) means that empty strings are automatically converted to nulls to be stored in
* the database. Set it to false to ensure that nulls and empty strings are kept intact in the database.
*
+ * @return $this
*/
public function setOptions(array $options = array()) {
if(array_key_exists("nullifyEmpty", $options)) {
$this->nullifyEmpty = $options["nullifyEmpty"] ? true : false;
}
+ return $this;
}
/**
@@ -110,7 +145,7 @@ public function prepValueForDB($value) {
* @return string
*/
public function forTemplate() {
- return nl2br($this->XML());
+ return nl2br(parent::forTemplate());
}
/**
@@ -170,9 +205,6 @@ public function LimitCharactersToClosestWord($limit = 20, $add = '...') {
/**
* Limit this field's content by a number of words.
*
- * CAUTION: This is not XML safe. Please use
- * {@link LimitWordCountXML()} instead.
- *
* @param int $numWords Number of words to limit by.
* @param string $add Ellipsis to add to the end of truncated string.
*
@@ -192,22 +224,6 @@ public function LimitWordCount($numWords = 26, $add = '...') {
return $ret;
}
- /**
- * Limit the number of words of the current field's
- * content. This is XML safe, so characters like &
- * are converted to &
- *
- * @param int $numWords Number of words to limit by.
- * @param string $add Ellipsis to add to the end of truncated string.
- *
- * @return string
- */
- public function LimitWordCountXML($numWords = 26, $add = '...') {
- $ret = $this->LimitWordCount($numWords, $add);
-
- return Convert::raw2xml($ret);
- }
-
/**
* Converts the current value for this StringField to lowercase.
*
@@ -219,6 +235,7 @@ public function LowerCase() {
/**
* Converts the current value for this StringField to uppercase.
+ *
* @return string
*/
public function UpperCase() {
@@ -226,11 +243,11 @@ public function UpperCase() {
}
/**
- * Return the value of the field stripped of html tags.
+ * Plain text version of this string
*
- * @return string
+ * @return string Plain text
*/
public function NoHTML() {
- return strip_tags($this->RAW());
+ return $this->RAW();
}
}
diff --git a/ORM/FieldType/DBText.php b/ORM/FieldType/DBText.php
index bb79fecdb50..5b50d89fb13 100644
--- a/ORM/FieldType/DBText.php
+++ b/ORM/FieldType/DBText.php
@@ -2,13 +2,14 @@
namespace SilverStripe\ORM\FieldType;
-use HTTP;
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
@@ -20,8 +21,8 @@
* );
*
*
- * @see HTMLText
- * @see HTMLVarchar
+ * @see DBHTMLText
+ * @see DBHTMLVarchar
* @see Varchar
*
* @package framework
@@ -30,17 +31,12 @@
class DBText extends DBString {
private static $casting = array(
- "AbsoluteLinks" => "Text",
"BigSummary" => "Text",
- "ContextSummary" => "Text",
+ "ContextSummary" => "HTMLText", // Always returns HTML as it contains formatting and highlighting
"FirstParagraph" => "Text",
"FirstSentence" => "Text",
- "LimitCharacters" => "Text",
"LimitSentences" => "Text",
"Summary" => "Text",
- 'EscapeXML' => 'Text',
- 'LimitWordCount' => 'Text',
- 'LimitWordCountXML' => 'HTMLText',
);
/**
@@ -51,242 +47,207 @@ public function requireField() {
$charset = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'charset');
$collation = Config::inst()->get('SilverStripe\ORM\Connect\MySQLDatabase', 'collation');
- $parts = array(
+ $parts = [
'datatype' => 'mediumtext',
'character set' => $charset,
'collate' => $collation,
+ 'default' => $this->defaultVal,
'arrayValue' => $this->arrayValue
- );
+ ];
- $values= array(
+ $values = [
'type' => 'text',
'parts' => $parts
- );
+ ];
DB::require_field($this->tableName, $this->name, $values);
}
- /**
- * Return the value of the field with relative links converted to absolute urls.
- * @return string
- */
- public function AbsoluteLinks() {
- return HTTP::absoluteURLs($this->RAW());
- }
-
/**
* Limit sentences, can be controlled by passing an integer.
*
- * @param int $sentCount The amount of sentences you want.
+ * @param int $maxSentences The amount of sentences you want.
* @return string
*/
- public function LimitSentences($sentCount = 2) {
- if(!is_numeric($sentCount)) {
- user_error("Text::LimitSentence() expects one numeric argument", E_USER_NOTICE);
- }
-
- $output = array();
- $data = trim(Convert::xml2raw($this->RAW()));
- $sentences = explode('.', $data);
+ public function LimitSentences($maxSentences = 2) {
+ if(!is_numeric($maxSentences)) {
+ throw new InvalidArgumentException("Text::LimitSentence() expects one numeric argument");
+ }
- if ($sentCount == 0) return '';
+ $value = $this->NoHTML();
+ if( !$value ) {
+ return "";
+ }
- for($i = 0; $i < $sentCount; $i++) {
- if(isset($sentences[$i])) {
- $sentence = trim($sentences[$i]);
- if(!empty($sentence)) $output[] .= $sentence;
+ // 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));
+ }
}
}
- return count($output)==0 ? '' : implode($output, '. ') . '.';
+ // 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);
+ }
}
/**
- * Caution: Not XML/HTML-safe - does not respect closing tags.
+ * Return the first string that finishes with a period (.) in this text.
+ *
+ * @return string
*/
public function FirstSentence() {
- $paragraph = Convert::xml2raw( $this->RAW() );
- if( !$paragraph ) return "";
+ return $this->LimitSentences(1);
+ }
- $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));
+ /**
+ * Builds a basic summary, up to a maximum number of words
+ *
+ * @param int $maxWords
+ * @param int $maxParagraphs Optional paragraph limit
+ * @return string
+ */
+ public function Summary($maxWords = 50, $maxParagraphs = 1) {
+ // Get plain-text version
+ $value = $this->NoHTML();
+ if(!$value) {
+ return '';
}
- }
- /* 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(20);
+ // 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);
}
- /**
- * Caution: Not XML/HTML-safe - does not respect closing tags.
- */
- public function Summary($maxWords = 50) {
- // get first sentence?
- // this needs to be more robust
- $value = Convert::xml2raw( $this->RAW() /*, true*/ );
- if(!$value) return '';
-
- // grab the first paragraph, or, failing that, the whole content
- if(strpos($value, "\n\n")) $value = substr($value, 0, strpos($value, "\n\n"));
+ // Find sentences
$sentences = explode('.', $value);
- $count = count(explode(' ', $sentences[0]));
+ $wordCount = count(preg_split('#\s+#', $sentences[0]));
// if the first sentence is too long, show only the first $maxWords words
- if($count > $maxWords) {
+ if($wordCount > $maxWords) {
return implode( ' ', array_slice(explode( ' ', $sentences[0] ), 0, $maxWords)) . '...';
}
// add each sentence while there are enough words to do so
$result = '';
do {
- $result .= trim(array_shift( $sentences )).'.';
- if(count($sentences) > 0) {
- $count += count(explode(' ', $sentences[0]));
- }
-
- // Ensure that we don't trim half way through a tag or a link
- $brokenLink = (
- substr_count($result,'<') != substr_count($result,'>')) ||
- (substr_count($result,']*>/', $result) && !preg_match( '/<\/a>/', $result)) $result .= '';
+ // 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 Convert::raw2xml($result);
+ return trim($result);
}
/**
* Performs the same function as the big summary, but doesn't trim new paragraphs off data.
- * Caution: Not XML/HTML-safe - does not respect closing tags.
+ *
+ * @param int $maxWords
+ * @return string
*/
- public function BigSummary($maxWords = 50, $plain = true) {
- $result = '';
-
- // get first sentence?
- // this needs to be more robust
- $data = $plain ? Convert::xml2raw($this->RAW(), true) : $this->RAW();
-
- if(!$data) return '';
-
- $sentences = explode('.', $data);
- $count = count(explode(' ', $sentences[0]));
-
- // if the first sentence is too long, show only the first $maxWords words
- if($count > $maxWords) {
- return implode(' ', array_slice(explode( ' ', $sentences[0] ), 0, $maxWords)) . '...';
+ public function BigSummary($maxWords = 50) {
+ return $this->Summary($maxWords, 0);
}
- // add each sentence while there are enough words to do so
- do {
- $result .= trim(array_shift($sentences));
- if($sentences) {
- $result .= '. ';
- $count += count(explode(' ', $sentences[0]));
- }
-
- // Ensure that we don't trim half way through a tag or a link
- $brokenLink = (
- substr_count($result,'<') != substr_count($result,'>')) ||
- (substr_count($result,']*>/', $result) && !preg_match( '/<\/a>/', $result)) {
- $result .= '';
- }
-
- return $result;
- }
-
/**
- * Caution: Not XML/HTML-safe - does not respect closing tags.
+ * Get first paragraph
+ *
+ * @return string
*/
- public function FirstParagraph($plain = 1) {
- // get first sentence?
- // this needs to be more robust
- $value = $this->RAW();
- if($plain && $plain != 'html') {
- $data = Convert::xml2raw($value);
- if(!$data) return "";
-
- // grab the first paragraph, or, failing that, the whole content
- $pos = strpos($data, "\n\n");
- if($pos) $data = substr($data, 0, $pos);
-
- return $data;
- } else {
- if(strpos($value, "") === false) return $value;
-
- $data = substr($value, 0, strpos($value, "") + 4);
-
- if(strlen($data) < 20 && strpos($value, "", strlen($data))) {
- $data = substr($value, 0, strpos( $value, "", strlen($data)) + 4 );
+ public function FirstParagraph() {
+ $value = $this->NoHTML();
+ if(empty($value)) {
+ return '';
}
- return $data;
+ // 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 boolean $string Supplied string ("keywords")
- * @param boolean $striphtml Strip HTML?
- * @param boolean $highlight Add a highlight element around search query?
- * @param string $prefix text
- * @param string $suffix
- *
- * @return string
+ * @param string $keywords Supplied string ("keywords"). Will fall back to 'Search' querystring arg.
+ * @param bool $highlight Add a highlight 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, $string = false, $striphtml = true, $highlight = true,
- $prefix = "... ", $suffix = "...") {
+ public function ContextSummary(
+ $characters = 500, $keywords = null, $highlight = true, $prefix = "... ", $suffix = "..."
+ ) {
- if(!$string) {
+ if(!$keywords) {
// Use the default "Search" request variable (from SearchForm)
- $string = isset($_REQUEST['Search']) ? $_REQUEST['Search'] : '';
+ $keywords = isset($_REQUEST['Search']) ? $_REQUEST['Search'] : '';
}
- // Remove HTML tags so we don't have to deal with matching tags
- $text = $striphtml ? $this->NoHTML() : $this->RAW();
+ // Get raw text value, but XML encode it (as we'll be merging with HTML tags soon)
+ $text = nl2br(Convert::raw2xml($this->NoHTML()));
+ $keywords = Convert::raw2xml($keywords);
// Find the search string
- $position = (int) stripos($text, $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"));
+ $position = max(
+ (int) strrpos(substr($text, 0, $position), ' '),
+ (int) strrpos(substr($text, 0, $position), "\n")
+ );
}
$summary = substr($text, $position, $characters);
- $stringPieces = explode(' ', $string);
+ $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) {
- $summary = str_ireplace($stringPiece, "$stringPiece",
- $summary);
+ $summary = str_ireplace(
+ $stringPiece,
+ "$stringPiece",
+ $summary
+ );
}
}
}
}
$summary = trim($summary);
- if($position > 0) $summary = $prefix . $summary;
- if(strlen($this->RAW()) > ($characters + $position)) $summary = $summary . $suffix;
+ // Add leading / trailing '...' if trimmed on either end
+ if($position > 0) {
+ $summary = $prefix . $summary;
+ }
+ if(strlen($this->value) > ($characters + $position)) {
+ $summary = $summary . $suffix;
+ }
return $summary;
}
@@ -295,20 +256,18 @@ public function ContextSummary($characters = 500, $string = false, $striphtml =
* Allows a sub-class of TextParser to be rendered.
*
* @see TextParser for implementation details.
- * @param string $parser
- * @return string
+ * @param string $parser Class name of parser (Must extend {@see TextParser})
+ * @return DBField Parsed value in the appropriate type
*/
- public function Parse($parser = "TextParser") {
- if($parser == "TextParser" || is_subclass_of($parser, "TextParser")) {
- $obj = new $parser($this->RAW());
- return $obj->parse();
- } else {
- // Fallback to using raw2xml and show a warning
- // TODO Don't kill script execution, we can continue without losing complete control of the app
- 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);
- return Convert::raw2xml($this->RAW());
+ 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();
}
/**
diff --git a/ORM/FieldType/DBVarchar.php b/ORM/FieldType/DBVarchar.php
index a0193fe8e19..c0b8d683272 100644
--- a/ORM/FieldType/DBVarchar.php
+++ b/ORM/FieldType/DBVarchar.php
@@ -10,9 +10,9 @@
/**
* Class Varchar represents a variable-length string of up to 255 characters, designed to store raw text
*
- * @see HTMLText
- * @see HTMLVarchar
- * @see Text
+ * @see DBHTMLText
+ * @see DBHTMLVarchar
+ * @see DBText
*
* @package framework
* @subpackage orm
diff --git a/Security/CMSSecurity.php b/Security/CMSSecurity.php
index a58cffc51d1..b4c8223e2a9 100644
--- a/Security/CMSSecurity.php
+++ b/Security/CMSSecurity.php
@@ -19,7 +19,7 @@
class CMSSecurity extends Security {
private static $casting = array(
- 'Title' => 'HTMLText'
+ 'Title' => 'HTMLFragment'
);
private static $allowed_actions = array(
diff --git a/Security/PermissionCheckboxSetField.php b/Security/PermissionCheckboxSetField.php
index 02bff51a1d3..afd3fbeaec4 100644
--- a/Security/PermissionCheckboxSetField.php
+++ b/Security/PermissionCheckboxSetField.php
@@ -90,7 +90,7 @@ public function getHiddenPermissions() {
/**
* @param array $properties
- * @return DBHTMLText
+ * @return string
*/
public function Field($properties = array()) {
Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/CheckboxSetField.css');
@@ -248,7 +248,7 @@ public function Field($properties = array()) {
}
}
if($this->readonly) {
- return DBField::create_field('HTMLText',
+ return
"
\n"
- );
+ "\n";
}
}
diff --git a/_config/model.yml b/_config/model.yml
index 65d31457b29..df88c582a93 100644
--- a/_config/model.yml
+++ b/_config/model.yml
@@ -25,6 +25,10 @@ Injector:
class: SilverStripe\ORM\FieldType\DBForeignKey
HTMLText:
class: SilverStripe\ORM\FieldType\DBHTMLText
+ properties:
+ ProcessShortcodes: true
+ HTMLFragment:
+ class: SilverStripe\ORM\FieldType\DBHTMLText
HTMLVarchar:
class: SilverStripe\ORM\FieldType\DBHTMLVarchar
Int:
diff --git a/api/RSSFeed.php b/api/RSSFeed.php
index 4be731064c5..01f838fd059 100644
--- a/api/RSSFeed.php
+++ b/api/RSSFeed.php
@@ -299,45 +299,41 @@ public function __construct($entry, $titleField, $descriptionField, $authorField
/**
* Get the description of this entry
*
- * @return string Returns the description of the entry.
+ * @return DBField Returns the description of the entry.
*/
public function Title() {
- return $this->rssField($this->titleField, 'Varchar');
+ return $this->rssField($this->titleField);
}
/**
* Get the description of this entry
*
- * @return string Returns the description of the entry.
+ * @return DBField Returns the description of the entry.
*/
public function Description() {
- return $this->rssField($this->descriptionField, 'HTMLText');
+ return $this->rssField($this->descriptionField);
}
/**
* Get the author of this entry
*
- * @return string Returns the author of the entry.
+ * @return DBField Returns the author of the entry.
*/
public function Author() {
- if($this->authorField) return $this->failover->obj($this->authorField);
+ return $this->rssField($this->authorField);
}
/**
- * Return the named field as an obj() call from $this->failover.
- * Default to the given class if there's no casting information.
+ * Return the safely casted field
+ *
+ * @param string $fieldName Name of field
+ * @return DBField
*/
- public function rssField($fieldName, $defaultClass = 'Varchar') {
+ public function rssField($fieldName) {
if($fieldName) {
- if($this->failover->castingHelper($fieldName)) {
- $value = $this->failover->$fieldName;
- $obj = $this->failover->obj($fieldName);
- $obj->setValue($value);
- return $obj;
- } else {
- return DBField::create_field($defaultClass, $this->failover->XML_val($fieldName), $fieldName);
- }
+ return $this->failover->obj($fieldName);
}
+ return null;
}
/**
diff --git a/api/XMLDataFormatter.php b/api/XMLDataFormatter.php
index 0786b95a15b..b7b98ff1aab 100644
--- a/api/XMLDataFormatter.php
+++ b/api/XMLDataFormatter.php
@@ -3,6 +3,8 @@
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\SS_List;
+use SilverStripe\ORM\FieldType\DBHTMLText;
+
/**
* @package framework
* @subpackage formatters
@@ -54,18 +56,21 @@ public function convertDataObjectWithoutHeader(DataObject $obj, $fields = null,
$xml = "<$className href=\"$objHref.xml\">\n";
foreach($this->getFieldsForObj($obj) as $fieldName => $fieldType) {
// Field filtering
- if($fields && !in_array($fieldName, $fields)) continue;
- $fieldValue = $obj->obj($fieldName)->forTemplate();
- if(!mb_check_encoding($fieldValue,'utf-8')) $fieldValue = "(data is badly encoded)";
+ if($fields && !in_array($fieldName, $fields)) {
+ continue;
+ }
+ $fieldObject = $obj->obj($fieldName);
+ $fieldValue = $fieldObject->forTemplate();
+ if(!mb_check_encoding($fieldValue, 'utf-8')) {
+ $fieldValue = "(data is badly encoded)";
+ }
if(is_object($fieldValue) && is_subclass_of($fieldValue, 'Object') && $fieldValue->hasMethod('toXML')) {
$xml .= $fieldValue->toXML();
} else {
- if('HTMLText' == $fieldType) {
+ if($fieldObject instanceof DBHTMLText) {
// Escape HTML values using CDATA
$fieldValue = sprintf('', str_replace(']]>', ']]]]>', $fieldValue));
- } else {
- $fieldValue = Convert::raw2xml($fieldValue);
}
$xml .= "<$fieldName>$fieldValue$fieldName>\n";
}
diff --git a/core/CustomMethods.php b/core/CustomMethods.php
index 96b53293c03..5cbe31dcc55 100644
--- a/core/CustomMethods.php
+++ b/core/CustomMethods.php
@@ -50,40 +50,41 @@ public function __call($method, $arguments) {
$this->defineMethods();
}
- // Validate method being invked
- $method = strtolower($method);
- if(!isset(self::$extra_methods[$class][$method])) {
- // Please do not change the exception code number below.
- $class = get_class($this);
- throw new BadMethodCallException("Object->__call(): the method '$method' does not exist on '$class'", 2175);
+ $config = $this->getExtraMethodConfig($method);
+ if(empty($config)) {
+ throw new BadMethodCallException(
+ "Object->__call(): the method '$method' does not exist on '$class'"
+ );
}
- $config = self::$extra_methods[$class][$method];
-
switch(true) {
- case isset($config['property']) :
+ case isset($config['property']) : {
$obj = $config['index'] !== null ?
$this->{$config['property']}[$config['index']] :
$this->{$config['property']};
- if($obj) {
- if(!empty($config['callSetOwnerFirst'])) $obj->setOwner($this);
+ if ($obj) {
+ if (!empty($config['callSetOwnerFirst'])) {
+ $obj->setOwner($this);
+ }
$retVal = call_user_func_array(array($obj, $method), $arguments);
- if(!empty($config['callSetOwnerFirst'])) $obj->clearOwner();
+ if (!empty($config['callSetOwnerFirst'])) {
+ $obj->clearOwner();
+ }
return $retVal;
}
- if(!empty($this->destroyed)) {
+ if (!empty($this->destroyed)) {
throw new BadMethodCallException(
"Object->__call(): attempt to call $method on a destroyed $class object"
);
} else {
throw new BadMethodCallException(
"Object->__call(): $class cannot pass control to $config[property]($config[index])."
- . ' Perhaps this object was mistakenly destroyed?'
+ . ' Perhaps this object was mistakenly destroyed?'
);
}
-
+ }
case isset($config['wrap']) :
array_unshift($arguments, $config['method']);
return call_user_func_array(array($this, $config['wrap']), $arguments);
@@ -107,8 +108,6 @@ public function __call($method, $arguments) {
* @uses addMethodsFrom()
*/
protected function defineMethods() {
- $class = get_class($this);
-
// Define from all registered callbacks
foreach($this->extra_method_registers as $callback) {
call_user_func($callback);
@@ -139,8 +138,21 @@ protected function registerExtraMethodCallback($name, $callback) {
* @return bool
*/
public function hasMethod($method) {
+ return method_exists($this, $method) || $this->getExtraMethodConfig($method);
+ }
+
+ /**
+ * Get meta-data details on a named method
+ *
+ * @param array $method
+ * @return array List of custom method details, if defined for this method
+ */
+ protected function getExtraMethodConfig($method) {
$class = get_class($this);
- return method_exists($this, $method) || isset(self::$extra_methods[$class][strtolower($method)]);
+ if(isset(self::$extra_methods[$class][strtolower($method)])) {
+ return self::$extra_methods[$class][strtolower($method)];
+ }
+ return null;
}
/**
diff --git a/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md b/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md
index 27e74ac5986..9896a5f8347 100644
--- a/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md
+++ b/docs/en/02_Developer_Guides/00_Model/04_Data_Types_and_Casting.md
@@ -189,10 +189,7 @@ the database. However, the template engine knows to escape fields without the `
to prevent them from rendering HTML interpreted by browsers. This escaping prevents attacks like CSRF or XSS (see
"[security](../security)"), which is important if these fields store user-provided data.
-
-You can disable this auto-escaping by using the `$MyField.RAW` escaping hints, or explicitly request escaping of HTML
-content via `$MyHtmlField.XML`.
-
+See the [Template casting](/developer_guides/templates/casting) section for controlling casting in your templates.
## Overloading
@@ -220,4 +217,4 @@ database column using `dbObject`.
## API Documentation
* [api:DataObject]
-* [api:DBField]
\ No newline at end of file
+* [api:DBField]
diff --git a/docs/en/02_Developer_Guides/01_Templates/09_Casting.md b/docs/en/02_Developer_Guides/01_Templates/09_Casting.md
index c35db3a63da..8e9a2edb45f 100644
--- a/docs/en/02_Developer_Guides/01_Templates/09_Casting.md
+++ b/docs/en/02_Developer_Guides/01_Templates/09_Casting.md
@@ -98,10 +98,50 @@ this purpose.
There's some exceptions to this rule, see the ["security" guide](../security).
-In case you want to explicitly allow un-escaped HTML input, the property can be cast as [api:HTMLText]. The following
-example takes the `Content` field in a `SiteTree` class, which is of this type. It forces the content into an explicitly
-escaped format.
+For every field used in templates, a casting helper will be applied. This will first check for any
+`casting` helper on your model specific to that field, and will fall back to the `default_cast` config
+in case none are specified.
+
+By default, `ViewableData.default_cast` is set to `Text`, which will ensure all fields have special
+characters HTML escaped by default.
+
+The most common casting types are:
+
+ * `Text` Which is a plain text string, and will be safely encoded via HTML entities when placed into
+ a template.
+ * `Varchar` which is the same as `Text` but for single-line text that should not have line breaks.
+ * `HTMLFragment` is a block of raw HTML, which should not be escaped. Take care to sanitise any HTML
+ value saved into the database.
+ * `HTMLText` is a `HTMLFragment`, but has shortcodes enabled. This should only be used for content
+ that is modified via a TinyMCE editor, which will insert shortcodes.
+ * `Int` for integers.
+ * `Decimal` for floating point values.
+ * `Boolean` For boolean values.
+ * `Datetime` for date and time.
+
+See the [Model data types and casting](/developer_guides/model/data_types_and_casting) section for
+instructions on configuring your model to declare casting types for fields.
+
+## Escape methods in templates
+
+Within the template, fields can have their encoding customised at a certain level with format methods.
+See [api:DBField] for the specific implementation, but they will generally follow the below rules:
+
+* `$Field` with no format method supplied will correctly cast itself for the HTML template, as defined
+ by the casting helper for that field. In most cases this is the best method to use for templates.
+* `$Field.XML` Will invoke `htmlentities` on special characters in the value, even if it's already
+ cast as HTML.
+* `$Field.ATT` will ensure the field is XML encoded for placement inside a HTML element property.
+ This will invoke `htmlentities` on the value (even if already cast as HTML) and will escape quotes.
+* `Field.JS` will cast this value as a javascript string. E.g. `var fieldVal = '$Field.JS';` can
+ be used in javascript defined in templates to encode values safely.
+* `$Field.CDATA` will cast this value safely for insertion as a literal string in an XML file.
+ E.g. `$Field.CDATA` will ensure that the `` body is safely escaped
+ as a string.
+
+
+Note: Take care when using `.XML` on `HTMLText` fields, as this will result in double-encoded
+html. To ensure that the correct encoding is used for that field in a template, simply use
+`$Field` by itself to allow the casting helper to determine the best encoding itself.
+
- :::ss
- $Content.XML
- // transforms e.g. "alert" to "<em>alert</em>"
diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md
index 49427c1348b..abcee089382 100644
--- a/docs/en/04_Changelogs/4.0.0.md
+++ b/docs/en/04_Changelogs/4.0.0.md
@@ -45,6 +45,11 @@
* `DataObject::can` has new method signature with `$context` parameter.
* `SiteTree.alternatePreviewLink` is deprecated. Use `updatePreviewLink` instead.
* `Injector` dependencies no longer automatically inherit from parent classes.
+ * `default_cast` is now enforced on all template variables. See upgrading notes below.
+ * `HTMLText` no longer enables shortcodes by default. You can specify the `db` option for
+ html fields as `HTMLText(['whitelist=meta,link'])`, or use a `ShortcodeHTMLText` as
+ a shorthand substitute.
+ * `FormField->dontEscape` has been removed. Escaping is now managed on a class by class basis.
## New API
@@ -99,6 +104,7 @@
* `FormAction::setValidationExempt` can be used to turn on or off form validation for individual actions
* `DataObject.table_name` config can now be used to customise the database table for any record.
* `DataObjectSchema` class added to assist with mapping between classes and tables.
+ * `FormField::Title` and `FormField::RightTitle` are now cast as plain text by default (but can be overridden).
### Front-end build tooling for CMS interface
@@ -168,7 +174,7 @@ admin/font/ => admin/client/dist/font/
* History.js
* `debugmethods` querystring argument has been removed from debugging.
-
+
* The following ClassInfo methods are now deprecated:
* `ClassInfo::baseDataClass` - Use `DataObject::getSchema()->baseDataClass()` instead.
* `ClassInfo::table_for_object_field` - Use `DataObject::getSchema()->tableForField()` instead
@@ -302,6 +308,64 @@ E.g.
## Upgrading
+
+### Explicit text casting is now enforced on all template variables
+
+Now whenever a `$Variable` is used in a template, regardless of whether any casts or methods are
+suffixed to the reference, it will be cast to either an explicit DBField for that field, or
+the value declared by the `default_cast` on the parent object.
+
+The default value of `default_cast` is `Text`, meaning that now many cases where a field was
+left un-uncoded, this will now be safely encoded via `Convert::raw2xml`. In cases where
+un-cast fields were used to place raw HTML into templates, this will now encode this until
+explicitly cast for that field.
+
+You can resolve this in your model by adding an explicit cast to HTML for those fields.
+
+
+Before:
+
+
+ :::ss
+
+ $SomeHTML
+
+
+
+ :::php
+ class MyObject extends ViewableData {
+ public function getSomeHTML {
+ $title = Convert::raw2xml($this->Title);
+ return "
";
+ }
+ }
+
+If you need to encode a field (such as HTMLText) for use in html attributes, use `.ATT`
+instead, or if used in an actual XML file use `.CDATA`.
+
+See the [Template casting](/developer_guides/templates/casting) section for specific details.
### Automatically upgrading
diff --git a/filesystem/File.php b/filesystem/File.php
index ffd3739d1ab..249b65f8783 100644
--- a/filesystem/File.php
+++ b/filesystem/File.php
@@ -116,7 +116,7 @@ class File extends DataObject implements ShortcodeHandler, AssetContainer, Thumb
);
private static $casting = array (
- 'TreeTitle' => 'HTMLText'
+ 'TreeTitle' => 'HTMLFragment'
);
/**
@@ -458,12 +458,11 @@ public function getCMSFields() {
CompositeField::create(
new ReadonlyField("FileType", _t('AssetTableField.TYPE','File type') . ':'),
new ReadonlyField("Size", _t('AssetTableField.SIZE','File size') . ':', $this->getSize()),
- ReadonlyField::create(
+ HTMLReadonlyField::create(
'ClickableURL',
_t('AssetTableField.URL','URL'),
sprintf('%s', $this->Link(), $this->Link())
- )
- ->setDontEscape(true),
+ ),
new DateField_Disabled("Created", _t('AssetTableField.CREATED','First uploaded') . ':'),
new DateField_Disabled("LastEdited", _t('AssetTableField.LASTEDIT','Last changed') . ':')
)
@@ -632,7 +631,10 @@ public function updateFilesystem() {
public function collateDescendants($condition, &$collator) {
if($children = $this->Children()) {
foreach($children as $item) {
- if(!$condition || eval("return $condition;")) $collator[] = $item;
+ /** @var File $item */
+ if(!$condition || eval("return $condition;")) {
+ $collator[] = $item;
+ }
$item->collateDescendants($condition, $collator);
}
return true;
diff --git a/filesystem/ImageManipulation.php b/filesystem/ImageManipulation.php
index b08f2df5e09..76196db219e 100644
--- a/filesystem/ImageManipulation.php
+++ b/filesystem/ImageManipulation.php
@@ -517,7 +517,7 @@ public function ThumbnailIcon($width, $height) {
*/
public function IconTag() {
return DBField::create_field(
- 'HTMLText',
+ 'HTMLFragment',
''
);
}
diff --git a/filesystem/storage/DBFile.php b/filesystem/storage/DBFile.php
index 90905cd3666..80dfc3410e6 100644
--- a/filesystem/storage/DBFile.php
+++ b/filesystem/storage/DBFile.php
@@ -100,7 +100,7 @@ protected function getStore() {
'Title' => 'Varchar',
'MimeType' => 'Varchar',
'String' => 'Text',
- 'Tag' => 'HTMLText',
+ 'Tag' => 'HTMLFragment',
'Size' => 'Varchar'
);
@@ -113,7 +113,7 @@ public function scaffoldFormField($title = null, $params = null) {
*
* @return string
*/
- public function forTemplate() {
+ public function XML() {
return $this->getTag() ?: '';
}
diff --git a/forms/CheckboxField.php b/forms/CheckboxField.php
index b2a2895ac39..e47b96aa79f 100644
--- a/forms/CheckboxField.php
+++ b/forms/CheckboxField.php
@@ -58,9 +58,13 @@ public function performReadonlyTransformation() {
}
public function Value() {
- return Convert::raw2xml($this->value ?
+ return $this->value ?
_t('CheckboxField.YESANSWER', 'Yes') :
- _t('CheckboxField.NOANSWER', 'No'));
+ _t('CheckboxField.NOANSWER', 'No');
+ }
+
+ public function getValueCast() {
+ return 'Text';
}
}
diff --git a/forms/ConfirmedPasswordField.php b/forms/ConfirmedPasswordField.php
index a5fe79dde35..f10562e4a75 100644
--- a/forms/ConfirmedPasswordField.php
+++ b/forms/ConfirmedPasswordField.php
@@ -144,7 +144,7 @@ public function __construct($name, $title = null, $value = "", $form = null, $sh
/**
* @param array $properties
*
- * @return DBHTMLText
+ * @return string
*/
public function Field($properties = array()) {
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
diff --git a/forms/CurrencyField.php b/forms/CurrencyField.php
index 7580665058c..35e2125867f 100644
--- a/forms/CurrencyField.php
+++ b/forms/CurrencyField.php
@@ -69,13 +69,14 @@ class CurrencyField_Readonly extends ReadonlyField{
*/
public function Field($properties = array()) {
if($this->value){
- $val = $this->dontEscape ? $this->value : Convert::raw2xml($this->value);
+ $val = Convert::raw2xml($this->value);
$val = _t('CurrencyField.CURRENCYSYMBOL', '$') . number_format(preg_replace('/[^0-9.]/',"",$val), 2);
+ $valforInput = Convert::raw2att($val);
} else {
$val = ''._t('CurrencyField.CURRENCYSYMBOL', '$').'0.00';
+ $valforInput = '';
}
- $valforInput = $this->value ? Convert::raw2att($val) : "";
- return "extraClass()."\" id=\"" . $this->id() . "\">$val"
+ return "extraClass()."\" id=\"" . $this->ID() . "\">$val"
. "name."\" value=\"".$valforInput."\" />";
}
@@ -102,12 +103,12 @@ class CurrencyField_Disabled extends CurrencyField{
*/
public function Field($properties = array()) {
if($this->value){
- $val = $this->dontEscape ? $this->value : Convert::raw2xml($this->value);
+ $val = Convert::raw2xml($this->value);
$val = _t('CurrencyField.CURRENCYSYMBOL', '$') . number_format(preg_replace('/[^0-9.]/',"",$val), 2);
+ $valforInput = Convert::raw2att($val);
} else {
- $val = ''._t('CurrencyField.CURRENCYSYMBOL', '$').'0.00';
+ $valforInput = '';
}
- $valforInput = $this->value ? Convert::raw2att($val) : "";
return "name."\" value=\"".$valforInput."\" />";
}
diff --git a/forms/DatalessField.php b/forms/DatalessField.php
index 97c0f07b944..9081d9127b4 100644
--- a/forms/DatalessField.php
+++ b/forms/DatalessField.php
@@ -32,7 +32,7 @@ public function getAttributes() {
* Returns the field's representation in the form.
* For dataless fields, this defaults to $Field.
*
- * @return HTMLText
+ * @return string
*/
public function FieldHolder($properties = array()) {
return $this->Field($properties);
@@ -57,6 +57,7 @@ public function performReadonlyTransformation() {
/**
* @param bool $bool
+ * @return $this
*/
public function setAllowHTML($bool) {
$this->allowHTML = $bool;
diff --git a/forms/DatetimeField.php b/forms/DatetimeField.php
index dca87b4eed7..96cbfc43e40 100644
--- a/forms/DatetimeField.php
+++ b/forms/DatetimeField.php
@@ -97,7 +97,7 @@ public function setName($name) {
/**
* @param array $properties
- * @return HTMLText
+ * @return string
*/
public function FieldHolder($properties = array()) {
$config = array(
@@ -112,16 +112,17 @@ public function FieldHolder($properties = array()) {
/**
* @param array $properties
- * @return HTMLText
+ * @return string
*/
public function Field($properties = array()) {
Requirements::css(FRAMEWORK_DIR . '/client/dist/styles/DatetimeField.css');
$tzField = ($this->getConfig('usertimezone')) ? $this->timezoneField->FieldHolder() : '';
- return DBField::create_field('HTMLText', $this->dateField->FieldHolder() .
- $this->timeField->FieldHolder() .
- $tzField .
- ''
+ return sprintf(
+ '%s%s%s',
+ $this->dateField->FieldHolder(),
+ $this->timeField->FieldHolder(),
+ $tzField
);
}
@@ -139,6 +140,7 @@ public function Field($properties = array()) {
* @param string|array $val String expects an ISO date format. Array notation with 'date' and 'time'
* keys can contain localized strings. If the 'dmyfields' option is used for {@link DateField},
* the 'date' value may contain array notation was well (see {@link DateField->setValue()}).
+ * @return $this
*/
public function setValue($val) {
$locale = new Zend_Locale($this->locale);
diff --git a/forms/DropdownField.php b/forms/DropdownField.php
index 98eb92afc1f..d1b440f483e 100644
--- a/forms/DropdownField.php
+++ b/forms/DropdownField.php
@@ -114,7 +114,7 @@ protected function getFieldOption($value, $title) {
/**
* @param array $properties
- * @return DBHTMLText
+ * @return string
*/
public function Field($properties = array()) {
$options = array();
diff --git a/forms/FileField.php b/forms/FileField.php
index c9e637405d9..975f5a7f0ae 100644
--- a/forms/FileField.php
+++ b/forms/FileField.php
@@ -87,7 +87,7 @@ public function __construct($name, $title = null, $value = null) {
/**
* @param array $properties
- * @return HTMLText
+ * @return string
*/
public function Field($properties = array()) {
$properties = array_merge($properties, array(
diff --git a/forms/Form.php b/forms/Form.php
index 56052dd341c..92a0e6e1d4b 100644
--- a/forms/Form.php
+++ b/forms/Form.php
@@ -5,6 +5,7 @@
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\ORM\SS_List;
+use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\Security\SecurityToken;
use SilverStripe\Security\NullSecurityToken;
@@ -215,6 +216,15 @@ class Form extends RequestHandler {
'forTemplate',
);
+ private static $casting = array(
+ 'AttributesHTML' => 'HTMLFragment',
+ 'FormAttributes' => 'HTMLFragment',
+ 'MessageType' => 'Text',
+ 'Message' => 'HTMLFragment',
+ 'FormName' => 'Text',
+ 'Legend' => 'HTMLFragment',
+ );
+
/**
* @var FormTemplateHelper
*/
@@ -1732,7 +1742,7 @@ public function buttonClicked() {
public function defaultAction() {
if($this->hasDefaultAction && $this->actions) {
return $this->actions->First();
- }
+ }
}
/**
@@ -1804,7 +1814,7 @@ public function getSecurityToken() {
public static function single_field_required() {
if(self::current_action() == 'callfieldmethod') {
return $_REQUEST['fieldName'];
- }
+ }
}
/**
diff --git a/forms/FormAction.php b/forms/FormAction.php
index 091f1c438c1..3aabbb9edd1 100644
--- a/forms/FormAction.php
+++ b/forms/FormAction.php
@@ -17,6 +17,14 @@
*/
class FormAction extends FormField {
+ /**
+ * @config
+ * @var array
+ */
+ private static $casting = [
+ 'ButtonContent' => 'HTMLFragment',
+ ];
+
/**
* Action name, normally prefixed with 'action_'
*
@@ -83,7 +91,7 @@ public function setFullAction($fullAction) {
/**
* @param array $properties
- * @return HTMLText
+ * @return string
*/
public function Field($properties = array()) {
$properties = array_merge(
@@ -100,7 +108,7 @@ public function Field($properties = array()) {
/**
* @param array $properties
- * @return HTMLText
+ * @return string
*/
public function FieldHolder($properties = array()) {
return $this->Field($properties);
@@ -110,22 +118,6 @@ public function Type() {
return 'action';
}
- public function Title() {
- $title = parent::Title();
-
- // Remove this method override in 4.0
- $decoded = Convert::xml2raw($title);
- if($title && $decoded !== $title) {
- Deprecation::notice(
- '4.0',
- 'The FormAction title field should not be html encoded. Use buttonContent to set custom html instead'
- );
- return $decoded;
- }
-
- return $title;
- }
-
public function getAttributes() {
$type = (isset($this->attributes['src'])) ? 'image' : 'submit';
@@ -141,7 +133,7 @@ public function getAttributes() {
}
/**
- * Add content inside a button field.
+ * Add content inside a button field. This should be pre-escaped raw HTML and should be used sparingly.
*
* @param string $content
* @return $this
@@ -152,7 +144,7 @@ public function setButtonContent($content) {
}
/**
- * Gets the content inside the button field
+ * Gets the content inside the button field. This is raw HTML, and should be used sparingly.
*
* @return string
*/
diff --git a/forms/FormField.php b/forms/FormField.php
index 6b8609202a6..614c9a61f71 100644
--- a/forms/FormField.php
+++ b/forms/FormField.php
@@ -131,12 +131,6 @@ class FormField extends RequestHandler {
*/
private static $default_classes = [];
-
- /**
- * @var bool
- */
- public $dontEscape;
-
/**
* Right-aligned, contextual label for the field.
*
@@ -258,6 +252,22 @@ class FormField extends RequestHandler {
*/
protected $schemaData = [];
+ private static $casting = array(
+ 'FieldHolder' => 'HTMLFragment',
+ 'Field' => 'HTMLFragment',
+ 'AttributesHTML' => 'HTMLFragment',
+ 'Value' => 'Text',
+ 'extraClass' => 'Text',
+ 'ID' => 'Text',
+ 'isReadOnly' => 'Boolean',
+ 'HolderID' => 'Text',
+ 'Title' => 'Text',
+ 'RightTitle' => 'Text',
+ 'MessageType' => 'Text',
+ 'Message' => 'HTMLFragment',
+ 'Description' => 'HTMLFragment',
+ );
+
/**
* Structured schema state representing the FormField's current data and validation.
* Used to render the FormField as a ReactJS Component on the front-end.
@@ -483,13 +493,14 @@ public function Title() {
}
/**
- * @param string $title
+ * Set the title of this formfield.
+ * Note: This expects escaped HTML.
*
+ * @param string $title Escaped HTML for title
* @return $this
*/
public function setTitle($title) {
$this->title = $title;
-
return $this;
}
@@ -504,13 +515,14 @@ public function RightTitle() {
}
/**
- * @param string $rightTitle
+ * Sets the right title for this formfield
+ * Note: This expects escaped HTML.
*
+ * @param string $rightTitle Escaped HTML for title
* @return $this
*/
public function setRightTitle($rightTitle) {
$this->rightTitle = $rightTitle;
-
return $this;
}
@@ -928,7 +940,6 @@ public function setSmallFieldHolderTemplate($smallFieldHolderTemplate) {
* such as an input tag.
*
* @param array $properties
- *
* @return string
*/
public function Field($properties = array()) {
@@ -1366,31 +1377,9 @@ public function castedCopy($classOrCopy) {
$field->setAttribute($attributeKey, $attributeValue);
}
- $field->dontEscape = $this->dontEscape;
-
return $field;
}
- /**
- * Determine if escaping of this field should be disabled
- *
- * @param bool $dontEscape
- * @return $this
- */
- public function setDontEscape($dontEscape) {
- $this->dontEscape = $dontEscape;
- return $this;
- }
-
- /**
- * Determine if escaping is disabled
- *
- * @return bool
- */
- public function getDontEscape() {
- return $this->dontEscape;
- }
-
/**
* Sets the component type the FormField will be rendered as on the front-end.
*
diff --git a/forms/HTMLReadonlyField.php b/forms/HTMLReadonlyField.php
new file mode 100644
index 00000000000..34f077caf64
--- /dev/null
+++ b/forms/HTMLReadonlyField.php
@@ -0,0 +1,12 @@
+ 'HTMLFragment'
+ ];
+}
diff --git a/forms/HiddenField.php b/forms/HiddenField.php
index d03f76b18d2..36dc4e764a3 100644
--- a/forms/HiddenField.php
+++ b/forms/HiddenField.php
@@ -12,8 +12,7 @@ class HiddenField extends FormField {
/**
* @param array $properties
- *
- * @return HTMLText
+ * @return string
*/
public function FieldHolder($properties = array()) {
return $this->Field($properties);
diff --git a/forms/InlineFormAction.php b/forms/InlineFormAction.php
index 19b49109e97..abffa01b4b1 100644
--- a/forms/InlineFormAction.php
+++ b/forms/InlineFormAction.php
@@ -1,9 +1,5 @@
extraClass = ' '.$extraClass;
@@ -34,24 +31,23 @@ public function performReadonlyTransformation() {
/**
* @param array $properties
- * @return HTMLText
+ * @return string
*/
public function Field($properties = array()) {
if($this->includeDefaultJS) {
- Requirements::javascriptTemplate(FRAMEWORK_DIR . '/client/dist/js/InlineFormAction.js',
- array('ID'=>$this->id()));
+ Requirements::javascriptTemplate(
+ FRAMEWORK_DIR . '/client/dist/js/InlineFormAction.js',
+ array('ID'=>$this->ID())
+ );
}
- return DBField::create_field(
- 'HTMLText',
- FormField::create_tag('input', array(
+ return FormField::create_tag('input', array(
'type' => 'submit',
'name' => sprintf('action_%s', $this->getName()),
'value' => $this->title,
'id' => $this->ID(),
'class' => sprintf('action%s', $this->extraClass),
- ))
- );
+ ));
}
public function Title() {
@@ -80,19 +76,17 @@ class InlineFormAction_ReadOnly extends FormField {
/**
* @param array $properties
- * @return HTMLText
+ * @return string
*/
public function Field($properties = array()) {
- return DBField::create_field('HTMLText',
- FormField::create_tag('input', array(
+ return FormField::create_tag('input', array(
'type' => 'submit',
'name' => sprintf('action_%s', $this->name),
'value' => $this->title,
- 'id' => $this->id(),
+ 'id' => $this->ID(),
'disabled' => 'disabled',
'class' => 'action disabled ' . $this->extraClass,
- ))
- );
+ ));
}
public function Title() {
diff --git a/forms/LiteralField.php b/forms/LiteralField.php
index c2b576329c8..7c6dc8453cf 100644
--- a/forms/LiteralField.php
+++ b/forms/LiteralField.php
@@ -14,6 +14,11 @@
* @subpackage fields-dataless
*/
class LiteralField extends DatalessField {
+
+ private static $casting = [
+ 'Value' => 'HTMLFragment',
+ ];
+
/**
* @var string|FormField
*/
diff --git a/forms/LookupField.php b/forms/LookupField.php
index d6d15c94d1f..3aeebb3b324 100644
--- a/forms/LookupField.php
+++ b/forms/LookupField.php
@@ -1,4 +1,5 @@
dontEscape) {
- $attrValue = Convert::raw2xml($attrValue);
- }
-
+ $attrValue = Convert::raw2xml($attrValue);
$inputValue = implode(', ', array_values($values));
} else {
$attrValue = '('._t('FormField.NONE', 'none').')';
@@ -58,7 +56,7 @@ public function Field($properties = array()) {
}
$properties = array_merge($properties, array(
- 'AttrValue' => $attrValue,
+ 'AttrValue' => DBField::create_field('HTMLFragment', $attrValue),
'InputValue' => $inputValue
));
diff --git a/forms/MoneyField.php b/forms/MoneyField.php
index 64362be65bf..7c89c9b0e43 100644
--- a/forms/MoneyField.php
+++ b/forms/MoneyField.php
@@ -52,15 +52,14 @@ public function __construct($name, $title = null, $value = "") {
/**
* @param array
- * @return HTMLText
+ * @return string
*/
public function Field($properties = array()) {
- return DBField::create_field('HTMLText',
+ return
"
" .
"
" . $this->fieldCurrency->SmallFieldHolder() . "
" .
"
" . $this->fieldAmount->SmallFieldHolder() . "
" .
- "
"
- );
+ "";
}
/**
diff --git a/forms/NullableField.php b/forms/NullableField.php
index 9e403a7f94b..e48a9a51c3c 100644
--- a/forms/NullableField.php
+++ b/forms/NullableField.php
@@ -106,7 +106,7 @@ public function getIsNullId() {
/**
* @param array $properties
*
- * @return HTMLText
+ * @return string
*/
public function Field($properties = array()) {
if($this->isReadonly()) {
@@ -117,12 +117,12 @@ public function Field($properties = array()) {
$nullableCheckbox->setValue(is_null($this->dataValue()));
- return DBField::create_field('HTMLText', sprintf(
+ return sprintf(
'%s %s %s',
$this->valueField->Field(),
$nullableCheckbox->Field(),
$this->getIsNullLabel()
- ));
+ );
}
/**
diff --git a/forms/NumericField.php b/forms/NumericField.php
index 8d5a4a9f0a7..0a31a15e5b0 100644
--- a/forms/NumericField.php
+++ b/forms/NumericField.php
@@ -199,10 +199,10 @@ public function performReadonlyTransformation() {
* @return string
*/
public function Value() {
- if($this->value) {
- return Convert::raw2xml((string) $this->value);
- }
+ return $this->value ?: '0';
+ }
- return '0';
+ public function getValueCast() {
+ return 'Decimal';
}
}
diff --git a/forms/PhoneNumberField.php b/forms/PhoneNumberField.php
index 563ceec1041..1040cc90702 100644
--- a/forms/PhoneNumberField.php
+++ b/forms/PhoneNumberField.php
@@ -31,7 +31,7 @@ public function __construct($name, $title = null, $value = '', $extension = null
/**
* @param array $properties
- * @return FieldGroup|HTMLText
+ * @return string
*/
public function Field($properties = array()) {
$fields = new FieldGroup( $this->name );
@@ -60,14 +60,17 @@ public function Field($properties = array()) {
}
$description = $this->getDescription();
- if($description) $fields->getChildren()->First()->setDescription($description);
+ if($description) {
+ $fields->getChildren()->first()->setDescription($description);
+ }
foreach($fields as $field) {
+ /** @var FormField $field */
$field->setDisabled($this->isDisabled());
$field->setReadonly($this->isReadonly());
}
- return $fields;
+ return $fields->Field($properties);
}
public function setValue( $value ) {
@@ -150,8 +153,7 @@ public function validate($validator){
$validator->validationError(
$this->name,
_t('PhoneNumberField.VALIDATION', "Please enter a valid phone number"),
- "validation",
- false
+ "validation"
);
return false;
}
diff --git a/forms/ReadonlyField.php b/forms/ReadonlyField.php
index a75f90dcb6d..f9987337dfd 100644
--- a/forms/ReadonlyField.php
+++ b/forms/ReadonlyField.php
@@ -40,7 +40,7 @@ public function performReadonlyTransformation() {
/**
* @param array $properties
- * @return HTMLText
+ * @return string
*/
public function Field($properties = array()) {
// Include a hidden field in the HTML
@@ -54,6 +54,31 @@ public function Field($properties = array()) {
}
}
+ public function getAttributes() {
+ return array_merge(
+ parent::getAttributes(),
+ array(
+ 'type' => 'hidden',
+ 'value' => $this->readonly ? null : $this->value,
+ )
+ );
+ }
+
+ public function Type() {
+ return 'readonly';
+ }
+
+ public function castingHelper($field) {
+ // Get dynamic cast for 'Value' field
+ if(strcasecmp($field, 'Value') === 0) {
+ return $this->getValueCast();
+ }
+
+ // Fall back to default casting
+ return parent::castingHelper($field);
+ }
+
+
/**
* If $dontEscape is true the returned value will be plain text
* and should be escaped in templates via .XML
@@ -64,34 +89,31 @@ public function Field($properties = array()) {
* @return mixed|string
*/
public function Value() {
- if($this->value) {
- if($this->dontEscape) {
- return $this->value;
- } else {
- return Convert::raw2xml($this->value);
- }
- } else {
- $value = '(' . _t('FormField.NONE', 'none') . ')';
- if($this->dontEscape) {
- return $value;
- } else {
- return ''.Convert::raw2xml($value).'';
- }
+ // Get raw value
+ $value = $this->dataValue();
+ if($value) {
+ return $value;
}
- }
- public function getAttributes() {
- return array_merge(
- parent::getAttributes(),
- array(
- 'type' => 'hidden',
- 'value' => $this->readonly ? null : $this->value,
- )
- );
+ // "none" text
+ $label = _t('FormField.NONE', 'none');
+ return "('{$label}')";
}
- public function Type() {
- return 'readonly';
+ /**
+ * Get custom cating helper for Value() field
+ *
+ * @return string
+ */
+ public function getValueCast() {
+ // Casting class for 'none' text
+ $value = $this->dataValue();
+ if(empty($value)) {
+ return 'HTMLFragment';
+ }
+
+ // Use default casting
+ return $this->config()->casting['Value'];
}
}
diff --git a/forms/SelectionGroup.php b/forms/SelectionGroup.php
index cfcb7f93196..fa424cd871d 100644
--- a/forms/SelectionGroup.php
+++ b/forms/SelectionGroup.php
@@ -1,6 +1,8 @@
ID() . '_' . (++$count);
$extra = array(
- "RadioButton" => FormField::create_tag(
+ "RadioButton" => DBField::create_field('HTMLFragment', FormField::create_tag(
'input',
array(
'class' => 'selector',
@@ -93,12 +95,12 @@ public function FieldList() {
'value' => $item->getValue(),
'checked' => $checked
)
- ),
- "RadioLabel" => FormField::create_tag(
+ )),
+ "RadioLabel" => DBField::create_field('HTMLFragment', FormField::create_tag(
'label',
array('for' => $itemID),
$item->getTitle()
- ),
+ )),
"Selected" => $firstSelected,
);
$newItems[] = $item->customise($extra);
diff --git a/forms/TextareaField.php b/forms/TextareaField.php
index 9110b3d014d..3d8f4710984 100644
--- a/forms/TextareaField.php
+++ b/forms/TextareaField.php
@@ -26,7 +26,7 @@ class TextareaField extends FormField {
*/
private static $casting = array(
'Value' => 'Text',
- 'ValueEntities' => 'HTMLText',
+ 'ValueEntities' => 'HTMLFragment',
);
/**
@@ -119,8 +119,6 @@ public function Type() {
/**
* Return value with all values encoded in html entities
*
- * Invoke with $ValueEntities.RAW to suppress HTMLText parsing shortcodes.
- *
* @return string Raw HTML
*/
public function ValueEntities() {
diff --git a/forms/ToggleCompositeField.php b/forms/ToggleCompositeField.php
index a4d626ab611..909272a39dd 100644
--- a/forms/ToggleCompositeField.php
+++ b/forms/ToggleCompositeField.php
@@ -35,8 +35,7 @@ public function __construct($name, $title, $children) {
* @inheritdoc
*
* @param array $properties
- *
- * @return string|HTMLText
+ * @return string
*/
public function FieldHolder($properties = array()) {
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
diff --git a/forms/TreeDropdownField.php b/forms/TreeDropdownField.php
index 673d3c6c62e..7aeef45feb4 100644
--- a/forms/TreeDropdownField.php
+++ b/forms/TreeDropdownField.php
@@ -216,7 +216,7 @@ public function setNumChildrenMethod($method) {
/**
* @param array $properties
- * @return DBHTMLText
+ * @return string
*/
public function Field($properties = array()) {
Requirements::add_i18n_javascript(FRAMEWORK_DIR . '/client/lang');
@@ -398,8 +398,9 @@ public function tree(SS_HTTPRequest $request) {
* Marking public function for the tree, which combines different filters sensibly.
* If a filter function has been set, that will be called. And if search text is set,
* filter on that too. Return true if all applicable conditions are true, false otherwise.
- * @param object $node
- * @return mixed
+ *
+ * @param mixed $node
+ * @return bool
*/
public function filterMarking($node) {
if ($this->filterCallback && !call_user_func($this->filterCallback, $node)) return false;
diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php
index 384f1f55605..a4b87f2645e 100644
--- a/forms/gridfield/GridField.php
+++ b/forms/gridfield/GridField.php
@@ -5,7 +5,7 @@
use SilverStripe\ORM\FieldType\DBField;
use SilverStripe\ORM\DataModel;
use SilverStripe\ORM\DataObjectInterface;
-
+use SilverStripe\ORM\FieldType\DBHTMLText;
/**
* Displays a {@link SS_List} in a grid format.
@@ -290,8 +290,7 @@ public function getState($getData = true) {
* Returns the whole gridfield rendered with all the attached components.
*
* @param array $properties
- *
- * @return HTMLText
+ * @return string
*/
public function FieldHolder($properties = array()) {
Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css');
@@ -512,14 +511,11 @@ public function FieldHolder($properties = array()) {
$header . "\n" . $footer . "\n" . $body
);
- $field = DBField::create_field('HTMLText', FormField::create_tag(
+ return FormField::create_tag(
'fieldset',
$fieldsetAttributes,
$content['before'] . $table . $content['after']
- ));
- $field->setOptions(array('shortcodes' => false));
-
- return $field;
+ );
}
/**
@@ -604,8 +600,7 @@ protected function newRowClasses($total, $index, $record) {
/**
* @param array $properties
- *
- * @return HTMLText
+ * @return string
*/
public function Field($properties = array()) {
$this->extend('onBeforeRender', $this);
diff --git a/forms/gridfield/GridFieldDetailForm.php b/forms/gridfield/GridFieldDetailForm.php
index 16832a020d8..8f16724e98d 100644
--- a/forms/gridfield/GridFieldDetailForm.php
+++ b/forms/gridfield/GridFieldDetailForm.php
@@ -1,10 +1,11 @@
getToplevelController();
diff --git a/forms/htmleditor/HTMLEditorField.php b/forms/htmleditor/HTMLEditorField.php
index b913a64e55f..ef72a0299eb 100644
--- a/forms/htmleditor/HTMLEditorField.php
+++ b/forms/htmleditor/HTMLEditorField.php
@@ -14,6 +14,10 @@
*/
class HTMLEditorField extends TextareaField {
+ private static $casting = [
+ 'Value' => 'HTMLText',
+ ];
+
/**
* Use TinyMCE's GZIP compressor
*
@@ -128,10 +132,7 @@ public function setValue($value) {
* @return HTMLEditorField_Readonly
*/
public function performReadonlyTransformation() {
- $field = $this->castedCopy('HTMLEditorField_Readonly');
- $field->dontEscape = true;
-
- return $field;
+ return $this->castedCopy('HTMLEditorField_Readonly');
}
public function performDisabledTransformation() {
@@ -150,7 +151,11 @@ public function Field($properties = array()) {
* @package forms
* @subpackage fields-formattedinput
*/
-class HTMLEditorField_Readonly extends ReadonlyField {
+class HTMLEditorField_Readonly extends HTMLReadonlyField {
+ private static $casting = [
+ 'Value' => 'HTMLText'
+ ];
+
public function Field($properties = array()) {
$valforInput = $this->value ? Convert::raw2att($this->value) : "";
return "id() . "\">"
diff --git a/parsers/BBCodeParser.php b/parsers/BBCodeParser.php
index c59360c7076..03c6f47b1bb 100644
--- a/parsers/BBCodeParser.php
+++ b/parsers/BBCodeParser.php
@@ -1,6 +1,8 @@
smilies_location;
- }
-
- /**
- * @deprecated 4.0 Use the "BBCodeParser.smilies_location" config setting instead
- */
- public static function set_icon_folder($path) {
- Deprecation::notice('4.0', 'Use the "BBCodeParser.smilies_location" config setting instead');
- static::config()->smilies_location = $path;
- }
-
- /**
- * @deprecated 4.0 Use the "BBCodeParser.autolink_urls" config setting instead
- */
- public static function autolinkUrls() {
- Deprecation::notice('4.0', 'Use the "BBCodeParser.autolink_urls" config setting instead');
- return static::config()->autolink_urls;
- }
-
- /**
- * @deprecated 4.0 Use the "BBCodeParser.autolink_urls" config setting instead
- */
- public static function disable_autolink_urls($autolink = false) {
- Deprecation::notice('4.0', 'Use the "BBCodeParser.autolink_urls" config setting instead');
- static::config()->autolink_urls = $autolink;
- }
-
- /**
- * @deprecated 4.0 Use the "BBCodeParser.allow_smilies" config setting instead
- */
- public static function smiliesAllowed() {
- Deprecation::notice('4.0', 'Use the "BBCodeParser.allow_smilies" config setting instead');
- return static::config()->allow_smilies;
- }
-
- /**
- * @deprecated 4.0 Use the "BBCodeParser.allow_smilies" config setting instead
- */
- public static function enable_smilies() {
- Deprecation::notice('4.0', 'Use the "BBCodeParser.allow_smilies" config setting instead');
- static::config()->allow_similies = true;
- }
-
-
public static function usable_tags() {
return new ArrayList(
array(
@@ -167,7 +117,7 @@ public function useable_tagsHTML(){
* Main BBCode parser method. This takes plain jane content and
* runs it through so many filters
*
- * @return Text
+ * @return DBField
*/
public function parse() {
$this->content = str_replace(array('&', '<', '>'), array('&', '<', '>'), $this->content);
@@ -197,7 +147,9 @@ public function parse() {
);
$this->content = preg_replace(array_keys($smilies), array_values($smilies), $this->content);
}
- return $this->content;
+
+ // Ensure to return cast value
+ return DBField::create_field('HTMLFragment', $this->content);
}
}
diff --git a/parsers/TextParser.php b/parsers/TextParser.php
index e09e1ebb033..97ed2d68510 100644
--- a/parsers/TextParser.php
+++ b/parsers/TextParser.php
@@ -26,6 +26,10 @@
* @subpackage misc
*/
abstract class TextParser extends Object {
+
+ /**
+ * @var string
+ */
protected $content;
/**
@@ -34,12 +38,15 @@ abstract class TextParser extends Object {
* @param string $content The contents of the dbfield
*/
public function __construct($content = "") {
+ parent::__construct();
$this->content = $content;
parent::__construct();
}
/**
* Convenience method, shouldn't really be used, but it's here if you want it
+ *
+ * @param string $content
*/
public function setContent($content = "") {
$this->content = $content;
@@ -48,6 +55,8 @@ public function setContent($content = "") {
/**
* Define your own parse method to parse $this->content appropriately.
* See the class doc-block for more implementation details.
+ *
+ * @return DBField
*/
abstract public function parse();
}
diff --git a/templates/RSSFeed.ss b/templates/RSSFeed.ss
index f4bc1ff971a..4975bbe9d2d 100644
--- a/templates/RSSFeed.ss
+++ b/templates/RSSFeed.ss
@@ -9,8 +9,8 @@
<% loop $Entries %>
$Title.XML
- $AbsoluteLink
- <% if $Description %>$Description.AbsoluteLinks.XML<% end_if %>
+ $AbsoluteLink.XML
+ <% if $Description %>$Description.AbsoluteLinks.CDATA<% end_if %>
<% if $Date %>$Date.Rfc822
<% else %>$Created.Rfc822<% end_if %>
<% if $Author %>$Author.XML<% end_if %>
diff --git a/tests/api/RSSFeedTest.php b/tests/api/RSSFeedTest.php
index 70bcbdef680..2e593659f5e 100644
--- a/tests/api/RSSFeedTest.php
+++ b/tests/api/RSSFeedTest.php
@@ -64,7 +64,7 @@ public function testRSSFeedWithShortcode() {
$this->assertContains('ItemD', $content);
$this->assertContains(
- '<p>ItemD Content test shortcode output</p>',
+ 'ItemD Content test shortcode output]]>',
$content
);
}
@@ -168,7 +168,7 @@ class RSSFeedTest_ItemD extends ViewableData {
// ItemD test fields - all fields use casting but Content & AltContent cast as HTMLText
private static $casting = array(
'Title' => 'Varchar',
- 'Content' => 'HTMLText'
+ 'Content' => 'HTMLText', // Supports shortcodes
);
public $Title = 'ItemD';
diff --git a/tests/email/EmailTest.php b/tests/email/EmailTest.php
index 4e9dfaf3cac..ad44e1a6e25 100644
--- a/tests/email/EmailTest.php
+++ b/tests/email/EmailTest.php
@@ -165,7 +165,7 @@ public function testSendHTML() {
'from@example.com',
'to@example.com',
'Test send plain',
- 'Testing Email->sendPlain()',
+ 'Testing Email->send()',
null,
'cc@example.com',
'bcc@example.com'
@@ -180,7 +180,7 @@ public function testSendHTML() {
$this->assertEquals('to@example.com', $sent['to']);
$this->assertEquals('from@example.com', $sent['from']);
$this->assertEquals('Test send plain', $sent['subject']);
- $this->assertContains('Testing Email->sendPlain()', $sent['content']);
+ $this->assertContains('Testing Email->send()', $sent['content']);
$this->assertNull($sent['plaincontent']);
$this->assertEquals(
array(
diff --git a/tests/forms/LookupFieldTest.php b/tests/forms/LookupFieldTest.php
index 17558a93e2b..99112389e09 100644
--- a/tests/forms/LookupFieldTest.php
+++ b/tests/forms/LookupFieldTest.php
@@ -35,10 +35,9 @@ public function testStringValueWithNumericArraySource() {
public function testUnknownStringValueWithNumericArraySource() {
$source = array(1 => 'one', 2 => 'two', 3 => 'three');
$f = new LookupField('test', 'test', $source);
- $f->setValue('w00t');
- $f->dontEscape = true; // simulates CMSMain->compareversions()
+ $f->setValue('w00t');
$this->assertEquals(
- 'w00t',
+ 'w00t',
trim($f->Field()->getValue())
);
}
diff --git a/tests/model/DBFieldTest.php b/tests/model/DBFieldTest.php
index 6ad22393747..52046087c2f 100644
--- a/tests/model/DBFieldTest.php
+++ b/tests/model/DBFieldTest.php
@@ -211,7 +211,7 @@ public function testExists() {
public function testStringFieldsWithMultibyteData() {
$plainFields = array('Varchar', 'Text');
- $htmlFields = array('HTMLVarchar', 'HTMLText');
+ $htmlFields = array('HTMLVarchar', 'HTMLText', 'HTMLFragment');
$allFields = array_merge($plainFields, $htmlFields);
$value = 'üåäöÜÅÄÖ';
diff --git a/tests/model/DBHTMLTextTest.php b/tests/model/DBHTMLTextTest.php
index f1f53dddfb3..35e4c0ae943 100644
--- a/tests/model/DBHTMLTextTest.php
+++ b/tests/model/DBHTMLTextTest.php
@@ -185,14 +185,14 @@ function testExists() {
}
function testWhitelist() {
- $textObj = new DBHTMLText('Test', 'meta,link');
+ $textObj = new DBHTMLText('Test', 'whitelist=meta,link');
$this->assertEquals(
'',
$textObj->whitelistContent('
Remove
Remove Text'),
'Removes any elements not in whitelist excluding text elements'
);
- $textObj = new DBHTMLText('Test', 'meta,link,text()');
+ $textObj = new DBHTMLText('Test', 'whitelist=meta,link,text()');
$this->assertEquals(
'Keep Text',
$textObj->whitelistContent('
Remove
Keep Text'),
diff --git a/tests/security/MemberTest.php b/tests/security/MemberTest.php
index 04b79601c29..6f604cb0664 100644
--- a/tests/security/MemberTest.php
+++ b/tests/security/MemberTest.php
@@ -236,7 +236,7 @@ public function testForgotPasswordEmaling() {
// Check existance of reset link
$this->assertEmailSent("testuser@example.com", null, 'Your password reset link',
- '/Security\/changepassword\?m='.$member->ID.'&t=[^"]+/');
+ '/Security\/changepassword\?m='.$member->ID.'&t=[^"]+/');
}
/**
diff --git a/tests/view/SSViewerTest.php b/tests/view/SSViewerTest.php
index 2f92f494e67..c5bb6297c2a 100644
--- a/tests/view/SSViewerTest.php
+++ b/tests/view/SSViewerTest.php
@@ -5,7 +5,7 @@
use SilverStripe\Security\Member;
use SilverStripe\Security\SecurityToken;
use SilverStripe\Security\Permission;
-
+use SilverStripe\Model\FieldType\DBField;
class SSViewerTest extends SapphireTest {
@@ -835,21 +835,17 @@ public function testCastingHelpers() {
$t = SSViewer::fromString('$HTMLValue.XML')->process($vd)
);
- // Uncasted value (falls back to ViewableData::$default_cast="HTMLText")
- $vd = new SSViewerTest_ViewableData(); // TODO Fix caching
+ // Uncasted value (falls back to ViewableData::$default_cast="Text")
+ $vd = new SSViewerTest_ViewableData();
$vd->UncastedValue = 'html';
$this->assertEquals(
- 'html',
+ '<b>html</b>',
$t = SSViewer::fromString('$UncastedValue')->process($vd)
);
- $vd = new SSViewerTest_ViewableData(); // TODO Fix caching
- $vd->UncastedValue = 'html';
$this->assertEquals(
'html',
$t = SSViewer::fromString('$UncastedValue.RAW')->process($vd)
);
- $vd = new SSViewerTest_ViewableData(); // TODO Fix caching
- $vd->UncastedValue = 'html';
$this->assertEquals(
'<b>html</b>',
$t = SSViewer::fromString('$UncastedValue.XML')->process($vd)
@@ -1247,8 +1243,14 @@ public function testRewriteHashlinks() {