Merge remote-tracking branch 'origin/3.2' into 3

This commit is contained in:
Damian Mooyman 2015-08-24 12:57:22 +12:00
commit 4ea344ac9c
34 changed files with 1539 additions and 31 deletions

View File

@ -71,6 +71,20 @@ class Controller extends RequestHandler implements TemplateGlobalProvider {
public function init() {
if($this->basicAuthEnabled) BasicAuth::protect_site_if_necessary();
// Directly access the session variable just in case the Group or Member tables don't yet exist
if(Member::config()->log_last_visited) {
Deprecation::notice(
'4.0',
'Member::$LastVisited is deprecated. From 4.0 onwards you should implement this as a custom extension'
);
if(Session::get('loggedInAs') && Security::database_is_ready() && ($member = Member::currentUser())) {
DB::prepared_query(
sprintf('UPDATE "Member" SET "LastVisited" = %s WHERE "ID" = ?', DB::get_conn()->now()),
array($member->ID)
);
}
}
// This is used to test that subordinate controllers are actually calling parent::init() - a common bug
$this->baseInitCalled = true;
}

View File

@ -61,6 +61,15 @@ class Cookie {
return self::get_inst()->getAll($includeUnsent);
}
/**
* @deprecated
*/
public static function forceExpiry($name, $path = null, $domain = null) {
Deprecation::notice('4.0', 'Use Cookie::force_expiry instead.');
return self::force_expiry($name, $path, $domain);
}
/**
* @param string
* @param string
@ -69,4 +78,20 @@ class Cookie {
public static function force_expiry($name, $path = null, $domain = null, $secure = false, $httpOnly = true) {
return self::get_inst()->forceExpiry($name, $path, $domain, $secure, $httpOnly);
}
/**
* @deprecated
*/
public static function set_report_errors($reportErrors) {
Deprecation::notice('4.0', 'Use "Cookie.report_errors" config setting instead');
Config::inst()->update('Cookie', 'report_errors', $reportErrors);
}
/**
* @deprecated
*/
public static function report_errors() {
Deprecation::notice('4.0', 'Use "Cookie.report_errors" config setting instead');
return Config::inst()->get('Cookie', 'report_errors');
}
}

View File

@ -394,6 +394,58 @@ abstract class Object {
return $default;
}
/**
* @deprecated
*/
public static function get_static($class, $name, $uncached = false) {
Deprecation::notice('4.0', 'Replaced by Config#get');
return Config::inst()->get($class, $name, Config::FIRST_SET);
}
/**
* @deprecated
*/
public static function set_static($class, $name, $value) {
Deprecation::notice('4.0', 'Replaced by Config#update');
Config::inst()->update($class, $name, $value);
}
/**
* @deprecated
*/
public static function uninherited_static($class, $name, $uncached = false) {
Deprecation::notice('4.0', 'Replaced by Config#get');
return Config::inst()->get($class, $name, Config::UNINHERITED);
}
/**
* @deprecated
*/
public static function combined_static($class, $name, $ceiling = false) {
if ($ceiling) throw new Exception('Ceiling argument to combined_static is no longer supported');
Deprecation::notice('4.0', 'Replaced by Config#get');
return Config::inst()->get($class, $name);
}
/**
* @deprecated
*/
public static function addStaticVars($class, $properties, $replace = false) {
Deprecation::notice('4.0', 'Replaced by Config#update');
foreach($properties as $prop => $value) self::add_static_var($class, $prop, $value, $replace);
}
/**
* @deprecated
*/
public static function add_static_var($class, $name, $value, $replace = false) {
Deprecation::notice('4.0', 'Replaced by Config#remove and Config#update');
if ($replace) Config::inst()->remove($class, $name);
Config::inst()->update($class, $name, $value);
}
/**
* Return TRUE if a class has a specified extension.
* This supports backwards-compatible format (static Object::has_extension($requiredExtension))
@ -441,7 +493,7 @@ abstract class Object {
* instances, not existing ones (including all instances created through {@link singleton()}).
*
* @see http://doc.silverstripe.org/framework/en/trunk/reference/dataextension
* @param string $class Class that should be extended - has to be a subclass of {@link Object}
* @param string $classOrExtension Class that should be extended - has to be a subclass of {@link Object}
* @param string $extension Subclass of {@link Extension} with optional parameters
* as a string, e.g. "Versioned" or "Translatable('Param')"
*/

239
dev/Profiler.php Normal file
View File

