Wiki enhancement: edit locking (uses AJAX)

This commit is contained in:
sam_marshall 2006-09-25 17:42:32 +00:00
parent f775a510d6
commit 22e846cde2
10 changed files with 291 additions and 11 deletions

View file

@ -94,6 +94,7 @@ $string['linkok'] = 'OK';
$string['linkschecked'] = 'Links checked';
$string['listall'] = 'List all';
$string['listcandidates'] = 'List candidates';
$string['lockcancelled'] = 'Your editing lock has been overridden and somebody else is now editing this page. If you wish to keep your changes, please select and copy them before clicking Cancel; then try to edit again.';
$string['meta'] = 'Meta data';
$string['moduledirectory'] = 'Module Directory';
$string['modulename'] = 'Wiki';
@ -116,10 +117,13 @@ $string['optional'] = 'Optional';
$string['orphanedpage'] = 'Orphaned page';
$string['orphanedpages'] = 'Orphaned pages';
$string['otherwikis'] = 'Other Wikis';
$string['overrideinfo'] = 'You can override this user\'s lock, but doing so may cause them to lose their changes! Please take care.';
$string['overridebutton'] = 'Override lock';
$string['ownerunknown'] = 'unknown';
$string['pageactions'] = 'Page actions';
$string['pageindex'] = 'Page Index';
$string['pageinfo'] = 'Page information';
$string['pagelocked'] = '<p><strong>This page is being edited by $a->name.</strong> They began editing at $a->since and still have the window open as of $a->seen.</p><p>You need to wait for them to finish before you can edit this page.</p>';
$string['pagename'] = 'Page name';
$string['pagenamechoice'] = '- or -';
$string['pageslinkingto'] = 'Pages linking to this page';
@ -179,6 +183,7 @@ $string['viewsmfor'] = 'View sitemap for';
$string['wantedpages'] = 'Wanted pages';
$string['wiki:participate'] = 'Edit wiki pages';
$string['wiki:manage'] = 'Manage wiki settings';
$string['wiki:overridelock'] = 'Override locked pages';
$string['wikidefaultpagename'] = 'WikiIndex';
$string['wikiexport'] = 'Export pages';
$string['wikiexportcomment'] = 'Here you can configure the export to your needs.';

31
mod/wiki/confirmlock.php Normal file
View file

@ -0,0 +1,31 @@
<?php
/**
* This script is called through AJAX. It confirms that a user is still
* trying to edit a page that they have locked (they haven't closed
* their browser window or something).
*
* @copyright &copy; 2006 The Open University
* @author s.marshall@open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package wiki
*//** */
require_once("../../config.php");
header('Content-Type: text/plain');
if(empty($_POST['lockid'])) {
print 'noid';
exit;
}
$lockid=(int)$_POST['lockid'];
if($lock=get_record('wiki_locks','id',$lockid)) {
$lock->lockedseen=time();
update_record('wiki_locks',$lock);
print 'ok';
} else {
print 'cancel'; // Tells user their lock has been cancelled.
}
?>

View file

@ -36,5 +36,21 @@ $mod_wiki_capabilities = array(
'coursecreator' => CAP_PREVENT,
'admin' => CAP_ALLOW
)
),
'mod/wiki:overridelock' => array(
'riskbitmask' => 0,
'captype' => 'write',
'contextlevel' => CONTEXT_MODULE,
'legacy' => array(
'guest' => CAP_PREVENT,
'student' => CAP_PREVENT,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'coursecreator' => CAP_ALLOW,
'admin' => CAP_ALLOW
)
)
);

View file

