MINOR merged from branches/2.3

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@69856 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2009-01-07 23:00:54 +00:00
parent d32d4d5204
commit 0747fc6d52
23 changed files with 267 additions and 59 deletions

View File

@ -60,21 +60,6 @@ set_include_path(get_include_path() . PATH_SEPARATOR . $path);
*/
define('MCE_ROOT', 'jsparty/tiny_mce2/');
/**
* Should passwords be encrypted (TRUE) or stored in clear text (FALSE)?
*/
Security::encrypt_passwords(true);
/**
* Which algorithm should be used to encrypt? Should a salt be used to
* increase the security?
*
* You can get a list of supported algorithms by calling
* {@link Security::get_encryption_algorithms()}
*/
Security::set_password_encryption_algorithm('sha1', true);
/**
* The secret key that needs to be sent along with pings to /Email_BounceHandler
*

View File

@ -73,16 +73,13 @@ class HTTP {
return $content;
}
static function setGetVar($varname, $varvalue, $currentURL = null) {
$currentURL = $currentURL ? $currentURL : $_SERVER['REQUEST_URI'];
$scriptbase = $currentURL;
$scriptbase = str_replace('&','&',$scriptbase);
$scriptbase = ereg_replace("&$varname=[^&]*",'',$scriptbase);
$scriptbase = ereg_replace("\?$varname=[^&]*&",'?',$scriptbase);
$scriptbase = ereg_replace("\?$varname=[^&]*",'',$scriptbase);
public static function setGetVar($varname, $varvalue, $currentURL = null) {
$scriptbase = $currentURL ? $currentURL : $_SERVER['REQUEST_URI'];
$scriptbase = str_replace('&', '&', $scriptbase);
$scriptbase = preg_replace('/\?' . quotemeta($varname) . '=([^&]*)&/', '?', $scriptbase);
$scriptbase = preg_replace('/([\?&]+)' . quotemeta($varname) . '=([^&]*)/', null, $scriptbase);
$suffix = '';
if(($hashPos = strpos($scriptbase,'#')) !== false) {
$suffix .= substr($scriptbase, $hashPos);
@ -136,7 +133,7 @@ class HTTP {
* Outputs appropriate header for downloading a file
* exits() after the call, so that no further output is given.
*
* @deprecated 2.3 Return a HTTPResponse::send_file() object instead
* @deprecated 2.3 Return a HTTPRequest::send_file() object instead
*/
static function sendFileToBrowser($fileData, $fileName, $mimeType = false) {
user_error("HTTP::sendFileToBrowser() deprecated; return a HTTPRequest::send_file() object instead", E_USER_NOTICE);

View File

@ -52,7 +52,7 @@ class Director {
* page.
*/
static function history($pagesBack = 1) {
return Session::get('history.' . sizeof(Session::get('history')) - $pagesBack - 1);
return Session::get('history.' . intval(sizeof(Session::get('history')) - $pagesBack - 1));
}
@ -159,6 +159,7 @@ class Director {
if(!$httpMethod) $httpMethod = ($postVars || is_array($postVars)) ? "POST" : "GET";
$urlWithQuerystring = $url;
if(strpos($url, '?') !== false) {
list($url, $getVarsEncoded) = explode('?', $url, 2);
parse_str($getVarsEncoded, $getVars);
@ -172,11 +173,11 @@ class Director {
$existingPostVars = $_POST;
$existingSessionVars = $_SESSION;
$existingCookies = $_COOKIE;
$existingServer = $_SERVER;
$existingCookieReportErrors = Cookie::report_errors();
Cookie::set_report_errors(false);
$existingRequirementsBackend = Requirements::backend();
Cookie::set_report_errors(false);
Requirements::set_backend(new Requirements_Backend());
// Replace the superglobals with appropriate test values
@ -185,6 +186,7 @@ class Director {
$_POST = (array)$postVars;
$_SESSION = $session ? $session->inst_getAll() : array();
$_COOKIE = array();
$_SERVER['REQUEST_URI'] = Director::baseURL() . $urlWithQuerystring;
$req = new HTTPRequest($httpMethod, $url, $getVars, $postVars, $body);
if($headers) foreach($headers as $k => $v) $req->addHeader($k, $v);
@ -196,6 +198,8 @@ class Director {
$_POST = $existingPostVars;
$_SESSION = $existingSessionVars;
$_COOKIE = $existingCookies;
$_SERVER = $existingServer;
Cookie::set_report_errors($existingCookieReportErrors);
Requirements::set_backend($existingRequirementsBackend);

View File

@ -44,6 +44,13 @@ class DB {
static function set_alternative_database_name($dbname) {
$_SESSION["alternativeDatabaseName"] = $dbname;
}
/**
* Get the name of the database in use
*/
static function get_alternative_database_name() {
return $_SESSION["alternativeDatabaseName"];
}
/**
* Connect to a database.

View File

@ -126,8 +126,8 @@ class MySQLDatabase extends Database {
}
public function createDatabase() {
$this->query("CREATE DATABASE $this->database");
$this->query("USE $this->database");
$this->query("CREATE DATABASE `$this->database`");
$this->query("USE `$this->database`");
$this->tableList = $this->fieldList = $this->indexList = null;

View File

@ -107,7 +107,7 @@
<label for="mysql_password">MySQL password:</label>
<span class="middleColumn"><input id="mysql_password" class="text" type="password" name="mysql[password]" value="<?php echo $databaseConfig['password']; ?>" /></span>
<label for="mysql_database">MySQL database:</label>
<span class="middleColumn"><input id="mysql_database" class="text" type="text" name="mysql[database]" value="<?php echo $databaseConfig['database']; ?>" onchange="this.value = this.value.replace(/[^A-Za-z0-9_]+/g,'');" /></span>
<span class="middleColumn"><input id="mysql_database" class="text" type="text" name="mysql[database]" value="<?php echo $databaseConfig['database']; ?>" onchange="this.value = this.value.replace(/[\/\\:*?&quot;<>|. \t]+/g,'');" /></span>
<input type="submit" class="action" value="Re-check requirements" />
</p>
<p class="mysql">SilverStripe stores its content in a MySQL database. Please provide the username and password to connect to the server here. If this account has permission to create databases, then we will create the database for you; otherwise, you must give the name of a database that already exists.</p>

View File

@ -17,6 +17,13 @@
* Usually you want to save data into a {@link DataObject} by using {@link saveInto()}.
* If you want to process the submitted data in any way, please use {@link getData()} rather than
* the raw request data.
*
* Validation
* Each form needs some form of {@link Validator} to trigger the {@link FormField->validate()} methods for each field.
* You can't disable validator for security reasons, because crucial behaviour like extension checks for file uploads depend on it.
* The default validator is an instance of {@link RequiredFields}.
* If you want to enforce serverside-validation to be ignored for a specific {@link FormField},
* you need to subclass it.
*
* @package forms
* @subpackage core
@ -96,10 +103,11 @@ class Form extends RequestHandler {
/**
* Create a new form, with the given fields an action buttons.
*
* @param controller The parent controller, necessary to create the appropriate form action tag.
* @param name The method on the controller that will return this form object.
* @param fields All of the fields in the form - a {@link FieldSet} of {@link FormField} objects.
* @param actions All of the action buttons in the form - a {@link FieldSet} of {@link FormAction} objects
* @param Controller $controller The parent controller, necessary to create the appropriate form action tag.
* @param String $name The method on the controller that will return this form object.
* @param FieldSet $fields All of the fields in the form - a {@link FieldSet} of {@link FormField} objects.
* @param FieldSet $actions All of the action buttons in the form - a {@link FieldSet} of {@link FormAction} objects
* @param Validator $validator Override the default validator instance (Default: {@link RequiredFields})
*/
function __construct($controller, $name, FieldSet $fields, FieldSet $actions, $validator = null) {
parent::__construct();
@ -115,10 +123,8 @@ class Form extends RequestHandler {
if(!$this->controller) user_error("$this->class form created without a controller", E_USER_ERROR);
// Form validation
if($validator) {
$this->validator = $validator;
$this->validator->setForm($this);
}
$this->validator = ($validator) ? $validator : new RequiredFields();
$this->validator->setForm($this);
// Form error controls
$errorInfo = Session::get("FormInfo.{$this->FormName()}");

View File

@ -32,6 +32,7 @@ class HasManyComplexTableField extends ComplexTableField {
if($controllerClass = $this->controllerClass()) {
$this->joinField = $this->getParentIdName($controllerClass, $this->sourceClass);
if(!$this->joinField) user_error("Can't find a has_one relationship from '$this->sourceClass' to '$controllerClass'", E_USER_WARNING);
} else {
user_error("Can't figure out the data class of $controller", E_USER_WARNING);
}
@ -148,6 +149,11 @@ class HasManyComplexTableField_Item extends ComplexTableField_Item {
function MarkingCheckbox() {
$name = $this->parent->Name() . '[]';
if(!$this->parent->joinField) {
user_error("joinField not set in HasManyComplexTableField '{$this->parent->name}'", E_USER_WARNING);
return null;
}
$joinVal = $this->item->{$this->parent->joinField};
$parentID = $this->parent->getControllerID();

View File

@ -18,7 +18,9 @@ class HeaderField extends DatalessField {
$args = func_get_args();
if(!isset($args[1]) || is_numeric($args[1])) {
$title = (isset($args[0])) ? $args[0] : null;
$name = $title; // this means i18nized fields won't be easily accessible through fieldByName()
// Use "HeaderField(title)" as the default field name for a HeaderField; if it's just set to title then we risk
// causing accidental duplicate-field creation.
$name = 'HeaderField' . $title; // this means i18nized fields won't be easily accessible through fieldByName()
$headingLevel = (isset($args[1])) ? $args[1] : null;
$allowHTML = (isset($args[2])) ? $args[2] : null;
$form = (isset($args[3])) ? $args[3] : null;

View File

@ -423,6 +423,7 @@ class HtmlEditorField_Toolbar extends RequestHandler {
new TextField('getimagesSearch', _t('HtmlEditorField.SEARCHFILENAME', 'Search by file name')),
new ThumbnailStripField('Image', 'FolderID', 'getimages'),
new TextField('AltText', _t('HtmlEditorField.ALTTEXT', 'Description'), '', 80),
new CheckboxField('Caption', _t('HtmlEditorField.CAPTION', 'Include as Caption')),
new HiddenField('CSSClass', _t('HtmlEditorField.CSSCLASS', 'Alignment / style')),
new LiteralField('AlignmentStyle', '<div id="ImageAligmentStyle" class="field text"><label>Alignment / style</label>'),
new LiteralField('AlignmentStyleLinks', '

View File

@ -40,7 +40,7 @@ class TreeDropdownField extends FormField {
Requirements::javascript(SAPPHIRE_DIR . "/javascript/TreeSelectorField.js");
if($this->value) {
$record = DataObject::get_by_id($this->sourceObject, $this->value);
$record = $this->getByKey($this->value);
$title = ($record) ? $record->Title : _t('DropdownField.CHOOSE', "(Choose)", PR_MEDIUM, 'Start-value of a dropdown');
} else {
$title = _t('DropdownField.CHOOSE', "(Choose)", PR_MEDIUM, 'Start-value of a dropdown');
@ -65,7 +65,6 @@ HTML;
if($this->treeBaseID) $obj = DataObject::get_by_id($this->sourceObject, $this->treeBaseID);
else $obj = singleton($this->sourceObject);
if($this->filterFunc) $obj->setMarkingFilterFunction($this->filterFunc);
else if($this->sourceObject == 'Folder') $obj->setMarkingFilter('ClassName', 'Folder');
$obj->markPartialTree();
@ -86,11 +85,12 @@ HTML;
* Return a subtree via Ajax
*/
public function getsubtree() {
if($this->keyField == "ID") $obj = DataObject::get_by_id($this->sourceObject, $_REQUEST['SubtreeRootID']);
else $obj = DataObject::get_one($this->sourceObject, "\"$this->keyField\" = '$_REQUEST[SubtreeRootID]'");
$obj = $this->getByKey($_REQUEST['SubtreeRootID']);
if(!$obj) user_error("Can't find database record $this->sourceObject with $this->keyField = $_REQUEST[SubtreeRootID]", E_USER_ERROR);
if($this->filterFunc) $obj->setMarkingFilterFunction($this->filterFunc);
else if($this->sourceObject == 'Folder') $obj->setMarkingFilter('ClassName', 'Folder');
$obj->markPartialTree();
$eval = '"<li id=\"selector-' . $this->name . '-$child->' . $this->keyField . '\" class=\"$child->class" . $child->markingClasses() . "\"><a>" . $child->' . $this->labelField . ' . "</a>"';
@ -99,14 +99,11 @@ HTML;
}
public function getByKey($key) {
if(!is_numeric($key)) {
return false;
}
if($this->keyField == 'ID') {
return DataObject::get_by_id($this->sourceObject, $key);
} else {
return DataObject::get_one($this->sourceObject, "\"$this->keyField\" = '$key'");
$SQL_key = Convert::raw2sql($key);
return DataObject::get_one($this->sourceObject, "\"$this->keyField\" = '$SQL_key'");
}
}
@ -114,8 +111,7 @@ HTML;
* Return the stack of values to be traversed to find the given key in the database
*/
public function getstack() {
if($this->keyField == "ID") $page = DataObject::get_by_id($this->sourceObject, $_REQUEST['SubtreeRootID']);
else $page = $this->getByKey($_REQUEST['SubtreeRootID']);
$page = $this->getByKey($_REQUEST['SubtreeRootID']);
while($page->ParentID) {
echo $ids[] = $page->ID;

View File

@ -54,7 +54,7 @@ abstract class Validator extends Object {
public function __construct() {
if(self::$javascript_validation_handler) $this->setJavascriptValidationHandler(self::$javascript_validation_handler);
if($this->javascriptValidationHandler) {
if($this->javascriptValidationHandler && $this->javascriptValidationHandler != 'none') {
Requirements::javascript(SAPPHIRE_DIR . '/javascript/Validator.js');
}
parent::__construct();

BIN
images/smilies/confused.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 700 B

BIN
images/smilies/cool.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 703 B

BIN
images/smilies/grin.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

BIN
images/smilies/sad.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

BIN
images/smilies/smile.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 699 B

BIN
images/smilies/tongue.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 706 B

View File

@ -16,8 +16,32 @@ unset($options);
*/
class BBCodeParser extends TextParser {
/**
* Set whether phrases starting with http:// or www. are automatically linked
* @var Boolean
*/
protected static $autolinkUrls = true;
/**
* Set whether similies :), :(, :P are converted to images
* @var Boolean
*/
protected static $allowSimilies = false;
/**
* Set the location of the smiles folder. By default use the ones in sapphire
* but this can be overridden by setting BBCodeParser::set_icon_folder('themes/yourtheme/images/');
* @var string
*/
protected static $smilies_location = 'sapphire/images/smilies';
static function smilies_location() {
return BBCodeParser::$smilies_location;
}
static function set_icon_folder($path) {
BBCodeParser::$smilies_location = $path;
}
static function autolinkUrls() {
return (self::$autolinkUrls != null) ? true : false;
}
@ -26,6 +50,15 @@ class BBCodeParser extends TextParser {
BBCodeParser::$autolinkUrls = $autolink;
}
static function smiliesAllowed() {
return (self::$allowSimilies != null) ? true : false;
}
static function enable_smilies() {
BBCodeParser::$allowSimilies = true;
}
static function usable_tags() {
return new DataObjectSet(
new ArrayData(array(
@ -98,6 +131,12 @@ class BBCodeParser extends TextParser {
return $useabletags."</ul>";
}
/**
* Main BBCode parser method. This takes plain jane content and
* runs it through so many filters
*
* @return Text
*/
function parse() {
$this->content = str_replace(array('&', '<', '>'), array('&amp;', '&lt;', '&gt;'), $this->content);
$this->content = SSHTMLBBCodeParser::staticQparse($this->content);
@ -108,6 +147,20 @@ class BBCodeParser extends TextParser {
$this->content = preg_replace("/\n\s*\n/", "</p><p>", $this->content);
$this->content = str_replace("\n", "<br />", $this->content);
if(BBCodeParser::smiliesAllowed()) {
$smilies = array(
'#(?<!\w):D(?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/grin.gif'> ", // :D
'#(?<!\w):\)(?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/smile.gif'> ", // :)
'#(?<!\w):-\)(?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/smile.gif'> ", // :-)
'#(?<!\w):\((?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/sad.gif'> ", // :(
'#(?<!\w):-\((?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/sad.gif'> ", // :-(
'#(?<!\w):p(?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/tongue.gif'> ", // :p
'#(?<!\w)8-\)(?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/cool.gif'> ", // 8-)
'#(?<!\w):\^\)(?!\w)#i' => " <img src='".BBCodeParser::smilies_location(). "/confused.gif'> " // :^)
);
$this->content = preg_replace(array_keys($smilies), array_values($smilies), $this->content);
}
return $this->content;
}

View File

@ -933,11 +933,13 @@ class Security extends Controller {
* @return bool
*/
public static function database_is_ready() {
return
ClassInfo::hasTable('Member') &&
ClassInfo::hasTable('Group') &&
ClassInfo::hasTable('Permission') &&
(($permissionFields = DB::fieldList('Permission')) && isset($permissionFields['Type'])) &&
$requiredTables = ClassInfo::dataClassesFor('Member');
$requiredTables[] = 'Group';
$requiredTables[] = 'Permission';
foreach($requiredTables as $table) if(!ClassInfo::hasTable($table)) return false;
return (($permissionFields = DB::fieldList('Permission')) && isset($permissionFields['Type'])) &&
(($memberFields = DB::fieldList('Member')) && isset($memberFields['RememberLoginToken']));
}

29
tests/HTTPTest.php Normal file
View File

@ -0,0 +1,29 @@
<?php
/**
* Tests the {@link HTTP} class
*
* @package sapphire
* @subpackage tests
*/
class HTTPTest extends SapphireTest {
/**
* Tests {@link HTTP::setGetVar()}
*/
public function testSetGetVar() {
$expected = array (
'/?foo=bar' => array('foo', 'bar', '/'),
'/?baz=buz&foo=bar' => array('foo', 'bar', '/?baz=buz'),
'/?buz=baz&foo=baz' => array('foo', 'baz', '/?foo=bar&buz=baz'),
'/?foo=var' => array('foo', 'var', '/?foo=&foo=bar'),
'/?foo[test]=var' => array('foo[test]', 'var', '/?foo[test]=another')
);
foreach($expected as $result => $args) {
$this->assertEquals(
call_user_func_array(array('HTTP', 'setGetVar'), $args), str_replace('&', '&amp;', $result)
);
}
}
}

View File

@ -0,0 +1,109 @@
<?php
/**
* Possible actions:
* - action_save
* - action_publish
* - action_unpublish
* - action_delete
* - action_deletefromlive
* - action_rollback
* - action_revert
*
* @package sapphire
* @subpackage tests
*/
if(class_exists('SiteTreeCMSWorkflow')) {
class SiteTreeActionsTest extends FunctionalTest {
function testDummy() {}
}
} else {
class SiteTreeActionsTest extends FunctionalTest {
static $fixture_file = 'sapphire/tests/SiteTreeActionsTest.yml';
function testActionsNewPage() {
$className = 'Page';
$page = new $className();
$page->Title = 'New ' . $className;
$page->URLSegment = "new-" . strtolower($className);
$page->ClassName = $className;
$page->ParentID = 0;
$page->ID = 'new-Page-1';
$author = $this->objFromFixture('Member', 'cmseditor');
$this->session()->inst_set('loggedInAs', $author->ID);
$actionsArr = $page->getCMSActions()->column('Name');
$this->assertContains('action_save',$actionsArr);
$this->assertContains('action_publish',$actionsArr);
$this->assertNotContains('action_unpublish',$actionsArr);
$this->assertContains('action_delete',$actionsArr);
$this->assertNotContains('action_deletefromlive',$actionsArr);
$this->assertNotContains('action_rollback',$actionsArr);
$this->assertNotContains('action_revert',$actionsArr);
}
function testActionsPublishedRecord() {
$page = new Page();
$page->write();
$page->publish('Stage', 'Live');
$author = $this->objFromFixture('Member', 'cmseditor');
$this->session()->inst_set('loggedInAs', $author->ID);
$actionsArr = $page->getCMSActions()->column('Name');
$this->assertContains('action_save',$actionsArr);
$this->assertContains('action_publish',$actionsArr);
$this->assertContains('action_unpublish',$actionsArr);
$this->assertContains('action_delete',$actionsArr);
$this->assertNotContains('action_deletefromlive',$actionsArr);
$this->assertNotContains('action_rollback',$actionsArr);
$this->assertNotContains('action_revert',$actionsArr);
}
function testActionsDeletedFromStageRecord() {
$page = new Page();
$page->write();
$page->publish('Stage', 'Live');
$page->deleteFromStage('Stage');
$author = $this->objFromFixture('Member', 'cmseditor');
$this->session()->inst_set('loggedInAs', $author->ID);
$actionsArr = $page->getCMSActions()->column('Name');
$this->assertNotContains('action_save',$actionsArr);
$this->assertNotContains('action_publish',$actionsArr);
$this->assertNotContains('action_unpublish',$actionsArr);
$this->assertNotContains('action_delete',$actionsArr);
$this->assertContains('action_deletefromlive',$actionsArr);
$this->assertNotContains('action_rollback',$actionsArr);
$this->assertContains('action_revert',$actionsArr);
}
function testActionsChangedOnStageRecord() {
$page = new Page();
$page->write();
$page->publish('Stage', 'Live');
$page->Content = 'Changed on Stage';
$page->write();
$page->flushCache();
$author = $this->objFromFixture('Member', 'cmseditor');
$this->session()->inst_set('loggedInAs', $author->ID);
$actionsArr = $page->getCMSActions()->column('Name');
$this->assertContains('action_save',$actionsArr);
$this->assertContains('action_publish',$actionsArr);
$this->assertContains('action_unpublish',$actionsArr);
$this->assertContains('action_delete',$actionsArr);
$this->assertNotContains('action_deletefromlive',$actionsArr);
$this->assertContains('action_rollback',$actionsArr);
$this->assertNotContains('action_revert',$actionsArr);
}
}
}
?>

View File

@ -0,0 +1,11 @@
Permission:
cmsmain:
Code: CMS_ACCESS_CMSMain
Group:
cmseditors:
Title: CMS Editors
Permissions: =>Permission.cmsmain
Member:
cmseditor:
Email: cmseditor@test.com
Groups: =>Group.cmseditors