@ -0,0 +1,239 @@
<?php
/********************************************************************************\
* Copyright (C) Carl Taylor (cjtaylor@adepteo.com) *
* Copyright (C) Torben Nehmer (torben@nehmer.net) for Code Cleanup *
* Licensed under the BSD license upon request *
\********************************************************************************/
/// Enable multiple timers to aid profiling of performance over sections of code
/**
* Execution time profiler.
*
* @deprecated 4.0 The Profiler class is deprecated, use third party tools like XHProf instead
*
* @package framework
* @subpackage misc
*/
class Profiler {
var $description;
var $startTime;
var $endTime;
var $initTime;
var $cur_timer;
var $stack;
var $trail;
var $trace;
var $count;
var $running;
protected static $inst;
/**
* Initialise the timer. with the current micro time
*/
public function Profiler( $output_enabled=false, $trace_enabled=false)
{
$this->description = array();
$this->startTime = array();
$this->endTime = array();
$this->initTime = 0;
$this->cur_timer = "";
$this->stack = array();
$this->trail = "";
$this->trace = "";
$this->count = array();
$this->running = array();
$this->initTime = $this->getMicroTime();
$this->output_enabled = $output_enabled;
$this->trace_enabled = $trace_enabled;
$this->startTimer('unprofiled');
}
// Public Methods
public static function init() {
Deprecation::notice('4.0', 'The Profiler class is deprecated, use third party tools like XHProf instead');
if(!self::$inst) self::$inst = new Profiler(true,true);
}
public static function mark($name, $level2 = "", $desc = "") {
if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2";
if(!self::$inst) self::$inst = new Profiler(true,true);
self::$inst->startTimer($name, $desc);
}
public static function unmark($name, $level2 = "", $desc = "") {
if($level2 && $_GET['debug_profile'] > 1) $name .= " $level2";
if(!self::$inst) self::$inst = new Profiler(true,true);
self::$inst->stopTimer($name, $desc);
}
public static function show($showTrace = false) {
if(!self::$inst) self::$inst = new Profiler(true,true);
echo "<div style=\"position: absolute; z-index: 100000; top: 20px; left: 20px; background-color: white;"
. " padding: 20px; border: 1px #AAA solid; height: 80%; overflow: auto;\">";
echo "<p><a href=\"#\" onclick=\"this.parentNode.parentNode.style.display = 'none'; return false;\">"
. "(Click to close)</a></p>";
self::$inst->printTimers();
if($showTrace) self::$inst->printTrace();
echo "</div>";
}
/**
* Start an individual timer
* This will pause the running timer and place it on a stack.
* @param string $name name of the timer
* @param string optional $desc description of the timer
*/
public function startTimer($name, $desc="" ){
$this->trace.="start $name\n";
$n=array_push( $this->stack, $this->cur_timer );
$this->__suspendTimer( $this->stack[$n-1] );
$this->startTime[$name] = $this->getMicroTime();
$this->cur_timer=$name;
$this->description[$name] = $desc;
if (!array_key_exists($name,$this->count))
$this->count[$name] = 1;
else
$this->count[$name]++;
}
/**
* Stop an individual timer
* Restart the timer that was running before this one
* @param string $name name of the timer
*/
public function stopTimer($name){
$this->trace.="stop $name\n";
$this->endTime[$name] = $this->getMicroTime();
if (!array_key_exists($name, $this->running))
$this->running[$name] = $this->elapsedTime($name);
else
$this->running[$name] += $this->elapsedTime($name);
$this->cur_timer=array_pop($this->stack);
$this->__resumeTimer($this->cur_timer);
}
/**
* measure the elapsed time of a timer without stoping the timer if
* it is still running
*/
public function elapsedTime($name){
// This shouldn't happen, but it does once.
if (!array_key_exists($name,$this->startTime))
return 0;
if(array_key_exists($name,$this->endTime)){
return ($this->endTime[$name] - $this->startTime[$name]);
} else {
$now=$this->getMicroTime();
return ($now - $this->startTime[$name]);
}
}//end start_time
/**
* Measure the elapsed time since the profile class was initialised
*
*/
public function elapsedOverall(){
$oaTime = $this->getMicroTime() - $this->initTime;
return($oaTime);
}//end start_time
/**
* print out a log of all the timers that were registered
*
*/
public function printTimers($enabled=false)
{
if($this->output_enabled||$enabled){
$TimedTotal = 0;
$tot_perc = 0;
ksort($this->description);
print("<pre>\n");
$oaTime = $this->getMicroTime() - $this->initTime;
echo"============================================================================\n";
echo " PROFILER OUTPUT\n";
echo"============================================================================\n";
print( "Calls Time Routine\n");
echo"-----------------------------------------------------------------------------\n";
while (list ($key, $val) = each ($this->description)) {
$t = $this->elapsedTime($key);
$total = $this->running[$key];
$count = $this->count[$key];
$TimedTotal += $total;
$perc = ($total/$oaTime)*100;
$tot_perc+=$perc;
// $perc=sprintf("%3.2f", $perc );
$lines[ sprintf( "%3d %3.4f ms (%3.2f %%) %s\n", $count, $total*1000, $perc, $key) ] = $total;
}
arsort($lines);
foreach($lines as $line => $total) {
echo $line;
}
echo "\n";
$missed=$oaTime-$TimedTotal;
$perc = ($missed/$oaTime)*100;
$tot_perc+=$perc;
// $perc=sprintf("%3.2f", $perc );
printf( " %3.4f ms (%3.2f %%) %s\n", $missed*1000,$perc, "Missed");
echo"============================================================================\n";
printf( " %3.4f ms (%3.2f %%) %s\n", $oaTime*1000,$tot_perc, "OVERALL TIME");
echo"============================================================================\n";
print("</pre>");
}
}
public function printTrace( $enabled=false )
{
if($this->trace_enabled||$enabled){
print("<pre>");
print("Trace\n$this->trace\n\n");
print("</pre>");
}
}
/// Internal Use Only Functions
/**
* Get the current time as accuratly as possible
*
*/
public function getMicroTime(){
$tmp=explode(' ', microtime());
$rt=$tmp[0]+$tmp[1];
return $rt;
}
/**
* resume an individual timer
*
*/
public function __resumeTimer($name){
$this->trace.="resume $name\n";
$this->startTime[$name] = $this->getMicroTime();
}
/**
* suspend an individual timer
*
*/
public function __suspendTimer($name){
$this->trace.="suspend $name\n";
$this->endTime[$name] = $this->getMicroTime();
if (!array_key_exists($name, $this->running))
$this->running[$name] = $this->elapsedTime($name);
else
$this->running[$name] += $this->elapsedTime($name);
}
}

View File

@ -446,6 +446,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
* {@link loadFixture()}
*/
public function clearFixtures() {
$this->fixtures = array();
$this->getFixtureFactory()->clear();
}

View File