@ -48,7 +48,7 @@
<INDEX NAME="pagename" UNIQUE="false" FIELDS="pagename" PREVIOUS="userid"/>
</INDEXES>
</TABLE>
<TABLE NAME="wiki_pages" COMMENT="Holds the Wiki-Pages" PREVIOUS="wiki_entries">
<TABLE NAME="wiki_pages" COMMENT="Holds the Wiki-Pages" PREVIOUS="wiki_entries" NEXT="wiki_locks">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="pagename"/>
<FIELD NAME="pagename" TYPE="char" LENGTH="160" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="version"/>
@ -66,11 +66,37 @@
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for wiki_pages" NEXT="wiki"/>
<!-- sam notes: if implemented, this foreign key will break, since the
field defaults to 0 not null and will therefore fail to find its
referent. -->
<KEY NAME="wiki" TYPE="foreign" FIELDS="wiki" REFTABLE="wiki" REFFIELDS="id" PREVIOUS="primary"/>
</KEYS>
<INDEXES>
<INDEX NAME="pagename-version-wiki" UNIQUE="true" FIELDS="pagename, version, wiki"/>
</INDEXES>
</TABLE>
<TABLE NAME="wiki_locks" COMMENT="Stores editing locks on Wiki pages" PREVIOUS="wiki_pages">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true" ENUM="false" NEXT="wikiid"/>
<FIELD NAME="wikiid" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="false" ENUM="false" PREVIOUS="id" NEXT="pagename"/>
<FIELD NAME="pagename" TYPE="char" LENGTH="160" NOTNULL="true" SEQUENCE="false" ENUM="false" PREVIOUS="wikiid" NEXT="lockedby"/>
<!-- If the page is or was locked, this field holds the userid of the locker -->
<FIELD NAME="lockedby" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="pagename" NEXT='lockedsince'/>
<!-- Time (seconds since epoch) at which lock began -->
<FIELD NAME="lockedsince" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="lockedby" NEXT='lockedseen'/>
<!-- Time (seconds since epoch) at which lock was last reconfirmed (we ignore lock if this is >2 mins ago) -->
<FIELD NAME="lockedseen" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" DEFAULT="0" SEQUENCE="false" ENUM="false" PREVIOUS="lockedsince"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="Primary key for wiki_locks" NEXT="wikiid"/>
<KEY NAME="wikiid" TYPE="foreign" FIELDS="wikiid" REFTABLE="wiki" REFFIELDS="id" PREVIOUS="primary"/>
</KEYS>
<INDEXES>
<!-- Main index used for retrieving locks -->
<INDEX NAME="wikiid-pagename" UNIQUE="false" FIELDS="wikiid,pagename" NEXT="lockedseen"/>
<!-- Secondary index used only during cron for deleting expired locks -->
<INDEX NAME="lockedseen" UNIQUE="false" FIELDS="lockedseen" PREVIOUS="wikiid-pagename"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>

View file

@ -160,7 +160,21 @@ function wiki_upgrade($oldversion) {
execute_sql("UPDATE {$CFG->prefix}wiki SET initialcontent='' WHERE initialcontent IS NULL");
table_column('wiki','initialcontent','initialcontent','varchar','255','','','not null');
}
if ($oldversion < 2006092502) {
modify_database("","
CREATE TABLE prefix_wiki_locks
(
id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
wikiid INT(10) UNSIGNED NOT NULL,
pagename VARCHAR(160) NOT NULL DEFAULT '',
lockedby INT(10) NOT NULL DEFAULT 0,
lockedsince INT(10) NOT NULL DEFAULT 0,
lockedseen INT(10) NOT NULL DEFAULT 0,
PRIMARY KEY(id),
UNIQUE KEY wiki_locks_uk(wikiid,pagename),
INDEX wiki_locks_ix(lockedseen)
);");
}
return true;
}

View file

@ -156,6 +156,21 @@ function wiki_upgrade($oldversion) {
ALTER COLUMN refs DROP NOT NULL');
}
if ($oldversion < 2006092502) {
modify_database("","
CREATE TABLE prefix_wiki_locks
(
id SERIAL PRIMARY KEY,
wikiid INTEGER NOT NULL,
pagename VARCHAR(160) NOT NULL DEFAULT '',
lockedby INTEGER NOT NULL DEFAULT 0,
lockedsince INTEGER NOT NULL DEFAULT 0,
lockedseen INTEGER NOT NULL DEFAULT 0
);");
modify_database("","CREATE INDEX prefix_wikilock_loc_ix ON prefix_wiki_locks (lockedseen);");
modify_database("","CREATE UNIQUE INDEX prefix_wikilock_wikpag_uix ON prefix_wiki_locks (wikiid, pagename);");
}
return true;
}

View file

