mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
parent
28ca23c4ee
commit
1686c83826
@ -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;
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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
239
dev/Profiler.php
Normal 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);
|
||||
}
|
||||
}
|
@ -446,6 +446,7 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
||||
* {@link loadFixture()}
|
||||
*/
|
||||
public function clearFixtures() {
|
||||
$this->fixtures = array();
|
||||
$this->getFixtureFactory()->clear();
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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')
|
||||
));
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
|
110
email/Mailer.php
110
email/Mailer.php
@ -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);
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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
101
forms/ToggleField.php
Normal 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
|
||||
<a href="#" class="triggerMore">$this->labelMore</a>
|
||||
</div>
|
||||
<div class="readonly typography contentMore">
|
||||
$this->value
|
||||
<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
190
model/Aggregate.php
Normal 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 <% cached List(Member).max(LastEdited) %> 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;
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
|
@ -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.'"';
|
||||
}
|
||||
|
@ -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 <% cached List(Member).max(LastEdited) %> 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;
|
||||
|
@ -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
|
||||
*
|
||||
|
20
search/filters/ExactMatchMultiFilter.php
Normal file
20
search/filters/ExactMatchMultiFilter.php
Normal 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);
|
||||
}
|
||||
}
|
16
search/filters/NegationFilter.php
Executable file
16
search/filters/NegationFilter.php
Executable 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);
|
||||
}
|
||||
}
|
||||
|
20
search/filters/StartsWithMultiFilter.php
Normal file
20
search/filters/StartsWithMultiFilter.php
Normal 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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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
17
tasks/DailyTask.php
Normal 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
16
tasks/HourlyTask.php
Normal 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
16
tasks/MonthlyTask.php
Normal 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 {
|
||||
|
||||
}
|
16
tasks/QuarterHourlyTask.php
Normal file
16
tasks/QuarterHourlyTask.php
Normal 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
73
tasks/ScheduledTask.php
Normal 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
16
tasks/WeeklyTask.php
Normal 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
16
tasks/YearlyTask.php
Normal 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 {
|
||||
|
||||
}
|
232
tests/model/AggregateTest.php
Normal file
232
tests/model/AggregateTest.php
Normal 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));
|
||||
}
|
||||
}
|
46
tests/model/AggregateTest.yml
Normal file
46
tests/model/AggregateTest.yml
Normal 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
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user