@ -83,6 +83,12 @@ class YamlFixture extends Object {
*/
protected $fixtureString;
/**
* @var FixtureFactory
* @deprecated 3.1 Use writeInto() and FixtureFactory instead
*/
protected $factory;
/**
* @param String Absolute file path, or relative path to {@link Director::baseFolder()}
*/
@ -117,6 +123,68 @@ class YamlFixture extends Object {
return $this->fixtureString;
}
/**
* Get the ID of an object from the fixture.
*
* @deprecated 4.0 Use writeInto() and FixtureFactory accessors instead
*
* @param $className The data class, as specified in your fixture file. Parent classes won't work
* @param $identifier The identifier string, as provided in your fixture file
*/
public function idFromFixture($className, $identifier) {
Deprecation::notice('4.0', 'Use writeInto() and FixtureFactory accessors instead');
if(!$this->factory) $this->factory = Injector::inst()->create('FixtureFactory');
return $this->factory->getId($className, $identifier);
}
/**
* Return all of the IDs in the fixture of a particular class name.
*
* @deprecated 4.0 Use writeInto() and FixtureFactory accessors instead
*
* @return A map of fixture-identifier => object-id
*/
public function allFixtureIDs($className) {
Deprecation::notice('4.0', 'Use writeInto() and FixtureFactory accessors instead');
if(!$this->factory) $this->factory = Injector::inst()->create('FixtureFactory');
return $this->factory->getIds($className);
}
/**
* Get an object from the fixture.
*
* @deprecated 4.0 Use writeInto() and FixtureFactory accessors instead
*
* @param $className The data class, as specified in your fixture file. Parent classes won't work
* @param $identifier The identifier string, as provided in your fixture file
*/
public function objFromFixture($className, $identifier) {
Deprecation::notice('4.0', 'Use writeInto() and FixtureFactory accessors instead');
if(!$this->factory) $this->factory = Injector::inst()->create('FixtureFactory');
return $this->factory->get($className, $identifier);
}
/**
* Load a YAML fixture file into the database.
* Once loaded, you can use idFromFixture() and objFromFixture() to get items from the fixture.
*
* Caution: In order to support reflexive relations which need a valid object ID,
* the record is written twice: first after populating all non-relational fields,
* then again after populating all relations (has_one, has_many, many_many).
*
* @deprecated 4.0 Use writeInto() and FixtureFactory instance instead
*/
public function saveIntoDatabase(DataModel $model) {
Deprecation::notice('4.0', 'Use writeInto() and FixtureFactory instance instead');
if(!$this->factory) $this->factory = Injector::inst()->create('FixtureFactory');
$this->writeInto($this->factory);
}
/**
* Persists the YAML data in a FixtureFactory,
* which in turn saves them into the database.

View File

@ -28,7 +28,7 @@ explicitly logging in or by invoking the "remember me" functionality.
public function updateCMSFields(FieldList $fields) {
$fields->addFieldsToTab('Root.Main', array(
ReadonlyField::create('LastVisited', 'Last visited'),
ReadonlyField::create('NumVisits', 'Number of visits')
ReadonlyField::create('NumVisit', 'Number of visits')
));
}

View File

@ -25,30 +25,14 @@
removes from both draft and live simultaneously.
* Most of the `Image` manipulation methods have been renamed
## Deprecated classes/methods removed
## Deprecated classes/methods
The following functionality deprecated in 3.0 has been removed:
* `ToggleField` was deprecated in 3.1, and has been removed. Use custom Javascript with `ReadonlyField` instead.
* `ExactMatchMultiFilter` was deprecated in 3.1, and has been removed. Use `ExactMatchFilter` instead.
* `NegationFilter` was deprecated in 3.1, and has been removed. Use `ExactMatchFilter:not` instead.
* `StartsWithMultiFilter` was deprecated in 3.1, and has been removed. Use `StartsWithFilter` instead.
* `ScheduledTask` and subclasses like `DailyTask` were deprecated in 3.1, and have been removed.
Use custom code instead, or a module like silverstripe-crontask: https://github.com/silverstripe-labs/silverstripe-crontask
* `Cookie::forceExpiry()` was removed. Use `Cookie::force_expiry()` instead
* `Object` statics removal: `get_static()`, `set_static()`, `uninherited_static()`, `combined_static()`,
`addStaticVars()` and `add_static_var()` removed. Use the Config methods instead.
* `GD` methods removed: `setGD()`, `getGD()`, `hasGD()`. Use `setImageResource()`, `getImageResource()`, and `hasImageResource()` instead
* `DataExtension::get_extra_config()` removed, no longer supports `extraStatics` or `extraDBFields`. Define your
statics on the class directly.
* `DataList::getRange()` removed. Use `limit()` instead.
* `SQLMap` removed. Call `map()` on a `DataList` or use `SS_Map` directly instead.
* `Profiler` removed. Use xhprof or xdebug for profiling instead.
* `Aggregate` removed. Call aggregate methods on a `DataList` instead e.g. `Member::get()->max('LastEdited')`
* `MySQLDatabase::set_connection_charset()` removed. Use `MySQLDatabase.connection_charset` config setting instead
* `SQLConditionalExpression/SQLQuery` `select()`, `limit()`, `orderby()`, `groupby()`, `having()`, `from()`, `leftjoin()`, `innerjoin()`, `where()` and `whereAny()` removed.
* `SQLQuery` methods `select()`, `limit()`, `orderby()`, `groupby()`, `having()`, `from()`, `leftjoin()`, `innerjoin()`, `where()` and `whereAny()` removed.
Use `set*()` and `add*()` methods instead.
* Template `<% control $MyList %>` syntax removed. Use `<% loop $MyList %>` instead.
* Removed `Member.LastVisited` and `Member.NumVisits` properties, see
[Howto: Track Member Logins](/extending/how_tos/track_member_logins) to restore functionality as custom code
## New and changed API
@ -199,6 +183,27 @@
## Upgrading Notes
### Disable `LastVisited` and `NumVisits` counter
These fields were deprecated in 3.1 due to performance concerns, and should be disabled unless required by
your application.
In order to disable these functions you can add the following yml to your configuration:
:::yaml
---
Name: disablevisits
---
Member:
log_num_visits: false
log_last_visited: false
This functionality will be removed in 4.0
[Howto: Track Member Logins](/developer-guides/extending/how_tos/track_member_logins) to restore functionality
as custom code
### UploadField "Select from files" shows files in all folders by default
In order to list files in a single folder by default (previous default behaviour),

View File

@ -491,3 +491,113 @@ class Mailer extends Object {
}
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function htmlEmail($to, $from, $subject, $htmlContent, $attachedFiles = false, $customheaders = false,
$plainContent = false) {
Deprecation::notice('4.0', 'Use Email->sendHTML() instead');
$mailer = Injector::inst()->create('Mailer');
return $mailer->sendHTML($to, $from, $subject, $plainContent, $attachedFiles, $customheaders = false);
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function plaintextEmail($to, $from, $subject, $plainContent, $attachedFiles, $customheaders = false) {
Deprecation::notice('4.0', 'Use Email->sendPlain() instead');
$mailer = Injector::inst()->create('Mailer');
return $mailer->sendPlain($to, $from, $subject, $plainContent, $attachedFiles, $customheaders = false);
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function encodeMultipart($parts, $contentType, $headers = false) {
Deprecation::notice('4.0', 'Use Email->$this->encodeMultipart() instead');
$mailer = Injector::inst()->create('Mailer');
return $mailer->encodeMultipart($parts, $contentType, $headers = false);
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function wrapImagesInline($htmlContent) {
Deprecation::notice('4.0', 'Functionality removed from core');
$mailer = Injector::inst()->create('Mailer');
return $mailer->wrapImagesInline($htmlContent);
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function wrapImagesInline_rewriter($url) {
Deprecation::notice('4.0', 'Functionality removed from core');
$mailer = Injector::inst()->create('Mailer');
return $mailer->wrapImagesInline_rewriter($url);
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function processHeaders($headers, $body = false) {
Deprecation::notice('4.0', 'Set headers through Email->addCustomHeader()');
$mailer = Injector::inst()->create('Mailer');
return $mailer->processHeaders($headers, $url);
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $extraHeaders = "") {
Deprecation::notice('4.0', 'Please add files through Email->attachFile()');
$mailer = Injector::inst()->create('Mailer');
return $mailer->encodeFileForEmail($file, $destFileName, $disposition, $extraHeaders);
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function QuotedPrintable_encode($quotprint) {
Deprecation::notice('4.0', 'No longer available, handled internally');
$mailer = Injector::inst()->create('Mailer');
return $mailer->QuotedPrintable_encode($quotprint);
}
/**
* @package framework
* @subpackage email
* @deprecated 3.1
*/
function validEmailAddr($emailAddress) {
Deprecation::notice('4.0', 'Use Email->validEmailAddr() instead');
$mailer = Injector::inst()->create('Mailer');
return $mailer->validEmailAddr($emailAddress);
}

View File

@ -86,10 +86,26 @@ class GDBackend extends Object implements Image_Backend {
$this->height = imagesy($resource);
}
/**
* @deprecated
*/
public function setGD($gd) {
Deprecation::notice('4.0', 'Use GD::setImageResource instead');
return $this->setImageResource($gd);
}
public function getImageResource() {
return $this->gd;
}
/**
* @deprecated
*/
public function getGD() {
Deprecation::notice('4.0', 'GD::getImageResource instead');
return $this->getImageResource();
}
/**
* @param string $filename
* @return boolean
@ -217,6 +233,16 @@ class GDBackend extends Object implements Image_Backend {
return $this->gd ? true : false;
}
/**
* @deprecated
*/
public function hasGD() {
Deprecation::notice('4.0', 'GD::hasImageResource instead',
Deprecation::SCOPE_CLASS);
return $this->hasImageResource();
}
/**
* Resize an image, skewing it as necessary.
*/

View File

@ -123,8 +123,22 @@ class DropdownField extends FormField {
* @param string $value The current value
* @param Form $form The parent form
*/
public function __construct($name, $title=null, $source=array(), $value='', $form=null) {
public function __construct($name, $title=null, $source=array(), $value='', $form=null, $emptyString=null) {
$this->setSource($source);
if($emptyString === true) {
Deprecation::notice('4.0',
'Please use setHasEmptyDefault(true) instead of passing a boolean true $emptyString argument',
Deprecation::SCOPE_GLOBAL);
}
if(is_string($emptyString)) {
Deprecation::notice('4.0', 'Please use setEmptyString() instead of passing a string emptyString argument.',
Deprecation::SCOPE_GLOBAL);
}
if($emptyString) $this->setHasEmptyDefault(true);
if(is_string($emptyString)) $this->setEmptyString($emptyString);
parent::__construct($name, ($title===null) ? $name : $title, $value, $form);
}

101
forms/ToggleField.php Normal file
View File

@ -0,0 +1,101 @@
<?php
/**
* ReadonlyField with added toggle-capabilities - will preview the first sentence of the contained text-value,
* and show the full content by a javascript-switch.
*
* @deprecated 3.1 Use custom javascript with a ReadonlyField.
*
* Caution: Strips HTML-encoding for the preview.
* @package forms
* @subpackage fields-dataless
*/
class ToggleField extends ReadonlyField {
/**
* @var $labelMore string Text shown as a link to see the full content of the field
*/
public $labelMore;
/**
* @var $labelLess string Text shown as a link to see the partial view of the field content
*/
public $labelLess;
/**
* @see Text
* @var $truncateMethod string (FirstSentence|FirstParagraph)
*/
public $truncateMethod = 'FirstSentence';
/**
* @var $truncateChars int Number of chars to preview (optional).
* Truncating will be applied with $truncateMethod by default.
*/
public $truncateChars;
/**
* @param name The field name
* @param title The field title
* @param value The current value
*/
public function __construct($name, $title = "", $value = "") {
Deprecation::notice('4.0', 'Use custom javascript with a ReadOnlyField');
$this->labelMore = _t('ToggleField.MORE', 'more');
$this->labelLess = _t('ToggleField.LESS', 'less');
$this->startClosed(true);
parent::__construct($name, $title, $value);
}
public function Field($properties = array()) {
$content = '';
Requirements::javascript(FRAMEWORK_DIR . '/thirdparty/jquery/jquery.js');
Requirements::javascript(FRAMEWORK_DIR . "/javascript/ToggleField.js");
if($this->startClosed) $this->addExtraClass('startClosed');
$valforInput = $this->value ? Convert::raw2att($this->value) : "";
$rawInput = Convert::html2raw($valforInput);
if($this->charNum) $reducedVal = substr($rawInput,0,$this->charNum);
else $reducedVal = DBField::create_field('Text',$rawInput)->{$this->truncateMethod}();
// only create togglefield if the truncated content is shorter
if(strlen($reducedVal) < strlen($rawInput)) {
$content = <<<HTML
<div class="readonly typography contentLess" style="display: none">
$reducedVal
&nbsp;<a href="#" class="triggerMore">$this->labelMore</a>
</div>
<div class="readonly typography contentMore">
$this->value
&nbsp;<a href="#" class="triggerLess">$this->labelLess</a>
</div>
<br />
<input type="hidden" name="$this->name" value="$valforInput" />
HTML;
} else {
$this->dontEscape = true;
$content = parent::Field();
}
return $content;
}
/**
* Determines if the field should render open or closed by default.
*
* @param boolean
*/
public function startClosed($bool) {
($bool) ? $this->addExtraClass('startClosed') : $this->removeExtraClass('startClosed');
}
public function Type() {
return "toggleField";
}
}

190
model/Aggregate.php Normal file
View File

@ -0,0 +1,190 @@
<?php
/**
* Calculate an Aggregate on a particular field of a particular DataObject type (possibly with
* an additional filter before the aggregate)
*
* Implemented as a class to provide a semi-DSL method of calculating Aggregates. DataObject has a function
* that will create & return an instance of this class with the DataObject type and filter set,
* but at that point we don't yet know the aggregate function or field
*
* This class captures any XML_val or unknown call, and uses that to get the field & aggregate function &
* then return the result
*
* Two ways of calling
*
* $aggregate->XML_val(aggregate_function, array(field)) - For templates
* $aggregate->aggregate_function(field) - For PHP
*
* Aggregate functions are uppercased by this class, but are otherwise assumed to be valid SQL functions. Some
* examples: Min, Max, Avg
*
* Aggregates are often used as portions of a cacheblock key. They are therefore cached themselves, in the 'aggregate'
* cache, although the invalidation logic prefers speed over keeping valid data.
* The aggregate cache is cleared through {@link DataObject::flushCache()}, which in turn is called on
* {@link DataObject->write()} and other write operations.
* This means most write operations to the database will invalidate the cache correctly.
* Use {@link Aggregate::flushCache()} to manually clear.
*
* NOTE: The cache logic uses tags, and so a backend that supports tags is required. Currently only the File
* backend (and the two-level backend with the File backend as the slow store) meets this requirement
*
* @deprecated 3.1 Use DataList to aggregate data
*
* @author hfried
* @package framework
* @subpackage core
*/
class Aggregate extends ViewableData {
private static $cache = null;
/** Build & cache the cache object */
protected static function cache() {
return self::$cache ? self::$cache : (self::$cache = SS_Cache::factory('aggregate'));
}
/**
* Clear the aggregate cache for a given type, or pass nothing to clear all aggregate caches.
* {@link $class} is just effective if the cache backend supports tags.
*/
public static function flushCache($class=null) {
$cache = self::cache();
$capabilities = $cache->getBackend()->getCapabilities();
if($capabilities['tags'] && (!$class || $class == 'DataObject')) {
$cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_TAG, array('aggregate'));
} elseif($capabilities['tags']) {
$tags = ClassInfo::ancestry($class);
foreach($tags as &$tag) {
$tag = preg_replace('/[^a-zA-Z0-9_]/', '_', $tag);
}
$cache->clean(Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG, $tags);
} else {
$cache->clean(Zend_Cache::CLEANING_MODE_ALL);
}
}
/**
* Constructor
*
* @deprecated 3.1 Use DataList to aggregate data
*
* @param string $type The DataObject type we are building an aggregate for
* @param string $filter (optional) An SQL filter to apply to the selected rows before calculating the aggregate
*/
public function __construct($type, $filter = '') {
Deprecation::notice('4.0', 'Call aggregate methods on a DataList directly instead. In templates'
. ' an example of the new syntax is &lt% cached List(Member).max(LastEdited) %&gt instead'
. ' (check partial-caching.md documentation for more details.)');
$this->type = $type;
$this->filter = $filter;
parent::__construct();
}
/**
* Build the SQLSelect to calculate the aggregate
* This is a seperate function so that subtypes of Aggregate can change just this bit
* @param string $attr - the SQL field statement for selection (i.e. "MAX(LastUpdated)")
* @return SQLSelect
*/
protected function query($attr) {
$query = DataList::create($this->type)->where($this->filter);
$query->setSelect($attr);
$query->setOrderBy(array());
$singleton->extend('augmentSQL', $query);
return $query;
}
/**
* Entry point for being called from a template.
*
* This gets the aggregate function
*
*/
public function XML_val($name, $args = null, $cache = false) {
$func = strtoupper( strpos($name, 'get') === 0 ? substr($name, 3) : $name );
$attribute = $args ? $args[0] : 'ID';
$table = null;
foreach (ClassInfo::ancestry($this->type, true) as $class) {
$fields = DataObject::database_fields($class, false);
if (array_key_exists($attribute, $fields)) { $table = $class; break; }
}
if (!$table) user_error("Couldn't find table for field $attribute in type {$this->type}", E_USER_ERROR);
$query = $this->query("$func(\"$table\".\"$attribute\")");
// Cache results of this specific SQL query until flushCache() is triggered.
$sql = $query->sql($parameters);
$cachekey = sha1($sql.'-'.var_export($parameters, true));
$cache = self::cache();
if (!($result = $cache->load($cachekey))) {
$result = (string)$query->execute()->value(); if (!$result) $result = '0';
$cache->save($result, null, array('aggregate', preg_replace('/[^a-zA-Z0-9_]/', '_', $this->type)));
}
return $result;
}
/**
* Entry point for being called from PHP.
*/
public function __call($method, $arguments) {
return $this->XML_val($method, $arguments);
}
}
/**
* A subclass of Aggregate that calculates aggregates for the result of a has_many query.
*
* @deprecated
*
* @author hfried
* @package framework
* @subpackage core
*/
class Aggregate_Relationship extends Aggregate {
/**
* Constructor
*
* @param DataObject $object The object that has_many somethings that we're calculating the aggregate for
* @param string $relationship The name of the relationship
* @param string $filter (optional) An SQL filter to apply to the relationship rows before calculating the
* aggregate
*/
public function __construct($object, $relationship, $filter = '') {
$this->object = $object;
$this->relationship = $relationship;
$this->has_many = $object->has_many($relationship);
$this->many_many = $object->many_many($relationship);
if (!$this->has_many && !$this->many_many) {
user_error("Could not find relationship $relationship on object class {$object->class} in"
. " Aggregate Relationship", E_USER_ERROR);
}
parent::__construct($this->has_many ? $this->has_many : $this->many_many[1], $filter);
}
protected function query($attr) {
if ($this->has_many) {
$query = $this->object->getComponentsQuery($this->relationship, $this->filter);
}
else {
$query = $this->object->getManyManyComponentsQuery($this->relationship, $this->filter);
}
$query->setSelect($attr);
$query->setGroupBy(array());
$singleton = singleton($this->type);
$singleton->extend('augmentSQL', $query);
return $query;
}
}

View File

@ -7,6 +7,28 @@
*/
abstract class DataExtension extends Extension {
public static function get_extra_config($class, $extension, $args) {
if(method_exists($extension, 'extraDBFields')) {
$extraStaticsMethod = 'extraDBFields';
} elseif(method_exists($extension, 'extraStatics')) {
$extraStaticsMethod = 'extraStatics';
} else {
return null;
}
Deprecation::notice('4.0',
"$extraStaticsMethod deprecated. Just define statics on your extension, or use get_extra_config",
Deprecation::SCOPE_GLOBAL);
$statics = Injector::inst()
->get($extension, true, $args)
->$extraStaticsMethod($class, $extension);
if ($statics) {
return $statics;
}
}
public static function unload_extra_statics($class, $extension) {
throw new Exception('unload_extra_statics gone');
}

View File

@ -489,6 +489,13 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab
throw new InvalidArgumentException("Bad field expression $field");
}
if (!$this->inAlterDataQueryCall) {
Deprecation::notice(
'4.0',
'getRelationName is mutating, and must be called inside an alterDataQuery block'
);
}
if(strpos($field,'.') === false) {
return '"'.$field.'"';
}

View File

@ -1633,6 +1633,14 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
->sort($sort);
}
/**
* @deprecated
*/
public function getComponentsQuery($componentName, $filter = "", $sort = "", $join = "", $limit = "") {
Deprecation::notice('4.0', "Use getComponents to get a filtered DataList for an object's relation");
return $this->getComponents($componentName, $filter, $sort, $join, $limit);
}
/**
* Find the foreign class of a relation on this DataObject, regardless of the relation type.
*
@ -3135,6 +3143,36 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
/**
* @deprecated
*/
public function Aggregate($class = null) {
Deprecation::notice('4.0', 'Call aggregate methods on a DataList directly instead. In templates'
. ' an example of the new syntax is &lt% cached List(Member).max(LastEdited) %&gt instead'
. ' (check partial-caching.md documentation for more details.)');
if($class) {
$list = new DataList($class);
$list->setDataModel(DataModel::inst());
} else if(isset($this)) {
$list = new DataList(get_class($this));
$list->setDataModel($this->model);
} else {
throw new \InvalidArgumentException("DataObject::aggregate() must be called as an instance method or passed"
. " a classname");
}
return $list;
}
/**
* @deprecated
*/
public function RelationshipAggregate($relationship) {
Deprecation::notice('4.0', 'Call aggregate methods on a relationship directly instead.');
return $this->$relationship();
}
/**
* Return the first item matching the given query.
* All calls to get_one() are cached.
@ -3183,6 +3221,8 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
* @return DataObject $this
*/
public function flushCache($persistent = true) {
if($persistent) Aggregate::flushCache($this->class);
if($this->class == 'DataObject') {
DataObject::$_cache_get_one = array();
return $this;

View File

@ -12,7 +12,7 @@ class MySQLDatabase extends SS_Database {
/**
* Default connection charset (may be overridden in $databaseConfig)
*
*
* @config
* @var String
*/
@ -49,6 +49,14 @@ class MySQLDatabase extends SS_Database {
}
}
/**
* @deprecated 4.0 Use "MySQLDatabase.connection_charset" config setting instead
*/
public static function set_connection_charset($charset = 'utf8') {
Deprecation::notice('4.0', 'Use "MySQLDatabase.connection_charset" config setting instead');
Config::inst()->update('MySQLDatabase', 'connection_charset', $charset);
}
/**
* Sets the SQL mode
*

View File

@ -0,0 +1,20 @@
<?php
/**
* @package framework
* @subpackage search
*/
/**
* Checks if a value is in a given set.
* SQL syntax used: Column IN ('val1','val2')
* @deprecated 3.1 Use ExactMatchFilter instead
*
* @package framework
* @subpackage search
*/
class ExactMatchMultiFilter extends ExactMatchFilter {
function __construct($fullName, $value = false, array $modifiers = array()) {
Deprecation::notice('4.0', 'Use ExactMatchFilter instead.');
parent::__construct($fullName, $value, $modifiers);
}
}

View File

@ -0,0 +1,16 @@
<?php
/**
* Matches on rows where the field is not equal to the given value.
*
* @deprecated 3.1 Use ExactMatchFilter:not instead
* @package framework
* @subpackage search
*/
class NegationFilter extends ExactMatchFilter {
function __construct($fullName, $value = false, array $modifiers = array()) {
Deprecation::notice('4.0', 'Use ExactMatchFilter:not instead.');
$modifiers[] = 'not';
parent::__construct($fullName, $value, $modifiers);
}
}

View File

@ -0,0 +1,20 @@
<?php
/**
* @package framework
* @subpackage search
*/
/**
* Checks if a value starts with one of the items of in a given set.
* @deprecated 3.1 Use StartsWithFilter instead
*
* @todo Add negation (NOT IN)6
* @package framework
* @subpackage search
*/
class StartsWithMultiFilter extends StartsWithFilter {
function __construct($fullName, $value = false, array $modifiers = array()) {
Deprecation::notice('4.0', 'Use StartsWithFilter instead.');
parent::__construct($fullName, $value, $modifiers);
}
}

View File

@ -245,10 +245,22 @@ class Group extends DataObject {
* including all members which are "inherited" from children groups of this record.
* See {@link DirectMembers()} for retrieving members without any inheritance.
*
* @param String $filter
* @param string $filter
* @return ManyManyList
*/
public function Members($filter = '') {
public function Members($filter = "", $sort = "", $join = "", $limit = "") {
if($sort || $join || $limit) {
Deprecation::notice('4.0',
"The sort, join, and limit arguments are deprecated, use sort(), join() and limit() on the resulting"
. " DataList instead.");
}
if($join) {
throw new \InvalidArgumentException(
'The $join argument has been removed. Use leftJoin($table, $joinClause) instead.'
);
}
// First get direct members as a base result
$result = $this->DirectMembers();
// Remove the default foreign key filter in prep for re-applying a filter containing all children groups.
@ -261,7 +273,7 @@ class Group extends DataObject {
}
// Now set all children groups as a new foreign key
$groups = Group::get()->byIDs($this->collateFamilyIDs());
$result = $result->forForeignID($groups->column('ID'))->where($filter);
$result = $result->forForeignID($groups->column('ID'))->where($filter)->sort($sort)->limit($limit);
return $result;
}

View File

@ -12,8 +12,8 @@
* @property string $RememberLoginToken
* @property string $TempIDHash
* @property string $TempIDExpired
* @property int $NumVisit
* @property string $LastVisited Date and time of last visit
* @property int $NumVisit @deprecated 4.0
* @property string $LastVisited @deprecated 4.0
* @property string $AutoLoginHash
* @property string $AutoLoginExpired
* @property string $PasswordEncryption
@ -35,6 +35,8 @@ class Member extends DataObject implements TemplateGlobalProvider {
'TempIDExpired' => 'SS_Datetime', // Expiry of temp login
'Password' => 'Varchar(160)',
'RememberLoginToken' => 'Varchar(160)', // Note: this currently holds a hash, not a token.
'NumVisit' => 'Int', // @deprecated 4.0
'LastVisited' => 'SS_Datetime', // @deprecated 4.0
'AutoLoginHash' => 'Varchar(160)', // Used to auto-login the user on password reset
'AutoLoginExpired' => 'SS_Datetime',
// This is an arbitrary code pointing to a PasswordEncryptor instance,
@ -81,6 +83,24 @@ class Member extends DataObject implements TemplateGlobalProvider {
*/
private static $notify_password_change = false;
/**
* Flag whether or not member visits should be logged (count only)
*
* @deprecated 4.0
* @var bool
* @config
*/
private static $log_last_visited = true;
/**
* Flag whether we should count number of visits
*
* @deprecated 4.0
* @var bool
* @config
*/
private static $log_num_visits = true;
/**
* All searchable database columns
* in this object, currently queried
@ -120,7 +140,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
'TempIDHash',
'TempIDExpired',
'Salt',
'NumVisit'
'NumVisit', // @deprecated 4.0
);
/**
@ -446,6 +466,8 @@ class Member extends DataObject implements TemplateGlobalProvider {
// This lets apache rules detect whether the user has logged in
if(Member::config()->login_marker_cookie) Cookie::set(Member::config()->login_marker_cookie, 1, 0);
$this->addVisit();
if($remember) {
// Store the hash and give the client the cookie with the token.
$generator = new RandomGenerator();
@ -474,6 +496,19 @@ class Member extends DataObject implements TemplateGlobalProvider {
$this->extend('memberLoggedIn');
}
/**
* @deprecated 4.0
*/
public function addVisit() {
if($this->config()->log_num_visits) {
Deprecation::notice(
'4.0',
'Member::$NumVisit is deprecated. From 4.0 onwards you should implement this as a custom extension'
);
$this->NumVisit++;
}
}
/**
* Trigger regeneration of TempID.
*
@ -549,7 +584,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
$member->RememberLoginToken = $hash;
Cookie::set('alc_enc', $member->ID . ':' . $token, 90, null, null, false, true);
$member->NumVisit++;
$member->addVisit();
$member->write();
// Audit logging hook
@ -707,6 +742,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
));
$fields->removeByName(static::config()->hidden_fields);
$fields->removeByName('LastVisited');
$fields->removeByName('FailedLoginCount');
@ -1315,7 +1351,9 @@ class Member extends DataObject implements TemplateGlobalProvider {
_t('Member.INTERFACELANG', "Interface Language", 'Language of the CMS'),
i18n::get_existing_translations()
));
$mainFields->removeByName($self->config()->hidden_fields);
$mainFields->makeFieldReadonly('LastVisited');
if( ! $self->config()->lock_out_after_incorrect_logins) {
$mainFields->removeByName('FailedLoginCount');
@ -1417,6 +1455,8 @@ class Member extends DataObject implements TemplateGlobalProvider {
$labels['Surname'] = _t('Member.SURNAME', 'Surname');
$labels['Email'] = _t('Member.EMAIL', 'Email');
$labels['Password'] = _t('Member.db_Password', 'Password');
$labels['NumVisit'] = _t('Member.db_NumVisit', 'Number of Visits');
$labels['LastVisited'] = _t('Member.db_LastVisited', 'Last Visited Date');
$labels['PasswordExpiry'] = _t('Member.db_PasswordExpiry', 'Password Expiry Date', 'Password expiry date');
$labels['LockedOutUntil'] = _t('Member.db_LockedOutUntil', 'Locked out until', 'Security related date');
$labels['Locale'] = _t('Member.db_Locale', 'Interface Locale');

View File

@ -74,4 +74,16 @@ class RandomGenerator {
return hash($algorithm, $this->generateEntropy());
}
/**
* @deprecated
*/
public function generateHash($algorithm = 'whirlpool') {
Deprecation::notice('4.0',
'RandomGenerator::generateHash is deprecated because of a confusing name that hints the output is secure, '.
'while in fact it is just a random string. Use RandomGenerator::randomToken instead.',
Deprecation::SCOPE_METHOD
);
return $this->randomToken($algorithm);
}
}

17
tasks/DailyTask.php Normal file
View File

@ -0,0 +1,17 @@
<?php
/**
* Classes that must be run daily extend this class.
*
* Please note: Subclasses of this task aren't extecuted automatically,
* they need to be triggered by an external automation tool like unix cron.
* See {@link ScheduledTask} for details.
*
* @deprecated 3.1
*
* @todo Improve documentation
* @package framework
* @subpackage cron
*/
class DailyTask extends ScheduledTask {
}

16
tasks/HourlyTask.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/**
* Classes that must be run hourly extend this class
*
* Please note: Subclasses of this task aren't extecuted automatically,
* they need to be triggered by an external automation tool like unix cron.
* See {@link ScheduledTask} for details.
*
* @deprecated 3.1
*
* @package framework
* @subpackage cron
*/
class HourlyTask extends ScheduledTask {
}

16
tasks/MonthlyTask.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/**
* Classes that must be run monthly extend this class
*
* Please note: Subclasses of this task aren't extecuted automatically,
* they need to be triggered by an external automation tool like unix cron.
* See {@link ScheduledTask} for details.
*
* @deprecated 3.1
*
* @package framework
* @subpackage cron
*/
class MonthlyTask extends ScheduledTask {
}

View File

@ -0,0 +1,16 @@
<?php
/**
* Classes that must be run quarter hourly extend this class
*
* Please note: Subclasses of this task aren't extecuted automatically,
* they need to be triggered by an external automation tool like unix cron.
* See {@link ScheduledTask} for details.
*
* @deprecated 3.1
*
* @package framework
* @subpackage cron
*/
class QuarterHourlyTask extends ScheduledTask {
}

73
tasks/ScheduledTask.php Normal file
View File

@ -0,0 +1,73 @@
<?php
/**
* Abstract task representing scheudled tasks.
*
* Scheduled tasks are tasks that are run at a certain time or set interval. For example, notify a page owner that
* their page is about to expire. Scheduled tasks are implemented as singleton instances and a single
* instance is responsibly directly or indirectly for executing all tasks that should be run at that time.
*
* You can use the different subclasses {@link HourlyTask}, {@link DailyTask},
* {@link WeeklyTask} to determine when a task should be run,
* and use automation tools such as unix cron to trigger them.
*
* <b>Usage</b>
*
* Implement a daily task by extending DailyTask and implementing process().
*
* <code>
* class MyTask extends DailyTask {
* function process() {
* // implement your task here
* }
* }
* </code>
*
* You can also implement the index() method to overwrite which singleton classes are instantiated and processed.
* By default, all subclasses of the task are instantiated and used. For the DailyTask class, this means
* that an instance of each subclass of DailyTask will be created.
*
* You can test your task from the command line by running the following command
* (replace <MyTask> is the classname of your task):
*
* <code>framework/cli-script.php /<MyTask></code>
*
* To perform all Daily tasks, run from the command line:
*
* <code>cli-script.php /DailyTask</code>
*
* <b>Example Cron Definition</b>
*
* <code>
* # Quarter-hourly task (every hour at 25 minutes past) (remove space between first * and /15)
* * /15 * * * * www-data /webroot/framework/cli-script.php /QuarterHourlyTask > /var/log/quarterhourlytask.log
*
* # HourlyTask (every hour at 25 minutes past)
* 25 * * * * www-data /webroot/framework/cli-script.php /HourlyTask > /var/log/hourlytask.log
*
* # DailyTask (every day at 6:25am)
* 25 6 * * * www-data /webroot/framework/cli-script.php /DailyTask > /var/log/dailytask.log
*
* # WeelkyTask (every Monday at 6:25am)
* 25 6 1 * * www-data /webroot/framework/cli-script.php /WeeklyTask > /var/log/weeklytask.log
* </code>
*
* @deprecated 3.1
*
* @todo Improve documentation
* @package framework
* @subpackage cron
*/
abstract class ScheduledTask extends CliController {
// this class exists as a logical extension
public function init() {
Deprecation::notice(
'3.1',
'ScheduledTask, QuarterHourlyTask, HourlyTask, DailyTask, MonthlyTask, WeeklyTask and ' .
'YearlyTask are deprecated, please extend from BuildTask or CliController, ' .
'and invoke them in self-defined frequencies through Unix cronjobs etc.'
);
parent::init();
}
}

16
tasks/WeeklyTask.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/**
* Classes that must be run weekly extend this class
*
* Please note: Subclasses of this task aren't extecuted automatically,
* they need to be triggered by an external automation tool like unix cron.
* See {@link ScheduledTask} for details.
*
* @deprecated 3.1
*
* @package framework
* @subpackage cron
*/
class WeeklyTask extends ScheduledTask {
}

16
tasks/YearlyTask.php Normal file
View File

@ -0,0 +1,16 @@
<?php
/**
* Classes that must be run yearly extend this class
*
* Please note: Subclasses of this task aren't extecuted automatically,
* they need to be triggered by an external automation tool like unix cron.
* See {@link ScheduledTask} for details.
*
* @deprecated 3.1
*
* @package framework
* @subpackage cron
*/
class YearlyTask extends ScheduledTask {
}

View File

@ -0,0 +1,232 @@
<?php
/*
* A hierarchy of data types, to...
*
* @deprecated
*
* This is testing`
* {@link DataObject::Aggregate()} and {@link DataObject::RelationshipAggregate()}
* which are deprecated. Aggregates are handled directly by DataList instead.
* This test should be removed or merged into DataListTest once those functions are
* removed from DataObject.
*/
class AggregateTest_Foo extends DataObject implements TestOnly {
private static $db = array(
"Foo" => "Int"
);
private static $has_one = array('Bar' => 'AggregateTest_Bar');
private static $belongs_many_many = array('Bazi' => 'AggregateTest_Baz');
}
/**
* @deprecated
*/
class AggregateTest_Fab extends AggregateTest_Foo {
private static $db = array(
"Fab" => "Int"
);
}
/**
* @deprecated
*/
class AggregateTest_Fac extends AggregateTest_Fab {
private static $db = array(
"Fac" => "Int"
);
}
/**
* @deprecated
*/
class AggregateTest_Bar extends DataObject implements TestOnly {
private static $db = array(
"Bar" => "Int"
);
private static $has_many = array(
"Foos" => "AggregateTest_Foo"
);
}
/**
* @deprecated
*/
class AggregateTest_Baz extends DataObject implements TestOnly {
private static $db = array(
"Baz" => "Int"
);
private static $many_many = array(
"Foos" => "AggregateTest_Foo"
);
}
/**
* @deprecated
*/
class AggregateTest extends SapphireTest {
protected static $fixture_file = 'AggregateTest.yml';
protected $extraDataObjects = array(
'AggregateTest_Foo',
'AggregateTest_Fab',
'AggregateTest_Fac',
'AggregateTest_Bar',
'AggregateTest_Baz'
);
protected $originalDeprecation;
public function setUp() {
parent::setUp();
// This test tests code that was deprecated after 2.4
$this->originalDeprecation = Deprecation::dump_settings();
Deprecation::notification_version('2.4');
}
public function tearDown() {
Deprecation::restore_settings($this->originalDeprecation);
parent::tearDown();
}
/**
* Test basic aggregation on a passed type
*/
public function testTypeSpecifiedAggregate() {
$foo = $this->objFromFixture('AggregateTest_Foo', 'foo1');
// Template style access
$this->assertEquals($foo->Aggregate('AggregateTest_Foo')->XML_val('Max', array('Foo')), 9);
$this->assertEquals($foo->Aggregate('AggregateTest_Fab')->XML_val('Max', array('Fab')), 3);
// PHP style access
$this->assertEquals($foo->Aggregate('AggregateTest_Foo')->Max('Foo'), 9);
$this->assertEquals($foo->Aggregate('AggregateTest_Fab')->Max('Fab'), 3);
}
/* */
/**
* Test basic aggregation on a given dataobject
* @return unknown_type
*/
public function testAutoTypeAggregate() {
$foo = $this->objFromFixture('AggregateTest_Foo', 'foo1');
$fab = $this->objFromFixture('AggregateTest_Fab', 'fab1');
// Template style access
$this->assertEquals($foo->Aggregate()->XML_val('Max', array('Foo')), 9);
$this->assertEquals($fab->Aggregate()->XML_val('Max', array('Fab')), 3);
// PHP style access
$this->assertEquals($foo->Aggregate()->Max('Foo'), 9);
$this->assertEquals($fab->Aggregate()->Max('Fab'), 3);
}
/* */
/**
* Test base-level field access - was failing due to use of custom_database_fields, not just database_fields
* @return unknown_type
*/
public function testBaseFieldAggregate() {
$foo = $this->objFromFixture('AggregateTest_Foo', 'foo1');
$this->assertEquals(
$this->formatDate($foo->Aggregate('AggregateTest_Foo')->Max('LastEdited')),
$this->formatDate(DataObject::get_one('AggregateTest_Foo', '', '', '"LastEdited" DESC')->LastEdited)
);
$this->assertEquals(
$this->formatDate($foo->Aggregate('AggregateTest_Foo')->Max('Created')),
$this->formatDate(DataObject::get_one('AggregateTest_Foo', '', '', '"Created" DESC')->Created)
);
}
/* */
/**
* Test aggregation takes place on the passed type & it's children only
*/
public function testChildAggregate() {
$foo = $this->objFromFixture('AggregateTest_Foo', 'foo1');
// For base classes, aggregate is calculcated on it and all children classes
$this->assertEquals($foo->Aggregate('AggregateTest_Foo')->Max('Foo'), 9);
// For subclasses, aggregate is calculated for that subclass and it's children only
$this->assertEquals($foo->Aggregate('AggregateTest_Fab')->Max('Foo'), 9);
$this->assertEquals($foo->Aggregate('AggregateTest_Fac')->Max('Foo'), 6);
}
/* */
/**
* Test aggregates are cached properly
*/
public function testCache() {
$this->markTestIncomplete();
}
/* */
/**
* Test cache is correctly flushed on write
*/
public function testCacheFlushing() {
$foo = $this->objFromFixture('AggregateTest_Foo', 'foo1');
$fab = $this->objFromFixture('AggregateTest_Fab', 'fab1');
// For base classes, aggregate is calculcated on it and all children classes
$this->assertEquals($fab->Aggregate('AggregateTest_Foo')->Max('Foo'), 9);
// For subclasses, aggregate is calculated for that subclass and it's children only
$this->assertEquals($fab->Aggregate('AggregateTest_Fab')->Max('Foo'), 9);
$this->assertEquals($fab->Aggregate('AggregateTest_Fac')->Max('Foo'), 6);
$foo->Foo = 12;
$foo->write();
// For base classes, aggregate is calculcated on it and all children classes
$this->assertEquals($fab->Aggregate('AggregateTest_Foo')->Max('Foo'), 12);
// For subclasses, aggregate is calculated for that subclass and it's children only
$this->assertEquals($fab->Aggregate('AggregateTest_Fab')->Max('Foo'), 9);
$this->assertEquals($fab->Aggregate('AggregateTest_Fac')->Max('Foo'), 6);
$fab->Foo = 15;
$fab->write();
// For base classes, aggregate is calculcated on it and all children classes
$this->assertEquals($fab->Aggregate('AggregateTest_Foo')->Max('Foo'), 15);
// For subclasses, aggregate is calculated for that subclass and it's children only
$this->assertEquals($fab->Aggregate('AggregateTest_Fab')->Max('Foo'), 15);
$this->assertEquals($fab->Aggregate('AggregateTest_Fac')->Max('Foo'), 6);
}
/* */
/**
* Test basic relationship aggregation
*/
public function testRelationshipAggregate() {
$bar1 = $this->objFromFixture('AggregateTest_Bar', 'bar1');
$this->assertEquals($bar1->RelationshipAggregate('Foos')->Max('Foo'), 8);
$baz1 = $this->objFromFixture('AggregateTest_Baz', 'baz1');
$this->assertEquals($baz1->RelationshipAggregate('Foos')->Max('Foo'), 8);
}
/* */
/**
* Copied from DataObject::__construct(), special case for MSSQLDatabase.
*
* @param String
* @return String
*/
protected function formatDate($dateStr) {
$dateStr = preg_replace('/:[0-9][0-9][0-9]([ap]m)$/i', ' \\1', $dateStr);
return date('Y-m-d H:i:s', strtotime($dateStr));
}
}

View File

@ -0,0 +1,46 @@
AggregateTest_Bar:
bar1:
Bar: 1
bar2:
Bar: 2
AggregateTest_Foo:
foo1:
Foo: 1
Bar: =>AggregateTest_Bar.bar1
foo2:
Foo: 2
Bar: =>AggregateTest_Bar.bar1
foo3:
Foo: 3
Bar: =>AggregateTest_Bar.bar2
AggregateTest_Fab:
fab1:
Foo: 7
Fab: 1
Bar: =>AggregateTest_Bar.bar1
fab2:
Foo: 8
Fab: 2
Bar: =>AggregateTest_Bar.bar1
fab3:
Foo: 9
Fab: 3
Bar: =>AggregateTest_Bar.bar2
AggregateTest_Fac:
fac1:
Foo: 4
Fac: 1
fac2:
Foo: 5
Fac: 2
fac3:
Foo: 6
Fac: 3
AggregateTest_Baz:
baz1:
Baz: 1
Foos: =>AggregateTest_Foo.foo1,=>AggregateTest_Foo.foo2,=>AggregateTest_Fab.fab1,=>AggregateTest_Fab.fab2
baz2:
Baz: 2
Foos: =>AggregateTest_Foo.foo3,=>AggregateTest_Fab.fab3

View File

@ -3751,6 +3751,15 @@ class SSTemplateParser extends Parser implements TemplateParser {
'}; $scope->popScope(); ';
}
/**
* The deprecated closed block handler for control blocks
* @deprecated
*/
function ClosedBlock_Handle_Control(&$res) {
Deprecation::notice('4.0', '<% control %> is deprecated. Use <% with %> or <% loop %> instead.');
return $this->ClosedBlock_Handle_Loop($res);
}
/**
* The closed block handler for with blocks
*/

View File

@ -931,6 +931,15 @@ class SSTemplateParser extends Parser implements TemplateParser {
'}; $scope->popScope(); ';
}
/**
* The deprecated closed block handler for control blocks
* @deprecated
*/
function ClosedBlock_Handle_Control(&$res) {
Deprecation::notice('4.0', '<% control %> is deprecated. Use <% with %> or <% loop %> instead.');
return $this->ClosedBlock_Handle_Loop($res);
}
/**
* The closed block handler for with blocks
*/