@ -11,6 +11,13 @@ $WIKI_TYPES = array ('teacher' => get_string('defaultcourseteacher'),
'student' => get_string('defaultcoursestudent') );
define("EWIKI_ESCAPE_AT", 0); # For the algebraic filter
// How long locks stay around without being confirmed (seconds)
define(WIKI_LOCK_PERSISTENCE,60);
// How often to confirm that you still want a lock
define(WIKI_LOCK_RECONFIRM,30);
/*** Moodle 1.7 compatibility functions *****
*
********************************************/
@ -135,6 +142,10 @@ function wiki_delete_instance($id) {
}
# Delete any dependent records here #
if(!delete_records("wiki_locks","wikiid",$wiki->id)) {
$result = false;
}
if (! delete_records("wiki", "id", $wiki->id)) {
$result = false;
}
@ -225,9 +236,10 @@ function wiki_cron () {
/// This function searches for things that need to be done, such
/// as sending out mail, toggling flags etc ...
global $CFG;
// Delete expired locks
$result=delete_records_select('wiki_locks','lockedseen < '.(time()-WIKI_LOCK_PERSISTENCE));
return true;
return $result;
}
function wiki_grades($wikiid) {

51
mod/wiki/overridelock.php Normal file
View file

@ -0,0 +1,51 @@
<?php
/**
* Handles what happens when a user with appropriate permission attempts to
* override a wiki page editing lock.
*
* @copyright &copy; 2006 The Open University
* @author s.marshall@open.ac.uk
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
* @package package_name
*//** */
require_once('../../config.php');
$id=required_param('id',PARAM_INT);
$page=required_param('page',PARAM_RAW);
if (! $cm = get_coursemodule_from_id('wiki', $id)) {
error("Course Module ID was incorrect");
}
if (! $course = get_record("course", "id", $cm->course)) {
error("Course is misconfigured");
}
if (! $wiki = get_record("wiki", "id", $cm->instance)) {
error("Course module is incorrect");
}
if(!confirm_sesskey()) {
error("Session key not set");
}
if(!data_submitted()) {
error("Only POST requests accepted");
}
require_course_login($course, true, $cm);
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
if(!has_capability('mod/wiki:overridelock', $modcontext)) {
error("You do not have the capability to override editing locks");
}
$actions = explode('/', $page,2);
if(count($actions)!=2) {
error("Unsupported page value");
}
$pagename=addslashes($actions[1]);
if(!delete_records('wiki_locks','pagename',$pagename,'wikiid', $wiki->id)) {
error('Unable to delete lock record');
}
redirect("view.php?id=$id&page=".urlencode($page));
?>

View file

@ -5,8 +5,8 @@
/// This fragment is called by moodle_needs_upgrading() and /admin/index.php
/////////////////////////////////////////////////////////////////////////////////
$module->version = 2006091800; // The current module version (Date: YYYYMMDDXX)
$module->version = 2006092502; // The current module version (Date: YYYYMMDDXX)
$module->requires = 2006080900; // The current module version (Date: YYYYMMDDXX)
$module->cron = 0; // Period for cron to check this module (secs)
$module->cron = 3600; // Period for cron to check this module (secs)
?>

View file

@ -2,11 +2,12 @@
/// Extended by Michael Schneider
/// This page prints a particular instance of wiki
global $CFG;
global $CFG,$USER;
require_once("../../config.php");
require_once("lib.php");
#require_once("$CFG->dirroot/course/lib.php"); // For side-blocks
require_once(dirname(__FILE__).'/../../lib/ajax/ajaxlib.php');
$ewiki_action = optional_param('ewiki_action', '', PARAM_ALPHA); // Action on Wiki-Page
$id = optional_param('id', 0, PARAM_INT); // Course Module ID, or
@ -21,6 +22,27 @@
// Only want to add edit log entries if we have made some changes ie submitted a form
$editsave = optional_param('thankyou', '');
if($page) {
// Split page command into action and page
$actions = explode('/', $page,2);
if(count($actions)==2) {
$pagename=$actions[1];
}
} else {
$actions=array('');
$pagename='';
}
// If true, we are 'really' on an editing page, not just on edit/something
$reallyedit=$actions[0]=='edit' && !$editsave && !$canceledit;
if(!$reallyedit && isset($_SESSION['lockid'])) {
if(!delete_records('wiki_locks','id',$_SESSION['lockid'])) {
unset($_SESSION['lockid']);
error("Unable to delete lock record. ".$_SESSION['lockid']);
}
unset($_SESSION['lockid']);
}
if ($id) {
if (! $cm = get_coursemodule_from_id('wiki', $id)) {
error("Course Module ID was incorrect");
@ -370,12 +392,100 @@
/// actions will have the form [action]/[pagename]. If the action is 'view' or the '/'
/// isn't there (so the action defaults to 'view'), filter it.
/// If the page does not yet exist, the display will default to 'edit'.
$actions = explode('/', $page);
if((count($actions) < 2 || $actions[0] == "view") &&
record_exists('wiki_pages', 'pagename', addslashes($page), 'wiki', $wiki_entry->id)) {
print(format_text($content, $moodle_format));
} else if($actions[0]=='edit' && $reallyedit) {
// Check the page isn't locked before printing out standard wiki content. (Locking
// is implemented as a wrapper over the existing wiki.)
$goahead=true;
$alreadyownlock=false;
if($lock=get_record('wiki_locks','pagename',$pagename,'wikiid', $wiki->id)) {
// Consider the page locked if the lock has been confirmed within WIKI_LOCK_PERSISTENCE seconds
if($lock->lockedby==$USER->id) {
// Cool, it's our lock, do nothing
$alreadyownlock=true;
$lockid=$lock->id;
} else if(time()-$lock->lockedseen < WIKI_LOCK_PERSISTENCE) {
$modcontext = get_context_instance(CONTEXT_MODULE, $cm->id);
$canoverridelock = has_capability('mod/wiki:overridelock', $modcontext);
$a=new stdClass;
$a->since=userdate($lock->lockedsince);
$a->seen=userdate($lock->lockedseen);
$user=get_record('user','id',$lock->lockedby);
$a->name=fullname($user,
has_capability('moodle/site:viewfullnames', $modcontext));
print_string('pagelocked','wiki',$a);
if($canoverridelock) {
$pageesc=htmlspecialchars($page);
$stroverrideinfo=get_string('overrideinfo','wiki');
$stroverridebutton=get_string('overridebutton','wiki');
$sesskey=sesskey();
print "
<form id='overridelock' method='post' action='overridelock.php'>
<input type='hidden' name='sesskey' value='$sesskey' />
<input type='hidden' name='id' value='$id' />
<input type='hidden' name='page' value='$pageesc' />
$stroverrideinfo
<input type='submit' value='$stroverridebutton' />
</form>
";
}
$goahead=false;
} else {
// Not locked any more. Get rid of the lock record.
if(!delete_records('wiki_locks','pagename',$pagename,'wikiid', $wiki->id)) {
error('Unable to delete lock record');
}
}
}
if($goahead) {
if(!$alreadyownlock) {
// Lock page
$newlock=new stdClass;
$newlock->lockedby=$USER->id;
$newlock->lockedsince=time();
$newlock->lockedseen=$newlock->lockedsince;
$newlock->wikiid=$wiki->id;
$newlock->pagename=$pagename;
if(!$lockid=insert_record('wiki_locks',$newlock)) {
error('Unable to insert lock record');
}
}
$_SESSION['lockid']=$lockid;
// Require AJAX library
print_require_js(array('yui_yahoo','yui_connection'));
$strlockcancelled=get_string('lockcancelled','wiki');
$intervalms=WIKI_LOCK_RECONFIRM*1000;
print "
<script type='text/javascript'>
var intervalID;
function handleResponse(o) {
if(o.responseText=='cancel') {
document.forms['ewiki'].elements['preview'].disabled=true;
document.forms['ewiki'].elements['save'].disabled=true;
clearInterval(intervalID);
alert('$strlockcancelled');
}
else {
}
function handleFailure(o) {
// Ignore for now
}
intervalID=setInterval(function() {
YAHOO.util.Connect.asyncRequest('POST','confirmlock.php',
{success:handleResponse,failure:handleFailure},'lockid=$lockid');
},$intervalms);
</script>
";
// Print editor etc
print $content;
}
} else {
print $content;
}
print $content2;