FEATURE concurrent editing alerts. if two users are editing a page, the will get a notice. If Bob deletes/saves the page Alice is working on, Alice will be notified.

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/cms/trunk@84231 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Tom Rix 2009-08-12 21:19:04 +00:00
parent c68e3f8875
commit 15d75ad293
2 changed files with 115 additions and 4 deletions

View File

@ -32,6 +32,8 @@ class LeftAndMain extends Controller {
static $tree_class = null;
static $edit_timeout = 180;
static $ForceReload;
static $allowed_actions = array(
@ -48,7 +50,7 @@ class LeftAndMain extends Controller {
'Member_ProfileForm',
'EditorToolbar',
'EditForm',
'pageStatus',
);
/**
@ -343,7 +345,7 @@ class LeftAndMain extends Controller {
}
$form = $this->EditForm();
if($form) return $form->formHtmlContent();
if ($form) return $form->formHtmlContent();
else return "";
}
public function getLastFormIn($html) {
@ -716,6 +718,9 @@ JS;
FormResponse::add("\$('Form_EditForm_StageURLSegment').value = \"{$record->URLSegment}\";");
}
$newVersion = ((int)$record->Version) + 1;
FormResponse::add("\$('Form_EditForm_Version').value = {$newVersion};");
// If the 'Save & Publish' button was clicked, also publish the page
if (isset($urlParams['publish']) && $urlParams['publish'] == 1) {
$record->doPublish();
@ -971,6 +976,8 @@ JS;
DataObject::delete_by_id($this->stat('tree_class'), $id);
$script .= "node = st.getTreeNodeByIdx($id); if(node) node.parentTreeNode.removeTreeNode(node); $('Form_EditForm').closeIfSetTo($id); \n";
if ($id == $this->currentPageID()) FormResponse::add('CurrentPage.isDeleted = 1;');
}
}
FormResponse::add($script);
@ -1038,6 +1045,52 @@ JS;
public function isCurrentPage(DataObject $page) {
return $page->ID == Session::get("{$this->class}.currentPage");
}
/**
* Get the staus of a certain page and version.
*
* This function is used for concurrent editing, and providing alerts
* when multiple users are editing a single page. It echoes a json
* encoded string to the UA.
*/
public function pageStatus() {
// If no ID is set, we're merely keeping the session alive
if (!isset($_REQUEST['ID'])) return 1;
$page = $this->getRecord($_REQUEST['ID']);
if (!$page) {
// Page has not been found
$return = array('status' => 'not_found');
} elseif ($page->getIsDeletedFromStage()) {
// Page has been deleted from stage
$return = array('status' => 'deleted');
} else {
// Mark me as editing if I'm not already
$page->UsersCurrentlyEditing()->add(Member::currentUser());
DB::query("UPDATE SiteTree_UsersCurrentlyEditing SET LastPing = '".date('Y-m-d H:i:s')."'
WHERE MemberID = ".Member::currentUserID()." AND SiteTreeID = {$page->ID}");
// Page exists, who else is editing it?
$names = array();
foreach($page->UsersCurrentlyEditing() as $user) {
if ($user->ID == Member::currentUserId()) continue;
$names[] = trim($user->FirstName . ' ' . $user->Surname);
}
$return = array('status' => 'editing', 'names' => $names);
// Has it been published since the CMS first loaded it?
$usersCurrentVersion = isset($_REQUEST['Version']) ? $_REQUEST['Version'] : $page->Version;
if ($usersCurrentVersion < $page->Version) {
$return = array('status' => 'not_current_version');
}
}
// Delete pings older than 3 minutes from the cache...
DB::query("DELETE FROM SiteTree_UsersCurrentlyEditing WHERE LastPing < '".date('Y-m-d H:i:s', time()-self::$edit_timeout)."'");
echo Convert::array2json($return);
return;
}
/**
* Return the CMS's HTML-editor toolbar

View File

@ -1,4 +1,5 @@
var _AJAX_LOADING = false;
var pagePingInterval = 15;
// Resize the tabs once the document is properly loaded
// @todo most of this file needs to be tidied up using jQuery
@ -865,9 +866,66 @@ function hideIndicator(id) {
Effect.Fade(id, {duration: 0.3});
}
var CurrentPage = {
id: function() { return $('Form_EditForm_ID').value; },
version: function() { return $('Form_EditForm_Version').value; },
isDeleted: function() { return $('SiteTree_Alert').getAttribute('deletedfromstage'); }
}
setInterval(function() {
new Ajax.Request("Security/ping");
}, 5*60*1000);
if ($('Form_EditForm_ID')) {
new Ajax.Request("admin/pageStatus?ID="+CurrentPage.id()+'&Version='+CurrentPage.version(), {
onSuccess: function(t) {
var data = eval('('+t.responseText+')');
var hasAlert = false;
switch(data.status) {
case 'editing':
$('SiteTree_Alert').style.border = '2px solid #B5D4FE';
$('SiteTree_Alert').style.backgroundColor = '#F8FAFC';
if (data.names.length) {
hasAlert = true;
$('SiteTree_Alert').innerHTML = "This page is also being edited by: "+data.names.join(', ');
}
break;
case 'deleted':
// handle deletion by another user (but not us, or if we're already looking at a deleted version)
if (CurrentPage.isDeleted() == 0) {
$('SiteTree_Alert').style.border = '2px solid #ffd324';
$('SiteTree_Alert').style.backgroundColor = '#fff6bf';
$('SiteTree_Alert').innerHTML = "This page has been deleted since you opened it.";
hasAlert = true;
}
break;
case 'not_current_version':
// handle another user publishing
$('SiteTree_Alert').style.border = '2px solid #FFD324';
$('SiteTree_Alert').style.backgroundColor = '#fff6bf';
$('SiteTree_Alert').innerHTML = "This page has been saved since you opened it. You may want to reload it, or risk overwriting changes.";
hasAlert = true;
break;
case 'not_found':
break;
}
if (hasAlert) {
$('SiteTree_Alert').style.padding = '5px';
$('SiteTree_Alert').style.marginBottom = '5px';
$('SiteTree_Alert').style.display = 'block';
} else {
$('SiteTree_Alert').innerHTML = '';
$('SiteTree_Alert').style.padding = '0px';
$('SiteTree_Alert').style.marginBottom = '0px';
if ($('SiteTree_Alert').style.display != 'none') $('SiteTree_Alert').style.display = 'none';
}
}
});
} else {
// We're not looking at a page, so at this stage, we're just
// pinging to keep the session alive.
new Ajax.Request("admin/pageStatus");
}
}, pagePingInterval*1000);
/**
* Find and enable TinyMCE on all htmleditor fields