Skip to content
Browse files

Wiki enhancement: edit locking (uses AJAX)

  • Loading branch information...
1 parent f775a51 commit 22e846cde22756790e20e1f3f466c743bb9a980c sam_marshall committed Sep 25, 2006
View
5 lang/en_utf8/wiki.php
@@ -94,6 +94,7 @@
$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['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['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.';
View
31 mod/wiki/confirmlock.php
@@ -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
16 mod/wiki/db/access.php
@@ -36,5 +36,21 @@
'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
28 mod/wiki/db/install.xml
@@ -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
16 mod/wiki/db/mysql.php
@@ -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
15 mod/wiki/db/postgres7.php
@@ -155,6 +155,21 @@ function wiki_upgrade($oldversion) {
modify_database('', 'ALTER TABLE prefix_wiki_pages
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
18 mod/wiki/lib.php
@@ -11,6 +11,13 @@
'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) {
@@ -1550,4 +1562,4 @@ function wiki_get_post_actions() {
}
-?>
+?>
View
51 mod/wiki/overridelock.php
@@ -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
4 mod/wiki/version.php
@@ -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
118 mod/wiki/view.php
@@ -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("$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;

0 comments on commit 22e846c

Please sign in to comment.
Something went wrong with that request. Please try again.