mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 12:05:37 +00:00
Making docs gender agnostic
This commit is contained in:
parent
0898487ad2
commit
d2a3da2203
@ -10,7 +10,7 @@ jQuery.noConflict();
|
||||
// Entwine's 'fromWindow::onresize' does not trigger on IE8. Use synthetic event.
|
||||
var cb = function() {$('.cms-container').trigger('windowresize');};
|
||||
|
||||
// Workaround to avoid IE8 infinite loops when elements are resized as a result of this event
|
||||
// Workaround to avoid IE8 infinite loops when elements are resized as a result of this event
|
||||
if($.browser.msie && parseInt($.browser.version, 10) < 9) {
|
||||
var newWindowWidth = $(window).width(), newWindowHeight = $(window).height();
|
||||
if(newWindowWidth != windowWidth || newWindowHeight != windowHeight) {
|
||||
@ -26,7 +26,7 @@ jQuery.noConflict();
|
||||
// setup jquery.entwine
|
||||
$.entwine.warningLevel = $.entwine.WARN_LEVEL_BESTPRACTISE;
|
||||
$.entwine('ss', function($) {
|
||||
|
||||
|
||||
/*
|
||||
* Handle messages sent via nested iframes
|
||||
* Messages should be raised via postMessage with an object with the 'type' parameter given.
|
||||
@ -35,20 +35,20 @@ jQuery.noConflict();
|
||||
* type should be one of:
|
||||
* - 'event' - Will trigger the given event (specified by 'event') on the target
|
||||
* - 'callback' - Will call the given method (specified by 'callback') on the target
|
||||
*/
|
||||
*/
|
||||
$(window).on("message", function(e) {
|
||||
var target,
|
||||
event = e.originalEvent,
|
||||
data = JSON.parse(event.data);
|
||||
|
||||
|
||||
// Reject messages outside of the same origin
|
||||
if($.path.parseUrl(window.location.href).domain !== $.path.parseUrl(event.origin).domain) return;
|
||||
|
||||
|
||||
// Get target of this action
|
||||
target = typeof(data.target) === 'undefined'
|
||||
? $(window)
|
||||
: $(data.target);
|
||||
|
||||
|
||||
// Determine action
|
||||
switch(data.type) {
|
||||
case 'event':
|
||||
@ -59,18 +59,18 @@ jQuery.noConflict();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Position the loading spinner animation below the ss logo
|
||||
*/
|
||||
*/
|
||||
var positionLoadingSpinner = function() {
|
||||
var offset = 120; // offset from the ss logo
|
||||
var spinner = $('.ss-loading-screen .loading-animation');
|
||||
var spinner = $('.ss-loading-screen .loading-animation');
|
||||
var top = ($(window).height() - spinner.height()) / 2;
|
||||
spinner.css('top', top + offset);
|
||||
spinner.show();
|
||||
};
|
||||
|
||||
|
||||
// apply an select element only when it is ready, ie. when it is rendered into a template
|
||||
// with css applied and got a width value.
|
||||
var applyChosen = function(el) {
|
||||
@ -89,13 +89,13 @@ jQuery.noConflict();
|
||||
setTimeout(function() {
|
||||
// Make sure it's visible before applying the ui
|
||||
el.show();
|
||||
applyChosen(el); },
|
||||
applyChosen(el); },
|
||||
500);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Compare URLs, but normalize trailing slashes in
|
||||
* Compare URLs, but normalize trailing slashes in
|
||||
* URL to work around routing weirdnesses in SS_HTTPRequest.
|
||||
* Also normalizes relative URLs by prefixing them with the <base>.
|
||||
*/
|
||||
@ -105,11 +105,11 @@ jQuery.noConflict();
|
||||
url2 = $.path.isAbsoluteUrl(url2) ? url2 : $.path.makeUrlAbsolute(url2, baseUrl);
|
||||
var url1parts = $.path.parseUrl(url1), url2parts = $.path.parseUrl(url2);
|
||||
return (
|
||||
url1parts.pathname.replace(/\/*$/, '') == url2parts.pathname.replace(/\/*$/, '') &&
|
||||
url1parts.pathname.replace(/\/*$/, '') == url2parts.pathname.replace(/\/*$/, '') &&
|
||||
url1parts.search == url2parts.search
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
$(window).bind('resize', positionLoadingSpinner).trigger('resize');
|
||||
|
||||
// global ajax handlers
|
||||
@ -142,7 +142,7 @@ jQuery.noConflict();
|
||||
reathenticate = xhr.getResponseHeader('X-Reauthenticate'),
|
||||
msgType = (xhr.status < 200 || xhr.status > 399) ? 'bad' : 'good',
|
||||
ignoredMessages = ['OK'];
|
||||
|
||||
|
||||
// Enable reauthenticate dialog if requested
|
||||
if(reathenticate) {
|
||||
$('.cms-container').showLoginDialog();
|
||||
@ -158,14 +158,14 @@ jQuery.noConflict();
|
||||
|
||||
/**
|
||||
* Main LeftAndMain interface with some control panel and an edit form.
|
||||
*
|
||||
*
|
||||
* Events:
|
||||
* ajaxsubmit - ...
|
||||
* validate - ...
|
||||
* aftersubmitform - ...
|
||||
*/
|
||||
$('.cms-container').entwine({
|
||||
|
||||
|
||||
/**
|
||||
* Tracks current panel request.
|
||||
*/
|
||||
@ -177,7 +177,7 @@ jQuery.noConflict();
|
||||
FragmentXHR: {},
|
||||
|
||||
StateChangeCount: 0,
|
||||
|
||||
|
||||
/**
|
||||
* Options for the threeColumnCompressor layout algorithm.
|
||||
*
|
||||
@ -198,7 +198,7 @@ jQuery.noConflict();
|
||||
// Browser detection
|
||||
if($.browser.msie && parseInt($.browser.version, 10) < 8) {
|
||||
$('.ss-loading-screen').append(
|
||||
'<p class="ss-loading-incompat-warning"><span class="notice">' +
|
||||
'<p class="ss-loading-incompat-warning"><span class="notice">' +
|
||||
'Your browser is not compatible with the CMS interface. Please use Internet Explorer 8+, Google Chrome or Mozilla Firefox.' +
|
||||
'</span></p>'
|
||||
).css('z-index', $('.ss-loading-screen').css('z-index')+1);
|
||||
@ -207,7 +207,7 @@ jQuery.noConflict();
|
||||
this._super();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Initialize layouts
|
||||
this.redraw();
|
||||
|
||||
@ -216,7 +216,7 @@ jQuery.noConflict();
|
||||
$('body').removeClass('loading');
|
||||
$(window).unbind('resize', positionLoadingSpinner);
|
||||
this.restoreTabState();
|
||||
|
||||
|
||||
this._super();
|
||||
},
|
||||
|
||||
@ -326,9 +326,9 @@ jQuery.noConflict();
|
||||
* Proxy around History.pushState() which handles non-HTML5 fallbacks,
|
||||
* as well as global change tracking. Change tracking needs to be synchronous rather than event/callback
|
||||
* based because the user needs to be able to abort the action completely.
|
||||
*
|
||||
*
|
||||
* See handleStateChange() for more details.
|
||||
*
|
||||
*
|
||||
* Parameters:
|
||||
* - {String} url
|
||||
* - {String} title New window title
|
||||
@ -343,20 +343,20 @@ jQuery.noConflict();
|
||||
// Check change tracking (can't use events as we need a way to cancel the current state change)
|
||||
var contentEls = this._findFragments(data.pjax ? data.pjax.split(',') : ['Content']);
|
||||
var trackedEls = contentEls.find(':data(changetracker)').add(contentEls.filter(':data(changetracker)'));
|
||||
|
||||
|
||||
if(trackedEls.length) {
|
||||
var abort = false;
|
||||
|
||||
|
||||
trackedEls.each(function() {
|
||||
if(!$(this).confirmUnsavedChanges()) abort = true;
|
||||
});
|
||||
|
||||
|
||||
if(abort) return;
|
||||
}
|
||||
|
||||
// Save tab selections so we can restore them later
|
||||
this.saveTabState();
|
||||
|
||||
|
||||
if(window.History.enabled) {
|
||||
$.extend(data, {__forceReferer: forceReferer});
|
||||
// Active menu item is set based on X-Controller ajax header,
|
||||
@ -382,31 +382,31 @@ jQuery.noConflict();
|
||||
|
||||
/**
|
||||
* Function: submitForm
|
||||
*
|
||||
*
|
||||
* Parameters:
|
||||
* {DOMElement} form - The form to be submitted. Needs to be passed
|
||||
* in to avoid entwine methods/context being removed through replacing the node itself.
|
||||
* {DOMElement} button - The pressed button (optional)
|
||||
* {Function} callback - Called in complete() handler of jQuery.ajax()
|
||||
* {Object} ajaxOptions - Object literal to merge into $.ajax() call
|
||||
*
|
||||
*
|
||||
* Returns:
|
||||
* (boolean)
|
||||
*/
|
||||
submitForm: function(form, button, callback, ajaxOptions) {
|
||||
var self = this;
|
||||
|
||||
|
||||
// look for save button
|
||||
if(!button) button = this.find('.Actions :submit[name=action_save]');
|
||||
// default to first button if none given - simulates browser behaviour
|
||||
if(!button) button = this.find('.Actions :submit:first');
|
||||
|
||||
|
||||
form.trigger('beforesubmitform');
|
||||
this.trigger('submitform', {form: form, button: button});
|
||||
|
||||
|
||||
// set button to "submitting" state
|
||||
$(button).addClass('loading');
|
||||
|
||||
|
||||
// validate if required
|
||||
var validationResult = form.validate();
|
||||
if(typeof validationResult!=='undefined' && !validationResult) {
|
||||
@ -417,12 +417,12 @@ jQuery.noConflict();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// get all data from the form
|
||||
var formData = form.serializeArray();
|
||||
// add button action
|
||||
formData.push({name: $(button).attr('name'), value:'1'});
|
||||
// Artificial HTTP referer, IE doesn't submit them via ajax.
|
||||
// Artificial HTTP referer, IE doesn't submit them via ajax.
|
||||
// Also rewrites anchors to their page counterparts, which is important
|
||||
// as automatic browser ajax response redirects seem to discard the hash/fragment.
|
||||
// TODO Replaces trailing slashes added by History after locale (e.g. admin/?locale=en/)
|
||||
@ -437,7 +437,7 @@ jQuery.noConflict();
|
||||
// sending back different `X-Pjax` headers and content
|
||||
jQuery.ajax(jQuery.extend({
|
||||
headers: {"X-Pjax" : "CurrentForm,Breadcrumbs"},
|
||||
url: form.attr('action'),
|
||||
url: form.attr('action'),
|
||||
data: formData,
|
||||
type: 'POST',
|
||||
complete: function() {
|
||||
@ -453,7 +453,7 @@ jQuery.noConflict();
|
||||
newContentEls.filter('form').trigger('aftersubmitform', {status: status, xhr: xhr, formData: formData});
|
||||
}
|
||||
}, ajaxOptions));
|
||||
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
@ -462,21 +462,21 @@ jQuery.noConflict();
|
||||
* To trigger loading, pass a new URL to window.History.pushState().
|
||||
* Use loadPanel() as a pushState() wrapper as it provides some additional functionality
|
||||
* like global changetracking and user aborts.
|
||||
*
|
||||
*
|
||||
* Due to the nature of history management, no callbacks are allowed.
|
||||
* Use the 'beforestatechange' and 'afterstatechange' events instead,
|
||||
* or overwrite the beforeLoad() and afterLoad() methods on the
|
||||
* or overwrite the beforeLoad() and afterLoad() methods on the
|
||||
* DOM element you're loading the new content into.
|
||||
* Although you can pass data into pushState(), it shouldn't contain
|
||||
* Although you can pass data into pushState(), it shouldn't contain
|
||||
* DOM elements or callback closures.
|
||||
*
|
||||
*
|
||||
* The passed URL should allow reconstructing important interface state
|
||||
* without additional parameters, in the following use cases:
|
||||
* - Explicit loading through History.pushState()
|
||||
* - Implicit loading through browser navigation event triggered by the user (forward or back)
|
||||
* - Full window refresh without ajax
|
||||
* For example, a ModelAdmin search event should contain the search terms
|
||||
* as URL parameters, and the result display should automatically appear
|
||||
* as URL parameters, and the result display should automatically appear
|
||||
* if the URL is loaded without ajax.
|
||||
*/
|
||||
handleStateChange: function() {
|
||||
@ -502,22 +502,22 @@ jQuery.noConflict();
|
||||
// that can be reloaded without reloading the whole window.
|
||||
if(contentEls.length < fragmentsArr.length) {
|
||||
fragments = 'Content', fragmentsArr = ['Content'];
|
||||
contentEls = this._findFragments(fragmentsArr);
|
||||
contentEls = this._findFragments(fragmentsArr);
|
||||
}
|
||||
|
||||
|
||||
this.trigger('beforestatechange', {state: state, element: contentEls});
|
||||
|
||||
// Set Pjax headers, which can declare a preference for the returned view.
|
||||
// The actually returned view isn't always decided upon when the request
|
||||
// is fired, so the server might decide to change it based on its own logic.
|
||||
headers['X-Pjax'] = fragments;
|
||||
|
||||
|
||||
// Set 'fake' referer - we call pushState() before making the AJAX request, so we have to
|
||||
// set our own referer here
|
||||
if (typeof state.data.__forceReferer !== 'undefined') {
|
||||
// Ensure query string is properly encoded if present
|
||||
var url = state.data.__forceReferer;
|
||||
|
||||
|
||||
try {
|
||||
// Prevent double-encoding by attempting to decode
|
||||
url = decodeURI(url);
|
||||
@ -528,7 +528,7 @@ jQuery.noConflict();
|
||||
headers['X-Backurl'] = encodeURI(url);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
contentEls.addClass('loading');
|
||||
var xhr = $.ajax({
|
||||
headers: headers,
|
||||
@ -543,7 +543,7 @@ jQuery.noConflict();
|
||||
self.trigger('afterstatechange', {data: data, status: status, xhr: xhr, element: els, state: state});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
this.setStateChangeXHR(xhr);
|
||||
},
|
||||
|
||||
@ -633,7 +633,7 @@ jQuery.noConflict();
|
||||
|
||||
// Support a full reload
|
||||
if(xhr.getResponseHeader('X-Reload') && xhr.getResponseHeader('X-ControllerURL')) {
|
||||
document.location.href = $('base').attr('href').replace(/\/*$/, '')
|
||||
document.location.href = $('base').attr('href').replace(/\/*$/, '')
|
||||
+ '/' + xhr.getResponseHeader('X-ControllerURL');
|
||||
return;
|
||||
}
|
||||
@ -651,7 +651,7 @@ jQuery.noConflict();
|
||||
if(xhr.getResponseHeader('Content-Type').match(/^((text)|(application))\/json[ \t]*;?/i)) {
|
||||
newFragments = data;
|
||||
} else {
|
||||
|
||||
|
||||
// Fall back to replacing the content fragment if HTML is returned
|
||||
var fragment = document.createDocumentFragment();
|
||||
jQuery.clean( [ data ], document, fragment, [] );
|
||||
@ -732,9 +732,9 @@ jQuery.noConflict();
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Parameters:
|
||||
*
|
||||
*
|
||||
* Parameters:
|
||||
* - fragments {Array}
|
||||
* Returns: jQuery collection
|
||||
*/
|
||||
@ -751,14 +751,14 @@ jQuery.noConflict();
|
||||
|
||||
/**
|
||||
* Function: refresh
|
||||
*
|
||||
*
|
||||
* Updates the container based on the current url
|
||||
*
|
||||
* Returns: void
|
||||
*/
|
||||
refresh: function() {
|
||||
$(window).trigger('statechange');
|
||||
|
||||
|
||||
$(this).redraw();
|
||||
},
|
||||
|
||||
@ -787,7 +787,7 @@ jQuery.noConflict();
|
||||
window.sessionStorage.setItem(tabsUrl, JSON.stringify(selectedTabs));
|
||||
} catch(err) {
|
||||
if (err.code === DOMException.QUOTA_EXCEEDED_ERR && window.sessionStorage.length === 0) {
|
||||
// If this fails we ignore the error as the only issue is that it
|
||||
// If this fails we ignore the error as the only issue is that it
|
||||
// does not remember the tab state.
|
||||
// This is a Safari bug which happens when private browsing is enabled.
|
||||
return;
|
||||
@ -847,7 +847,7 @@ jQuery.noConflict();
|
||||
|
||||
var s = window.sessionStorage;
|
||||
if(url) {
|
||||
s.removeItem('tabs-' + url);
|
||||
s.removeItem('tabs-' + url);
|
||||
} else {
|
||||
for(var i=0;i<s.length;i++) {
|
||||
if(s.key(i).match(/^tabs-/)) s.removeItem(s.key(i));
|
||||
@ -868,7 +868,7 @@ jQuery.noConflict();
|
||||
.replace(/#.*/, '')
|
||||
.replace($('base').attr('href'), '');
|
||||
},
|
||||
|
||||
|
||||
showLoginDialog: function() {
|
||||
var tempid = $('body').data('member-tempid'),
|
||||
dialog = $('.leftandmain-logindialog'),
|
||||
@ -876,13 +876,13 @@ jQuery.noConflict();
|
||||
|
||||
// Force regeneration of any existing dialog
|
||||
if(dialog.length) dialog.remove();
|
||||
|
||||
|
||||
// Join url params
|
||||
url = $.path.addSearchParams(url, {
|
||||
'tempid': tempid,
|
||||
'BackURL': window.location.href
|
||||
});
|
||||
|
||||
|
||||
// Show a placeholder for instant feedback. Will be replaced with actual
|
||||
// form dialog once its loaded.
|
||||
dialog = $('<div class="leftandmain-logindialog"></div>');
|
||||
@ -891,12 +891,12 @@ jQuery.noConflict();
|
||||
$('body').append(dialog);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Login dialog page
|
||||
$('.leftandmain-logindialog').entwine({
|
||||
onmatch: function() {
|
||||
this._super();
|
||||
|
||||
|
||||
// Create jQuery dialog
|
||||
this.ssdialog({
|
||||
iframeUrl: this.data('url'),
|
||||
@ -912,7 +912,7 @@ jQuery.noConflict();
|
||||
},
|
||||
close: function() {
|
||||
$('.ui-widget-overlay').removeClass('leftandmain-logindialog-overlay');
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
onunmatch: function() {
|
||||
@ -943,7 +943,7 @@ jQuery.noConflict();
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Add loading overlay to selected regions in the CMS automatically.
|
||||
* Not applied to all "*.loading" elements to avoid secondary regions
|
||||
@ -988,7 +988,7 @@ jQuery.noConflict();
|
||||
return;
|
||||
}
|
||||
|
||||
var href = this.attr('href'),
|
||||
var href = this.attr('href'),
|
||||
url = (href && !href.match(/^#/)) ? href : this.data('href'),
|
||||
data = {pjax: this.data('pjaxTarget')};
|
||||
|
||||
@ -1006,17 +1006,17 @@ jQuery.noConflict();
|
||||
onclick: function(e) {
|
||||
$(this).removeClass('ui-button-text-only');
|
||||
$(this).addClass('ss-ui-button-loading ui-button-text-icons');
|
||||
|
||||
|
||||
var loading = $(this).find(".ss-ui-loading-icon");
|
||||
|
||||
|
||||
if(loading.length < 1) {
|
||||
loading = $("<span></span>").addClass('ss-ui-loading-icon ui-button-icon-primary ui-icon');
|
||||
|
||||
|
||||
$(this).prepend(loading);
|
||||
}
|
||||
|
||||
|
||||
loading.show();
|
||||
|
||||
|
||||
var href = this.attr('href'), url = href ? href : this.data('href');
|
||||
|
||||
jQuery.ajax({
|
||||
@ -1024,16 +1024,16 @@ jQuery.noConflict();
|
||||
// Ensure that form view is loaded (rather than whole "Content" template)
|
||||
complete: function(xmlhttp, status) {
|
||||
var msg = (xmlhttp.getResponseHeader('X-Status')) ? xmlhttp.getResponseHeader('X-Status') : xmlhttp.responseText;
|
||||
|
||||
|
||||
try {
|
||||
if (typeof msg != "undefined" && msg !== null) eval(msg);
|
||||
}
|
||||
catch(e) {}
|
||||
|
||||
|
||||
loading.hide();
|
||||
|
||||
|
||||
$(".cms-container").refresh();
|
||||
|
||||
|
||||
$(this).removeClass('ss-ui-button-loading ui-button-text-icons');
|
||||
$(this).addClass('ui-button-text-only');
|
||||
},
|
||||
@ -1064,14 +1064,14 @@ jQuery.noConflict();
|
||||
dialog = $('<div class="ss-ui-dialog" id="' + id + '" />');
|
||||
$('body').append(dialog);
|
||||
}
|
||||
|
||||
|
||||
var extraClass = this.data('popupclass')?this.data('popupclass'):'';
|
||||
|
||||
|
||||
dialog.ssdialog({iframeUrl: this.attr('href'), autoOpen: true, dialogExtraClass: extraClass});
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Add styling to all contained buttons, and create buttonsets if required.
|
||||
*/
|
||||
@ -1101,20 +1101,20 @@ jQuery.noConflict();
|
||||
if(window.debug) console.log('redraw', this.attr('class'), this.get(0));
|
||||
|
||||
// Remove whitespace to avoid gaps with inline elements
|
||||
this.contents().filter(function() {
|
||||
return (this.nodeType == 3 && !/\S/.test(this.nodeValue));
|
||||
this.contents().filter(function() {
|
||||
return (this.nodeType == 3 && !/\S/.test(this.nodeValue));
|
||||
}).remove();
|
||||
|
||||
// Init buttons if required
|
||||
this.find('.ss-ui-button').each(function() {
|
||||
if(!$(this).data('button')) $(this).button();
|
||||
});
|
||||
|
||||
|
||||
// Mark up buttonsets
|
||||
this.find('.ss-ui-buttonset').buttonset();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Duplicates functionality in DateField.js, but due to using entwine we can match
|
||||
* the DOM element on creation, rather than onclick - which allows us to decorate
|
||||
@ -1136,23 +1136,23 @@ jQuery.noConflict();
|
||||
$(this).datepicker(config);
|
||||
// // Unfortunately jQuery UI only allows configuration of icon images, not sprites
|
||||
// this.next('button').button('option', 'icons', {primary : 'ui-icon-calendar'});
|
||||
|
||||
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Styled dropdown select fields via chosen. Allows things like search and optgroup
|
||||
* selection support. Rather than manually adding classes to selects we want
|
||||
* selection support. Rather than manually adding classes to selects we want
|
||||
* styled, we style everything but the ones we tell it not to.
|
||||
*
|
||||
* For the CMS we also need to tell the parent div that his has a select so
|
||||
* For the CMS we also need to tell the parent div that it has a select so
|
||||
* we can fix the height cropping.
|
||||
*/
|
||||
|
||||
|
||||
$('.cms .field.dropdown select, .cms .field select[multiple], .fieldholder-small select.dropdown').entwine({
|
||||
onmatch: function() {
|
||||
if(this.is('.no-chzn')) {
|
||||
@ -1169,20 +1169,20 @@ jQuery.noConflict();
|
||||
|
||||
// Apply Chosen
|
||||
applyChosen(this);
|
||||
|
||||
|
||||
this._super();
|
||||
},
|
||||
onunmatch: function() {
|
||||
this._super();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$(".cms-panel-layout").entwine({
|
||||
redraw: function() {
|
||||
if(window.debug) console.log('redraw', this.attr('class'), this.get(0));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* Overload the default GridField behaviour (open a new URL in the browser)
|
||||
* with the CMS-specific ajax loading.
|
||||
@ -1200,7 +1200,7 @@ jQuery.noConflict();
|
||||
|
||||
/**
|
||||
* Generic search form in the CMS, often hooked up to a GridField results display.
|
||||
*/
|
||||
*/
|
||||
$('.cms-search-form').entwine({
|
||||
onsubmit: function(e) {
|
||||
// Remove empty elements and make the URL prettier
|
||||
@ -1258,7 +1258,7 @@ jQuery.noConflict();
|
||||
},
|
||||
onremove: function() {
|
||||
if(window.debug) console.log('saving', this.data('url'), this);
|
||||
|
||||
|
||||
// Save the HTML state at the last possible moment.
|
||||
// Don't store the DOM to avoid memory leaks.
|
||||
if(!this.data('deferredNoCache')) window._panelDeferredCache[this.data('url')] = this.html();
|
||||
@ -1317,7 +1317,7 @@ jQuery.noConflict();
|
||||
if(!this.data('uiTabs')) this.tabs({
|
||||
active: (activeTab.index() != -1) ? activeTab.index() : 0,
|
||||
beforeLoad: function(e, ui) {
|
||||
// Disable automatic ajax loading of tabs without matching DOM elements,
|
||||
// Disable automatic ajax loading of tabs without matching DOM elements,
|
||||
// determining if the current URL differs from the tab URL is too error prone.
|
||||
return false;
|
||||
},
|
||||
@ -1338,7 +1338,7 @@ jQuery.noConflict();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Ensure hash links are prefixed with the current page URL,
|
||||
* otherwise jQuery interprets them as being external.
|
||||
@ -1353,7 +1353,7 @@ jQuery.noConflict();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}(jQuery));
|
||||
|
||||
var statusMessage = function(text, type) {
|
||||
|
@ -149,7 +149,7 @@ When a string is literal (contains no variable substitutions), the apostrophe or
|
||||
When a literal string itself contains apostrophes, it is permitted to demarcate the string with quotation marks or "double quotes".
|
||||
|
||||
:::php
|
||||
$greeting = "She said 'hello'";
|
||||
$greeting = "They said 'hello'";
|
||||
|
||||
This syntax is preferred over escaping apostrophes as it is much easier to read.
|
||||
|
||||
|
@ -222,7 +222,7 @@ In order for this to work, the CMS templates declare certain sections as "PJAX f
|
||||
through a `data-pjax-fragment` attribute. These names correlate to specific
|
||||
rendering logic in the PHP controllers, through the `[api:PjaxResponseNegotiator]` class.
|
||||
|
||||
Through a custom `X-Pjax` HTTP header, the client can declare which view he's expecting,
|
||||
Through a custom `X-Pjax` HTTP header, the client can declare which view they're expecting,
|
||||
through identifiers like `CurrentForm` or `Content` (see `[api:LeftAndMain->getResponseNegotiator()]`).
|
||||
These identifiers are passed to `loadPanel()` via the `pjax` data option.
|
||||
The HTTP response is a JSON object literal, with template replacements keyed by their Pjax fragment.
|
||||
|
@ -156,4 +156,4 @@ cases.
|
||||
## Summary ##
|
||||
|
||||
The code presented gives you a fully functioning alternating button, similar to the defaults that come with the the CMS.
|
||||
These alternating buttons can be used to give user the advantage of visual feedback upon his actions.
|
||||
These alternating buttons can be used to give user the advantage of visual feedback upon their actions.
|
||||
|
@ -35,14 +35,14 @@ Thanks to Rutger de Jong for reporting.
|
||||
Severity: Moderate
|
||||
|
||||
Autologin tokens (remember me and reset password) are stored in the database as a plain text.
|
||||
If attacker obtained the database he would be able to gain access to accounts that have requested a password change, or have "remember me" enabled.
|
||||
If attacker obtained the database they would be able to gain access to accounts that have requested a password change, or have "remember me" enabled.
|
||||
|
||||
### Security: Privilege escalation through profile form
|
||||
|
||||
Severity: Moderate
|
||||
|
||||
A logged-in CMS user can gain additional privileges by crafting a request
|
||||
to his/her profile form which resets another user's password.
|
||||
to their profile form which resets another user's password.
|
||||
This method can potentially be used by CSRF attacks as well.
|
||||
Thanks to Nathaniel Carew (Sense of Security) for reporting.
|
||||
|
||||
|
@ -607,9 +607,7 @@ when using deprecated functionality (through the new `Deprecation` class).
|
||||
* 2012-04-12 [e9dc610](https://github.com/silverstripe/sapphire/commit/e9dc610) API-CHANGE: new GridFieldFooter component (Julian Seidenberg)
|
||||
* 2012-04-10 [9888f98](https://github.com/silverstripe/silverstripe-cms/commit/9888f98) ENHANCMENT: Link pages in reports to cms edit (Andrew O'Neil)
|
||||
* 2012-04-10 [1516934](https://github.com/silverstripe/silverstripe-cms/commit/1516934) Revert "BUGFIX: SSF-168 fixing rendering issue in Chrome, which displays extra control at the bottom of the window in a report that is of a certain length" (Julian Seidenberg)
|
||||
* 2012-04-06 [797d526](https://github.com/silverstripe/sapphire/commit/797d526) For png images with transparency, the imagesaveaplpha() needs to be set to true on the source image in order for
|
||||
|
||||
he alpha to be preserved when using the modifier methods. (jmwohl)
|
||||
* 2012-04-06 [797d526](https://github.com/silverstripe/sapphire/commit/797d526) For png images with transparency, the imagesaveaplpha() needs to be set to true on the source image in order for the alpha to be preserved when using the modifier methods. (jmwohl)
|
||||
* 2012-04-05 [e76913f](https://github.com/silverstripe/sapphire/commit/e76913f) API-CHANGE: adding a default option of null to the $args argument in DataExtension::add_to_class. The args argument isn't used anywhere in the class and adding a third argument to every call to this function is tedious. (Julian Seidenberg)
|
||||
* 2012-04-04 [5826b36](https://github.com/silverstripe/sapphire/commit/5826b36) ENHACEMENT: SSF-168 updated the font for titles on print stylesheets (Felipe Skroski)
|
||||
* 2012-04-04 [349a04d](https://github.com/silverstripe/silverstripe-cms/commit/349a04d) API-CHANGE: SSF-168 changing the API/code-conventions for excluding specific reports. get_reports method now returns an ArrayList instead of an array of SS_Reports. (Julian Seidenberg)
|
||||
|
@ -22,7 +22,7 @@ Thanks to Rutger de Jong for reporting.
|
||||
Severity: Moderate
|
||||
|
||||
A logged-in CMS user can gain additional privileges by crafting a request
|
||||
to his/her profile form which resets another user's password.
|
||||
to their profile form which resets another user's password.
|
||||
This method can potentially be used by CSRF attacks as well.
|
||||
Thanks to Nathaniel Carew (Sense of Security) for reporting.
|
||||
|
||||
|
@ -25,7 +25,7 @@ API changes related to the below security patch:
|
||||
Severity: Moderate
|
||||
|
||||
Autologin tokens (remember me and reset password) are stored in the database as a plain text.
|
||||
If attacker obtained the database he would be able to gain access to accounts that have requested a password change, or have "remember me" enabled.
|
||||
If attacker obtained the database they would be able to gain access to accounts that have requested a password change, or have "remember me" enabled.
|
||||
|
||||
## Changelog
|
||||
|
||||
|
@ -119,7 +119,7 @@ translators.
|
||||
### I'm seeing lots of duplicated translations, what should I do?
|
||||
|
||||
For now, please translate all duplications - sometimes they might be intentional, but mostly the developer just didn't
|
||||
know his phrase was already translated. Please contact us about any duplicates that might be worth merging.
|
||||
know their phrase was already translated. Please contact us about any duplicates that might be worth merging.
|
||||
|
||||
### What happened to translate.silverstripe.org?
|
||||
|
||||
|
@ -60,11 +60,11 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
);
|
||||
|
||||
private static $has_one = array();
|
||||
|
||||
|
||||
private static $has_many = array();
|
||||
|
||||
|
||||
private static $many_many = array();
|
||||
|
||||
|
||||
private static $many_many_extraFields = array();
|
||||
|
||||
private static $default_sort = '"Surname", "FirstName"';
|
||||
@ -72,7 +72,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
private static $indexes = array(
|
||||
'Email' => true,
|
||||
//Removed due to duplicate null values causing MSSQL problems
|
||||
//'AutoLoginHash' => Array('type'=>'unique', 'value'=>'AutoLoginHash', 'ignoreNulls'=>true)
|
||||
//'AutoLoginHash' => Array('type'=>'unique', 'value'=>'AutoLoginHash', 'ignoreNulls'=>true)
|
||||
);
|
||||
|
||||
/**
|
||||
@ -80,7 +80,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
* @var boolean
|
||||
*/
|
||||
private static $notify_password_change = false;
|
||||
|
||||
|
||||
/**
|
||||
* All searchable database columns
|
||||
* in this object, currently queried
|
||||
@ -97,7 +97,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
'Surname',
|
||||
'Email',
|
||||
);
|
||||
|
||||
|
||||
private static $summary_fields = array(
|
||||
'FirstName',
|
||||
'Surname',
|
||||
@ -122,13 +122,13 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
'Salt',
|
||||
'NumVisit'
|
||||
);
|
||||
|
||||
|
||||
/**
|
||||
* @config
|
||||
* @var Array See {@link set_title_columns()}
|
||||
*/
|
||||
private static $title_format = null;
|
||||
|
||||
|
||||
/**
|
||||
* The unique field used to identify this member.
|
||||
* By default, it's "Email", but another common
|
||||
@ -138,13 +138,13 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
* @var string
|
||||
*/
|
||||
private static $unique_identifier_field = 'Email';
|
||||
|
||||
|
||||
/**
|
||||
* @config
|
||||
* {@link PasswordValidator} object for validating user's password
|
||||
*/
|
||||
private static $password_validator = null;
|
||||
|
||||
|
||||
/**
|
||||
* @config
|
||||
* The number of days that a password should be valid for.
|
||||
@ -155,8 +155,8 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
/**
|
||||
* @config
|
||||
* @var Int Number of incorrect logins after which
|
||||
* the user is blocked from further attempts for the timespan
|
||||
* defined in {@link $lock_out_delay_mins}.
|
||||
* the user is blocked from further attempts for the timespan
|
||||
* defined in {@link $lock_out_delay_mins}.
|
||||
*/
|
||||
private static $lock_out_after_incorrect_logins = 10;
|
||||
|
||||
@ -166,7 +166,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
* Only applies if {@link $lock_out_after_incorrect_logins} greater than 0.
|
||||
*/
|
||||
private static $lock_out_delay_mins = 15;
|
||||
|
||||
|
||||
/**
|
||||
* @config
|
||||
* @var String If this is set, then a session cookie with the given name will be set on log-in,
|
||||
@ -177,7 +177,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
/**
|
||||
* Indicates that when a {@link Member} logs in, Member:session_regenerate_id()
|
||||
* should be called as a security precaution.
|
||||
*
|
||||
*
|
||||
* This doesn't always work, especially if you're trying to set session cookies
|
||||
* across an entire site using the domain parameter to session_set_cookie_params()
|
||||
*
|
||||
@ -218,7 +218,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
parent::populateDefaults();
|
||||
$this->Locale = i18n::get_closest_translation(i18n::get_locale());
|
||||
}
|
||||
|
||||
|
||||
public function requireDefaultRecords() {
|
||||
parent::requireDefaultRecords();
|
||||
// Default groups should've been built by Group->requireDefaultRecords() already
|
||||
@ -233,7 +233,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
public static function default_admin() {
|
||||
// Check if set
|
||||
if(!Security::has_default_admin()) return null;
|
||||
|
||||
|
||||
// Find or create ADMIN group
|
||||
singleton('Group')->requireDefaultRecords();
|
||||
$adminGroup = Permission::get_groups_by_permission('ADMIN')->First();
|
||||
@ -264,13 +264,13 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
* If this is called, then a session cookie will be set to "1" whenever a user
|
||||
* logs in. This lets 3rd party tools, such as apache's mod_rewrite, detect
|
||||
* whether a user is logged in or not and alter behaviour accordingly.
|
||||
*
|
||||
*
|
||||
* One known use of this is to bypass static caching for logged in users. This is
|
||||
* done by putting this into _config.php
|
||||
* <pre>
|
||||
* Member::set_login_marker_cookie("SS_LOGGED_IN");
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* And then adding this condition to each of the rewrite rules that make use of
|
||||
* the static cache.
|
||||
* <pre>
|
||||
@ -283,7 +283,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
public static function set_login_marker_cookie($cookieName) {
|
||||
Deprecation::notice('3.2', 'Use the "Member.login_marker_cookie" config setting instead');
|
||||
self::config()->login_marker_cookie = $cookieName;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the passed password matches the stored one (if the member is not locked out).
|
||||
@ -304,7 +304,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
|
||||
$e = PasswordEncryptor::create_for_algorithm($this->PasswordEncryption);
|
||||
if(!$e->check($this->Password, $password, $this->Salt, $this)) {
|
||||
$iidentifierField =
|
||||
$iidentifierField =
|
||||
$result->error(_t (
|
||||
'Member.ERRORWRONGCRED',
|
||||
'The provided details don\'t seem to be correct. Please try again.'
|
||||
@ -350,7 +350,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
|
||||
/**
|
||||
* Regenerate the session_id.
|
||||
* This wrapper is here to make it easier to disable calls to session_regenerate_id(), should you need to.
|
||||
* This wrapper is here to make it easier to disable calls to session_regenerate_id(), should you need to.
|
||||
* They have caused problems in certain
|
||||
* quirky problems (such as using the Windmill 0.3.6 proxy).
|
||||
*/
|
||||
@ -359,15 +359,15 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
|
||||
// This can be called via CLI during testing.
|
||||
if(Director::is_cli()) return;
|
||||
|
||||
|
||||
$file = '';
|
||||
$line = '';
|
||||
|
||||
|
||||
// @ is to supress win32 warnings/notices when session wasn't cleaned up properly
|
||||
// There's nothing we can do about this, because it's an operating system function!
|
||||
if(!headers_sent($file, $line)) @session_regenerate_id(true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the field used for uniquely identifying a member
|
||||
* in the database. {@see Member::$unique_identifier_field}
|
||||
@ -379,7 +379,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
Deprecation::notice('3.2', 'Use the "Member.unique_identifier_field" config setting instead');
|
||||
return Member::config()->unique_identifier_field;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the field used for uniquely identifying a member
|
||||
* in the database. {@see Member::$unique_identifier_field}
|
||||
@ -391,14 +391,14 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
Deprecation::notice('3.2', 'Use the "Member.unique_identifier_field" config setting instead');
|
||||
Member::config()->unique_identifier_field = $field;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set a {@link PasswordValidator} object to use to validate member's passwords.
|
||||
*/
|
||||
public static function set_password_validator($pv) {
|
||||
self::$password_validator = $pv;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current {@link PasswordValidator}
|
||||
*/
|
||||
@ -416,7 +416,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
Deprecation::notice('3.2', 'Use the "Member.password_expiry_days" config setting instead');
|
||||
self::config()->password_expiry_days = $days;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Configure the security system to lock users out after this many incorrect logins
|
||||
*
|
||||
@ -426,8 +426,8 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
Deprecation::notice('3.2', 'Use the "Member.lock_out_after_incorrect_logins" config setting instead');
|
||||
self::config()->lock_out_after_incorrect_logins = $numLogins;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function isPasswordExpired() {
|
||||
if(!$this->PasswordExpiry) return false;
|
||||
return strtotime(date('Y-m-d')) >= strtotime($this->PasswordExpiry);
|
||||
@ -461,10 +461,10 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
Cookie::set('alc_enc', null);
|
||||
Cookie::force_expiry('alc_enc');
|
||||
}
|
||||
|
||||
|
||||
// Clear the incorrect log-in count
|
||||
$this->registerSuccessfulLogin();
|
||||
|
||||
|
||||
// Don't set column if its not built yet (the login might be precursor to a /dev/build...)
|
||||
if(array_key_exists('LockedOutUntil', DB::fieldList('Member'))) {
|
||||
$this->LockedOutUntil = null;
|
||||
@ -473,7 +473,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
$this->regenerateTempID();
|
||||
|
||||
$this->write();
|
||||
|
||||
|
||||
// Audit logging hook
|
||||
$this->extend('memberLoggedIn');
|
||||
}
|
||||
@ -497,7 +497,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
* Check if the member ID logged in session actually
|
||||
* has a database record of the same ID. If there is
|
||||
* no logged in user, FALSE is returned anyway.
|
||||
*
|
||||
*
|
||||
* @return boolean TRUE record found FALSE no record found
|
||||
*/
|
||||
public static function logged_in_session_exists() {
|
||||
@ -506,10 +506,10 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
if($member->exists()) return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Log the user in if the "remember login" cookie is set
|
||||
*
|
||||
@ -519,7 +519,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
public static function autoLogin() {
|
||||
// Don't bother trying this multiple times
|
||||
self::$_already_tried_to_auto_log_in = true;
|
||||
|
||||
|
||||
if(strpos(Cookie::get('alc_enc'), ':') && !Session::get("loggedInAs")) {
|
||||
list($uid, $token) = explode(':', Cookie::get('alc_enc'), 2);
|
||||
$SQL_uid = Convert::raw2sql($uid);
|
||||
@ -541,7 +541,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
if(Member::config()->login_marker_cookie) {
|
||||
Cookie::set(Member::config()->login_marker_cookie, 1, 0, null, null, false, true);
|
||||
}
|
||||
|
||||
|
||||
$generator = new RandomGenerator();
|
||||
$token = $generator->randomToken('sha1');
|
||||
$hash = $member->encryptWithUserSettings($token);
|
||||
@ -550,7 +550,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
|
||||
$member->NumVisit++;
|
||||
$member->write();
|
||||
|
||||
|
||||
// Audit logging hook
|
||||
$member->extend('memberAutoLoggedIn');
|
||||
}
|
||||
@ -574,12 +574,12 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
Cookie::set('alc_enc', null); // // Clear the Remember Me cookie
|
||||
Cookie::force_expiry('alc_enc');
|
||||
|
||||
// Switch back to live in order to avoid infinite loops when
|
||||
// Switch back to live in order to avoid infinite loops when
|
||||
// redirecting to the login screen (if this login screen is versioned)
|
||||
Session::clear('readingMode');
|
||||
|
||||
$this->write();
|
||||
|
||||
|
||||
// Audit logging hook
|
||||
$this->extend('memberLoggedOut');
|
||||
}
|
||||
@ -685,7 +685,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
|
||||
/**
|
||||
* Returns the fields for the member form - used in the registration/profile module.
|
||||
* It should return fields that are editable by the admin and the logged-in user.
|
||||
* It should return fields that are editable by the admin and the logged-in user.
|
||||
*
|
||||
* @return FieldList Returns a {@link FieldList} containing the fields for
|
||||
* the member form.
|
||||
@ -746,7 +746,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
return Member::get()->byId($id);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns true if the current member is a repeat visitor who has logged in more than once.
|
||||
*/
|
||||
@ -803,14 +803,14 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
if($this->SetPassword) $this->Password = $this->SetPassword;
|
||||
|
||||
// If a member with the same "unique identifier" already exists with a different ID, don't allow merging.
|
||||
// Note: This does not a full replacement for safeguards in the controller layer (e.g. in a registration form),
|
||||
// Note: This does not a full replacement for safeguards in the controller layer (e.g. in a registration form),
|
||||
// but rather a last line of defense against data inconsistencies.
|
||||
$identifierField = Member::config()->unique_identifier_field;
|
||||
if($this->$identifierField) {
|
||||
// Note: Same logic as Member_Validator class
|
||||
$idClause = ($this->ID) ? sprintf(" AND \"Member\".\"ID\" <> %d", (int)$this->ID) : '';
|
||||
$existingRecord = DataObject::get_one(
|
||||
'Member',
|
||||
'Member',
|
||||
sprintf(
|
||||
"\"%s\" = '%s' %s",
|
||||
$identifierField,
|
||||
@ -820,8 +820,8 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
);
|
||||
if($existingRecord) {
|
||||
throw new ValidationException(new ValidationResult(false, _t(
|
||||
'Member.ValidationIdentifierFailed',
|
||||
'Can\'t overwrite existing member #{id} with identical identifier ({name} = {value}))',
|
||||
'Member.ValidationIdentifierFailed',
|
||||
'Can\'t overwrite existing member #{id} with identical identifier ({name} = {value}))',
|
||||
'Values in brackets show "fieldname = value", usually denoting an existing email address',
|
||||
array(
|
||||
'id' => $existingRecord->ID,
|
||||
@ -835,9 +835,9 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
// We don't send emails out on dev/tests sites to prevent accidentally spamming users.
|
||||
// However, if TestMailer is in use this isn't a risk.
|
||||
if(
|
||||
(Director::isLive() || Email::mailer() instanceof TestMailer)
|
||||
(Director::isLive() || Email::mailer() instanceof TestMailer)
|
||||
&& $this->isChanged('Password')
|
||||
&& $this->record['Password']
|
||||
&& $this->record['Password']
|
||||
&& $this->config()->notify_password_change
|
||||
) {
|
||||
$e = Member_ChangePasswordEmail::create();
|
||||
@ -879,10 +879,10 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
if(!$this->Locale) {
|
||||
$this->Locale = i18n::get_locale();
|
||||
}
|
||||
|
||||
|
||||
parent::onBeforeWrite();
|
||||
}
|
||||
|
||||
|
||||
public function onAfterWrite() {
|
||||
parent::onAfterWrite();
|
||||
|
||||
@ -890,10 +890,10 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
MemberPassword::log($this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If any admin groups are requested, deny the whole save operation.
|
||||
*
|
||||
*
|
||||
* @param Array $ids Database IDs of Group records
|
||||
* @return boolean
|
||||
*/
|
||||
@ -921,7 +921,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
if($groups) foreach($groups as $group) {
|
||||
if($this->inGroup($group, $strict)) return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -944,9 +944,9 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
} else {
|
||||
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;
|
||||
@ -954,32 +954,32 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds the member to a group. This will create the group if the given
|
||||
* group code does not return a valid group object.
|
||||
* Adds the member to a group. This will create the group if the given
|
||||
* group code does not return a valid group object.
|
||||
*
|
||||
* @param string $groupcode
|
||||
* @param string Title of the group
|
||||
*/
|
||||
public function addToGroupByCode($groupcode, $title = "") {
|
||||
$group = DataObject::get_one('Group', "\"Code\" = '" . Convert::raw2sql($groupcode). "'");
|
||||
|
||||
|
||||
if($group) {
|
||||
$this->Groups()->add($group);
|
||||
}
|
||||
else {
|
||||
if(!$title) $title = $groupcode;
|
||||
|
||||
|
||||
$group = new Group();
|
||||
$group->Code = $groupcode;
|
||||
$group->Title = $title;
|
||||
$group->write();
|
||||
|
||||
|
||||
$this->Groups()->add($group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Removes a member from a group.
|
||||
*
|
||||
@ -987,12 +987,12 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
*/
|
||||
public function removeFromGroupByCode($groupcode) {
|
||||
$group = Group::get()->filter(array('Code' => $groupcode))->first();
|
||||
|
||||
|
||||
if($group) {
|
||||
$this->Groups()->remove($group);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Array $columns Column names on the Member record to show in {@link getTitle()}.
|
||||
* @param String $sep Separator
|
||||
@ -1007,7 +1007,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
/**
|
||||
* Get the complete name of the member, by default in the format "<Surname>, <FirstName>".
|
||||
* Falls back to showing either field on its own.
|
||||
*
|
||||
*
|
||||
* You can overload this getter with {@link set_title_format()}
|
||||
* and {@link set_title_sql()}.
|
||||
*
|
||||
@ -1041,7 +1041,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
/**
|
||||
* Return a SQL CONCAT() fragment suitable for a SELECT statement.
|
||||
* Useful for custom queries which assume a certain member title format.
|
||||
*
|
||||
*
|
||||
* @param String $tableName
|
||||
* @return String SQL
|
||||
*/
|
||||
@ -1055,7 +1055,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
foreach($format['columns'] as $column) {
|
||||
$columnsWithTablename[] = "\"$tableName\".\"$column\"";
|
||||
}
|
||||
|
||||
|
||||
return "(".join(" $op '".$format['sep']."' $op ", $columnsWithTablename).")";
|
||||
} else {
|
||||
return "(\"$tableName\".\"Surname\" $op ' ' $op \"$tableName\".\"FirstName\")";
|
||||
@ -1102,7 +1102,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
* Override the default getter for DateFormat so the
|
||||
* default format for the user's locale is used
|
||||
* if the user has not defined their own.
|
||||
*
|
||||
*
|
||||
* @return string ISO date format
|
||||
*/
|
||||
public function getDateFormat() {
|
||||
@ -1120,7 +1120,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
* Override the default getter for TimeFormat so the
|
||||
* default format for the user's locale is used
|
||||
* if the user has not defined their own.
|
||||
*
|
||||
*
|
||||
* @return string ISO date format
|
||||
*/
|
||||
public function getTimeFormat() {
|
||||
@ -1147,7 +1147,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
public function Groups() {
|
||||
$groups = Member_GroupSet::create('Group', 'Group_Members', 'GroupID', 'MemberID');
|
||||
$groups = $groups->forForeignID($this->ID);
|
||||
|
||||
|
||||
$this->extend('updateGroups', $groups);
|
||||
|
||||
return $groups;
|
||||
@ -1159,19 +1159,19 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
public function DirectGroups() {
|
||||
return $this->getManyManyComponents('Groups');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a member SQLMap of members in specific groups
|
||||
*
|
||||
*
|
||||
* If no $groups is passed, all members will be returned
|
||||
*
|
||||
*
|
||||
* @param mixed $groups - takes a SS_List, an array or a single Group.ID
|
||||
* @return SQLMap Returns an SQLMap that returns all Member data.
|
||||
* @see map()
|
||||
*/
|
||||
public static function map_in_groups($groups = null) {
|
||||
$groupIDList = array();
|
||||
|
||||
|
||||
if($groups instanceof SS_List) {
|
||||
foreach( $groups as $group ) {
|
||||
$groupIDList[] = $group->ID;
|
||||
@ -1181,18 +1181,18 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
} elseif($groups) {
|
||||
$groupIDList[] = $groups;
|
||||
}
|
||||
|
||||
|
||||
// No groups, return all Members
|
||||
if(!$groupIDList) {
|
||||
return Member::get()->sort(array('Surname'=>'ASC', 'FirstName'=>'ASC'))->map();
|
||||
}
|
||||
|
||||
|
||||
$membersList = new ArrayList();
|
||||
// This is a bit ineffective, but follow the ORM style
|
||||
foreach(Group::get()->byIDs($groupIDList) as $group) {
|
||||
$membersList->merge($group->Members());
|
||||
}
|
||||
|
||||
|
||||
$membersList->removeDuplicates('ID');
|
||||
return $membersList->map();
|
||||
}
|
||||
@ -1211,19 +1211,19 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
public static function mapInCMSGroups($groups = null) {
|
||||
if(!$groups || $groups->Count() == 0) {
|
||||
$perms = array('ADMIN', 'CMS_ACCESS_AssetAdmin');
|
||||
|
||||
|
||||
if(class_exists('CMSMain')) {
|
||||
$cmsPerms = singleton('CMSMain')->providePermissions();
|
||||
} else {
|
||||
$cmsPerms = singleton('LeftAndMain')->providePermissions();
|
||||
}
|
||||
|
||||
|
||||
if(!empty($cmsPerms)) {
|
||||
$perms = array_unique(array_merge($perms, array_keys($cmsPerms)));
|
||||
}
|
||||
|
||||
|
||||
$SQL_perms = "'" . implode("', '", Convert::raw2sql($perms)) . "'";
|
||||
|
||||
|
||||
$groups = DataObject::get('Group')
|
||||
->innerJoin(
|
||||
"Permission",
|
||||
@ -1244,7 +1244,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
$filterClause = ($groupIDList)
|
||||
? "\"GroupID\" IN (" . implode( ',', $groupIDList ) . ")"
|
||||
: "";
|
||||
|
||||
|
||||
return Member::get()->where($filterClause)->sort("\"Surname\", \"FirstName\"")
|
||||
->innerJoin("Group_Members", "\"MemberID\"=\"Member\".\"ID\"")
|
||||
->innerJoin("Group", "\"Group\".\"ID\"=\"GroupID\"")
|
||||
@ -1272,7 +1272,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
unset($groupList[$index]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $groupList;
|
||||
}
|
||||
|
||||
@ -1292,10 +1292,10 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
$mainFields = $fields->fieldByName("Root")->fieldByName("Main")->Children;
|
||||
|
||||
$password = new ConfirmedPasswordField(
|
||||
'Password',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'Password',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
true // showOnClick
|
||||
);
|
||||
$password->setCanBeEmpty(true);
|
||||
@ -1303,12 +1303,12 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
$mainFields->replaceField('Password', $password);
|
||||
|
||||
$mainFields->replaceField('Locale', new DropdownField(
|
||||
"Locale",
|
||||
_t('Member.INTERFACELANG', "Interface Language", 'Language of the CMS'),
|
||||
"Locale",
|
||||
_t('Member.INTERFACELANG', "Interface Language", 'Language of the CMS'),
|
||||
i18n::get_existing_translations()
|
||||
));
|
||||
$mainFields->removeByName($self->config()->hidden_fields);
|
||||
|
||||
|
||||
if( ! $self->config()->lock_out_after_incorrect_logins) {
|
||||
$mainFields->removeByName('FailedLoginCount');
|
||||
}
|
||||
@ -1333,7 +1333,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
->setMultiple(true)
|
||||
->setSource($groupsMap)
|
||||
->setAttribute(
|
||||
'data-placeholder',
|
||||
'data-placeholder',
|
||||
_t('Member.ADDGROUP', 'Add group', 'Placeholder text for a dropdown')
|
||||
)
|
||||
);
|
||||
@ -1357,7 +1357,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
|
||||
$permissionsTab = $fields->fieldByName("Root")->fieldByName('Permissions');
|
||||
if($permissionsTab) $permissionsTab->addExtraClass('readonly');
|
||||
|
||||
|
||||
$defaultDateFormat = Zend_Locale_Format::getDateFormat(new Zend_Locale($self->Locale));
|
||||
$dateFormatMap = array(
|
||||
'MMM d, yyyy' => Zend_Date::now()->toString('MMM d, yyyy'),
|
||||
@ -1375,7 +1375,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
)
|
||||
);
|
||||
$dateFormatField->setValue($self->DateFormat);
|
||||
|
||||
|
||||
$defaultTimeFormat = Zend_Locale_Format::getTimeFormat(new Zend_Locale($self->Locale));
|
||||
$timeFormatMap = array(
|
||||
'h:mm a' => Zend_Date::now()->toString('h:mm a'),
|
||||
@ -1392,18 +1392,18 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
);
|
||||
$timeFormatField->setValue($self->TimeFormat);
|
||||
});
|
||||
|
||||
|
||||
return parent::getCMSFields();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields
|
||||
*
|
||||
*
|
||||
*/
|
||||
public function fieldLabels($includerelations = true) {
|
||||
$labels = parent::fieldLabels($includerelations);
|
||||
|
||||
|
||||
$labels['FirstName'] = _t('Member.FIRSTNAME', 'First Name');
|
||||
$labels['Surname'] = _t('Member.SURNAME', 'Surname');
|
||||
$labels['Email'] = _t('Member.EMAIL', 'Email');
|
||||
@ -1421,7 +1421,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
}
|
||||
return $labels;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Users can view their own record.
|
||||
* Otherwise they'll need ADMIN or CMS_ACCESS_SecurityAdmin permissions.
|
||||
@ -1429,54 +1429,54 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
*/
|
||||
public function canView($member = null) {
|
||||
if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
|
||||
|
||||
|
||||
// extended access checks
|
||||
$results = $this->extend('canView', $member);
|
||||
if($results && is_array($results)) {
|
||||
if(!min($results)) return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
|
||||
// members can usually edit their own record
|
||||
if($member && $this->ID == $member->ID) return true;
|
||||
|
||||
|
||||
if(
|
||||
Permission::checkMember($member, 'ADMIN')
|
||||
|| Permission::checkMember($member, 'CMS_ACCESS_SecurityAdmin')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Users can edit their own record.
|
||||
* Otherwise they'll need ADMIN or CMS_ACCESS_SecurityAdmin permissions
|
||||
*/
|
||||
public function canEdit($member = null) {
|
||||
if(!$member || !(is_a($member, 'Member')) || is_numeric($member)) $member = Member::currentUser();
|
||||
|
||||
|
||||
// extended access checks
|
||||
$results = $this->extend('canEdit', $member);
|
||||
if($results && is_array($results)) {
|
||||
if(!min($results)) return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
|
||||
// No member found
|
||||
if(!($member && $member->exists())) return false;
|
||||
|
||||
|
||||
// If the requesting member is not an admin, but has access to manage members,
|
||||
// he still can't edit other members with ADMIN permission.
|
||||
// This is a bit weak, strictly speaking he shouldn't be allowed to
|
||||
// they still can't edit other members with ADMIN permission.
|
||||
// This is a bit weak, strictly speaking they shouldn't be allowed to
|
||||
// perform any action that could change the password on a member
|
||||
// with "higher" permissions than himself, but thats hard to determine.
|
||||
// with "higher" permissions than himself, but thats hard to determine.
|
||||
if(!Permission::checkMember($member, 'ADMIN') && Permission::checkMember($this, 'ADMIN')) return false;
|
||||
|
||||
return $this->canView($member);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Users can edit their own record.
|
||||
* Otherwise they'll need ADMIN or CMS_ACCESS_SecurityAdmin permissions
|
||||
@ -1497,7 +1497,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
// Members are not allowed to remove themselves,
|
||||
// since it would create inconsistencies in the admin UIs.
|
||||
if($this->ID && $member->ID == $this->ID) return false;
|
||||
|
||||
|
||||
return $this->canEdit($member);
|
||||
}
|
||||
|
||||
@ -1507,7 +1507,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
*/
|
||||
public function validate() {
|
||||
$valid = parent::validate();
|
||||
|
||||
|
||||
if(!$this->ID || $this->isChanged('Password')) {
|
||||
if($this->Password && self::$password_validator) {
|
||||
$valid->combineAnd(self::$password_validator->validate($this->Password, $this));
|
||||
@ -1521,26 +1521,26 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Change password. This will cause rehashing according to
|
||||
* the `PasswordEncryption` property.
|
||||
*
|
||||
*
|
||||
* @param String $password Cleartext password
|
||||
*/
|
||||
public function changePassword($password) {
|
||||
$this->Password = $password;
|
||||
$valid = $this->validate();
|
||||
|
||||
|
||||
if($valid->valid()) {
|
||||
$this->AutoLoginHash = null;
|
||||
$this->write();
|
||||
}
|
||||
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Tell this member that someone made a failed attempt at logging in as them.
|
||||
* This can be used to lock the user out temporarily if too many failed attempts are made.
|
||||
@ -1550,7 +1550,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
// Keep a tally of the number of failed log-ins so that we can lock people out
|
||||
$this->FailedLoginCount = $this->FailedLoginCount + 1;
|
||||
$this->write();
|
||||
|
||||
|
||||
if($this->FailedLoginCount >= self::config()->lock_out_after_incorrect_logins) {
|
||||
$lockoutMins = self::config()->lock_out_delay_mins;
|
||||
$this->LockedOutUntil = date('Y-m-d H:i:s', time() + $lockoutMins*60);
|
||||
@ -1569,18 +1569,18 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
$this->write();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the HtmlEditorConfig for this user to be used in the CMS.
|
||||
* This is set by the group. If multiple configurations are set,
|
||||
* the one with the highest priority wins.
|
||||
*
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHtmlEditorConfigForCMS() {
|
||||
$currentName = '';
|
||||
$currentPriority = 0;
|
||||
|
||||
|
||||
foreach($this->Groups() as $group) {
|
||||
$configName = $group->HtmlEditorConfig;
|
||||
if($configName) {
|
||||
@ -1591,7 +1591,7 @@ class Member extends DataObject implements TemplateGlobalProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// If can't find a suitable editor, just default to cms
|
||||
return $currentName ? $currentName : 'cms';
|
||||
}
|
||||
@ -1622,7 +1622,7 @@ class Member_GroupSet extends ManyManyList {
|
||||
$this->foreignKey = $foreignKey;
|
||||
$this->extraFields = $extraFields;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Link this group set to a specific member.
|
||||
*/
|
||||
@ -1640,7 +1640,7 @@ class Member_GroupSet extends ManyManyList {
|
||||
$groupIDs = DataObject::get("Group")->byIDs($groupIDs)->column("ParentID");
|
||||
$groupIDs = array_filter($groupIDs);
|
||||
}
|
||||
|
||||
|
||||
// Add a filter to this DataList
|
||||
if($allGroupIDs) {
|
||||
return "\"Group\".\"ID\" IN (" . implode(',', $allGroupIDs) .")";
|
||||
@ -1667,7 +1667,7 @@ class Member_ChangePasswordEmail extends Email {
|
||||
protected $from = ''; // setting a blank from address uses the site's default administrator email
|
||||
protected $subject = '';
|
||||
protected $ss_template = 'ChangePasswordEmail';
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
@ -1687,7 +1687,7 @@ class Member_ForgotPasswordEmail extends Email {
|
||||
protected $from = ''; // setting a blank from address uses the site's default administrator email
|
||||
protected $subject = '';
|
||||
protected $ss_template = 'ForgotPasswordEmail';
|
||||
|
||||
|
||||
public function __construct() {
|
||||
parent::__construct();
|
||||
|
||||
@ -1742,7 +1742,7 @@ class Member_Validator extends RequiredFields {
|
||||
*/
|
||||
public function php($data) {
|
||||
$valid = parent::php($data);
|
||||
|
||||
|
||||
$identifierField = Member::config()->unique_identifier_field;
|
||||
$SQL_identifierField = Convert::raw2sql($data[$identifierField]);
|
||||
$member = DataObject::get_one('Member', "\"$identifierField\" = '{$SQL_identifierField}'");
|
||||
|
@ -223,7 +223,7 @@ JS;
|
||||
return $this->controller->redirect(Director::absoluteBaseURL() . Security::config()->default_login_dest);
|
||||
}
|
||||
|
||||
// Redirect the user to the page where he came from
|
||||
// Redirect the user to the page where they came from
|
||||
$member = Member::currentUser();
|
||||
if($member) {
|
||||
$firstname = Convert::raw2xml($member->FirstName);
|
||||
|
@ -1,22 +1,22 @@
|
||||
<?php
|
||||
|
||||
class GridFieldEditButtonTest extends SapphireTest {
|
||||
|
||||
|
||||
/** @var ArrayList */
|
||||
protected $list;
|
||||
|
||||
|
||||
/** @var GridField */
|
||||
protected $gridField;
|
||||
|
||||
|
||||
/** @var Form */
|
||||
protected $form;
|
||||
|
||||
|
||||
/** @var string */
|
||||
protected static $fixture_file = 'GridFieldActionTest.yml';
|
||||
|
||||
/** @var array */
|
||||
protected $extraDataObjects = array('GridFieldAction_Delete_Team', 'GridFieldAction_Edit_Team');
|
||||
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->list = new DataList('GridFieldAction_Edit_Team');
|
||||
@ -24,19 +24,19 @@ class GridFieldEditButtonTest extends SapphireTest {
|
||||
$this->gridField = new GridField('testfield', 'testfield', $this->list, $config);
|
||||
$this->form = new Form(new Controller(), 'mockform', new FieldList(array($this->gridField)), new FieldList());
|
||||
}
|
||||
|
||||
|
||||
public function testShowEditLinks() {
|
||||
if(Member::currentUser()) { Member::currentUser()->logOut(); }
|
||||
|
||||
|
||||
$content = new CSSContentParser($this->gridField->FieldHolder());
|
||||
// Check that there are content
|
||||
$this->assertEquals(3, count($content->getBySelector('.ss-gridfield-item')));
|
||||
// Make sure that there are edit links, even though the user doesn't have "edit" permissions
|
||||
// (he can still view the records)
|
||||
// (they can still view the records)
|
||||
$this->assertEquals(2, count($content->getBySelector('.edit-link')),
|
||||
'Edit links should show when not logged in.');
|
||||
}
|
||||
|
||||
|
||||
public function testShowEditLinksWithAdminPermission() {
|
||||
$this->logInWithPermission('ADMIN');
|
||||
$content = new CSSContentParser($this->gridField->FieldHolder());
|
||||
@ -50,7 +50,7 @@ class GridFieldAction_Edit_Team extends DataObject implements TestOnly {
|
||||
'Name' => 'Varchar',
|
||||
'City' => 'Varchar'
|
||||
);
|
||||
|
||||
|
||||
public function canView($member = null) {
|
||||
return true;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user