Merged from branches/2.3

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@75582 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Sean Harvey 2009-04-29 00:07:39 +00:00
parent 56fbe55a47
commit 13b358a8dd
28 changed files with 478 additions and 266 deletions

View File

@ -294,6 +294,7 @@ class RestfulServer extends Controller {
$formatter = DataFormatter::for_extension($extension);
}elseif($includeAcceptHeader && !empty($accept) && $accept != '*/*') {
$formatter = DataFormatter::for_mimetypes($mimetypes);
if(!$formatter) $formatter = DataFormatter::for_extension(self::$default_extension);
} elseif(!empty($contentType)) {
$formatter = DataFormatter::for_mimetype($contentType);
} else {

View File

@ -699,10 +699,15 @@ class Requirements_Backend {
} elseif(Director::fileExists($fileOrUrl)) {
$prefix = Director::absoluteBaseURL();
$mtimesuffix = "";
$suffix = '';
if(strpos($fileOrUrl, '?') !== false) {
$suffix = '&' . substr($fileOrUrl, strpos($fileOrUrl, '?')+1);
$fileOrUrl = substr($fileOrUrl, 0, strpos($fileOrUrl, '?'));
}
if(Requirements::get_suffix_requirements()) {
$mtimesuffix = "?m=" . filemtime(Director::baseFolder() . '/' . $fileOrUrl);
}
return "{$prefix}{$fileOrUrl}{$mtimesuffix}";
return "{$prefix}{$fileOrUrl}{$mtimesuffix}{$suffix}";
} else {
return false;
}

View File

@ -2826,11 +2826,13 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
*
* @uses $field_labels
* @uses FormField::name_to_label()
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
* @return array of all element labels if no argument given
* @return string of label if field
*/
public function fieldLabels() {
public function fieldLabels($includerelations = true) {
$customLabels = $this->stat('field_labels');
$autoLabels = array();
@ -2845,6 +2847,11 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
'has_many' => (array) Object::uninherited_static($ancestorClass, 'has_many'),
'many_many' => (array) Object::uninherited_static($ancestorClass, 'many_many')
);
if($includerelations){
$types['has_one'] = (array)singleton($ancestorClass)->uninherited('has_one', true);
$types['has_many'] = (array)singleton($ancestorClass)->uninherited('has_many', true);
$types['many_many'] = (array)singleton($ancestorClass)->uninherited('many_many', true);
}
foreach($types as $type => $attrs) {
foreach($attrs as $name => $spec)
$autoLabels[$name] = _t("{$ancestorClass}.{$type}_{$name}",FormField::name_to_label($name));
@ -3176,4 +3183,4 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity
}
?>
?>

View File

@ -137,25 +137,20 @@ class DataObjectSet extends ViewableData implements IteratorAggregate {
}
/**
* Returns an array of ID => $titleField
* @param string $index The field you wish to use as a key for the array
* @param string $titleField The field or method you wish to use to get values for the map
* @param string $emptyString String for no option selected
* @param bool $sort If you want to sort the map based on $titleField
* @return array
*/
public function toDropDownMap($index = "ID",$titleField = "Title",$emptyString = null, $sort = false){
$map = array();
foreach($this->items as $item) {
$map[$item->$index] = $item->$titleField;
}
if($emptyString) {
$map = array("0"=>"$emptyString") + $map;
}
if($sort) {
asort($map);
}
return $map;
* Returns an array of ID => Title for the items in this set.
*
* This is an alias of {@link DataObjectSet->map()}
*
* @deprecated 2.5 Please use map() instead
*
* @param string $index The field to use as a key for the array
* @param string $titleField The field (or method) to get values for the map
* @param string $emptyString Empty option text e.g "(Select one)"
* @param bool $sort Sort the map alphabetically based on the $titleField value
* @return array
*/
public function toDropDownMap($index = 'ID', $titleField = 'Title', $emptyString = null, $sort = false) {
return $this->map($index, $titleField, $emptyString, $sort);
}
/**
@ -591,6 +586,10 @@ class DataObjectSet extends ViewableData implements IteratorAggregate {
/**
* Returns the dataset as an array of ID => "FirstName Surname"
*
* @deprecated 2.4 Please use map() instead, pointing the title
* to a method name that does the concatenation of values.
*
* @param string $key Field name to index the array.
* @param array $values An array of fieldnames to insert in array
* @param boolean $withdash Add dashes inbetween values
@ -613,38 +612,26 @@ class DataObjectSet extends ViewableData implements IteratorAggregate {
}
/**
* Returns the dataset as an array of ID => Title.
*
* @todo Duplication from toDropdownMap()
* Returns an array of ID => Title for the items in this set.
*
* @param string $key The field you wish to use as a key for the array
* @param string $value The field or method you wish to use to get values for the map
* @param string $includeBlank String for no option selected
* @return array
* @param string $index The field to use as a key for the array
* @param string $titleField The field (or method) to get values for the map
* @param string $emptyString Empty option text e.g "(Select one)"
* @param bool $sort Sort the map alphabetically based on the $titleField value
* @return array
*/
public function map($key = "ID", $value = "Title", $includeBlank=null) {
public function map($index = 'ID', $titleField = 'Title', $emptyString = null, $sort = false) {
$map = array();
/* Don't do this, add this locally.
* Reasons: 1: In some case this blank value don't/mustn't present.
2: In some case, this balnk value should be customised, such as (Select from below)
3: In some case, the key need to be explicitly "0", cos "" and "0" need to be treated differently
*/
//$map[''] = "(Select)";
/* Instead do this as an option */
if($includeBlank) $map[''] = $includeBlank;
foreach($this->items as $item ){
if(is_array($value)){
foreach($value as $individul){
if($map[$item->$key]) $map[$item->$key] .=" - ";
$map[$item->$key] .= $item->$individul;
}
}else{
$map[$item->$key] = $item->$value;
if($this->items) {
foreach($this->items as $item) {
$map[$item->$index] = ($item->hasMethod($titleField)) ? $item->$titleField() : $item->$titleField;
}
}
if($emptyString) $map = array('0' => "$emptyString") + $map;
if($sort) asort($map);
return $map;
}
@ -653,7 +640,7 @@ class DataObjectSet extends ViewableData implements IteratorAggregate {
*
* Question: should any args be passed to the filter function?
*
* @todo deprecate toDropdownMap() and map_multiple(), rename this method to map()
* @deprecated 2.4 Please use map() instead
*/
public function filter_map($key, $value) {
$map = array();

View File

@ -133,8 +133,13 @@ class ErrorPage extends Page {
return $this->extension_instances['Versioned']->publish($fromStage, $toStage, $createNewVersion);
}
function fieldLabels() {
$labels = parent::fieldLabels();
/**
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
*/
function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['ErrorCode'] = _t('ErrorPage.CODE', "Error code");
return $labels;

View File

@ -389,9 +389,9 @@ class Image extends File {
/**
* Get the orientation of this image.
* @return ORIENTATION_SQUARE | ORIENTATION_PORTRAIT | ORIENTATION_LANDSCAPE
* @return ORIENTATION_SQUARE | ORIENTATION_PORTRAIT | ORIENTATION_LANDSCAPE
*/
function getOrienation() {
function getOrientation() {
$width = $this->getWidth();
$height = $this->getHeight();
if($width > $height) {

View File

@ -1302,8 +1302,13 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
return $fields;
}
function fieldLabels() {
$labels = parent::fieldLabels();
/**
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
*/
function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['Title'] = _t('SiteTree.PAGETITLE', "Page name");
$labels['MenuTitle'] = _t('SiteTree.MENUTITLE', "Navigation label");
@ -1324,11 +1329,14 @@ class SiteTree extends DataObject implements PermissionProvider,i18nEntityProvid
$labels['CanViewType'] = _t('SiteTree.Viewers', 'Viewers Groups');
$labels['CanEditType'] = _t('SiteTree.Editors', 'Editors Groups');
$labels['ToDo'] = _t('SiteTree.ToDo', 'Todo Notes');
$labels['Parent'] = _t('SiteTree.has_one_Parent', 'Parent Page', PR_MEDIUM, 'The parent page in the site hierarchy');
$labels['Comments'] = _t('SiteTree.Comments', 'Comments');
$labels['LinkTracking'] = _t('SiteTree.many_many_LinkTracking', 'Link Tracking');
$labels['ImageTracking'] = _t('SiteTree.many_many_ImageTracking', 'Image Tracking');
$labels['BackLinkTracking'] = _t('SiteTree.many_many_BackLinkTracking', 'Backlink Tracking');
if($includerelations){
$labels['Parent'] = _t('SiteTree.has_one_Parent', 'Parent Page', PR_MEDIUM, 'The parent page in the site hierarchy');
$labels['LinkTracking'] = _t('SiteTree.many_many_LinkTracking', 'Link Tracking');
$labels['ImageTracking'] = _t('SiteTree.many_many_ImageTracking', 'Image Tracking');
$labels['BackLinkTracking'] = _t('SiteTree.many_many_BackLinkTracking', 'Backlink Tracking');
}
return $labels;
}

View File

@ -21,12 +21,6 @@
font-size: 11px;
}
/* popup */
.ComplexTableField_ItemRequest_Popup {
height: 100%;
overflow: auto;
}
/* table */
.ComplexTableField {
margin-bottom: 10px;

View File

@ -205,7 +205,8 @@ abstract class BulkLoader extends ViewableData {
$spec = array();
// get database columns (fieldlabels include fieldname as a key)
$spec['fields'] = (array)singleton($this->objectClass)->fieldLabels();
// using $$includerelations flag as false, so that it only contain $db fields
$spec['fields'] = (array)singleton($this->objectClass)->fieldLabels($includerelations=false);
$has_ones = singleton($this->objectClass)->has_one();
$has_manys = singleton($this->objectClass)->has_many();

View File

@ -110,7 +110,7 @@ function htmlEmail($to, $from, $subject, $htmlContent, $attachedFiles = false, $
// Include any specified attachments as additional parts
foreach($attachedFiles as $file) {
if($file['tmp_name'] && $file['name']) {
if(isset($file['tmp_name']) && isset($file['name'])) {
$messageParts[] = encodeFileForEmail($file['tmp_name'], $file['name']);
} else {
$messageParts[] = encodeFileForEmail($file);
@ -200,7 +200,7 @@ function plaintextEmail($to, $from, $subject, $plainContent, $attachedFiles, $cu
// Include any specified attachments as additional parts
foreach($attachedFiles as $file) {
if($file['tmp_name'] && $file['name']) {
if(isset($file['tmp_name']) && isset($file['name'])) {
$messageParts[] = encodeFileForEmail($file['tmp_name'], $file['name']);
} else {
$messageParts[] = encodeFileForEmail($file);
@ -329,10 +329,45 @@ function processHeaders($headers, $body = false) {
return $res;
}
/*
/**
* Encode the contents of a file for emailing, including headers
*
* $file can be an array, in which case it expects these members:
* 'filename' - the filename of the file
* 'contents' - the raw binary contents of the file as a string
* and can optionally include these members:
* 'mimetype' - the mimetype of the file (calculated from filename if missing)
* 'contentLocation' - the 'Content-Location' header value for the file
*
* $file can also be a string, in which case it is assumed to be the filename
*
* h5. contentLocation
*
* Content Location is one of the two methods allowed for embedding images into an html email. It's also the simplest, and best supported
*
* Assume we have an email with this in the body:
*
* <img src="http://example.com/image.gif" />
*
* To display the image, an email viewer would have to download the image from the web every time it is displayed. Due to privacy issues, most
* viewers will not display any images unless the user clicks 'Show images in this email'. Not optimal.
*
* However, we can also include a copy of this image as an attached file in the email. By giving it a contentLocation of "http://example.com/image.gif"
* most email viewers will use this attached copy instead of downloading it. Better, most viewers will show it without a 'Show images in this email'
* conformation.
*
* Here is an example of passing this information through Email.php:
*
* $email = new Email();
* $email->attachments[] = array(
* 'filename' => BASE_PATH . "/themes/mytheme/images/header.gif",
* 'contents' => file_get_contents(BASE_PATH . "/themes/mytheme/images/header.gif"),
* 'mimetype' => 'image/gif',
* 'contentLocation' => Director::absoluteBaseURL() . "/themes/mytheme/images/header.gif"
* );
*
*/
function encodeFileForEmail($file, $destFileName = false, $disposition = "attachment", $extraHeaders = "") {
function encodeFileForEmail($file, $destFileName = false, $disposition = NULL, $extraHeaders = "") {
if(!$file) {
user_error("encodeFileForEmail: not passed a filename and/or data", E_USER_WARNING);
return;
@ -354,6 +389,8 @@ function encodeFileForEmail($file, $destFileName = false, $disposition = "attach
$mimeType = $file['mimetype'] ? $file['mimetype'] : getMimeType($file['filename']);
if(!$mimeType) $mimeType = "application/unknown";
if (empty($disposition)) $disposition = isset($file['contentLocation']) ? 'inline' : 'attachment';
// Encode for emailing
if (substr($file['mimetype'], 0, 4) != 'text') {
$encoding = "base64";
@ -366,8 +403,12 @@ function encodeFileForEmail($file, $destFileName = false, $disposition = "attach
}
$headers = "Content-type: $mimeType;\n\tname=\"$base\"\n".
"Content-Transfer-Encoding: $encoding\n".
"Content-Disposition: $disposition;\n\tfilename=\"$base\"\n" . $extraHeaders . "\n";
"Content-Transfer-Encoding: $encoding\n".
"Content-Disposition: $disposition;\n\tfilename=\"$base\"\n" ;
if ( isset($file['contentLocation']) ) $headers .= 'Content-Location: ' . $file['contentLocation'] . "\n" ;
$headers .= $extraHeaders . "\n";
// Return completed packet
return $headers . $file['contents'];

View File

@ -598,8 +598,13 @@ class File extends DataObject {
self::$cache_file_fields = null;
}
function fieldLabels() {
$labels = parent::fieldLabels();
/**
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
*/
function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['Name'] = _t('File.Name', 'Name');
$labels['Title'] = _t('File.Title', 'Title');
$labels['Filename'] = _t('File.Filename', 'Filename');

View File

@ -200,14 +200,6 @@ class ComplexTableField extends TableListField {
$this->detailFormFields = $detailFormFields;
$this->controller = $controller;
$this->pageSize = 10;
Requirements::javascript(THIRDPARTY_DIR . "/greybox/AmiJS.js");
Requirements::javascript(THIRDPARTY_DIR . "/greybox/greybox.js");
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableListField.js');
Requirements::javascript(SAPPHIRE_DIR . "/javascript/ComplexTableField.js");
Requirements::css(THIRDPARTY_DIR . "/greybox/greybox.css");
Requirements::css(SAPPHIRE_DIR . "/css/ComplexTableField.css");
parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
@ -246,6 +238,15 @@ class ComplexTableField extends TableListField {
* @return String
*/
function FieldHolder() {
Requirements::javascript(THIRDPARTY_DIR . "/greybox/AmiJS.js");
Requirements::javascript(THIRDPARTY_DIR . "/greybox/greybox.js");
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableListField.js');
Requirements::javascript(SAPPHIRE_DIR . "/javascript/ComplexTableField.js");
Requirements::css(THIRDPARTY_DIR . "/greybox/greybox.css");
Requirements::css(SAPPHIRE_DIR . "/css/TableListField.css");
Requirements::css(SAPPHIRE_DIR . "/css/ComplexTableField.css");
// set caption if required
if($this->popupCaption) {
$id = $this->id();
@ -1014,36 +1015,13 @@ class ComplexTableField_Item extends TableListField_Item {
*/
class ComplexTableField_Popup extends Form {
protected $sourceClass;
protected $dataObject;
function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {
$this->dataObject = $dataObject;
/**
* WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES
* Some have special requirements.
*/
//Requirements::css(CMS_DIR . '/css/layout.css');
Requirements::css(SAPPHIRE_DIR . '/css/Form.css');
Requirements::css(SAPPHIRE_DIR . '/css/ComplexTableField_popup.css');
Requirements::css(CMS_DIR . '/css/typography.css');
Requirements::css(CMS_DIR . '/css/cms_right.css');
Requirements::javascript(THIRDPARTY_DIR . "/prototype.js");
Requirements::javascript(THIRDPARTY_DIR . "/behaviour.js");
Requirements::javascript(THIRDPARTY_DIR . "/prototype_improvements.js");
Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/scriptaculous.js");
Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/controls.js");
Requirements::javascript(THIRDPARTY_DIR . "/layout_helpers.js");
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(CMS_DIR . "/javascript/LeftAndMain.js");
Requirements::javascript(CMS_DIR . "/javascript/LeftAndMain_right.js");
Requirements::javascript(SAPPHIRE_DIR . "/javascript/TableField.js");
Requirements::javascript(SAPPHIRE_DIR . "/javascript/ComplexTableField.js");
Requirements::javascript(SAPPHIRE_DIR . "/javascript/ComplexTableField_popup.js");
if($this->dataObject->hasMethod('getRequirementsForPopup')) {
$this->dataObject->getRequirementsForPopup();
}
Requirements::clear();
$actions = new FieldSet();
if(!$readonly) {
@ -1059,8 +1037,31 @@ class ComplexTableField_Popup extends Form {
parent::__construct($controller, $name, $fields, $actions, $validator);
}
function FieldHolder() {
return $this->renderWith('ComplexTableField_Form');
function forTemplate() {
$ret = parent::forTemplate();
/**
* WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES
* Some have special requirements.
*/
Requirements::css(SAPPHIRE_DIR . '/css/Form.css');
Requirements::css(SAPPHIRE_DIR . '/css/ComplexTableField_popup.css');
Requirements::css(CMS_DIR . '/css/typography.css');
Requirements::css(CMS_DIR . '/css/cms_right.css');
Requirements::javascript(THIRDPARTY_DIR . "/prototype.js");
Requirements::javascript(THIRDPARTY_DIR . "/behaviour.js");
Requirements::javascript(THIRDPARTY_DIR . "/prototype_improvements.js");
Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/scriptaculous.js");
Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/controls.js");
Requirements::javascript(THIRDPARTY_DIR . "/layout_helpers.js");
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . "/javascript/ComplexTableField_popup.js");
if($this->dataObject->hasMethod('getRequirementsForPopup')) {
$this->dataObject->getRequirementsForPopup();
}
return $ret;
}
}

View File

@ -22,11 +22,6 @@ class HasManyComplexTableField extends ComplexTableField {
function __construct($controller, $name, $sourceClass, $fieldList = null, $detailFormFields = null, $sourceFilter = "", $sourceSort = "", $sourceJoin = "") {
parent::__construct($controller, $name, $sourceClass, $fieldList, $detailFormFields, $sourceFilter, $sourceSort, $sourceJoin);
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . "/javascript/HasManyFileField.js");
Requirements::javascript(SAPPHIRE_DIR . '/javascript/RelationComplexTableField.js');
Requirements::css(SAPPHIRE_DIR . '/css/HasManyFileField.css');
$this->Markable = true;
@ -39,6 +34,17 @@ class HasManyComplexTableField extends ComplexTableField {
}
function FieldHolder() {
$ret = parent::FieldHolder();
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . "/javascript/HasManyFileField.js");
Requirements::javascript(SAPPHIRE_DIR . '/javascript/RelationComplexTableField.js');
Requirements::css(SAPPHIRE_DIR . '/css/HasManyFileField.css');
return $ret;
}
/**
* Try to determine the DataObject that this field is built on top of
*/

View File

@ -15,7 +15,7 @@ class HtmlEditorField extends TextareaField {
/**
* Construct a new HtmlEditor field
*/
function __construct($name, $title = null, $rows = 20, $cols = 20, $value = "", $form = null) {
function __construct($name, $title = null, $rows = 30, $cols = 20, $value = "", $form = null) {
parent::__construct($name, $title, $rows, $cols, $value, $form);
$this->extraClass = 'typography';
}

View File

@ -5,9 +5,7 @@
* @package forms
* @subpackage fields-relational
*/
class ScaffoldingComplexTableField_Popup extends Form {
protected $sourceClass;
protected $dataObject;
class ScaffoldingComplexTableField_Popup extends ComplexTableField_Popup {
public static $allowed_actions = array(
'filter', 'record', 'httpSubmission', 'handleAction', 'handleField'
@ -15,45 +13,9 @@ class ScaffoldingComplexTableField_Popup extends Form {
function __construct($controller, $name, $fields, $validator, $readonly, $dataObject) {
$this->dataObject = $dataObject;
Requirements::clear();
/**
* WARNING: DO NOT CHANGE THE ORDER OF THESE JS FILES
* Some have special requirements.
*/
//Requirements::css(CMS_DIR . 'css/layout.css');
Requirements::css(SAPPHIRE_DIR . '/css/Form.css');
Requirements::css(SAPPHIRE_DIR . '/css/ComplexTableField_popup.css');
Requirements::css(CMS_DIR . '/css/typography.css');
Requirements::css(CMS_DIR . '/css/cms_right.css');
Requirements::css(THIRDPARTY_DIR . '/jquery/plugins/autocomplete/jquery.ui.autocomplete.css');
Requirements::javascript(THIRDPARTY_DIR . "/prototype.js");
Requirements::javascript(THIRDPARTY_DIR . "/behaviour.js");
Requirements::javascript(THIRDPARTY_DIR . "/prototype_improvements.js");
Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/scriptaculous.js");
Requirements::javascript(THIRDPARTY_DIR . "/scriptaculous/controls.js");
Requirements::javascript(THIRDPARTY_DIR . "/layout_helpers.js");
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(CMS_DIR . "/javascript/LeftAndMain.js");
Requirements::javascript(CMS_DIR . "/javascript/LeftAndMain_right.js");
Requirements::javascript(SAPPHIRE_DIR . "/javascript/TableField.js");
Requirements::javascript(SAPPHIRE_DIR . "/javascript/ComplexTableField.js");
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');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/plugins/form/jquery.form.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/plugins/dimensions/jquery.dimensions.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery/plugins/autocomplete/jquery.ui.autocomplete.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/ScaffoldComplexTableField.js');
Requirements::javascript(CMS_DIR . '/javascript/ModelAdmin.js');
if($this->dataObject->hasMethod('getRequirementsForPopup')) {
$this->dataObject->getRequirementsForPopup();
}
$actions = new FieldSet();
if(!$readonly) {
$actions->push(
@ -64,13 +26,8 @@ class ScaffoldingComplexTableField_Popup extends Form {
$fields->push(new HiddenField("ComplexTableField_Path", Director::absoluteBaseURL()));
parent::__construct($controller, $name, $fields, $actions, $validator);
parent::__construct($controller, $name, $fields, $validator, $readonly, $dataObject);
}
function FieldHolder() {
return $this->renderWith('ComplexTableField_Form');
}
/**
* Handle a generic action passed in by the URL mapping.

View File

@ -113,9 +113,6 @@ class TableField extends TableListField {
$sourceFilter = "\"$filterField\" = '" . Convert::raw2sql($sourceFilter) . "'";
}
parent::__construct($name, $sourceClass, $fieldList, $sourceFilter, $sourceSort, $sourceJoin);
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableField.js');
}
/**
@ -475,6 +472,15 @@ class TableField extends TableListField {
* Sets the template to be rendered with
*/
function FieldHolder() {
Requirements::javascript(THIRDPARTY_DIR . '/prototype.js');
Requirements::javascript(THIRDPARTY_DIR . '/behaviour.js');
Requirements::javascript(THIRDPARTY_DIR . '/prototype_improvements.js');
Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/effects.js');
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableListField.js');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableField.js');
Requirements::css(SAPPHIRE_DIR . '/css/TableListField.css');
return $this->renderWith($this->template);
}

View File

@ -248,14 +248,6 @@ class TableListField extends FormField {
$this->readOnly = false;
parent::__construct($name);
Requirements::javascript(THIRDPARTY_DIR . '/prototype.js');
Requirements::javascript(THIRDPARTY_DIR . '/behaviour.js');
Requirements::javascript(THIRDPARTY_DIR . '/prototype_improvements.js');
Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/effects.js');
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableListField.js');
Requirements::css(SAPPHIRE_DIR . '/css/TableListField.css');
}
/**
@ -283,6 +275,13 @@ class TableListField extends FormField {
}
function FieldHolder() {
Requirements::javascript(THIRDPARTY_DIR . '/prototype.js');
Requirements::javascript(THIRDPARTY_DIR . '/behaviour.js');
Requirements::javascript(THIRDPARTY_DIR . '/prototype_improvements.js');
Requirements::javascript(THIRDPARTY_DIR . '/scriptaculous/effects.js');
Requirements::add_i18n_javascript(SAPPHIRE_DIR . '/javascript/lang');
Requirements::javascript(SAPPHIRE_DIR . '/javascript/TableListField.js');
Requirements::css(SAPPHIRE_DIR . '/css/TableListField.css');
if($this->clickAction) {
$id = $this->id();

View File

@ -162,6 +162,12 @@ TableField.prototype = {
if(newRow.id != "new"){
this.resetNames(newRow);
}
// Make sure all inputs have unique IDs
for(var i = 0; i < inputs.length; i++) {
inputs[i].id += "-" + this.newRowID;
}
// Change the ID to a unique one
newRow.id = "New_" + this.newRowID;

View File

@ -111,17 +111,24 @@ class Group extends DataObject {
return $fields;
}
function fieldLabels() {
$labels = parent::fieldLabels();
/**
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
*/
function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['Title'] = _t('SecurityAdmin.GROUPNAME', 'Group name');
$labels['Description'] = _t('Group.Description', 'Description');
$labels['Code'] = _t('Group.Code', 'Group Code', PR_MEDIUM, 'Programmatical code identifying a group');
$labels['Locked'] = _t('Group.Locked', 'Locked?', PR_MEDIUM, 'Group is locked in the security administration area');
$labels['Sort'] = _t('Group.Sort', 'Sort Order');
$labels['IPRestrictions'] = _t('Group.IPRestrictions', 'IP Address Restrictions');
$labels['Parent'] = _t('Group.Parent', 'Parent Group', PR_MEDIUM, 'One group has one parent group');
$labels['Permissions'] = _t('Group.has_many_Permissions', 'Permissions', PR_MEDIUM, 'One group has many permissions');
$labels['Members'] = _t('Group.many_many_Members', 'Members', PR_MEDIUM, 'One group has many members');
if($includerelations){
$labels['Parent'] = _t('Group.Parent', 'Parent Group', PR_MEDIUM, 'One group has one parent group');
$labels['Permissions'] = _t('Group.has_many_Permissions', 'Permissions', PR_MEDIUM, 'One group has many permissions');
$labels['Members'] = _t('Group.many_many_Members', 'Members', PR_MEDIUM, 'One group has many members');
}
return $labels;
}

View File

@ -33,8 +33,13 @@ class LoginAttempt extends DataObject {
static $belongs_many_many = array();
function fieldLabels() {
$labels = parent::fieldLabels();
/**
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
*/
function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['Email'] = _t('LoginAttempt.Email', 'Email Address');
$labels['Status'] = _t('LoginAttempt.Status', 'Status');
$labels['IP'] = _t('LoginAttempt.IP', 'IP Address');

View File

@ -559,6 +559,8 @@ class Member extends DataObject {
user_error('Member::inGroup(): Wrong format for $group parameter', E_USER_ERROR);
}
if(!$groupCheckObj) return false;
$groupCandidateObjs = ($strict) ? $this->getManyManyComponents("Groups") : $this->Groups();
if($groupCandidateObjs) foreach($groupCandidateObjs as $groupCandidateObj) {
if($groupCandidateObj->ID == $groupCheckObj->ID) return true;
@ -874,8 +876,13 @@ class Member extends DataObject {
return $fields;
}
function fieldLabels() {
$labels = parent::fieldLabels();
/**
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
*/
function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['FirstName'] = _t('Member.FIRSTNAME');
$labels['Surname'] = _t('Member.SURNAME');
@ -886,8 +893,9 @@ class Member extends DataObject {
$labels['PasswordExpiry'] = _t('Member.db_PasswordExpiry', 'Password Expiry Date', PR_MEDIUM, 'Password expiry date');
$labels['LockedOutUntil'] = _t('Member.db_LockedOutUntil', 'Locked out until', PR_MEDIUM, 'Security related date');
$labels['Locale'] = _t('Member.db_Locale', 'Interface Locale');
$labels['Groups'] = _t('Member.belongs_many_many_Groups', 'Groups', PR_MEDIUM, 'Security Groups this member belongs to');
if($includerelations){
$labels['Groups'] = _t('Member.belongs_many_many_Groups', 'Groups', PR_MEDIUM, 'Security Groups this member belongs to');
}
return $labels;
}

View File

@ -43,9 +43,22 @@ class MemberLoginForm extends LoginForm {
$backURL = Session::get('BackURL');
}
if($checkCurrentUser && Member::currentUserID()) {
$fields = new FieldSet();
$actions = new FieldSet(new FormAction("logout", _t('Member.BUTTONLOGINOTHER', "Log in as someone else")));
// We assume if session is storing a member ID, that member exists in the DB
$sessMemberExistsInDB = true;
if($sessionMemberID = Member::currentUserID()) {
$sessMemberInDB = DataObject::get_by_id('Member', $sessionMemberID);
if(!($sessMemberInDB && $sessMemberInDB->exists())) {
$sessMemberExistsInDB = false;
}
}
if($checkCurrentUser && Member::currentUserID() && $sessMemberExistsInDB) {
$fields = new FieldSet(
new HiddenField("AuthenticationMethod", null, $this->authenticator_class, $this)
);
$actions = new FieldSet(
new FormAction("logout", _t('Member.BUTTONLOGINOTHER', "Log in as someone else"))
);
} else {
if(!$fields) {
$fields = new FieldSet(

143
tests/DataObjectSetTest.php Normal file
View File

@ -0,0 +1,143 @@
<?php
/**
* Test the {@link DataObjectSet} class.
*
* @package sapphire
* @subpackage tests
*/
class DataObjectSetTest extends SapphireTest {
static $fixture_file = 'sapphire/tests/DataObjectTest.yml';
function testIterator() {
$set = new DataObjectSet(array(
$one = new DataObject(array('Title'=>'one')),
$two = new DataObject(array('Title'=>'two')),
$three = new DataObject(array('Title'=>'three')),
$four = new DataObject(array('Title'=>'four'))
));
// test Pos() with foreach()
$i = 0;
foreach($set as $item) {
$i++;
$this->assertEquals($i, $item->Pos(), "Iterator position is set correctly on ViewableData when iterated with foreach()");
}
// test Pos() manually
$this->assertEquals(1, $one->Pos());
$this->assertEquals(2, $two->Pos());
$this->assertEquals(3, $three->Pos());
$this->assertEquals(4, $four->Pos());
// test DataObjectSet->Count()
$this->assertEquals(4, $set->Count());
// test DataObjectSet->First()
$this->assertSame($one, $set->First());
// test DataObjectSet->Last()
$this->assertSame($four, $set->Last());
// test ViewableData->First()
$this->assertTrue($one->First());
$this->assertFalse($two->First());
$this->assertFalse($three->First());
$this->assertFalse($four->First());
// test ViewableData->Last()
$this->assertFalse($one->Last());
$this->assertFalse($two->Last());
$this->assertFalse($three->Last());
$this->assertTrue($four->Last());
// test ViewableData->Middle()
$this->assertFalse($one->Middle());
$this->assertTrue($two->Middle());
$this->assertTrue($three->Middle());
$this->assertFalse($four->Middle());
// test ViewableData->Even()
$this->assertFalse($one->Even());
$this->assertTrue($two->Even());
$this->assertFalse($three->Even());
$this->assertTrue($four->Even());
// test ViewableData->Odd()
$this->assertTrue($one->Odd());
$this->assertFalse($two->Odd());
$this->assertTrue($three->Odd());
$this->assertFalse($four->Odd());
}
/**
* Test {@link DataObjectSet->Count()}
*/
function testCount() {
$comments = DataObject::get('PageComment');
/* There are a total of 8 items in the set */
$this->assertEquals($comments->Count(), 8, 'There are a total of 8 items in the set');
}
/**
* Test {@link DataObjectSet->First()}
*/
function testFirst() {
$comments = DataObject::get('PageComment');
/* The first object is Joe's comment */
$this->assertEquals($comments->First()->ID, 1, 'The first object has an ID of "1"');
$this->assertEquals($comments->First()->Name, 'Joe', 'The first object has a Name field value of "Joe"');
}
/**
* Test {@link DataObjectSet->Last()}
*/
function testLast() {
$comments = DataObject::get('PageComment');
/* The last object is Dean's comment */
$this->assertEquals($comments->Last()->ID, 8, 'The last object has an ID of "8"');
$this->assertEquals($comments->Last()->Name, 'Dean', 'The last object has a Name field value of "Dean"');
}
/**
* Test {@link DataObjectSet->map()}
*/
function testMap() {
$comments = DataObject::get('PageComment');
/* Now we get a map of all the PageComment records */
$map = $comments->map('ID', 'Title', '(Select one)');
$expectedMap = array(
0 => '(Select one)',
1 => 'Joe',
2 => 'Jane',
3 => 'Bob',
4 => 'Bob',
5 => 'Ernie',
6 => 'Jimmy',
7 => 'Dean',
8 => 'Dean'
);
/* There are 9 items in the map. 8 are records. 1 is the empty value */
$this->assertEquals(count($map), 9, 'There are 9 items in the map. 8 are records. 1 is the empty value');
/* We have the same map as our expected map, asserted above */
$this->assertSame($expectedMap, $map, 'The map we generated is exactly the same as the asserted one');
/* toDropDownMap() is an alias of map() - let's make a map from that */
$map2 = $comments->toDropDownMap('ID', 'Title', '(Select one)');
/* There are 9 items in the map. 8 are records. 1 is the empty value */
$this->assertEquals(count($map), 9, 'There are 9 items in the map. 8 are records. 1 is the empty value.');
/* We have the same map as our expected map, asserted above */
$this->assertSame($expectedMap, $map2, 'The map we generated is exactly the same as the asserted one');
}
}
?>

View File

@ -122,6 +122,26 @@ class RequirementsTest extends SapphireTest {
Requirements::delete_combined_files('RequirementsTest_bc.js');
}
function testArgsInUrls() {
// Clear previous requirements
Requirements::clear();
// clearing all previously generated requirements (just in case)
Requirements::clear_combined_files();
Requirements::delete_combined_files('RequirementsTest_bc.js');
Requirements::javascript(SAPPHIRE_DIR . '/tests/forms/RequirementsTest_a.js?test=1&test=2&test=3');
Requirements::css(SAPPHIRE_DIR . '/tests/forms/RequirementsTest_a.css?test=1&test=2&test=3');
$html = Requirements::includeInHTML(false, self::$html_template);
/* Javascript has correct path */
$this->assertTrue((bool)preg_match('/src=".*\/RequirementsTest_a\.js\?m=\d\d+&test=1&test=2&test=3/', $html), 'javascript has correct path');
/* CSS has correct path */
$this->assertTrue((bool)preg_match('/href=".*\/RequirementsTest_a\.css\?m=\d\d+&test=1&test=2&test=3/', $html), 'css has correct path');
}
/**
* This is a bit of a hack, as it alters the Requirements
* statics globally for all tests.

View File

@ -219,8 +219,13 @@ class i18nTest_DataObject extends DataObject implements TestOnly {
'ManyManyRelation' => 'Member'
);
function fieldLabels() {
$labels = parent::fieldLabels();
/**
*
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
*
*/
function fieldLabels($includerelations = true) {
$labels = parent::fieldLabels($includerelations);
$labels['MyProperty'] = _t('i18nTest_DataObject.MyProperty', 'My Property');
return $labels;

View File

@ -1,70 +0,0 @@
<?php
/**
* @package sapphire
* @subpackage tests
*/
class DataObjectSetTest extends SapphireTest {
function testIterator() {
$set = new DataObjectSet(array(
$one = new DataObject(array('Title'=>'one')),
$two = new DataObject(array('Title'=>'two')),
$three = new DataObject(array('Title'=>'three')),
$four = new DataObject(array('Title'=>'four'))
));
// test Pos() with foreach()
$i = 0;
foreach($set as $item) {
$i++;
$this->assertEquals($i, $item->Pos(), "Iterator position is set correctly on ViewableData when iterated with foreach()");
}
// test Pos() manually
$this->assertEquals(1, $one->Pos());
$this->assertEquals(2, $two->Pos());
$this->assertEquals(3, $three->Pos());
$this->assertEquals(4, $four->Pos());
// test DataObjectSet->Count()
$this->assertEquals(4, $set->Count());
// test DataObjectSet->First()
$this->assertSame($one, $set->First());
// test DataObjectSet->Last()
$this->assertSame($four, $set->Last());
// test ViewableData->First()
$this->assertTrue($one->First());
$this->assertFalse($two->First());
$this->assertFalse($three->First());
$this->assertFalse($four->First());
// test ViewableData->Last()
$this->assertFalse($one->Last());
$this->assertFalse($two->Last());
$this->assertFalse($three->Last());
$this->assertTrue($four->Last());
// test ViewableData->Middle()
$this->assertFalse($one->Middle());
$this->assertTrue($two->Middle());
$this->assertTrue($three->Middle());
$this->assertFalse($four->Middle());
// test ViewableData->Even()
$this->assertFalse($one->Even());
$this->assertTrue($two->Even());
$this->assertFalse($three->Even());
$this->assertTrue($four->Even());
// test ViewableData->Odd()
$this->assertTrue($one->Odd());
$this->assertFalse($two->Odd());
$this->assertTrue($three->Odd());
$this->assertFalse($four->Odd());
}
}
?>

View File

@ -255,5 +255,9 @@ class MemberTest extends SapphireTest {
$accountingmember->inGroup($managementgroup),
'Users of group are not members of any siblings'
);
$this->assertFalse(
$staffmember->inGroup('does-not-exist'),
'Non-existant group returns false'
);
}
}

View File

@ -37,13 +37,61 @@ class SecurityTest extends FunctionalTest {
parent::tearDown();
}
function testExternalBackUrlRedirectionDisallowed() {
$page = new SiteTree();
$page->URLSegment = 'testpage';
$page->Title = 'Testpage';
$page->write();
$page->publish('Stage','Live');
function testLogInAsSomeoneElse() {
$member = DataObject::get_one('Member');
/* Log in with any user that we can find */
$this->session()->inst_set('loggedInAs', $member->ID);
/* View the Security/login page */
$this->get('Security/login');
$items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.action');
/* We have only 1 input, one to allow the user to log in as someone else */
$this->assertEquals(count($items), 1, 'There is 1 input, allowing the user to log in as someone else.');
$this->autoFollowRedirection = true;
/* Submit the form, using only the logout action and a hidden field for the authenticator */
$response = $this->submitForm(
'MemberLoginForm_LoginForm',
null,
array(
'AuthenticationMethod' => 'MemberAuthenticator',
'action_dologout' => 1,
)
);
/* We get a good response */
$this->assertEquals($response->getStatusCode(), 200, 'We have a 200 OK response');
$this->assertNotNull($response->getBody(), 'There is body content on the page');
/* Log the user out */
$this->session()->inst_set('loggedInAs', null);
}
function testMemberIDInSessionDoesntExistInDatabaseHasToLogin() {
/* Log in with a Member ID that doesn't exist in the DB */
$this->session()->inst_set('loggedInAs', 500);
$this->autoFollowRedirection = true;
/* Attempt to get into the admin section */
$this->get('admin');
$items = $this->cssParser()->getBySelector('#MemberLoginForm_LoginForm input.text');
/* We have 2 text inputs - one for email, and another for the password */
$this->assertEquals(count($items), 2, 'There are 2 inputs - one for email, another for password');
$this->autoFollowRedirection = false;
/* Log the user out */
$this->session()->inst_set('loggedInAs', null);
}
function testExternalBackUrlRedirectionDisallowed() {
// Test internal relative redirect
$response = $this->doTestLoginForm('noexpiry@silverstripe.com', '1nitialPassword', 'testpage');
$this->assertEquals(302, $response->getStatusCode());