mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Merged branches/2.3 into trunk
git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@66395 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
parent
ece0130baa
commit
2984355f43
1
Makefile
1
Makefile
@ -10,6 +10,7 @@ URL=`php5 ./cli-script.php SapphireInfo/baseurl`
|
||||
test: phpunit
|
||||
|
||||
phpunit:
|
||||
php5 ./cli-script.php dev/build
|
||||
php5 ./cli-script.php dev/tests/all flush=1
|
||||
|
||||
windmill:
|
||||
|
@ -5,6 +5,8 @@
|
||||
* @subpackage misc
|
||||
*/
|
||||
class Cookie extends Object {
|
||||
static $report_errors = true;
|
||||
|
||||
/**
|
||||
* Set a cookie variable
|
||||
* @param name The variable name
|
||||
@ -13,11 +15,12 @@ class Cookie extends Object {
|
||||
*/
|
||||
static function set($name, $value, $expiryDays = 90) {
|
||||
if(!headers_sent($file, $line)) {
|
||||
setcookie($name, $value, time()+(86400*$expiryDays), Director::baseURL());
|
||||
$_COOKIE[$name] = $value;
|
||||
$expiry = $expiryDays > 0 ? time()+(86400*$expiryDays) : 0;
|
||||
setcookie($name, $value, $expiry, Director::baseURL());
|
||||
} else {
|
||||
// if(Director::isDevMode()) user_error("Cookie '$name' can't be set. The site started outputting was content at line $line in $file", E_USER_WARNING);
|
||||
if(self::$report_errors) user_error("Cookie '$name' can't be set. The site started outputting was content at line $line in $file", E_USER_WARNING);
|
||||
}
|
||||
$_COOKIE[$name] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -32,6 +35,13 @@ class Cookie extends Object {
|
||||
setcookie( $name, null, time() - 86400 );
|
||||
}
|
||||
}
|
||||
|
||||
static function set_report_errors($reportErrors) {
|
||||
self::$report_errors = $reportErrors;
|
||||
}
|
||||
static function report_errors() {
|
||||
return self::$report_errors;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
@ -520,6 +520,16 @@ class ManifestBuilder {
|
||||
private static function get_children($class) {
|
||||
return isset(self::$extendsArray[$class]) ? self::$extendsArray[$class] : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the Manifest has been included
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
static function has_been_included() {
|
||||
global $_CLASS_MANIFEST, $_TEMPLATE_MANIFEST, $_CSS_MANIFEST, $_ALL_CLASSES;
|
||||
return (bool)(empty($_CLASS_MANIFEST) && empty($_TEMPLATE_MANIFEST) && empty($_CSS_MANIFEST) && empty($_ALL_CLASSES));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a flat array with all children of a given class
|
||||
|
@ -755,7 +755,7 @@ class Requirements_Backend {
|
||||
*
|
||||
*/
|
||||
function process_combined_files() {
|
||||
if(Director::isDev()) {
|
||||
if(Director::isDev() && !SapphireTest::is_running_test()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -345,10 +345,28 @@ class SSViewer extends Object {
|
||||
}
|
||||
|
||||
static function parseTemplateContent($content, $template="") {
|
||||
// Add template filename comments on dev sites
|
||||
if(Director::isDev() && $template) {
|
||||
// If this template is a full HTML page, then put the comments just inside the HTML tag to prevent any IE glitches
|
||||
if(stripos($content, "<html") !== false) {
|
||||
$content = preg_replace('/(<html[^>]*>)/i', "\\1<!-- template $template -->", $content);
|
||||
$content = preg_replace('/(<\/html[^>]*>)/i', "\\1<!-- end template $template -->", $content);
|
||||
} else {
|
||||
$content = "<!-- template $template -->\n" . $content . "\n<!-- end template $template -->";
|
||||
}
|
||||
}
|
||||
|
||||
while(true) {
|
||||
$oldContent = $content;
|
||||
|
||||
// Add include filename comments on dev sites
|
||||
if(Director::isDev()) $replacementCode = 'return "<!-- include " . SSViewer::getTemplateFile($matches[1]) . "-->\n"
|
||||
. SSViewer::getTemplateContent($matches[1])
|
||||
. "\n<!-- end include " . SSViewer::getTemplateFile($matches[1]) . "-->";';
|
||||
else $replacementCode = 'return SSViewer::getTemplateContent($matches[1]);';
|
||||
|
||||
$content = preg_replace_callback('/<' . '% include +([A-Za-z0-9_]+) +%' . '>/', create_function(
|
||||
'$matches', 'return SSViewer::getTemplateContent($matches[1]);'
|
||||
'$matches', $replacementCode
|
||||
), $content);
|
||||
if($oldContent == $content) break;
|
||||
}
|
||||
|
@ -79,9 +79,6 @@ class Controller extends RequestHandler {
|
||||
if(Director::isTest() && $this->basicAuthEnabled && Security::database_is_ready()) {
|
||||
BasicAuth::requireLogin("SilverStripe test website. Use your CMS login", "ADMIN");
|
||||
}
|
||||
|
||||
//
|
||||
Cookie::set("PastVisitor", true);
|
||||
|
||||
// Directly access the session variable just in case the Group or Member tables don't yet exist
|
||||
if(Session::get('loggedInAs') && Security::database_is_ready()) {
|
||||
@ -405,7 +402,8 @@ class Controller extends RequestHandler {
|
||||
* @return boolean
|
||||
*/
|
||||
function PastVisitor() {
|
||||
return Cookie::get("PastVisitor") ? true : false;
|
||||
user_error("Controller::PastVisitor() is deprecated", E_USER_NOTICE);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -170,12 +170,20 @@ class Director {
|
||||
$existingGetVars = $_GET;
|
||||
$existingPostVars = $_POST;
|
||||
$existingSessionVars = $_SESSION;
|
||||
$existingCookies = $_COOKIE;
|
||||
|
||||
$existingCookieReportErrors = Cookie::report_errors();
|
||||
Cookie::set_report_errors(false);
|
||||
|
||||
$existingRequirementsBackend = Requirements::backend();
|
||||
Requirements::set_backend(new Requirements_Backend());
|
||||
|
||||
// Replace the superglobals with appropriate test values
|
||||
$_REQUEST = array_merge((array)$getVars, (array)$postVars);
|
||||
$_GET = (array)$getVars;
|
||||
$_POST = (array)$postVars;
|
||||
$_SESSION = $session ? $session->inst_getAll() : array();
|
||||
$_COOKIE = array();
|
||||
|
||||
$req = new HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
|
||||
if($headers) foreach($headers as $k => $v) $req->addHeader($k, $v);
|
||||
@ -186,6 +194,9 @@ class Director {
|
||||
$_GET = $existingGetVars;
|
||||
$_POST = $existingPostVars;
|
||||
$_SESSION = $existingSessionVars;
|
||||
$_COOKIE = $existingCookies;
|
||||
Cookie::set_report_errors($existingCookieReportErrors);
|
||||
Requirements::set_backend($existingRequirementsBackend);
|
||||
|
||||
// These are needed so that calling Director::test() doesnt muck with whoever is calling it.
|
||||
// Really, it's some inapproriate coupling and should be resolved by making less use of statics
|
||||
@ -692,7 +703,7 @@ class Director {
|
||||
if(self::$environment_type) return self::$environment_type == 'dev';
|
||||
|
||||
// Check if we are running on one of the development servers
|
||||
if(in_array($_SERVER['HTTP_HOST'], Director::$dev_servers)) {
|
||||
if(isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], Director::$dev_servers)) {
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
@ -717,7 +728,7 @@ class Director {
|
||||
}
|
||||
|
||||
// Check if we are running on one of the test servers
|
||||
if(in_array($_SERVER['HTTP_HOST'], Director::$test_servers)) {
|
||||
if(isset($_SERVER['HTTP_HOST']) && in_array($_SERVER['HTTP_HOST'], Director::$test_servers)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -72,7 +72,7 @@ class ModelAsController extends Controller implements NestedController {
|
||||
|
||||
return $controller;
|
||||
} else {
|
||||
return "The requested page couldn't be found.";
|
||||
return new HTTPResponse("The requested page couldn't be found.",404);
|
||||
}
|
||||
|
||||
} else {
|
||||
|
@ -2062,7 +2062,11 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
|
||||
// Get the tables to join to
|
||||
$tableClasses = ClassInfo::dataClassesFor($this->class);
|
||||
if(!$tableClasses) {
|
||||
user_error("DataObject::buildSQL: Can't find data classes (classes linked to tables) for $this->class", E_USER_ERROR);
|
||||
if(!ManifestBuilder::has_been_included()) {
|
||||
user_error("DataObjects have been requested before the manifest is loaded. Please ensure you are not querying the database in _config.php.", E_USER_ERROR);
|
||||
} else {
|
||||
user_error("DataObject::buildSQL: Can't find data classes (classes linked to tables) for $this->class. Please ensure you run dev/build after creating a new DataObject.", E_USER_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
$baseClass = array_shift($tableClasses);
|
||||
@ -2202,6 +2206,10 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
|
||||
* @return mixed The objects matching the filter, in the class specified by $containerClass
|
||||
*/
|
||||
public function instance_get($filter = "", $sort = "", $join = "", $limit="", $containerClass = "DataObjectSet") {
|
||||
if(!DB::isActive()) {
|
||||
user_error("DataObjects have been requested before the database is ready. Please ensure your database connection details are correct, your database has been built, and that you are not trying to query the database in _config.php.", E_USER_ERROR);
|
||||
}
|
||||
|
||||
$query = $this->extendedSQL($filter, $sort, $limit, $join);
|
||||
$records = $query->execute();
|
||||
|
||||
@ -2300,6 +2308,10 @@ class DataObject extends ViewableData implements DataObjectInterface,i18nEntityP
|
||||
* @return DataObject The first item matching the query
|
||||
*/
|
||||
public function instance_get_one($filter, $orderby = null) {
|
||||
if(!DB::isActive()) {
|
||||
user_error("DataObjects have been requested before the database is ready. Please ensure your database connection details are correct, your database has been built, and that you are not trying to query the database in _config.php.", E_USER_ERROR);
|
||||
}
|
||||
|
||||
$query = $this->buildSQL($filter);
|
||||
$query->limit = "1";
|
||||
if($orderby) {
|
||||
|
@ -94,16 +94,11 @@ class ErrorPage extends Page {
|
||||
* @param boolean $createNewVersion Set this to true to create a new version number. By default, the existing version number will be copied over.
|
||||
*/
|
||||
function publish($fromStage, $toStage, $createNewVersion = false) {
|
||||
$alc_enc = isset($_COOKIE['alc_enc']) ? $_COOKIE['alc_enc'] : null;
|
||||
Cookie::set('alc_enc', null);
|
||||
|
||||
$oldStage = Versioned::current_stage();
|
||||
|
||||
// Run the page
|
||||
Requirements::clear();
|
||||
$controller = new ErrorPage_Controller($this);
|
||||
$errorContent = $controller->handleRequest(new HTTPRequest('GET',''))->getBody();
|
||||
Requirements::clear();
|
||||
$response = Director::test($this->Link());
|
||||
$errorContent = $response->getBody();
|
||||
|
||||
if(!file_exists(ASSETS_PATH)) {
|
||||
mkdir(ASSETS_PATH, 02775);
|
||||
@ -116,9 +111,6 @@ class ErrorPage extends Page {
|
||||
|
||||
// Restore the version we're currently connected to.
|
||||
Versioned::reading_stage($oldStage);
|
||||
|
||||
// Log back in
|
||||
if(isset($alc_enc)) Cookie::set('alc_enc', $alc_enc);
|
||||
|
||||
return $this->extension_instances['Versioned']->publish($fromStage, $toStage, $createNewVersion);
|
||||
}
|
||||
|
@ -432,8 +432,8 @@ class SQLQuery extends Object {
|
||||
* @return boolean
|
||||
*/
|
||||
function filtersOnID() {
|
||||
return ($query->where && count($query->where) == 1 &&
|
||||
(strpos($query->where[0], ".`ID` = ") || strpos($query->where[0], ".ID = ") || strpos($query->where[0], "ID = ") )
|
||||
return ($this->where && count($this->where) == 1 &&
|
||||
(strpos($this->where[0], ".`ID` = ") || strpos($this->where[0], ".ID = ") || strpos($this->where[0], "ID = ") )
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1370,8 +1370,6 @@ class SiteTree extends DataObject {
|
||||
// Handle activities undertaken by decorators
|
||||
$this->extend('onBeforePublish', $original);
|
||||
|
||||
$this->AssignedToID = 0;
|
||||
$this->RequestedByID = 0;
|
||||
$this->Status = "Published";
|
||||
//$this->PublishedByID = Member::currentUser()->ID;
|
||||
$this->write();
|
||||
@ -1410,8 +1408,6 @@ class SiteTree extends DataObject {
|
||||
*/
|
||||
function doRollbackTo($version) {
|
||||
$this->publish($version, "Stage", true);
|
||||
$this->AssignedToID = 0;
|
||||
$this->RequestedByID = 0;
|
||||
$this->Status = "Saved (update)";
|
||||
$this->writeWithoutVersion();
|
||||
}
|
||||
@ -1424,8 +1420,6 @@ class SiteTree extends DataObject {
|
||||
|
||||
// Use a clone to get the updates made by $this->publish
|
||||
$clone = DataObject::get_by_id("SiteTree", $this->ID);
|
||||
$clone->AssignedToID = 0;
|
||||
$clone->RequestedByID = 0;
|
||||
$clone->Status = "Published";
|
||||
$clone->writeWithoutVersion();
|
||||
}
|
||||
@ -1443,11 +1437,9 @@ class SiteTree extends DataObject {
|
||||
* Changing the condition from empty($this->ID) to
|
||||
* !$this->ID && !$this->record['ID'] fixed this.
|
||||
*/
|
||||
if(empty($this->ID))
|
||||
return true;
|
||||
if(empty($this->ID)) return true;
|
||||
|
||||
if(is_numeric($this->ID))
|
||||
return false;
|
||||
if(is_numeric($this->ID)) return false;
|
||||
|
||||
return stripos($this->ID, 'new') === 0;
|
||||
}
|
||||
|
@ -28,8 +28,6 @@ class VirtualPage extends Page {
|
||||
$nonVirtualFields = array(
|
||||
"SecurityTypeID",
|
||||
"OwnerID",
|
||||
"AssignedToID",
|
||||
"RequestedByID",
|
||||
"URLSegment",
|
||||
"Sort",
|
||||
"Status",
|
||||
|
@ -43,8 +43,8 @@ class ForeignKey extends Int {
|
||||
$field = new FileField($relationName, $title, $this->value);
|
||||
}
|
||||
} else {
|
||||
$objs = DataObject::get($this->object->class);
|
||||
$titleField = (singleton($this->object->class)->hasField('Title')) ? "Title" : "Name";
|
||||
$objs = DataObject::get($hasOneClass);
|
||||
$titleField = (singleton($hasOneClass)->hasField('Title')) ? "Title" : "Name";
|
||||
$map = ($objs) ? $objs->toDropdownMap("ID", $titleField) : false;
|
||||
$field = new DropdownField($this->name, $title, $map, null, null, ' ');
|
||||
}
|
||||
|
@ -73,7 +73,11 @@ class CsvBulkLoader extends BulkLoader {
|
||||
// trigger custom search method for finding a relation based on the given value
|
||||
// and write it back to the relation (or create a new object)
|
||||
$relationName = $this->relationCallbacks[$fieldName]['relationname'];
|
||||
$relationObj = $obj->{$this->relationCallbacks[$fieldName]['callback']}($val, $record);
|
||||
if($this->hasMethod($this->relationCallbacks[$fieldName]['callback'])) {
|
||||
$relationObj = $this->{$this->relationCallbacks[$fieldName]['callback']}(&$obj, $val, $record);
|
||||
} elseif($obj->hasMethod($this->relationCallbacks[$fieldName]['callback'])) {
|
||||
$relationObj = $obj->{$this->relationCallbacks[$fieldName]['callback']}($val, $record);
|
||||
}
|
||||
if(!$relationObj || !$relationObj->exists()) {
|
||||
$relationClass = $obj->has_one($relationName);
|
||||
$relationObj = new $relationClass();
|
||||
|
@ -22,19 +22,31 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
||||
protected $originalMailer;
|
||||
protected $originalMemberPasswordValidator;
|
||||
protected $originalRequirements;
|
||||
protected $originalIsRunningTest;
|
||||
|
||||
protected $mailer;
|
||||
|
||||
protected static $is_running_test = false;
|
||||
|
||||
public static function is_running_test() {
|
||||
return self::$is_running_test;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var YamlFixture
|
||||
*/
|
||||
protected $fixture;
|
||||
|
||||
function setUp() {
|
||||
// Mark test as being run
|
||||
$this->originalIsRunningTest = self::$is_running_test;
|
||||
self::$is_running_test = true;
|
||||
|
||||
// Remove password validation
|
||||
$this->originalMemberPasswordValidator = Member::password_validator();
|
||||
$this->originalRequirements = Requirements::backend();
|
||||
Member::set_password_validator(null);
|
||||
Cookie::set_report_errors(false);
|
||||
|
||||
$className = get_class($this);
|
||||
$fixtureFile = eval("return {$className}::\$fixture_file;");
|
||||
@ -127,6 +139,10 @@ class SapphireTest extends PHPUnit_Framework_TestCase {
|
||||
|
||||
// Restore requirements
|
||||
Requirements::set_backend($this->originalRequirements);
|
||||
|
||||
// Mark test as no longer being run - we use originalIsRunningTest to allow for nested SapphireTest calls
|
||||
self::$is_running_test = $this->originalIsRunningTest;
|
||||
$this->originalIsRunningTest = null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -480,6 +480,8 @@ class File extends DataObject {
|
||||
'jpg' => 'JPEG image - good for photos',
|
||||
'jpeg' => 'JPEG image - good for photos',
|
||||
'png' => 'PNG image - good general-purpose format',
|
||||
'ico' => 'Icon image',
|
||||
'tiff' => 'Tagged image format',
|
||||
'doc' => 'Word document',
|
||||
'xls' => 'Excel spreadsheet',
|
||||
'zip' => 'ZIP compressed file',
|
||||
@ -490,7 +492,11 @@ class File extends DataObject {
|
||||
'wav' => 'WAV audo file',
|
||||
'avi' => 'AVI video file',
|
||||
'mpg' => 'MPEG video file',
|
||||
'mpeg' => 'MPEG video file'
|
||||
'mpeg' => 'MPEG video file',
|
||||
'js' => 'Javascript file',
|
||||
'css' => 'CSS file',
|
||||
'html' => 'HTML file',
|
||||
'htm' => 'HTML file'
|
||||
);
|
||||
|
||||
$ext = $this->getExtension();
|
||||
|
@ -39,10 +39,15 @@ class ConfirmedPasswordField extends FormField {
|
||||
public $canBeEmpty = false;
|
||||
|
||||
/**
|
||||
* Hide the two password fields by default,
|
||||
* and provide a link instead that toggles them.
|
||||
*
|
||||
* @var boolean
|
||||
* If set to TRUE, the "password" and "confirm password"
|
||||
* formfields will be hidden via CSS and JavaScript by default,
|
||||
* and triggered by a link. An additional hidden field
|
||||
* determines if showing the fields has been triggered,
|
||||
* and just validates/saves the input in this case.
|
||||
* This behaviour works unobtrusively, without JavaScript enabled
|
||||
* the fields show, validate and save by default.
|
||||
*
|
||||
* @param boolean $showOnClick
|
||||
*/
|
||||
protected $showOnClick = false;
|
||||
|
||||
@ -52,7 +57,7 @@ class ConfirmedPasswordField extends FormField {
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $showOnClickTitle = 'Change Password';
|
||||
public $showOnClickTitle;
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
@ -73,11 +78,12 @@ class ConfirmedPasswordField extends FormField {
|
||||
(isset($titleConfirmField)) ? $titleConfirmField : _t('Member.CONFIRMPASSWORD', 'Confirm Password')
|
||||
)
|
||||
);
|
||||
|
||||
$this->showOnClick = $showOnClick;
|
||||
if($this->showOnClick) {
|
||||
|
||||
// has to be called in constructor because Field() isn't triggered upon saving the instance
|
||||
if($showOnClick) {
|
||||
$this->children->push(new HiddenField("{$name}[_PasswordFieldVisible]"));
|
||||
}
|
||||
$this->showOnClick = $showOnClick;
|
||||
|
||||
// we have labels for the subfields
|
||||
$title = false;
|
||||
@ -94,12 +100,22 @@ class ConfirmedPasswordField extends FormField {
|
||||
$content = '';
|
||||
|
||||
if($this->showOnClick) {
|
||||
if($this->showOnClickTitle) {
|
||||
$title = $this->showOnClickTitle;
|
||||
} else {
|
||||
$title = _t(
|
||||
'ConfirmedPasswordField.SHOWONCLICKTITLE',
|
||||
'Change Password',
|
||||
PR_MEDIUM,
|
||||
'Label of the link which triggers display of the "change password" formfields'
|
||||
);
|
||||
}
|
||||
|
||||
$content .= "<div class=\"showOnClick\">\n";
|
||||
$content .= "<a href=\"#\"" . $this->getTabIndexHTML() . ">{$this->showOnClickTitle}</a>\n";
|
||||
$content .= "<a href=\"#\"" . $this->getTabIndexHTML() . ">{$title}</a>\n";
|
||||
$content .= "<div class=\"showOnClickContainer\">";
|
||||
}
|
||||
|
||||
|
||||
foreach($this->children as $field) {
|
||||
$content .= $field->FieldHolder();
|
||||
}
|
||||
@ -121,6 +137,24 @@ class ConfirmedPasswordField extends FormField {
|
||||
$this->canBeEmpty = (bool)$value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The title on the link which triggers display of the
|
||||
* "password" and "confirm password" formfields.
|
||||
* Only used if {@link setShowOnClick()} is set to TRUE.
|
||||
*
|
||||
* @param $title
|
||||
*/
|
||||
public function setShowOnClickTitle($title) {
|
||||
$this->showOnClickTitle = $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getShowOnClickTitle() {
|
||||
return $this->showOnClickTitle;
|
||||
}
|
||||
|
||||
function setRightTitle($title) {
|
||||
foreach($this->children as $field) {
|
||||
$field->setRightTitle($title);
|
||||
|
@ -515,11 +515,11 @@ class FieldSet extends DataObjectSet {
|
||||
* @return Position in children collection (first position starts with 0). Returns FALSE if the field can't be found.
|
||||
*/
|
||||
function fieldPosition($field) {
|
||||
if(is_string($field)) $field = $this->fieldByName($field);
|
||||
if(is_object($field)) $field = $field->Name();
|
||||
|
||||
$i = 0;
|
||||
foreach($this->dataFields() as $child) {
|
||||
if($child == $field) return $i;
|
||||
if($child->Name() == $field) return $i;
|
||||
$i++;
|
||||
}
|
||||
|
||||
|
@ -251,13 +251,16 @@ class FormField extends RequestHandler {
|
||||
* Set the field value.
|
||||
* Returns $this.
|
||||
*/
|
||||
function setValue($value) { $this->value = $value; return $this; }
|
||||
function setValue($value) {
|
||||
$this->value = $value; return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the field name
|
||||
*/
|
||||
function setName($name) { $this->name = $name; }
|
||||
|
||||
function setName($name) {
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the container form.
|
||||
|
@ -21,9 +21,13 @@ class HasManyComplexTableField extends ComplexTableField {
|
||||
protected $relationAutoSetting = false;
|
||||
|
||||
function __construct($controller, $name, $sourceClass, $fieldList, $detailFormFields = null, $sourceFilter = "", $sourceSort = "", $sourceJoin = "") {
|
||||
|
||||
parent::__construct($controller, $name, $sourceClass, $fieldList, $detailFormFields, $sourceFilter, $sourceSort, $sourceJoin);
|
||||
|
||||
Requirements::javascript(SAPPHIRE_DIR . "/javascript/i18n.js");
|
||||
Requirements::javascript(SAPPHIRE_DIR . "/javascript/HasManyFileField.js");
|
||||
Requirements::javascript(SAPPHIRE_DIR . '/javascript/RelationComplexTableField.js');
|
||||
Requirements::css(SAPPHIRE_DIR . '/css/HasManyFileField.css');
|
||||
|
||||
$this->Markable = true;
|
||||
|
||||
if($controllerClass = $this->controllerClass()) {
|
||||
@ -34,14 +38,6 @@ class HasManyComplexTableField extends ComplexTableField {
|
||||
|
||||
}
|
||||
|
||||
function Field() {
|
||||
Requirements::javascript(SAPPHIRE_DIR . "/javascript/i18n.js");
|
||||
Requirements::javascript(SAPPHIRE_DIR . "/javascript/HasManyFileField.js");
|
||||
Requirements::javascript(SAPPHIRE_DIR . '/javascript/RelationComplexTableField.js');
|
||||
Requirements::css(SAPPHIRE_DIR . '/css/HasManyFileField.css');
|
||||
return parent::Field();
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to determine the DataObject that this field is built on top of
|
||||
*/
|
||||
|
@ -39,6 +39,7 @@ class ScaffoldingComplexTableField_Popup extends Form {
|
||||
Requirements::javascript(SAPPHIRE_DIR . "/javascript/ComplexTableField_popup.js");
|
||||
// jQuery requirements (how many of these are actually needed?)
|
||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
|
||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery_improvements.js');
|
||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery/plugins/livequery/jquery.livequery.js');
|
||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery/ui/ui.core.js');
|
||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery/ui/ui.tabs.js');
|
||||
|
@ -54,6 +54,7 @@ class TabSet extends CompositeField {
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/behaviour.js");
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/prototype_improvements.js");
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/jquery/jquery.js");
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/jquery/jquery_improvements.js");
|
||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery/plugins/livequery/jquery.livequery.js');
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/tabstrip/tabstrip.js");
|
||||
Requirements::css(THIRDPARTY_DIR . "/tabstrip/tabstrip.css");
|
||||
|
@ -161,8 +161,6 @@ Behaviour.register({
|
||||
}
|
||||
}
|
||||
});
|
||||
// TODO Performance-issue: Behaviour is possibly applied twice
|
||||
Behaviour.apply('#$formID');
|
||||
JS;
|
||||
|
||||
Requirements::customScript($js);
|
||||
|
@ -9,8 +9,27 @@
|
||||
*/
|
||||
class SearchForm extends Form {
|
||||
|
||||
/**
|
||||
* @var boolean $showInSearchTurnOn
|
||||
* @deprecated 2.3 SiteTree->ShowInSearch should always be respected
|
||||
*/
|
||||
protected $showInSearchTurnOn;
|
||||
|
||||
/**
|
||||
* @var int $numPerPage How many results are shown per page.
|
||||
* Relies on pagination being implemented in the search results template.
|
||||
*/
|
||||
protected $numPerPage = 10;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param Controller $controller
|
||||
* @param string $name The name of the form (used in URL addressing)
|
||||
* @param FieldSet $fields Optional, defaults to a single field named "Search". Search logic needs to be customized
|
||||
* if fields are added to the form.
|
||||
* @param FieldSet $actions Optional, defaults to a single field named "Go".
|
||||
* @param boolean $showInSearchTurnOn DEPRECATED 2.3
|
||||
*/
|
||||
function __construct($controller, $name, $fields = null, $actions = null, $showInSearchTurnOn = true) {
|
||||
$this->showInSearchTurnOn = $showInSearchTurnOn;
|
||||
|
||||
@ -53,10 +72,17 @@ class SearchForm extends Form {
|
||||
|
||||
/**
|
||||
* Return dataObjectSet of the results using $_REQUEST to get info from form.
|
||||
* Wraps around {@link searchEngine()}
|
||||
* Wraps around {@link searchEngine()}.
|
||||
*
|
||||
* @param int $numPerPage DEPRECATED 2.3 Use SearchForm->numPerPage
|
||||
* @param array $data Request data as an associative array. Should contain at least a key 'Search' with all searched keywords.
|
||||
* @return DataObjectSet
|
||||
*/
|
||||
public function getResults($numPerPage = 10){
|
||||
$keywords = $_REQUEST['Search'];
|
||||
public function getResults($numPerPage = null, $data = null){
|
||||
// legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.com tutorials
|
||||
if(!isset($data)) $data = $_REQUEST;
|
||||
|
||||
$keywords = $data['Search'];
|
||||
|
||||
$andProcessor = create_function('$matches','
|
||||
return " +" . $matches[2] . " +" . $matches[4] . " ";
|
||||
@ -73,14 +99,20 @@ class SearchForm extends Form {
|
||||
$keywords = $this->addStarsToKeywords($keywords);
|
||||
|
||||
if(strpos($keywords, '"') !== false || strpos($keywords, '+') !== false || strpos($keywords, '-') !== false || strpos($keywords, '*') !== false) {
|
||||
return $this->searchEngine($keywords, $numPerPage, "Relevance DESC", "", true);
|
||||
$results = $this->searchEngine($keywords, $numPerPage, "Relevance DESC", "", true);
|
||||
} else {
|
||||
return $this->searchEngine($keywords, $numPerPage);
|
||||
$sortBy = "Relevance DESC";
|
||||
}
|
||||
$results = $this->searchEngine($keywords, $numPerPage);
|
||||
}
|
||||
|
||||
// filter by permission
|
||||
if($results) foreach($results as $result) {
|
||||
if(!$result->canView()) $results->remove($result);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
function addStarsToKeywords($keywords) {
|
||||
protected function addStarsToKeywords($keywords) {
|
||||
if(!trim($keywords)) return "";
|
||||
// Add * to each keyword
|
||||
$splitWords = split(" +" , trim($keywords));
|
||||
@ -97,13 +129,18 @@ class SearchForm extends Form {
|
||||
}
|
||||
return implode(" ", $newWords);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* The core search engine, used by this class and its subclasses to do fun stuff.
|
||||
* Searches both SiteTree and File.
|
||||
*
|
||||
* @param string $keywords Keywords as a string.
|
||||
*/
|
||||
public function searchEngine($keywords, $numPerPage = 10, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) {
|
||||
public function searchEngine($keywords, $numPerPage = null, $sortBy = "Relevance DESC", $extraFilter = "", $booleanSearch = false, $alternativeFileFilter = "", $invertedMatch = false) {
|
||||
if(!$numPerPage) $numPerPage = $this->numPerPage;
|
||||
$fileFilter = '';
|
||||
$keywords = addslashes($keywords);
|
||||
|
||||
@ -139,14 +176,14 @@ class SearchForm extends Form {
|
||||
$baseClass = reset($queryContent->from);
|
||||
// There's no need to do all that joining
|
||||
$queryContent->from = array(str_replace('`','',$baseClass) => $baseClass);
|
||||
$queryContent->select = array("ClassName","$baseClass.ID","ParentID","Title","URLSegment","Content","LastEdited","Created","_utf8'' AS Filename", "_utf8'' AS Name", "$relevanceContent AS Relevance");
|
||||
$queryContent->select = array("ClassName","$baseClass.ID","ParentID","Title","URLSegment","Content","LastEdited","Created","_utf8'' AS Filename", "_utf8'' AS Name", "$relevanceContent AS Relevance", "CanViewType");
|
||||
$queryContent->orderby = null;
|
||||
|
||||
$queryFiles = singleton('File')->extendedSQL($notMatch . $matchFile . $fileFilter, "");
|
||||
$baseClass = reset($queryFiles->from);
|
||||
// There's no need to do all that joining
|
||||
$queryFiles->from = array(str_replace('`','',$baseClass) => $baseClass);
|
||||
$queryFiles->select = array("ClassName","$baseClass.ID","_utf8'' AS ParentID","Title","_utf8'' AS URLSegment","Content","LastEdited","Created","Filename","Name","$relevanceFile AS Relevance");
|
||||
$queryFiles->select = array("ClassName","$baseClass.ID","_utf8'' AS ParentID","Title","_utf8'' AS URLSegment","Content","LastEdited","Created","Filename","Name","$relevanceFile AS Relevance","NULL AS CanViewType");
|
||||
$queryFiles->orderby = null;
|
||||
|
||||
$fullQuery = $queryContent->sql() . " UNION " . $queryFiles->sql() . " ORDER BY $sortBy LIMIT $limit";
|
||||
@ -164,8 +201,17 @@ class SearchForm extends Form {
|
||||
return $doSet;
|
||||
}
|
||||
|
||||
public function getSearchQuery() {
|
||||
return Convert::raw2xml($_REQUEST['Search']);
|
||||
/**
|
||||
* Get the search query for display in a "You searched for ..." sentence.
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
*/
|
||||
public function getSearchQuery($data = null) {
|
||||
// legacy usage: $data was defaulting to $_REQUEST, parameter not passed in doc.silverstripe.com tutorials
|
||||
if(!isset($data)) $data = $_REQUEST;
|
||||
|
||||
return Convert::raw2xml($data['Search']);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -811,7 +811,13 @@ class Member extends DataObject {
|
||||
|
||||
$mainFields = $fields->fieldByName("Root")->fieldByName("Main")->Children;
|
||||
|
||||
$password = new ConfirmedPasswordField('Password', 'Password');
|
||||
$password = new ConfirmedPasswordField(
|
||||
'Password',
|
||||
'Password',
|
||||
null,
|
||||
null,
|
||||
true // showOnClick
|
||||
);
|
||||
$password->setCanBeEmpty(true);
|
||||
$mainFields->replaceField('Password', $password);
|
||||
|
||||
|
@ -115,16 +115,18 @@ class MemberLoginForm extends LoginForm {
|
||||
Director::redirect('Security/changepassword');
|
||||
|
||||
|
||||
} else if(isset($_REQUEST['BackURL']) && $backURL = $_REQUEST['BackURL']) {
|
||||
} elseif(isset($_REQUEST['BackURL']) && $backURL = $_REQUEST['BackURL']) {
|
||||
Session::clear("BackURL");
|
||||
Director::redirect($backURL);
|
||||
} else {
|
||||
$member=Member::CurrentUser();
|
||||
$firstname = Convert::raw2xml($member->FirstName);
|
||||
Session::set("Security.Message.message",
|
||||
sprintf(_t('Member.WELCOMEBACK', "Welcome Back, %s"), $firstname)
|
||||
);
|
||||
Session::set("Security.Message.type", "good");
|
||||
$member = Member::currentUser();
|
||||
if($member) {
|
||||
$firstname = Convert::raw2xml($member->FirstName);
|
||||
Session::set('Security.Message.message',
|
||||
sprintf(_t('Member.WELCOMEBACK', "Welcome Back, %s"), $firstname)
|
||||
);
|
||||
Session::set("Security.Message.type", "good");
|
||||
}
|
||||
Director::redirectBack();
|
||||
}
|
||||
} else {
|
||||
|
@ -316,6 +316,7 @@ class Security extends Controller {
|
||||
$link_base = Director::absoluteURL($this->Link("login"));
|
||||
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/jquery/jquery.js");
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/jquery/jquery_improvements.js");
|
||||
Requirements::javascript(THIRDPARTY_DIR . '/jquery/plugins/livequery/jquery.livequery.js');
|
||||
Requirements::javascript(THIRDPARTY_DIR . "/tabstrip/tabstrip.js");
|
||||
Requirements::css(THIRDPARTY_DIR . "/tabstrip/tabstrip.css");
|
||||
|
146
tests/search/SearchFormTest.php
Normal file
146
tests/search/SearchFormTest.php
Normal file
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* @package sapphire
|
||||
* @subpackage testing
|
||||
*
|
||||
* @todo Fix unpublished pages check in testPublishedPagesMatchedByTitle()
|
||||
* @todo All tests run on unpublished pages at the moment, due to the searchform not distinguishing between them
|
||||
*/
|
||||
class SearchFormTest extends FunctionalTest {
|
||||
|
||||
static $fixture_file = 'sapphire/tests/search/SearchFormTest.yml';
|
||||
|
||||
protected $mockController;
|
||||
|
||||
function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$holderPage = $this->objFromFixture('SiteTree', 'searchformholder');
|
||||
$this->mockController = new ContentController($holderPage);
|
||||
$this->mockController->setSession(new Session(Controller::curr()->getSession()));
|
||||
$this->mockController->pushCurrent();
|
||||
}
|
||||
|
||||
function tearDown() {
|
||||
$this->mockController->popCurrent();
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
function testPublishedPagesMatchedByTitle() {
|
||||
$sf = new SearchForm($this->mockController, 'SearchForm');
|
||||
|
||||
$publishedPage = $this->objFromFixture('SiteTree', 'publicPublishedPage');
|
||||
$publishedPage->publish('Stage', 'Live');
|
||||
$results = $sf->getResults(null, array('Search'=>'publicPublishedPage'));
|
||||
$this->assertContains(
|
||||
$publishedPage->ID,
|
||||
$results->column('ID'),
|
||||
'Published pages are found by searchform'
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
function testUnpublishedPagesNotIncluded() {
|
||||
$sf = new SearchForm($this->mockController, 'SearchForm');
|
||||
|
||||
$results = $sf->getResults(null, array('Search'=>'publicUnpublishedPage'));
|
||||
$unpublishedPage = $this->objFromFixture('SiteTree', 'publicUnpublishedPage');
|
||||
$this->assertNotContains(
|
||||
$unpublishedPage->ID,
|
||||
$results->column('ID'),
|
||||
'Unpublished pages are not found by searchform'
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
function testPagesRestrictedToLoggedinUsersNotIncluded() {
|
||||
$sf = new SearchForm($this->mockController, 'SearchForm');
|
||||
|
||||
$page = $this->objFromFixture('SiteTree', 'restrictedViewLoggedInUsers');
|
||||
$results = $sf->getResults(null, array('Search'=>'restrictedViewLoggedInUsers'));
|
||||
$this->assertNotContains(
|
||||
$page->ID,
|
||||
$results->column('ID'),
|
||||
'Page with "Restrict to logged in users" doesnt show without valid login'
|
||||
);
|
||||
|
||||
$member = $this->objFromFixture('Member', 'randomuser');
|
||||
$member->logIn();
|
||||
$results = $sf->getResults(null, array('Search'=>'restrictedViewLoggedInUsers'));
|
||||
$this->assertContains(
|
||||
$page->ID,
|
||||
$results->column('ID'),
|
||||
'Page with "Restrict to logged in users" shows if login is present'
|
||||
);
|
||||
$member->logOut();
|
||||
}
|
||||
|
||||
function testPagesRestrictedToSpecificGroupNotIncluded() {
|
||||
$sf = new SearchForm($this->mockController, 'SearchForm');
|
||||
|
||||
$page = $this->objFromFixture('SiteTree', 'restrictedViewOnlyWebsiteUsers');
|
||||
$results = $sf->getResults(null, array('Search'=>'restrictedViewOnlyWebsiteUsers'));
|
||||
$this->assertNotContains(
|
||||
$page->ID,
|
||||
$results->column('ID'),
|
||||
'Page with "Restrict to these users" doesnt show without valid login'
|
||||
);
|
||||
|
||||
$member = $this->objFromFixture('Member', 'randomuser');
|
||||
$member->logIn();
|
||||
$results = $sf->getResults(null, array('Search'=>'restrictedViewOnlyWebsiteUsers'));
|
||||
$this->assertNotContains(
|
||||
$page->ID,
|
||||
$results->column('ID'),
|
||||
'Page with "Restrict to these users" doesnt show if logged in user is not in the right group'
|
||||
);
|
||||
$member->logOut();
|
||||
|
||||
$member = $this->objFromFixture('Member', 'websiteuser');
|
||||
$member->logIn();
|
||||
$results = $sf->getResults(null, array('Search'=>'restrictedViewOnlyWebsiteUsers'));
|
||||
$this->assertContains(
|
||||
$page->ID,
|
||||
$results->column('ID'),
|
||||
'Page with "Restrict to these users" shows if user in this group is logged in'
|
||||
);
|
||||
$member->logOut();
|
||||
}
|
||||
|
||||
function testInheritedRestrictedPagesNotInlucded() {
|
||||
$sf = new SearchForm($this->mockController, 'SearchForm');
|
||||
|
||||
$page = $this->objFromFixture('SiteTree', 'inheritRestrictedView');
|
||||
|
||||
$results = $sf->getResults(null, array('Search'=>'inheritRestrictedView'));
|
||||
$this->assertNotContains(
|
||||
$page->ID,
|
||||
$results->column('ID'),
|
||||
'Page inheriting "Restrict to loggedin users" doesnt show without valid login'
|
||||
);
|
||||
|
||||
$member = $this->objFromFixture('Member', 'websiteuser');
|
||||
$member->logIn();
|
||||
$results = $sf->getResults(null, array('Search'=>'inheritRestrictedView'));
|
||||
$this->assertContains(
|
||||
$page->ID,
|
||||
$results->column('ID'),
|
||||
'Page inheriting "Restrict to loggedin users" shows if user in this group is logged in'
|
||||
);
|
||||
$member->logOut();
|
||||
}
|
||||
|
||||
function testDisabledShowInSearchFlagNotIncluded() {
|
||||
$sf = new SearchForm($this->mockController, 'SearchForm');
|
||||
|
||||
$page = $this->objFromFixture('SiteTree', 'dontShowInSearchPage');
|
||||
$results = $sf->getResults(null, array('Search'=>'dontShowInSearchPage'));
|
||||
$this->assertNotContains(
|
||||
$page->ID,
|
||||
$results->column('ID'),
|
||||
'Page with "Show in Search" disabled doesnt show'
|
||||
);
|
||||
}
|
||||
}
|
||||
?>
|
33
tests/search/SearchFormTest.yml
Normal file
33
tests/search/SearchFormTest.yml
Normal file
@ -0,0 +1,33 @@
|
||||
Group:
|
||||
websiteusers:
|
||||
Title: View certain restricted pages
|
||||
Member:
|
||||
randomuser:
|
||||
Email: randomuser@test.com
|
||||
Password: test
|
||||
websiteuser:
|
||||
Email: websiteuser@test.com
|
||||
Password: test
|
||||
Groups: =>Group.websiteusers
|
||||
SiteTree:
|
||||
searchformholder:
|
||||
URLSegment: searchformholder
|
||||
Title: searchformholder
|
||||
publicPublishedPage:
|
||||
Title: publicPublishedPage
|
||||
publicUnpublishedPage:
|
||||
Title: publicUnpublishedPage
|
||||
restrictedViewLoggedInUsers:
|
||||
CanViewType: LoggedInUsers
|
||||
Title: restrictedViewLoggedInUsers
|
||||
restrictedViewOnlyWebsiteUsers:
|
||||
CanViewType: OnlyTheseUsers
|
||||
ViewerGroups: =>Group.websiteusers
|
||||
Title: restrictedViewOnlyWebsiteUsers
|
||||
inheritRestrictedView:
|
||||
CanViewType: Inherit
|
||||
Parent: =>SiteTree.restrictedViewLoggedInUsers
|
||||
Title: inheritRestrictedView
|
||||
dontShowInSearchPage:
|
||||
Title: dontShowInSearchPage
|
||||
ShowInSearch: 0
|
Loading…
Reference in New Issue
Block a user