]> git.mjollnir.org Git - moodle.git/commitdiff
Wiki enhancement: edit locking (uses AJAX)
authorsam_marshall <sam_marshall>
Mon, 25 Sep 2006 17:42:32 +0000 (17:42 +0000)
committersam_marshall <sam_marshall>
Mon, 25 Sep 2006 17:42:32 +0000 (17:42 +0000)
lang/en_utf8/wiki.php
mod/wiki/confirmlock.php [new file with mode: 0644]
mod/wiki/db/access.php
mod/wiki/db/install.xml
mod/wiki/db/mysql.php
mod/wiki/db/postgres7.php
mod/wiki/lib.php
mod/wiki/overridelock.php [new file with mode: 0644]
mod/wiki/version.php
mod/wiki/view.php

index e6b51e3d13b76013b375f967bdf6f722bbbc3a1a..9e8b6e620f08a308f98bfdfaff2f772b8248ae79 100644 (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.';
diff --git a/mod/wiki/confirmlock.php b/mod/wiki/confirmlock.php
new file mode 100644 (file)
index 0000000..ce8f924
--- /dev/null
@@ -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.
+}
+
+?>
\ No newline at end of file
index 135edf64573187fe8408bffb7fd766db7238d9b8..13acf5b9edb1bfee4f431b802761e424db286e79 100644 (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
+        )
     )
 );
index 38f800ba2313adecef03eadb059f2b87b434f214..35c479d7d95e35274e96edaa703782bb3ea19226 100644 (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"/>
       </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>
\ No newline at end of file
index a75660d6bb387ef93c6f8940871b8585c6198315..32884a54d514384ed8a22c85c29cbadde33f1072 100644 (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;
 }
 
index da338e649f9c038b3be1eeba66ba72ee4fc52c3c..2d89821c3379817658cc5b21fbf78c3ae6109d57 100644 (file)
@@ -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;
 }
index 57e0348ea637c56325a4d89278bcd277d248a319..384091f96734ca3054a8560b1c4ab3a8dd948920 100644 (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) {
@@ -1550,4 +1562,4 @@ function wiki_get_post_actions() {
 }
 
 
-?>
+?>
\ No newline at end of file
diff --git a/mod/wiki/overridelock.php b/mod/wiki/overridelock.php
new file mode 100644 (file)
index 0000000..0b83b9b
--- /dev/null
@@ -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));
+?>
\ No newline at end of file
index 87b1847a9d0bf0b93c904ca60abb64eeb31e1a16..392193bc7673388de31e73d34d2375d569589058 100644 (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)
 
 ?>
index cfea9ca33bbf5e5afe7e2083ac5ddeed52f32d20..72381ea25483f2c6788b90ec3b0495a7eb7fb63d 100644 (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("$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
     // 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");
     /// 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;