silverstripe-framework/core/ViewableData.php

1233 lines
32 KiB
PHP
Raw Normal View History

<?php
/**
* Generic class for all data that will be accessed from a view.
*
* View interrogate their controllers to provide them with the data they need. They to this by
* calling the methods provided by the ViewableData base-class, from which most Sapphire objects
* are inherited.
*
*
* ViewableData cover page controls, controllers, and data objects. It's the basic unit of
* data exchange. More specifically, it's anything that can be put into a view.
* @package sapphire
* @subpackage view
*/
class ViewableData extends Object implements IteratorAggregate {
/**
* The iterator position.
* @var int
*/
protected $iteratorPos;
/**
* Total number of items in the iterator.
* @var int
*/
protected $iteratorTotalItems;
/**
* Failover object.
* @var ViewableData
*/
protected $failover;
/**
* A cast of this object's controls in object format
* @var array
*/
protected $_object_cache = array();
/**
* A cast of this object's controls in XML-safe format
* @var array
*/
protected $_xml_cache = array();
/**
* A cast of this object's controls in their native format (used by cachedCall)
* @var array
*/
protected $_natural_cache = array();
/**
* @var $customisedObj ViewableData_Customised|ViewableData_ObjectCustomised
* Saves past customisations to make them available on subsequent rendering-calls.
* E.g. This enables LeftAndMain to access customisations on controller-actions in
* Left() and Right().
*/
protected $customisedObj;
/**
* Define custom methods for this object. Called once per class.
* Implements failover and cached methods.
*/
function defineMethods() {
// Set up failover
if($this->failover) {
$this->addMethodsFrom('failover');
}
if(isset($_GET['debugfailover'])) {
Debug::message("$this->class / $this->failover");
}
// Set up cached methods
$methodNames = $this->allMethodNames();
foreach($methodNames as $methodName) {
if($methodName[0] == "_") {
$trimmedName = substr($methodName,1);
$this->createMethod($trimmedName, "return \$obj->cachedCall('$methodName', '$trimmedName', \$args);");
}
}
parent::defineMethods();
}
/**
* Returns a "1 record iterator"
* Views <%control %> tags operate by looping over an item for as many instances as are
* available. When you stick a single ViewableData object in a control tag, the foreach()
* loop still needs to work. We do this by creating an iterator that only returns one record.
* This will always return the current ViewableData object.
* @return ViewableData_Iterator A 1 record iterator
*/
function getIterator() {
return new ViewableData_Iterator($this);
}
/**
* Accessor overloader.
* Allows default getting of fields via $this->getVal(), or mediation via a
* getParamName() method.
* @param string $field The field name.
* @return mixed The field.
*/
public function __get($field) {
if($this->hasMethod($funcName = "get$field")) {
return $this->$funcName();
} else if($this->hasField($field)) {
return $this->getField($field);
} else if($this->failover) {
return $this->failover->$field;
}
}
/**
* Setter overloader.
* Allows default setting of fields in $this->setValue(), or mediation via a
* getParamName() method.
* @param string $field The field name.
* @param mixed $val The field value.
*/
public function __set($field, $val) {
if($this->hasMethod($funcName = "set$field")) {
return $this->$funcName($val);
} else {
$this->setField($field, $val);
}
}
/**
* Is-set overloader.
* Will check to see if the given field exists on this object. Calls the hasField() method,
* as well as checking failover classes.
* @param string $field The field name.
* @return boolean True if field exists
*/
public function __isset($field) {
if($this->hasField($field)) {
return true;
}
if($this->failover && $this->failover->hasField($field)) {
return true;
}
return false;
}
/**
* Get a field by it's name. This should be overloaded in child classes.
* @param string $field fieldname
*/
protected function getField($field) {
}
/**
* Set a fields value. This should be overloaded in child classes.
* @param string $field The field name.
* @param mixed $val The field value.
*/
protected function setField($field, $val) {
$this->$field = $val;
}
/**
* Checks if a field exists on this object. This should be overloaded in child classes.
* @param string $field The field name
* @return boolean
*/
public function hasField($field) {
}
/**
* Cache used by castingHelperPair().
* @var array
*/
protected static $castingHelperPair_cache;
/**
* Returns the "casting helper" for the given field and the casting class name. A casting helper
* is a piece of PHP code that, when evaluated, will create an object to represent the value.
*
* The return value is an map containing two values:
* - className: The name of the class (eg: 'Varchar')
* - castingHelper: The casting helper (eg: 'return new Varchar($fieldName);')
*
* @param string $field The field name
* @return array
*/
public function castingHelperPair($field) {
$class = $this->class;
if(!isset(self::$castingHelperPair_cache[$class])) {
if($this->failover) {
$this->failover->buildCastingHelperCache(self::$castingHelperPair_cache[$class]);
}
$this->buildCastingHelperCache(self::$castingHelperPair_cache[$class]);
self::$castingHelperPair_cache[$class]['ClassName'] = array("className" => "Varchar", "castingHelper" => "return new Varchar(\$fieldName);");
}
return isset(self::$castingHelperPair_cache[$class][$field]) ? self::$castingHelperPair_cache[$class][$field] : null;
}
/**
* A helper function used by castingHelperPair() to build the cache.
* @param array
*/
public function buildCastingHelperCache(&$cache) {
$class = $this->class ? $this->class : get_class($this);
$classes = ClassInfo::ancestry($class);
foreach($classes as $componentClass) {
if($componentClass == "ViewableData") $isViewableData = true;
if($componentClass == "DataObject") $isDataObject = true;
if(isset($isDataObject) && $isDataObject) {
$fields = Object::uninherited_static($componentClass, 'db');
if($fields) foreach($fields as $fieldName => $fieldSchema) {
$cache[$fieldName] = ViewableData::castingObjectCreatorPair($fieldSchema);
}
}
if(isset($isViewableData) && $isViewableData) {
$fields = Object::uninherited_static($componentClass, 'casting');
if($fields) foreach($fields as $fieldName => $fieldSchema) {
$cache[$fieldName] = ViewableData::castingObjectCreatorPair($fieldSchema);
}
}
}
}
/**
* Returns the "casting helper" for the given field. A casting helper
* is a piece of PHP code that, when evaluated, will create an object to represent the value.
* @param string $field The field name.
* @return string
*/
public function castingHelper($field) {
$pair = $this->castingHelperPair($field);
return $pair['castingHelper'];
}
/**
* Converts a field spec into an object creator.
* For example: "Int" becomes "new Int($fieldName);" and "Varchar(50)" becomes "new Varchar($fieldName, 50);"
* @param string $fieldSchema The field spec.
* @return string
*/
public static function castingObjectCreator($fieldSchema) {
if(strpos($fieldSchema,'(') === false) {
return "return Object::create('{$fieldSchema}',\$fieldName);";
} else {
return "return new " . ereg_replace('^([^(]+)\\(','\\1($fieldName,', $fieldSchema) . ';';
}
}
/**
* Converts a field spec into an object creator pair; this is a map containing className and castingHelper.
* See {@link castingObjectCreator} for more information.
* @param string $fieldSchema The field spec.
* @return array
*/
public static function castingObjectCreatorPair($fieldSchema) {
if(strpos($fieldSchema,'(') === false) {
return array(
'className' => $fieldSchema,
'castingHelper' => "return Object::create('{$fieldSchema}',\$fieldName);"
);
} else if(ereg('^([^(]+)\\(', $fieldSchema, $parts)) {
return array(
'className' => $parts[1],
'castingHelper' => "return new " . ereg_replace('^([^(]+)\\(','\\1($fieldName,', $fieldSchema) . ';',
);
} else {
user_error("castingObjectCreatorPair: Bad field schema '$fieldSchema' in class $this->class", E_USER_WARNING);
}
}
/**
* Return the string-format type for the given field.
*
* @param string $fieldName
* @return string 'xml'|'raw'
*/
function escapeTypeForField($fieldName) {
$helperPair = $this->castingHelperPair($fieldName);
$castedClass = $helperPair['className'];
if(!$castedClass || $castedClass == 'HTMLText' || $castedClass == 'HTMLVarchar') return "xml";
else return "raw";
}
/**
* Return the object version of the given field/method.
* @param string $fieldName The name of the field/method.
* @param array $args The arugments.
* @param boolean $forceReturnObject If true, this method will *always* return an object. If there's
* no sensible one available, it will return new ViewableData()
* @return mixed;
*/
public function obj($fieldName, $args = null, $forceReturnObject = false) {
if(isset($_GET['debug_profile'])) {
Profiler::mark("template($fieldName)", " on $this->class object");
}
if($args) {
$identifier = $fieldName . ',' . implode(',', $args);
} else {
$identifier = $fieldName;
}
// Fix for PHP 5.3 - $args cannot be null
if(is_null($args))
$args=Array();
if(isset($this->_object_cache[$identifier])) {
$fieldObj = $this->_object_cache[$identifier];
} else {
if($this->hasMethod($fieldName)) {
$val = call_user_func_array(array(&$this, $fieldName), $args);
} else {
$val = $this->$fieldName;
}
$this->_natural_cache[$identifier] = $val;
if(is_object($val)) {
$fieldObj = $val;
} else {
$helperPair = $this->castingHelperPair($fieldName);
if(!$helperPair && $this->failover) {
$helperPair = $this->failover->castingHelperPair($fieldName);
}
$constructor = $helperPair['castingHelper'];
if($constructor) {
$fieldObj = eval($constructor);
if($this->hasMethod('getAllFields')) {
$fieldObj->setValue($val, $this->getAllFields());
} else {
$fieldObj->setValue($val);
}
}
}
$this->_object_cache[$identifier] = isset($fieldObj) ? $fieldObj : null;
}
if(!isset($fieldObj) && $forceReturnObject){
$fieldObj = new ViewableData();
}
if(isset($_GET['debug_profile'])) {
Profiler::unmark("template($fieldName)", " on $this->class object");
}
return isset($fieldObj) ? $fieldObj : null;
}
/**
* Return the value (non-object) version of the given field/method.
* @deprecated ViewableData->val() is deprecated, use XML_val() instead
*/
public function val($fieldName, $args = null) {
return $this->XML_val($fieldName, $args);
}
/**
* Returns the value of the given field / method in an XML-safe format.
* @param string $fieldName The field name.
* @param array $args The arguments.
* @param boolean $cache Cache calls to this function.
* @return string
*/
public function XML_val($fieldName, $args = null, $cache = false) {
if(isset($_GET['debug_profile'])) {
Profiler::mark("template($fieldName)", " on $this->class object");
}
if($cache) {
if($args) {
$identifier = $fieldName . ',' . implode(',', $args);
} else {
$identifier = $fieldName;
}
if(isset($this->_xml_cache[$identifier])) {
if(isset($_GET['debug_profile'])) {
Profiler::unmark("template($fieldName)", " on $this->class object");
}
return $this->_xml_cache[$identifier];
}
}
// Fix for PHP 5.3 - $args cannot be null
if(is_null($args))
$args=Array();
// This will happen when cachedCall was called on an object; don't bother re-calling the method, just
// do the conversion step below
if($cache && isset($this->_object_cache[$identifier])) {
$val = $this->_object_cache[$identifier];
// Get the field / method
} else {
if($this->hasMethod($fieldName)) {
$val = call_user_func_array(array(&$this, $fieldName), $args);
} else {
$val = $this->$fieldName;
}
if(isset($identifier)) {
$this->_natural_cache[$identifier] = $val;
}
}
// Case 1: object; converted to XML_val() by
if(is_object($val)) {
if($cache) {
$this->_object_cache[$identifier] = $val;
}
$val = $val->forTemplate();
if($cache) {
$this->_xml_cache[$identifier] = $val;
}
} else {
// Identify the 'casted class' of this field, which will give us some hints about what kind of
// data has been returned
if(isset($_GET['debug_profile'])) {
Profiler::mark('casting cost');
}
// Case 2: Check if the value is raw and must be made XML-safe
if($this->escapeTypeForField($fieldName) != 'xml') $val = Convert::raw2xml($val);
if(isset($_GET['debug_profile'])) {
Profiler::unmark('casting cost');
}
if($cache) {
$this->_xml_cache[$identifier] = $val;
}
}
if(isset($_GET['debug_profile'])) {
Profiler::unmark("template($fieldName)", " on $this->class object");
}
return $val;
}
/**
* Return a named array of calls to XML_val with different parameters.
* Each value in the array is used as the first argument to XML_val. The result is a named array of the return values.
*
* The intended use-case is when converting simple templates to PHP methods to optimise code, as we did in the form classes.
* If you're calling renderWith more than a few times on a very simple template, this can be useful.
*
* extract(getXMLValues(array('Title','Field','Message')))
* // You can now use $Title, $Field, and $Message as you would in a template
*
* @param array $elementList The list of field names.
* @return array
*/
public function getXMLValues($elementList) {
foreach($elementList as $elementName) {
$result[$elementName] = $this->XML_val($elementName);
}
return $result;
}
/**
* Return the value of the given field without any escaping.
* @param string $fieldName The field name.
* @param array $args The arguments.
* @return string
*/
public function RAW_val($fieldName, $args = null) {
return Convert::xml2raw($this->XML_val($fieldName, $args));
}
/**
* Return the value of the given field in an SQL safe format.
* @param string $fieldName The field name.
* @param array $args The arguments.
* @return string
*/
public function SQL_val($fieldName, $args = null) {
return Convert::xml2sql($this->XML_val($fieldName, $args));
}
/**
* Return the value of the given field in an JavaScript safe format.
* @param string $fieldName The field name.
* @param array $args The arguments.
* @return string
*/
public function JS_val($fieldName, $args = null) {
return Convert::xml2js($this->XML_val($fieldName, $args));
}
/**
* Return the value of the given field in an XML attribute safe format.
* @param string $fieldName The field name.
* @param array $args The arguments.
* @return string
*/
public function ATT_val($fieldName, $args = null) {
return Convert::xml2att($this->XML_val($fieldName, $args));
}
/**
* SSViewer's data-access method.
* All template calls to ViewableData are fed through this function. It takes care of caching
* data, and linking up parents to support Menu1_Menu2() syntax for nested data.
* @param string $funcName the method to call
* @param string $identifier
* @param array $args The arguments
* @return mixed
*/
function cachedCall($funcName, $identifier = null, $args = null) {
if(isset($_GET['debug_profile'])) {
Profiler::mark("template($funcName)", " on $this->class");
}
if(!$identifier) {
if($args) {
$identifier = $funcName . ',' . implode(',', $args);
} else {
$identifier = $funcName;
}
}
// Fix for PHP 5.3 - $args cannot be null
if(is_null($args))
$args=Array();
if(isset($this->_natural_cache[$identifier])) {
if(isset($_GET['debug_profile'])) {
Profiler::unmark("template($funcName)", " on $this->class");
}
return $this->_natural_cache[$identifier];
}
if($this->hasMethod($funcName)) {
$val = call_user_func_array(array(&$this, $funcName), $args);
} else {
$val = $this->$funcName;
}
$this->_natural_cache[$identifier] = $val;
if(is_object($val)) {
$this->_object_cache[$identifier] = $val;
} else {
$helperPair = $this->castingHelperPair($funcName);
$castedClass = $helperPair['className'];
if($castedClass && $castedClass != 'HTMLText' && $castedClass != 'HTMLVarchar' && $castedClass != 'Text') {
$val = Convert::raw2xml($val);
}
$this->_xml_cache[$identifier] = $val;
}
if(isset($_GET['debug_profile'])) {
Profiler::unmark("template($funcName)", " on $this->class");
}
return $val;
}
/**
* @param $obj ViewableData_Customised|ViewableData_ObjectCustomised
*/
function setCustomisedObj($obj) {
$this->customisedObj = $obj;
}
/**
* Returns true if the given method/parameter has a value
* If the item is an object, it will use the exists() method to determine existence
* @param string $funcName The function name.
* @param array $args The arguments.
* @return boolean
*/
function hasValue($funcName, $args = null) {
$test = $this->cachedCall($funcName, null, $args);
if(is_object($test)) {
return $test->exists();
} else if($test && $test !== '<p></p>') {
return true;
}
}
/**
* Set up the "iterator properties" for this object.
* These are properties that give information about where we are in the set.
* @param int $pos Position in iterator
* @param int $totalItems Total number of items
*/
function iteratorProperties($pos, $totalItems) {
$this->iteratorPos = $pos;
$this->iteratorTotalItems = $totalItems;
}
/**
* Returns true if this item is the first in the container set.
* @return boolean
*/
function First() {
return $this->iteratorPos == 0;
}
/**
* Returns true if this item is the last in the container set.
* @return boolean
*/
function Last() {
return $this->iteratorPos == $this->iteratorTotalItems - 1;
}
/**
* Returns 'first' if this item is the first in the container set.
* Returns 'last' if this item is the last in the container set.
*/
function FirstLast() {
if($this->iteratorPos == 0) {
return "first";
} else if($this->iteratorPos == $this->iteratorTotalItems - 1) {
return "last";
} else {
return "";
}
}
/**
* Returns 'middle' if this item is between first and last.
* @return boolean
*/
function MiddleString(){
if($this->Middle())
return "middle";
else
return "";
}
/**
* Returns true if this item is one of the middle items in the container set.
* @return boolean
*/
function Middle() {
return $this->iteratorPos > 0 && $this->iteratorPos < $this->iteratorTotalItems - 1;
}
/**
* Returns true if this item is an even item in the container set.
* @return boolean
*/
function Even() {
return $this->iteratorPos % 2;
}
/**
* Returns true if this item is an even item in the container set.
* @return boolean
*/
function Odd() {
return !$this->Even();
}
/**
* Returns 'even' if this item is an even item in the container set.
* Returns 'odd' if this item is an odd item in the container set.
* @return string
*/
function EvenOdd() {
return $this->Even() ? 'even' : 'odd';
}
/**
* Returns the numerical number of this item in the dataset.
* The count starts from $startIndex, which defaults to 1.
* @param int $startIndex Number to start count from.
* @return int
*/
function Pos($startIndex = 1) {
return $this->iteratorPos + $startIndex;
}
/**
* Return the total number of "sibling" items in the dataset.
* @return int
*/
function TotalItems() {
return $this->iteratorTotalItems;
}
/**
* Returns the currently logged in user.
* @return Member
*/
function CurrentMember() {
return Member::currentUser();
}
/**
* Return a CSRF-preventing ID to insert into a form.
*
* @return string
*/
public function getSecurityID() {
$token = SecurityToken::inst();
return $token->getValue();
}
/**
* Returns the Security ID.
* This is used to prevent CRSF attacks in forms.
* @return int
*/
function SecurityID() {
if(Session::get('SecurityID')) {
$securityID = Session::get('SecurityID');
} else {
$securityID = rand();
Session::set('SecurityID', $securityID);
}
return $securityID;
}
/**
* Checks if the current user has the given permission.
* Can be used to implement security-specific sections within templates
* @return int The Permission record-ID if the permission can be found, null otherwise
*/
function HasPerm($permCode) {
return Permission::check($permCode);
}
/**
* Add some arbitrary data to this viewabledata object. Returns a new object with the
* merged data.
* @param mixed $data The data to add.
* @return ViewableData
*/
function customise($data) {
if(is_array($data)) {
return new ViewableData_Customised($this, $data);
} else if(is_object($data)) {
return new ViewableData_ObjectCustomised($this, $data);
} else {
return $this;
}
}
/**
* Render this data using the given template, and return the result as a string
* You can pass one of the following:
* - A template name.
* - An array of template names. The first template that exists will be used.
* - An SSViewer object.
* @param string|array|SSViewer The template.
* @return string
*/
function renderWith($template, $params = null) {
if(!is_object($template)) {
$template = new SSViewer($template);
}
// if the object is already customised (e.g. through Controller->run()), use it
$obj = ($this->customisedObj) ? $this->customisedObj : $this;
if($params) $obj = $this->customise($params);
if(is_a($template,'SSViewer')) {
return $template->process($obj);
} else {
user_error("ViewableData::renderWith() Was passed a $template->class object instead of a SSViewer object", E_USER_ERROR);
}
}
/**
* Return the site's absolute base URL, with a slash on the end.
* @return string
*/
function BaseHref() {
return Director::absoluteBaseURL();
}
/**
* When rendering some objects it is necessary to iterate over the object being rendered, to
* do this, you need access to itself.
*
* @return ViewableData
*/
function Me() {
return $this;
}
/**
* Returns wether the current request is triggered
* by an XMLHTTPRequest object.
*
* @return bool
*/
function IsAjax() {
return Director::is_ajax();
}
/**
* @return string Locale configured in environment settings or user profile (e.g. 'en_US')
*/
function i18nLocale() {
return i18n::get_locale();
}
/**
* Return a Debugger object.
* This is set up like so that you can put $Debug.Content into your template to get debugging
* information about $Content.
* @return ViewableData_Debugger
*/
function Debug() {
$d = new ViewableData_Debugger($this);
return $d->forTemplate();
}
/**
* Returns the current controller
* @return Controller
*/
function CurrentPage() {
return Controller::curr();
}
/**
* Returns the top level ViewableData being rendered.
* @return ViewableData
*/
function Top() {
return SSViewer::topLevel();
}
/**
* Returns the root directory of the theme we're working with.
* This can be useful for referencing images within the theme. For example, you might put a reference to
* <img src="$ThemeDir/images/something.gif"> in your template.
*
* If your image is within a subtheme, such as mytheme_forum, you can set the subtheme parameter. For example,
* <img src="$ThemeDir(forum)/images/something.gif">
*
* We don't recommend that you use this method when no theme is selected. That is, we recommend that you only put
* $ThemeDir into your theme templates. However, if no theme is selected, this will be the project folder/
*
* @param subtheme The subtheme name.
*/
public function ThemeDir($subtheme = null) {
$theme = SSViewer::current_theme();
if($theme) {
return "themes/$theme" . ($subtheme ? "_$subtheme" : "");
} else {
return project();
}
}
/**
* Get part of class ancestry for css-class-usage.
* Avoids having to subclass just to built templates with new css-classes,
* and allows for versatile css inheritance and overrides.
*
Merging in refactored Translatable architecture from trunk, including related/required changesets like enhancements to Object static handling (see details below) ------------------------------------------------------------------------ r68900 | sminnee | 2008-12-15 14:30:41 +1300 (Mon, 15 Dec 2008) | 1 line Static caching merges from dnc branch ------------------------------------------------------------------------ r68917 | sminnee | 2008-12-15 14:49:06 +1300 (Mon, 15 Dec 2008) | 1 line Merged Requirements fix from nestedurls branch ------------------------------------------------------------------------ r70033 | aoneil | 2009-01-13 14:03:41 +1300 (Tue, 13 Jan 2009) | 2 lines Add translation migration task ------------------------------------------------------------------------ r70072 | ischommer | 2009-01-13 17:34:27 +1300 (Tue, 13 Jan 2009) | 5 lines API CHANGE Removed obsolete internal Translatable methods: hasOwnTranslatableFields(), allFieldsInTable() ENHANCEMENT Removed $create flag in Translatable::getTranslation() and replaced with explit action createTranslation() ENHANCEMENT Sorting return array of Translatable::getTranslatedLangs() ENHANCEMENT Added a note about saving a page before creating a translation MINOR Added phpdoc to Translatable ------------------------------------------------------------------------ r70073 | ischommer | 2009-01-13 17:34:45 +1300 (Tue, 13 Jan 2009) | 1 line ENHANCEMENT Added basic unit tests to new Translatable API ------------------------------------------------------------------------ r70080 | aoneil | 2009-01-13 18:04:21 +1300 (Tue, 13 Jan 2009) | 3 lines BUGFIX: Fix translatable migration regenerating URLSegments when it shouldn't BUGFIX: Fix translatable migration not writing records to Live properly ------------------------------------------------------------------------ r70118 | ischommer | 2009-01-14 11:28:24 +1300 (Wed, 14 Jan 2009) | 3 lines API CHANGE Removed obsolete Translatable::table_exists() ENHANCEMENT Made Translatable constructor arguments optional, as by default all database fields are marked translatable MINOR More unit tests for Translatable ------------------------------------------------------------------------ r70138 | ischommer | 2009-01-14 17:00:30 +1300 (Wed, 14 Jan 2009) | 1 line BUGFIX Disabled assumption that SQLQuery->filtersOnID() should only kick in when exactly one WHERE clause is given - this is very fragile and hard to test. It would return TRUE on $where = "SiteTree.ID = 5", but not on $where = array("Lang = 'de'", "SiteTree.ID = 5") ------------------------------------------------------------------------ r70214 | ischommer | 2009-01-15 18:56:25 +1300 (Thu, 15 Jan 2009) | 3 lines BUGFIX Falling back to Translatable::current_lang() if no $context object is given, in augmentAllChildrenIncludingDeleted() and AllChildrenIncludingDeleted() MINOR phpdoc for Translatable MINOR Added more Translatable unit tests ------------------------------------------------------------------------ r70306 | ischommer | 2009-01-16 17:14:34 +1300 (Fri, 16 Jan 2009) | 9 lines ENHANCEMENT Recursively creating translations for parent pages to ensure that a translated page is still accessible by traversing the tree, e.g. in "cms translation mode" (in Translatable->onBeforeWrite()) ENHANCEMENT Simplified AllChildrenIncludingDeleted() to not require a special augmentAllChildrenIncludingDeleted() implementation: We don't combine untranslated/translated children any longer (which was used in CMS tree view), but rather just show translated records ENHANCEMENT Ensuring uniqueness of URL segments by appending "-<langcode>" to new translations (in Translatable->onBeforeWrite()) ENHANCEMENT Added Translatable->alternateGetByUrl() as a hook into SiteTree::get_by_url() ENHANCEMENT Adding link back to original page in CMS editform for translations BUGFIX Excluding HiddenField instances from Translatable->updateCMSFields() BUGFIX Don't require a record to be written (through exists()) when checking Translatable->isTranslation() or Translatable->hasTranslation() MINOR Don't use createMethod() shortcut for Translatable->AllChildrenIncludingDeleted() MINOR Added Translatable unit tests ------------------------------------------------------------------------ r70318 | ischommer | 2009-01-19 11:46:16 +1300 (Mon, 19 Jan 2009) | 1 line BUGFIX Reverted special cases for Translatable in Versioned->canBeVersioned() (originally committed in r42119) - was checking for existence of underscores in table names as an indication of the "_lang" suffix, which is no longer needed. It was also a flawed assumption which tripped over classes like TranslatableTest_TestPage ------------------------------------------------------------------------ r70319 | ischommer | 2009-01-19 11:47:02 +1300 (Mon, 19 Jan 2009) | 1 line ENHANCEMENT Disabled Translatab-e>augmentWrite() - was only needed for the blacklist fields implementation which is inactive for the moment ------------------------------------------------------------------------ r70326 | ischommer | 2009-01-19 14:25:23 +1300 (Mon, 19 Jan 2009) | 2 lines ENHANCEMENT Making ErrorPage static HTML files translatable (#2233) ENHANCEMENT Added ErrorPage::$static_filepath to flexibly set location of static error pages (defaults to /assets) ------------------------------------------------------------------------ r70327 | ischommer | 2009-01-19 15:18:41 +1300 (Mon, 19 Jan 2009) | 1 line FEATURE Enabled specifying a language through a hidden field in SearchForm which limits the search to pages in this language (incl. unit tests) ------------------------------------------------------------------------ r71258 | sharvey | 2009-02-03 15:49:34 +1300 (Tue, 03 Feb 2009) | 2 lines BUGFIX: Fix translatable being enabled when it shouldn't be ------------------------------------------------------------------------ r71340 | ischommer | 2009-02-04 14:36:12 +1300 (Wed, 04 Feb 2009) | 1 line BUGFIX Including Hierarchy->children in flushCache() and renamed to _cache_children. This caused problems in TranslatableTest when re-using the same SiteTree->Children() method with different languages on the same object (even with calling flushCache() inbetween the calls) ------------------------------------------------------------------------ r71567 | gmunn | 2009-02-10 13:49:16 +1300 (Tue, 10 Feb 2009) | 1 line 'URLSegment' on line 484 and 494 now escaped ------------------------------------------------------------------------ r72054 | ischommer | 2009-02-23 10:30:41 +1300 (Mon, 23 Feb 2009) | 3 lines BUGFIX Fixed finding a translated homepage without an explicit URLSegment (e.g. http://mysite.com/?lang=de) - see #3540 ENHANCEMENT Added Translatable::get_homepage_urlsegment_by_language() ENHANCEMENT Added RootURLController::get_default_homepage_urlsegment() ------------------------------------------------------------------------ r72367 | ischommer | 2009-03-03 11:13:30 +1300 (Tue, 03 Mar 2009) | 2 lines ENHANCEMENT Added i18n::get_lang_from_locale() and i18n::convert_rfc1766() ENHANCEMENT Using IETF/HTTP compatible "long" language code in SiteTree->MetaTags(). This means the default <meta type="content-language..."> value will be "en-US" instead of "en". The locale can be either set through the Translatable content language, or through i18n::set_locale() ------------------------------------------------------------------------ r73036 | sminnee | 2009-03-14 13:16:32 +1300 (Sat, 14 Mar 2009) | 1 line ENHANCEMENT #3032 ajshort: Use static methods for accessing static data ------------------------------------------------------------------------ r73059 | sminnee | 2009-03-15 14:09:59 +1300 (Sun, 15 Mar 2009) | 2 lines ENHANCEMENT: Added Object::clearCache() to clear a cache BUGFIX: Make object cache testing more robust ------------------------------------------------------------------------ r73338 | ischommer | 2009-03-19 05:13:40 +1300 (Thu, 19 Mar 2009) | 9 lines API CHANGE Added concept of "translation groups" to Translatable- every page can belong to a group of related translations, rather than having an explicit "original", meaning you can have pages in "non-default" languages which have no representation in other language trees. This group is recorded in a new table "<classname>_translationgroups". Translatable->createTranslation() and Translatable->onBeforeWrite() will automatically associate records in this groups. Added Translatable->addTranslationGroup(), Translatable->removeTranslationGroup(), Translatable->getTranslationGroup() API CHANGE Removed Translatable->isTranslation() - after the new "translation group" model, every page is potentially a translation API CHANGE Translatable->findOriginalIDs(), Translatable->setOriginalPage(), Translatable->getOriginalPage() ENHANCEMENT Translatable->getCMSFields() will now always show the "create translation" option, not only on default languages - meaning you can create translations based on other translations ENHANCEMENT Translatable language dropdown in CMS will always show all available languages, rather than filtering by already existing translations ENHANCEMENT Added check for an existing record in Translatable->createTranslation() BUGFIX Removed Translatable->getLang() which overloaded the $db property - it was causing side effects during creation of SiteTree default records. BUGFIX Added check in Translatable->augmentSQL() to avoid reapplying "Lang = ..." filter twice BUGFIX Removed bypass in Translatable->AllChildrenIncludingDeleted() ------------------------------------------------------------------------ r73339 | ischommer | 2009-03-19 05:15:46 +1300 (Thu, 19 Mar 2009) | 1 line BUGFIX Disabled "untranslated" CSS class for SiteTree elements - doesn't apply any longer with the new "translation groups" concept ------------------------------------------------------------------------ r73341 | ischommer | 2009-03-19 06:01:51 +1300 (Thu, 19 Mar 2009) | 1 line BUGFIX Disabled auto-excluding of default language from the "available languages" array in LanguageDropdownField - due to the new "translation groups" its possible to have a translation from another language into the default language ------------------------------------------------------------------------ r73342 | ischommer | 2009-03-19 06:13:23 +1300 (Thu, 19 Mar 2009) | 4 lines BUGFIX Setting ParentID of translated record if recursively creating parents in Translatable::onBeforeWrite() BUGFIX Fixing inline form action for "create translation" BUGFIX Removed link to "original page" for a translation - no longer valid MINOR documentation for Translatable ------------------------------------------------------------------------ r73464 | ischommer | 2009-03-20 20:51:00 +1300 (Fri, 20 Mar 2009) | 1 line MINOR documentation ------------------------------------------------------------------------ r73465 | ischommer | 2009-03-20 20:58:52 +1300 (Fri, 20 Mar 2009) | 1 line BUGFIX Fixed Hierarchy->Children() testing in TranslatableTest - with the new datamodel you can't call Children() in a different language regardless of Translatable::set_reading_lang(), the Children() call has to be made from a parent in the same language ------------------------------------------------------------------------ r73466 | ischommer | 2009-03-20 21:36:40 +1300 (Fri, 20 Mar 2009) | 2 lines ENHANCEMENT Added Translatable::get_locale_from_lang(), Translatable::get_common_locales(), $common_locales and $likely_subtags in preparation to switch Translatable from using short "lang" codes to proper long locales API CHANGE Deprecated Translatable::set_default_lang(), Translatable::default_lang() ------------------------------------------------------------------------ r73467 | ischommer | 2009-03-20 21:38:57 +1300 (Fri, 20 Mar 2009) | 1 line ENHANCEMENT Supporting "Locale-English" and "Locale-Native" as listing arguments in LanguageDropdownField ------------------------------------------------------------------------ r73468 | ischommer | 2009-03-20 21:47:06 +1300 (Fri, 20 Mar 2009) | 7 lines ENHANCEMENT Adjusted SearchForm, Debug, ErrorPage, SiteTree to using locales instead of lang codes API CHANGE Changed Translatable datamodel to use locales ("en_US") instead of lang values ("en). API CHANGE Changed Translatable::$default_lang to $default_locale, Translatable::$reading_lang to $reading_locale API CHANGE Using "locale" instead of "lang" in Translatable::choose_site_lang() to auto-detect language from cookies or GET parameters API CHANGE Deprecated Translatable::is_default_lang(), set_default_lang(), get_default_lang(), current_lang(), set_reading_lang(), get_reading_lang(), get_by_lang(), get_one_by_lang() API CHANGE Removed Translatable::get_original() - with the new "translation groups" concept there no longer is an original for a translation BUGFIX Updated MigrateTranslatableTask to new Locale based datamodel ------------------------------------------------------------------------ r73470 | ischommer | 2009-03-20 21:56:57 +1300 (Fri, 20 Mar 2009) | 1 line MINOR fixed typo ------------------------------------------------------------------------ r73472 | sminnee | 2009-03-21 17:30:04 +1300 (Sat, 21 Mar 2009) | 1 line BUGFIX: Fixed translatable test execution by making protected methods public ------------------------------------------------------------------------ r73473 | sminnee | 2009-03-21 18:10:05 +1300 (Sat, 21 Mar 2009) | 1 line ENHANCEMENT: Added Object::combined_static(), which gets all values of a static property from each class in the hierarchy ------------------------------------------------------------------------ r73883 | ischommer | 2009-04-01 08:32:19 +1300 (Wed, 01 Apr 2009) | 1 line BUGFIX Making $_SINGLETONS a global instead of a static in Core.php so it can be re-used in other places ------------------------------------------------------------------------ r73951 | ischommer | 2009-04-02 05:35:32 +1300 (Thu, 02 Apr 2009) | 3 lines API CHANGE Deprecated Translatable::enable() and i18n::enable()- use Object::add_extension('SiteTree','Translatable'), Deprecated Translatable::disable() and i18n::disable() - use Object::remove_extension('SiteTree','Translatable'), Deprecated Translatable::enabled() - use $myPage->hasExtension('Translatable') API CHANGE Removed Translatable::creating_from() - doesn't apply any longer ENHANCEMENT Translatable extension is no longer hooked up to SiteTree by default, which should improve performance and memory usage for sites not using Translatable. Please use Object::add_extension('SiteTree','Translatable') in your _config.php instead. Adjusted several classes (Image, ErrorPage, RootURLController) to the new behaviour. ------------------------------------------------------------------------ r73882 | ischommer | 2009-04-01 08:31:21 +1300 (Wed, 01 Apr 2009) | 1 line ENHANCEMENT Added DataObjectDecorator->setOwner() ------------------------------------------------------------------------ r73884 | ischommer | 2009-04-01 08:32:51 +1300 (Wed, 01 Apr 2009) | 1 line ENHANCEMENT Added Extension::get_classname_without_arguments() ------------------------------------------------------------------------ r73900 | ischommer | 2009-04-01 11:27:53 +1300 (Wed, 01 Apr 2009) | 7 lines API CHANGE Deprecated Object->extInstance(), use getExtensionInstance() instead ENHANCEMENT Added Object->getExtensionInstances() ENHANCEMENT Added Object::get_extensions() ENHANCEMENT Unsetting class caches when using Object::add_extension() to avoid problems with defineMethods etc. BUGFIX Fixed extension comparison with case sensitivity and stripping arguments in Object::has_extension() BUGFIX Unsetting all cached singletons in Object::remove_extension() to avoid outdated extension_instances MINOR Documentation in Object ------------------------------------------------------------------------ r74017 | ischommer | 2009-04-03 10:49:40 +1300 (Fri, 03 Apr 2009) | 1 line ENHANCEMENT Improved deprecated fallbacks in Translatable by auto-converting short language codes to long locales and vice versa through i18n::get_lang_from_locale()/i18n::get_locale_from_lang() ------------------------------------------------------------------------ r74030 | ischommer | 2009-04-03 11:41:26 +1300 (Fri, 03 Apr 2009) | 1 line MINOR Re-added Translatable::default_lang() for more graceful fallback to Translatable::default_locale() ------------------------------------------------------------------------ r74065 | ischommer | 2009-04-04 05:38:51 +1300 (Sat, 04 Apr 2009) | 1 line BUGFIX Re-added Translatable->isTranslation() for more friendly deprecation (originally removed in r73338) ------------------------------------------------------------------------ r74069 | ischommer | 2009-04-04 09:43:01 +1300 (Sat, 04 Apr 2009) | 1 line BUGFIX Fixed legacy handling of Translatable::enable(),Translatable::disable() and Translatable::is_enabled() - applying extension to SiteTree instead of Page to avoid datamodel clashes ------------------------------------------------------------------------ r74070 | ischommer | 2009-04-04 10:23:51 +1300 (Sat, 04 Apr 2009) | 1 line API CHANGE Deprecated Translatable::choose_site_lang(), use choose_site_locale() ------------------------------------------------------------------------ r74941 | ischommer | 2009-04-22 15:22:09 +1200 (Wed, 22 Apr 2009) | 2 lines ENHANCEMENT Adding SapphireTest::set_up_once() and SapphireTest::tear_down_once() for better test performance with state that just needs to be initialized once per test case (not per test method). Added new SapphireTestSuite to support this through PHPUnit. ENHANCEMENT Using set_up_once() in TranslatableTest and TranslatableSearchFormTest for better test run performance ------------------------------------------------------------------------ r74942 | ischommer | 2009-04-22 15:24:50 +1200 (Wed, 22 Apr 2009) | 1 line BUGFIX Fixed TranslatableSearchFormTest->setUp() method ------------------------------------------------------------------------ r73509 | ischommer | 2009-03-23 11:59:14 +1300 (Mon, 23 Mar 2009) | 1 line MINOR phpdoc documentation ------------------------------------------------------------------------ git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.3@74986 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-04-23 03:45:10 +02:00
* <code>
* <body class="$CSSClasses">
Merging in refactored Translatable architecture from trunk, including related/required changesets like enhancements to Object static handling (see details below) ------------------------------------------------------------------------ r68900 | sminnee | 2008-12-15 14:30:41 +1300 (Mon, 15 Dec 2008) | 1 line Static caching merges from dnc branch ------------------------------------------------------------------------ r68917 | sminnee | 2008-12-15 14:49:06 +1300 (Mon, 15 Dec 2008) | 1 line Merged Requirements fix from nestedurls branch ------------------------------------------------------------------------ r70033 | aoneil | 2009-01-13 14:03:41 +1300 (Tue, 13 Jan 2009) | 2 lines Add translation migration task ------------------------------------------------------------------------ r70072 | ischommer | 2009-01-13 17:34:27 +1300 (Tue, 13 Jan 2009) | 5 lines API CHANGE Removed obsolete internal Translatable methods: hasOwnTranslatableFields(), allFieldsInTable() ENHANCEMENT Removed $create flag in Translatable::getTranslation() and replaced with explit action createTranslation() ENHANCEMENT Sorting return array of Translatable::getTranslatedLangs() ENHANCEMENT Added a note about saving a page before creating a translation MINOR Added phpdoc to Translatable ------------------------------------------------------------------------ r70073 | ischommer | 2009-01-13 17:34:45 +1300 (Tue, 13 Jan 2009) | 1 line ENHANCEMENT Added basic unit tests to new Translatable API ------------------------------------------------------------------------ r70080 | aoneil | 2009-01-13 18:04:21 +1300 (Tue, 13 Jan 2009) | 3 lines BUGFIX: Fix translatable migration regenerating URLSegments when it shouldn't BUGFIX: Fix translatable migration not writing records to Live properly ------------------------------------------------------------------------ r70118 | ischommer | 2009-01-14 11:28:24 +1300 (Wed, 14 Jan 2009) | 3 lines API CHANGE Removed obsolete Translatable::table_exists() ENHANCEMENT Made Translatable constructor arguments optional, as by default all database fields are marked translatable MINOR More unit tests for Translatable ------------------------------------------------------------------------ r70138 | ischommer | 2009-01-14 17:00:30 +1300 (Wed, 14 Jan 2009) | 1 line BUGFIX Disabled assumption that SQLQuery->filtersOnID() should only kick in when exactly one WHERE clause is given - this is very fragile and hard to test. It would return TRUE on $where = "SiteTree.ID = 5", but not on $where = array("Lang = 'de'", "SiteTree.ID = 5") ------------------------------------------------------------------------ r70214 | ischommer | 2009-01-15 18:56:25 +1300 (Thu, 15 Jan 2009) | 3 lines BUGFIX Falling back to Translatable::current_lang() if no $context object is given, in augmentAllChildrenIncludingDeleted() and AllChildrenIncludingDeleted() MINOR phpdoc for Translatable MINOR Added more Translatable unit tests ------------------------------------------------------------------------ r70306 | ischommer | 2009-01-16 17:14:34 +1300 (Fri, 16 Jan 2009) | 9 lines ENHANCEMENT Recursively creating translations for parent pages to ensure that a translated page is still accessible by traversing the tree, e.g. in "cms translation mode" (in Translatable->onBeforeWrite()) ENHANCEMENT Simplified AllChildrenIncludingDeleted() to not require a special augmentAllChildrenIncludingDeleted() implementation: We don't combine untranslated/translated children any longer (which was used in CMS tree view), but rather just show translated records ENHANCEMENT Ensuring uniqueness of URL segments by appending "-<langcode>" to new translations (in Translatable->onBeforeWrite()) ENHANCEMENT Added Translatable->alternateGetByUrl() as a hook into SiteTree::get_by_url() ENHANCEMENT Adding link back to original page in CMS editform for translations BUGFIX Excluding HiddenField instances from Translatable->updateCMSFields() BUGFIX Don't require a record to be written (through exists()) when checking Translatable->isTranslation() or Translatable->hasTranslation() MINOR Don't use createMethod() shortcut for Translatable->AllChildrenIncludingDeleted() MINOR Added Translatable unit tests ------------------------------------------------------------------------ r70318 | ischommer | 2009-01-19 11:46:16 +1300 (Mon, 19 Jan 2009) | 1 line BUGFIX Reverted special cases for Translatable in Versioned->canBeVersioned() (originally committed in r42119) - was checking for existence of underscores in table names as an indication of the "_lang" suffix, which is no longer needed. It was also a flawed assumption which tripped over classes like TranslatableTest_TestPage ------------------------------------------------------------------------ r70319 | ischommer | 2009-01-19 11:47:02 +1300 (Mon, 19 Jan 2009) | 1 line ENHANCEMENT Disabled Translatab-e>augmentWrite() - was only needed for the blacklist fields implementation which is inactive for the moment ------------------------------------------------------------------------ r70326 | ischommer | 2009-01-19 14:25:23 +1300 (Mon, 19 Jan 2009) | 2 lines ENHANCEMENT Making ErrorPage static HTML files translatable (#2233) ENHANCEMENT Added ErrorPage::$static_filepath to flexibly set location of static error pages (defaults to /assets) ------------------------------------------------------------------------ r70327 | ischommer | 2009-01-19 15:18:41 +1300 (Mon, 19 Jan 2009) | 1 line FEATURE Enabled specifying a language through a hidden field in SearchForm which limits the search to pages in this language (incl. unit tests) ------------------------------------------------------------------------ r71258 | sharvey | 2009-02-03 15:49:34 +1300 (Tue, 03 Feb 2009) | 2 lines BUGFIX: Fix translatable being enabled when it shouldn't be ------------------------------------------------------------------------ r71340 | ischommer | 2009-02-04 14:36:12 +1300 (Wed, 04 Feb 2009) | 1 line BUGFIX Including Hierarchy->children in flushCache() and renamed to _cache_children. This caused problems in TranslatableTest when re-using the same SiteTree->Children() method with different languages on the same object (even with calling flushCache() inbetween the calls) ------------------------------------------------------------------------ r71567 | gmunn | 2009-02-10 13:49:16 +1300 (Tue, 10 Feb 2009) | 1 line 'URLSegment' on line 484 and 494 now escaped ------------------------------------------------------------------------ r72054 | ischommer | 2009-02-23 10:30:41 +1300 (Mon, 23 Feb 2009) | 3 lines BUGFIX Fixed finding a translated homepage without an explicit URLSegment (e.g. http://mysite.com/?lang=de) - see #3540 ENHANCEMENT Added Translatable::get_homepage_urlsegment_by_language() ENHANCEMENT Added RootURLController::get_default_homepage_urlsegment() ------------------------------------------------------------------------ r72367 | ischommer | 2009-03-03 11:13:30 +1300 (Tue, 03 Mar 2009) | 2 lines ENHANCEMENT Added i18n::get_lang_from_locale() and i18n::convert_rfc1766() ENHANCEMENT Using IETF/HTTP compatible "long" language code in SiteTree->MetaTags(). This means the default <meta type="content-language..."> value will be "en-US" instead of "en". The locale can be either set through the Translatable content language, or through i18n::set_locale() ------------------------------------------------------------------------ r73036 | sminnee | 2009-03-14 13:16:32 +1300 (Sat, 14 Mar 2009) | 1 line ENHANCEMENT #3032 ajshort: Use static methods for accessing static data ------------------------------------------------------------------------ r73059 | sminnee | 2009-03-15 14:09:59 +1300 (Sun, 15 Mar 2009) | 2 lines ENHANCEMENT: Added Object::clearCache() to clear a cache BUGFIX: Make object cache testing more robust ------------------------------------------------------------------------ r73338 | ischommer | 2009-03-19 05:13:40 +1300 (Thu, 19 Mar 2009) | 9 lines API CHANGE Added concept of "translation groups" to Translatable- every page can belong to a group of related translations, rather than having an explicit "original", meaning you can have pages in "non-default" languages which have no representation in other language trees. This group is recorded in a new table "<classname>_translationgroups". Translatable->createTranslation() and Translatable->onBeforeWrite() will automatically associate records in this groups. Added Translatable->addTranslationGroup(), Translatable->removeTranslationGroup(), Translatable->getTranslationGroup() API CHANGE Removed Translatable->isTranslation() - after the new "translation group" model, every page is potentially a translation API CHANGE Translatable->findOriginalIDs(), Translatable->setOriginalPage(), Translatable->getOriginalPage() ENHANCEMENT Translatable->getCMSFields() will now always show the "create translation" option, not only on default languages - meaning you can create translations based on other translations ENHANCEMENT Translatable language dropdown in CMS will always show all available languages, rather than filtering by already existing translations ENHANCEMENT Added check for an existing record in Translatable->createTranslation() BUGFIX Removed Translatable->getLang() which overloaded the $db property - it was causing side effects during creation of SiteTree default records. BUGFIX Added check in Translatable->augmentSQL() to avoid reapplying "Lang = ..." filter twice BUGFIX Removed bypass in Translatable->AllChildrenIncludingDeleted() ------------------------------------------------------------------------ r73339 | ischommer | 2009-03-19 05:15:46 +1300 (Thu, 19 Mar 2009) | 1 line BUGFIX Disabled "untranslated" CSS class for SiteTree elements - doesn't apply any longer with the new "translation groups" concept ------------------------------------------------------------------------ r73341 | ischommer | 2009-03-19 06:01:51 +1300 (Thu, 19 Mar 2009) | 1 line BUGFIX Disabled auto-excluding of default language from the "available languages" array in LanguageDropdownField - due to the new "translation groups" its possible to have a translation from another language into the default language ------------------------------------------------------------------------ r73342 | ischommer | 2009-03-19 06:13:23 +1300 (Thu, 19 Mar 2009) | 4 lines BUGFIX Setting ParentID of translated record if recursively creating parents in Translatable::onBeforeWrite() BUGFIX Fixing inline form action for "create translation" BUGFIX Removed link to "original page" for a translation - no longer valid MINOR documentation for Translatable ------------------------------------------------------------------------ r73464 | ischommer | 2009-03-20 20:51:00 +1300 (Fri, 20 Mar 2009) | 1 line MINOR documentation ------------------------------------------------------------------------ r73465 | ischommer | 2009-03-20 20:58:52 +1300 (Fri, 20 Mar 2009) | 1 line BUGFIX Fixed Hierarchy->Children() testing in TranslatableTest - with the new datamodel you can't call Children() in a different language regardless of Translatable::set_reading_lang(), the Children() call has to be made from a parent in the same language ------------------------------------------------------------------------ r73466 | ischommer | 2009-03-20 21:36:40 +1300 (Fri, 20 Mar 2009) | 2 lines ENHANCEMENT Added Translatable::get_locale_from_lang(), Translatable::get_common_locales(), $common_locales and $likely_subtags in preparation to switch Translatable from using short "lang" codes to proper long locales API CHANGE Deprecated Translatable::set_default_lang(), Translatable::default_lang() ------------------------------------------------------------------------ r73467 | ischommer | 2009-03-20 21:38:57 +1300 (Fri, 20 Mar 2009) | 1 line ENHANCEMENT Supporting "Locale-English" and "Locale-Native" as listing arguments in LanguageDropdownField ------------------------------------------------------------------------ r73468 | ischommer | 2009-03-20 21:47:06 +1300 (Fri, 20 Mar 2009) | 7 lines ENHANCEMENT Adjusted SearchForm, Debug, ErrorPage, SiteTree to using locales instead of lang codes API CHANGE Changed Translatable datamodel to use locales ("en_US") instead of lang values ("en). API CHANGE Changed Translatable::$default_lang to $default_locale, Translatable::$reading_lang to $reading_locale API CHANGE Using "locale" instead of "lang" in Translatable::choose_site_lang() to auto-detect language from cookies or GET parameters API CHANGE Deprecated Translatable::is_default_lang(), set_default_lang(), get_default_lang(), current_lang(), set_reading_lang(), get_reading_lang(), get_by_lang(), get_one_by_lang() API CHANGE Removed Translatable::get_original() - with the new "translation groups" concept there no longer is an original for a translation BUGFIX Updated MigrateTranslatableTask to new Locale based datamodel ------------------------------------------------------------------------ r73470 | ischommer | 2009-03-20 21:56:57 +1300 (Fri, 20 Mar 2009) | 1 line MINOR fixed typo ------------------------------------------------------------------------ r73472 | sminnee | 2009-03-21 17:30:04 +1300 (Sat, 21 Mar 2009) | 1 line BUGFIX: Fixed translatable test execution by making protected methods public ------------------------------------------------------------------------ r73473 | sminnee | 2009-03-21 18:10:05 +1300 (Sat, 21 Mar 2009) | 1 line ENHANCEMENT: Added Object::combined_static(), which gets all values of a static property from each class in the hierarchy ------------------------------------------------------------------------ r73883 | ischommer | 2009-04-01 08:32:19 +1300 (Wed, 01 Apr 2009) | 1 line BUGFIX Making $_SINGLETONS a global instead of a static in Core.php so it can be re-used in other places ------------------------------------------------------------------------ r73951 | ischommer | 2009-04-02 05:35:32 +1300 (Thu, 02 Apr 2009) | 3 lines API CHANGE Deprecated Translatable::enable() and i18n::enable()- use Object::add_extension('SiteTree','Translatable'), Deprecated Translatable::disable() and i18n::disable() - use Object::remove_extension('SiteTree','Translatable'), Deprecated Translatable::enabled() - use $myPage->hasExtension('Translatable') API CHANGE Removed Translatable::creating_from() - doesn't apply any longer ENHANCEMENT Translatable extension is no longer hooked up to SiteTree by default, which should improve performance and memory usage for sites not using Translatable. Please use Object::add_extension('SiteTree','Translatable') in your _config.php instead. Adjusted several classes (Image, ErrorPage, RootURLController) to the new behaviour. ------------------------------------------------------------------------ r73882 | ischommer | 2009-04-01 08:31:21 +1300 (Wed, 01 Apr 2009) | 1 line ENHANCEMENT Added DataObjectDecorator->setOwner() ------------------------------------------------------------------------ r73884 | ischommer | 2009-04-01 08:32:51 +1300 (Wed, 01 Apr 2009) | 1 line ENHANCEMENT Added Extension::get_classname_without_arguments() ------------------------------------------------------------------------ r73900 | ischommer | 2009-04-01 11:27:53 +1300 (Wed, 01 Apr 2009) | 7 lines API CHANGE Deprecated Object->extInstance(), use getExtensionInstance() instead ENHANCEMENT Added Object->getExtensionInstances() ENHANCEMENT Added Object::get_extensions() ENHANCEMENT Unsetting class caches when using Object::add_extension() to avoid problems with defineMethods etc. BUGFIX Fixed extension comparison with case sensitivity and stripping arguments in Object::has_extension() BUGFIX Unsetting all cached singletons in Object::remove_extension() to avoid outdated extension_instances MINOR Documentation in Object ------------------------------------------------------------------------ r74017 | ischommer | 2009-04-03 10:49:40 +1300 (Fri, 03 Apr 2009) | 1 line ENHANCEMENT Improved deprecated fallbacks in Translatable by auto-converting short language codes to long locales and vice versa through i18n::get_lang_from_locale()/i18n::get_locale_from_lang() ------------------------------------------------------------------------ r74030 | ischommer | 2009-04-03 11:41:26 +1300 (Fri, 03 Apr 2009) | 1 line MINOR Re-added Translatable::default_lang() for more graceful fallback to Translatable::default_locale() ------------------------------------------------------------------------ r74065 | ischommer | 2009-04-04 05:38:51 +1300 (Sat, 04 Apr 2009) | 1 line BUGFIX Re-added Translatable->isTranslation() for more friendly deprecation (originally removed in r73338) ------------------------------------------------------------------------ r74069 | ischommer | 2009-04-04 09:43:01 +1300 (Sat, 04 Apr 2009) | 1 line BUGFIX Fixed legacy handling of Translatable::enable(),Translatable::disable() and Translatable::is_enabled() - applying extension to SiteTree instead of Page to avoid datamodel clashes ------------------------------------------------------------------------ r74070 | ischommer | 2009-04-04 10:23:51 +1300 (Sat, 04 Apr 2009) | 1 line API CHANGE Deprecated Translatable::choose_site_lang(), use choose_site_locale() ------------------------------------------------------------------------ r74941 | ischommer | 2009-04-22 15:22:09 +1200 (Wed, 22 Apr 2009) | 2 lines ENHANCEMENT Adding SapphireTest::set_up_once() and SapphireTest::tear_down_once() for better test performance with state that just needs to be initialized once per test case (not per test method). Added new SapphireTestSuite to support this through PHPUnit. ENHANCEMENT Using set_up_once() in TranslatableTest and TranslatableSearchFormTest for better test run performance ------------------------------------------------------------------------ r74942 | ischommer | 2009-04-22 15:24:50 +1200 (Wed, 22 Apr 2009) | 1 line BUGFIX Fixed TranslatableSearchFormTest->setUp() method ------------------------------------------------------------------------ r73509 | ischommer | 2009-03-23 11:59:14 +1300 (Mon, 23 Mar 2009) | 1 line MINOR phpdoc documentation ------------------------------------------------------------------------ git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.3@74986 467b73ca-7a2a-4603-9d3b-597d59a354a9
2009-04-23 03:45:10 +02:00
* </code>
*
* @uses ClassInfo
*
* @param string Classname to stop traversing upwards the ancestry (Default: ViewableData)
* @return string space-separated attribute encoded classes
*/
function CSSClasses($stopAtClass = false) {
global $_ALL_CLASSES;
if(!$stopAtClass) $stopAtClass = 'ViewableData';
$classes = array();
$classAnchestry = ClassInfo::ancestry($this->class);
$viewableDataAnchestry = ClassInfo::ancestry($stopAtClass);
foreach($classAnchestry as $anchestor) {
if(!in_array($anchestor, $viewableDataAnchestry)) $classes[] = $anchestor;
}
// optionally add template identifier
if(isset($this->template) && !in_array($this->template, $classes)) {
$classes[] = $this->template;
}
return Convert::raw2att(implode(" ", $classes));
}
/**
* Object-casting information for class methods
* @var mixed
*/
public static $casting = array(
'BaseHref' => 'Varchar',
'CSSClasses' => 'Varchar',
);
/**
* Keep a record of the parent node of this data node.
* @var mixed
*/
protected $parent = null;
/**
* Keep a record of the parent node of this data node.
* @var mixed
*/
protected $namedAs = null;
}
/**
* A ViewableData object that has been customised with extra data. Use
* ViewableData->customise() to create.
* @package sapphire
* @subpackage view
*/
class ViewableData_Customised extends ViewableData {
public function castingHelperPair($field) {
return $this->obj->castingHelperPair($field);
}
function __construct($obj, $extraData) {
$this->obj = $obj;
$this->obj->setCustomisedObj($this);
$this->extraData = $extraData;
parent::__construct();
}
function __call($funcName, $args) {
if(isset($this->extraData[$funcName])) {
return $this->extraData[$funcName];
} else {
return call_user_func_array(array(&$this->obj, $funcName), $args);
}
}
function __get($fieldName) {
if(isset($this->extraData[$fieldName])) {
return $this->extraData[$fieldName];
}
return $this->obj->$fieldName;
}
function __set($fieldName, $val) {
if(isset($this->extraData[$fieldName])) unset($this->extraData[$fieldName]);
return $this->obj->$fieldName = $val;
}
function hasMethod($funcName) {
return isset($this->extraData[$funcName]) || $this->obj->hasMethod($funcName);
}
function XML_val($fieldName, $args = null, $cache = false) {
if(isset($this->extraData[$fieldName])) {
if(isset($_GET['debug_profile'])) {
Profiler::mark("template($fieldName)", " on $this->class object");
}
if(is_object($this->extraData[$fieldName])) {
$val = $this->extraData[$fieldName]->forTemplate();
} else {
$val = $this->extraData[$fieldName];
}
if(isset($_GET['debug_profile'])) {
Profiler::unmark("template($fieldName)", " on $this->class object");
}
return $val;
} else {
return $this->obj->XML_val($fieldName, $args, $cache);
}
}
function obj($fieldName, $args = null, $forceReturnObject = false) {
if(isset($this->extraData[$fieldName])) {
if(!is_object($this->extraData[$fieldName])) {
user_error("ViewableData_Customised::obj() '$fieldName' was requested from the array data as an object but it's not an object. I can't cast it.", E_USER_WARNING);
}
return $this->extraData[$fieldName];
} else {
return $this->obj->obj($fieldName, $args, $forceReturnObject);
}
}
function cachedCall($funcName, $identifier = null, $args = null) {
if(isset($this->extraData[$funcName])) {
return $this->extraData[$funcName];
} else {
return $this->obj->cachedCall($funcName, $identifier, $args);
}
}
function customise($data) {
if(is_array($data)) {
$this->extraData = array_merge($this->extraData, $data);
return $this;
} else {
return parent::customise($data);
}
}
/**
* Original ViewableData object
* @var ViewableDate
*/
protected $obj;
/**
* Array containing the extra data
* @var array
*/
protected $extraData;
}
/**
* A ViewableData object that has been customised with an extra object. Use
* ViewableData->customise() to create.
* @package sapphire
* @subpackage view
*/
class ViewableData_ObjectCustomised extends ViewableData {
function __construct($obj, $extraObj) {
$this->obj = $obj;
$this->extraObj = $extraObj;
$this->obj->setCustomisedObj($this);
parent::__construct();
}
function __call($funcName, $args) {
if($this->extraObj->hasMethod($funcName)) {
return call_user_func_array(array(&$this->extraObj, $funcName), $args);
} else {
return call_user_func_array(array(&$this->obj, $funcName), $args);
}
}
function __get($fieldName) {
if($this->extraObj->hasField($fieldName)) {
return $this->extraObj->$fieldName;
} else {
return $this->obj->$fieldName;
}
}
function __set($fieldName, $val) {
$this->extraObj->$fieldName = $val;
$this->obj->$fieldName = $val;
}
function hasMethod($funcName) {
return $this->extraObj->hasMethod($funcName) || $this->obj->hasMethod($funcName);
}
function cachedCall($funcName, $identifier = null, $args = null) {
$result = $this->extraObj->cachedCall($funcName, $identifier, $args);
if(!$result) {
$result = $this->obj->cachedCall($funcName, $identifier, $args);
}
return $result;
}
function obj($fieldName, $args = null, $forceReturnObject = false) {
if($this->extraObj->hasMethod($fieldName) || $this->extraObj->hasField($fieldName)) {
return $this->extraObj->obj($fieldName, $args, $forceReturnObject);
} else {
return $this->obj->obj($fieldName, $args, $forceReturnObject);
}
}
/**
* The extra object.
* @var ViewableData
*/
protected $extraObj;
/**
* The original object.
* @var ViewableData
*/
protected $obj;
}
/**
* Debugger helper.
* @package sapphire
* @subpackage view
* @todo Finish this off
*/
class ViewableData_Debugger extends ViewableData {
/**
* The original object
* @var ViewableData
*/
protected $obj;
function __construct($obj) {
$this->obj = $obj;
parent::__construct();
}
/**
* Return debugging information, as XHTML. If a field name is passed,
* it will show debugging information on that field, otherwise it will show
* information on all methods and fields.
* @var string $field The field name.
* @return string
*/
function forTemplate($field = null) {
if($field) {
return "<b>Info on $field:<br/>" .
($this->obj->hasMethod($field) ? "Has method '$field'. " : "") .
($this->obj->hasField($field) ? "Has field '$field'. " : "");
} else {
echo "<b>Debug: all methods available in {$this->obj->class}</b><br/>";
echo "<ul>";
$names = $this->obj->allMethodNames();
foreach($names as $name) {
if(strtoupper($name[0]) == $name[0] && $name[0] != "_") {
echo "<li>\$$name</li>";
}
}
echo "</ul>";
if($this->obj->hasMethod('getAllFields')) {
echo "<b>Debug: all fields available in {$this->obj->class}</b><br/>";
echo "<ul>";
$data = $this->obj->getAllFields();
foreach($data as $key => $val) {
echo "<li>\$$key</li>";
}
echo "</ul>";
}
}
if($this->obj->hasMethod('data')) {
if($this->obj->data() != $this->obj) {
$d = new ViewableData_Debugger($this->obj->data());
echo $d->forTemplate();
}
}
}
}
/**
* Implementation of a "1 record iterator"
* Views <%control %> tags operate by looping over an item for as many instances as are
* available. When you stick a single ViewableData object in a control tag, the foreach()
* loop still needs to work. We do this by creating an iterator that only returns one record.
* This will always return the current ViewableData object.
*/
class ViewableData_Iterator implements Iterator {
function __construct($viewableData) {
$this->viewableData = $viewableData;
$this->show = true;
}
/**
* Internal state toggler
* @var bool
*/
private $show;
/**
* This will always return the current ViewableData object.
*/
public function current() {
if($this->show) {
return $this->viewableData;
}
}
/**
* Rewinds the iterator back to the start.
*/
public function rewind() {
$this->show = true;
}
/**
* Return the key for the current object.
*/
public function key() {
return 0;
}
/**
* Get the next object.
*/
public function next() {
if($this->show) {
$this->show = false;
return $this->viewableData;
} else {
return null;
}
}
/**
* Check if there is a current object.
*/
public function valid() {
return $this->show;
}
}
?>