diff --git a/lang/en_utf8/moodle.php b/lang/en_utf8/moodle.php
index 8d37530..d92b434 100644
--- a/lang/en_utf8/moodle.php
+++ b/lang/en_utf8/moodle.php
@@ -195,6 +195,7 @@ $string['bypassed'] = 'Bypassed';
 $string['cachecontrols'] = 'Cache Controls';
 $string['cancel'] = 'Cancel';
 $string['cancelled'] = 'Cancelled';
+$string['cantdetectconnection'] = 'Cannot detect a connection. ';
 $string['categories'] = 'Course categories';
 $string['category'] = 'Category';
 $string['categoryadded'] = 'The category \'$a\' was added';
@@ -745,6 +746,10 @@ $string['gdnot'] = 'GD is not installed';
 $string['general'] = 'General';
 $string['gettheselogs'] = 'Get these logs';
 $string['go'] = 'Go';
+$string['gooffline'] = 'Go offline';
+$string['goofflinetitle'] = 'Currently online: click to go offline';
+$string['goonline'] = 'Go online';
+$string['goonlinetitle'] = 'Currently offline: click to go online';
 $string['gotoyourserver'] = '(Links back to your server)';
 $string['gpl'] = 'Copyright (C) 1999 onwards  Martin Dougiamas  (http://moodle.com)
 
@@ -1049,6 +1054,7 @@ $string['moveup'] = 'Move up';
 $string['msnid'] = 'MSN ID';
 $string['mustchangepassword'] = 'The new password must be different than the current one';
 $string['mustconfirm'] = 'You need to confirm your login';
+$string['mustinstallgears'] = '"You must install Gears first';
 $string['mycourses'] = 'My courses';
 $string['name'] = 'Name';
 $string['namelams'] = 'LAMS';
@@ -1246,6 +1252,7 @@ $string['phpinfo'] = 'PHP info';
 $string['pleaseclose'] = 'Please close this window now.';
 $string['pleasesearchmore'] = 'Please search some more';
 $string['pleaseusesearch'] = 'Please use the search';
+$string['pleasewait'] = 'Please wait ...';
 $string['plugincheck'] = 'Plugins check';
 $string['pluginchecknotice'] = 'The following tables show the modules, blocks and filters that have been detected in your current Moodle installation;
 They indicate which plugins are standard, and which are not. All non-standard plugins should be checked and upgraded to their most recent versions
@@ -1599,6 +1606,8 @@ $string['trackforumsyes'] = 'Yes: highlight new posts for me';
 $string['trysearching'] = 'Try searching instead.';
 $string['turneditingoff'] = 'Turn editing off';
 $string['turneditingon'] = 'Turn editing on';
+$string['unavailableextlink'] = 'External links are unavailable offline';
+$string['unavailablefeature'] = 'This feature is unavailable offline';
 $string['undecided'] = 'Undecided';
 $string['unenrol'] = 'Unenrol';
 $string['unenrolroleusers'] = 'Unenrol users';
diff --git a/lib/datalib.php b/lib/datalib.php
index adfd7d7..f2f1793 100644
--- a/lib/datalib.php
+++ b/lib/datalib.php
@@ -2040,7 +2040,7 @@ function add_to_log($courseid, $module, $action, $url='', $info='', $cm=0, $user
     // Note that this function intentionally does not follow the normal Moodle DB access idioms.
     // This is for a good reason: it is the most frequently used DB update function,
     // so it has been optimised for speed.
-    global $DB, $CFG, $USER;
+    global $DB, $CFG, $USER, $PAGE;
 
     if ($cm === '' || is_null($cm)) { // postgres won't translate empty string to its default
         $cm = 0;
@@ -2092,6 +2092,7 @@ function add_to_log($courseid, $module, $action, $url='', $info='', $cm=0, $user
     $log = array('time'=>$timenow, 'userid'=>$userid, 'course'=>$courseid, 'ip'=>$REMOTE_ADDR, 'module'=>$module,
                  'cmid'=>$cm, 'action'=>$action, 'url'=>$url, 'info'=>$info);
     $result = $DB->insert_record_raw('log', $log, false);
+    $PAGE->requires->data_for_js('logdata', $log);
 
     // MDL-11893, alert $CFG->supportemail if insert into log failed
     if (!$result and $CFG->supportemail and empty($CFG->noemailever)) {
@@ -2112,7 +2113,7 @@ function add_to_log($courseid, $module, $action, $url='', $info='', $cm=0, $user
     if (!$result) {
         debugging('Error: Could not insert a new entry to the Moodle log', DEBUG_ALL);
     }
-
+    
 }
 
 /**
diff --git a/lib/offline/gears_init.js b/lib/offline/gears_init.js
new file mode 100644
index 0000000..bb1652e
--- /dev/null
+++ b/lib/offline/gears_init.js
@@ -0,0 +1,87 @@
+// Copyright 2007, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice,
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Sets up google.gears.*, which is *the only* supported way to access Gears.
+//
+// Circumvent this file at your own risk!
+//
+// In the future, Gears may automatically define google.gears.* without this
+// file. Gears may use these objects to transparently fix bugs and compatibility
+// issues. Applications that use the code below will continue to work seamlessly
+// when that happens.
+
+(function() {
+    // We are already defined. Hooray!
+    if (window.google && google.gears) {
+        return;
+    }
+
+    var factory = null;
+
+    // Firefox
+    if (typeof GearsFactory != 'undefined') {
+        factory = new GearsFactory();
+    } else {
+        // IE
+        try {
+            factory = new ActiveXObject('Gears.Factory');
+            // privateSetGlobalObject is only required and supported on IE Mobile on
+            // WinCE.
+            if (factory.getBuildInfo().indexOf('ie_mobile') != -1) {
+                factory.privateSetGlobalObject(this);
+            }
+        } catch (e) {
+            // Safari
+            if ((typeof navigator.mimeTypes != 'undefined')
+            && navigator.mimeTypes["application/x-googlegears"]) {
+                factory = document.createElement("object");
+                factory.style.display = "none";
+                factory.width = 0;
+                factory.height = 0;
+                factory.type = "application/x-googlegears";
+                document.documentElement.appendChild(factory);
+            }
+        }
+    }
+
+    // *Do not* define any objects if Gears is not installed. This mimics the
+    // behavior of Gears defining the objects in the future.
+    if (!factory) {
+        return;
+    }
+
+    // Now set up the objects, being careful not to overwrite anything.
+    //
+    // Note: In Internet Explorer for Windows Mobile, you can't add properties to
+    // the window object. However, global objects are automatically added as
+    // properties of the window object in all browsers.
+    if (!window.google) {
+        google = {};
+    }
+
+    if (!google.gears) {
+        google.gears = {factory: factory};
+    }
+})();
diff --git a/lib/offline/go_offline.js b/lib/offline/go_offline.js
new file mode 100644
index 0000000..84e7088
--- /dev/null
+++ b/lib/offline/go_offline.js
@@ -0,0 +1,306 @@
+// Copyright 2007, Google Inc.
+//
+// Redistribution and use in source and binary forms, with or without 
+// modification, are permitted provided that the following conditions are met:
+//
+//  1. Redistributions of source code must retain the above copyright notice, 
+//     this list of conditions and the following disclaimer.
+//  2. Redistributions in binary form must reproduce the above copyright notice,
+//     this list of conditions and the following disclaimer in the documentation
+//     and/or other materials provided with the distribution.
+//  3. Neither the name of Google Inc. nor the names of its contributors may be
+//     used to endorse or promote products derived from this software without
+//     specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
+// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
+// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
+// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
+// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+var OFFLINE_MANIFEST_FILENAME = moodle_cfg['wwwroot']+"/lib/offline/manifest.php";
+var OFFLINE_STORE_NAME = "offline_moodle";
+
+var TURBO_MANIFEST_FILENAME = moodle_cfg['wwwroot']+"/lib/offline/turbo_manifest.php";
+var TURBO_STORE_NAME = "turbo_moodle";
+
+var localServer;
+var store;
+
+var workerPool;
+var childId;
+
+var request;
+var numPings = 0;
+var TIME_BETWEEN_PINGS = 1*1000;
+var PING_TIMEOUT_SECONDS = 1*1000;
+var isConnected;
+var creatingStore = false;
+var loading = true;
+
+var sUrl = moodle_cfg['wwwroot']+"/lib/offline/update_db.php";
+var handleFailure = function(o){
+    offline_message('Synch failed', 'offline-message');
+}
+var db;
+var callback = 
+{
+    success: handleSuccess,
+    failure: handleFailure
+};
+
+// Called onload to initialize local server and store variables
+function offline_init() {
+
+    // Making sure GG is installed and has permissions
+    if (!window.google || !google.gears) {
+        offline_message(mstr.moodle.mustinstallgears, "offline-message");
+        return;
+    } 
+    if (!google.gears.factory.hasPermission) {
+        var siteName = 'Moodle';
+        var icon = moodle_cfg['wwwroot']+'/pix/moodlelogo.gif';
+        var msg = 'This site would like to use Google Gears to enable fast, '
+        + 'offline capabilities of its resources.';
+        var allowed = google.gears.factory.getPermission(siteName, icon, msg);
+    }
+
+    // Initiate the local store and check if we are in offline mode
+    localServer = google.gears.factory.create("beta.localserver");
+    offlineStore = localServer.createManagedStore(OFFLINE_STORE_NAME);       
+    try {
+        if(localServer.canServeLocally(document.URL)) {
+            offline_link(offline_remove_store, mstr.moodle.goonline, mstr.moodle.goonlinetitle);
+            offline_disable_inactive_links();
+            if(logdata != null) {
+                var timenow = Math.round(new Date().getTime()/1000);
+                var db = google.gears.factory.create('beta.database');
+                db.open('offline-database');
+                db.execute('create table if not exists Logs' +
+                ' (Time int, UserID text, CourseID text, IP text, Module text, CmID text, Action text, URL text, Info text)');
+                db.execute('insert into Logs values (?, ?, ?, ?, ?, ?, ?, ?, ?)', [timenow, logdata['userid'], logdata['course'], logdata['ip'], logdata['module'], logdata['cmid'], logdata['action'], logdata['url'], logdata['info']]);
+                db.close();
+            }
+        }
+        else {
+            offline_link(offline_create_store, mstr.moodle.gooffline, mstr.moodle.goofflinetitle);
+            
+        }
+    } catch (e) {
+        offline_message(e.message, "offline-message");
+    }
+
+    // Checking server availability
+    request = google.gears.factory.create('beta.httprequest');
+    offline_is_server_available();
+
+    // This is for turbo (caching in the background)
+    workerPool = google.gears.factory.create('beta.workerpool');
+    workerPool.onmessage = offline_parent_handler;
+    try {
+        childId = workerPool.createWorkerFromUrl(moodle_cfg['wwwroot']+'/lib/offline/worker.js');
+    } catch (e) {
+        offline_message('Could not create worker: ' + e.message, "offline-message");
+    }
+    workerPool.sendMessage({tmf: TURBO_MANIFEST_FILENAME, tsn: TURBO_STORE_NAME}, childId);
+
+}
+
+function offline_parent_handler(messageText, sender, message) {
+    offline_message('Worker message: ' + message.body, "offline-message");
+}
+
+function offline_ping_success() {
+    if(request.responseText != "" && request.responseText.indexOf("404 Page not found") == -1){
+        if (!isConnected || loading ) {
+            document.getElementById('offline-img').innerHTML = "<img src='"+ moodle_cfg['wwwroot'] + "/pix/t/go.gif' title='Server available' />";
+        }
+        isConnected = true;
+    } else {  
+        if (isConnected || loading ) {
+            document.getElementById('offline-img').innerHTML = "<img src='"+ moodle_cfg['wwwroot'] + "/pix/t/stop.gif' title='Server unavailable' />"; 
+        }
+        isConnected = false;
+    }
+    loading = false;
+}
+
+function offline_is_server_available() {  
+    var resource_to_test = moodle_cfg['wwwroot']+"/lib/offline/test.txt"; 
+    resource_to_test += "?q=" + Math.floor(Math.random() * 100000);  
+    request.open('GET', resource_to_test);  
+    window.setTimeout("offline_ping_success()",PING_TIMEOUT_SECONDS);  
+    request.onreadystatechange = function() {    
+        if (request.readyState == 4) {
+            numPings++;
+        }
+    };
+    request.send();
+    window.setTimeout("offline_is_server_available()",TIME_BETWEEN_PINGS);
+}
+
+
+function offline_disable_inactive_links() {
+    for (i=document.links.length-1; i >= 0; i--) {
+        link = document.links[i].href;
+        if (link.indexOf('http') != 0) {
+            link = document.URL + link;
+        }
+        if (link.indexOf(moodle_cfg['wwwroot']) != 0) {
+            element = document.links[i];
+            element.removeAttribute("href");
+            element.title = mstr.moodle.unavailableextlink;
+        }
+        else {
+            try {
+                if(!localServer.canServeLocally(link)) {
+                    element = document.links[i];
+                    element.removeAttribute("href");
+                    element.title = mstr.moodle.unavailablefeature;
+                    if(element.type == 'submit') {
+                        element.disabled = true;
+                    }
+
+                }
+            } catch (e) {
+                alert(e.message+' '+link);
+            }
+        }
+    }
+    
+    var f;
+    var e;
+    for( f=0; f<document.forms.length; f++) {
+        for( e=0; e<document.forms[f].elements.length; e++) {
+            document.forms[f].elements[e].disabled = true;
+        }
+    } 
+}
+
+function offline_create_store() {
+    if(!isConnected){
+        offline_message(mstr.moodle.cantdetectconnection, "offline-message");
+        return;
+    }
+
+    if(creatingStore){
+        return;
+    }
+    creatingStore = true;
+    
+    if(!localServer.canServeLocally(moodle_cfg['wwwroot']+"/lib/offline/gears_init.php")) {
+        OFFLINE_MANIFEST_FILENAME = moodle_cfg['wwwroot']+"/lib/offline/manifest_init.php";
+    }
+    store = localServer.createManagedStore(OFFLINE_STORE_NAME);
+    store.manifestUrl = OFFLINE_MANIFEST_FILENAME;
+    store.checkForUpdate();
+
+    // The progressbar
+    offline_message("", "offline-message");
+    offline_message("0%", "pb-percentage");
+    var Dom = YAHOO.util.Dom, Event = YAHOO.util.Event, pb, percentage;
+    YAHOO.util.Event.onAvailable('pb', function () {
+    pb = new YAHOO.widget.ProgressBar({value:0, height:'10px', width: 100, barColor:'orange',backColor:'white',border:'thin solid black'});
+    pb.render('pb');    
+    });   
+    Event.on('pb-percentage','DOMSubtreeModified',function() {
+        var newVal = parseInt(Dom.get('pb-percentage').innerHTML,10);
+        pb.set('value',newVal);
+    });
+
+    var timerId = window.setInterval(function() {
+        if (store.currentVersion) {
+            window.clearInterval(timerId);
+            offline_message("", "offline-message");
+            offline_message("", "pb-percentage");
+            creatingStore = false;
+            pb.destroy();
+            offline_link(offline_remove_store, mstr.moodle.goonline, mstr.moodle.goonlinetitle);
+            offline_disable_inactive_links();
+
+        } else if (store.updateStatus == 3) {
+            offline_message("Error: Unable to go offline. Please check your connection and try again.", "offline-message");
+            offline_message("", "pb-percentage");
+            creatingStore = false;
+            pb.destroy();
+
+        } else {
+            store.onprogress = function(details) {
+                percentage = Math.round(100*details.filesComplete/details.filesTotal);
+                offline_message(percentage + "%", "pb-percentage");
+            };
+        }
+    }, 500);  
+
+}
+
+function offline_update_logs(rs){
+    
+    if(!rs.isValidRow()) {
+        rs.close(); 
+        db.remove();
+        offline_message("Erased. ", "offline-message");
+        offline_link(offline_create_store, mstr.moodle.gooffline, mstr.moodle.goofflinetitle);
+        window.location.reload();       
+    }
+    
+    var postData = 'time=' + rs.field(0) + '&userid=' + rs.field(1) + '&course=' + rs.field(2) + '&ip=' + rs.field(3) + '&module=' + rs.field(4) + '&cmid=' + rs.field(5)
+    + '&action=' + rs.field(6) + '&url=' + rs.field(7) + '&info=' + rs.field(8);
+    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
+
+    var timerId = window.setInterval(function() {
+        if(!YAHOO.util.Connect.isCallInProgress(request)) {
+            window.clearInterval(timerId);
+            rs.next();
+            offline_update_logs(rs);
+        }
+    }, 100);
+
+}
+
+
+function offline_remove_store() {
+    if (!window.google || !google.gears) {
+        alert(mstr.moodle.mustinstallgears);
+        return;
+    }
+    
+    offline_message(mstr.moodle.pleasewait, "offline-message");
+    
+    if (isConnected) {
+    
+        offline_message("Please wait. ", "offline-message");
+        localServer.removeManagedStore(OFFLINE_STORE_NAME);
+        
+        // Synchronizing logs
+        db = google.gears.factory.create('beta.database');
+        db.open('offline-database');    
+        var rs = db.execute('select * from Logs order by Time');
+        offline_update_logs(rs);
+    
+    }
+    else {
+        offline_message(mstr.moodle.cantdetectconnection, "offline-message");
+    }
+}
+
+function offline_message(string, id) {
+    var elm = document.getElementById(id);
+    elm.innerHTML = string;
+}
+
+function offline_link(functn, status, title) {
+    var link = document.createElement('a');
+    link.href = "###";
+    link.innerHTML = status;
+    link.onclick = functn;
+    link.title = title;
+    var elm = document.getElementById("offline-status");
+    elm.innerHTML = '';
+    elm.appendChild(link);
+}
diff --git a/lib/offline/lib.php b/lib/offline/lib.php
new file mode 100644
index 0000000..8b627ef
--- /dev/null
+++ b/lib/offline/lib.php
@@ -0,0 +1,245 @@
+<?php
+
+/**
+ * Retrieve all static files for the turbo manifest
+ *
+ * @return string[] The array of static files
+ */
+function offline_get_static_files(){
+    
+    global $CFG, $THEME;
+    
+    // Include static JavaScript files
+    $files = array(
+        $CFG->wwwroot.'/lib/javascript-static.js',
+        $CFG->wwwroot.'/lib/javascript-deprecated.js',
+        $CFG->wwwroot.'/lib/javascript-mod.php',
+        $CFG->wwwroot.'/lib/overlib/overlib.js',
+        $CFG->wwwroot.'/lib/overlib/overlib_cssstyle.js',
+        $CFG->wwwroot.'/lib/cookies.js',
+        $CFG->wwwroot.'/lib/ufo.js',
+        $CFG->wwwroot.'/lib/dropdown.js',
+        $CFG->wwwroot.'/mod/forum/forum.js',
+        $CFG->wwwroot.'/pix/t/stop.gif',
+        $CFG->wwwroot.'/pix/t/go.gif',
+      );
+
+    foreach(get_list_of_plugins() as $module){
+        $files[] = $CFG->wwwroot.'/mod/'.$module.'/icon.gif';
+    }
+
+    $themefiles = offline_get_files_from_dir($CFG->dirroot.'/theme/'.current_theme());
+    $themefiles = str_replace($CFG->dirroot.'/theme',$CFG->themewww,$themefiles);
+    
+    $files = array_merge($files, $THEME->get_stylesheet_urls(), $themefiles);
+    $files[] = $CFG->wwwroot.'/lib/offline/gears_init.js';
+    $files = str_replace('&amp;','&', $files);
+    
+    return $files;
+}
+
+/**
+ * Retrieve all static files for the turbo manifest
+ *
+ * @return string[] The array of static files
+ */
+function offline_get_turbo_files(){
+    
+    global $CFG, $THEME;
+    
+    require_once($CFG->dirroot .'/course/lib.php');
+    require_once($CFG->libdir .'/ajax/ajaxlib.php');
+    
+    $pixfiles = offline_get_files_from_dir($CFG->dirroot.'/pix');
+    $pixfiles = str_replace($CFG->dirroot,$CFG->wwwroot,$pixfiles);
+    $tinymcefiles = offline_get_files_from_dir($CFG->dirroot.'/lib/editor/tinymce');
+    $tinymcefiles = str_replace($CFG->dirroot.'/lib/editor/tinymce', $CFG->wwwroot.'/lib/editor/tinymce', $tinymcefiles);
+    $yuifiles = offline_get_files_from_dir($CFG->dirroot.'/lib/yui');
+    $yuifiles = str_replace($CFG->dirroot.'/lib/yui', $CFG->wwwroot.'/lib/yui', $yuifiles);
+
+    $files = array_merge($pixfiles, $tinymcefiles, $yuifiles);
+    $files = str_replace('&amp;','&', $files);
+    
+    return $files;
+}
+
+/**
+ * Retrieve all dynamic files
+ *
+ * @return string[] The array of dynamic files
+ */
+function offline_get_dynamic_files(){
+    global $CFG, $COURSE, $USER, $DB;
+    
+    require_once($CFG->dirroot .'/course/lib.php');
+    
+    // Include homepage and accessible course pages
+    $files = array(
+        '.',
+        $CFG->wwwroot.'/',
+        $CFG->wwwroot.'/index.php',
+        $CFG->wwwroot.'/lib/offline/go_offline.js',
+      );
+
+    // get all accessible courses
+    if (isloggedin() and !has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)) 
+        and !isguest() and empty($CFG->disablemycourses)) {
+
+        $courses  = get_my_courses($USER->id, 'visible DESC,sortorder ASC', array('summary'));
+
+    } else if ((!has_capability('moodle/site:config', get_context_instance(CONTEXT_SYSTEM)) 
+        and !isguest()) or ($DB->count_records('course') <= FRONTPAGECOURSELIMIT)) {
+
+        $categories = get_child_categories(0);  
+        if (is_array($categories) && count($categories) == 1) {
+            $category   = array_shift($categories);
+            $courses    = get_courses_wmanagers($category->id,
+                                                'c.sortorder ASC',
+                                                array('password','summary','currency'));
+        } else {
+            $courses    = get_courses_wmanagers('all',
+                                                'c.sortorder ASC',
+                                                array('password','summary','currency'));
+        }
+        unset($categories);
+    } 
+
+    // make sure the course is visible and retrieve other modules and main course pages
+    foreach ($courses as $course) {
+        if ($course->visible == 1
+            || has_capability('moodle/course:viewhiddencourses',$course->context)) {
+            $files[] = $CFG->wwwroot.'/course/view.php?id='.$course->id;
+
+            //Get all the module main pages
+            foreach(get_list_of_plugins() as $module){
+                if($module != 'label') {
+                    $files[] = $CFG->wwwroot.'/mod/'.$module.'/index.php?id='.$course->id;
+                }
+            }
+
+            require_once($CFG->dirroot . '/mod/forum/lib.php');
+            //Get all the relevant forums
+            $forums = forum_get_readable_forums($USER->id, $course->id);
+            foreach ($forums as $forum) {
+                $files[] = $CFG->wwwroot.'/mod/forum/view.php?f='.$forum->id;
+            }
+            $modinfo =& get_fast_modinfo($COURSE);
+            get_all_mods($course->id, $mods, $modnames, $modnamesplural, $modnamesused);
+            foreach($mods as $mod) {
+                if ($mod->modname == 'forum') {
+                    $files[] = $CFG->wwwroot.'/mod/forum/view.php?id='.$mod->id;
+                    $cm = get_coursemodule_from_id('forum', $mod->id);
+                    $discussions = forum_get_discussions($cm);
+                    foreach($discussions as $d){ 
+                        $files[] = $CFG->wwwroot.'/mod/forum/discuss.php?d='.$d->discussion;
+                    }               
+                }
+            }
+        }
+    }
+    $files = str_replace('&amp;','&', $files);
+    return $files;
+}
+
+/**
+ * Retrieve recursively all the files in a directory, except
+ * .php and system files
+ *
+ * @param string The directory path
+ * @return string[] The array of files in the directory
+ */
+function offline_get_files_from_dir($dir){
+    if (!is_dir($dir)) {
+        return null;
+    }
+    $handle = opendir($dir);
+    $files = array();
+    while ($file = readdir($handle)) {
+        $path = $dir . '/' . $file;
+        if ($file !== '.' && $file !== '..' && is_dir($dir . '/'.$file) ) {
+            $files = array_merge($files, offline_get_files_from_dir($dir.'/'.$file));
+        }  else if (strpos($file, '.') != 0 && !strchr($file,'.php')) {
+            $files[] = $path;
+        }
+    }
+    return $files;
+}
+
+/**
+ * Get all the links in a given URL
+ *
+ * @param string The URL
+ * @return object The list of links and names
+ */
+function offline_get_page_links($link) {
+    $ret = array();
+    $dom = new domDocument;
+
+    @$dom->loadHTML(file_get_contents($link));
+    $dom->preserveWhiteSpace = false;
+    $links = $dom->getElementsByTagName('a');
+
+    foreach ($links as $tag) {
+        $ret[$tag->getAttribute('href')] = $tag->childNodes->item(0)->nodeValue;
+    }
+    return $ret;
+}
+
+/**
+ * Determine the manifest version
+ *
+ * @param string An existing previous version
+ * @return string The new version
+ */
+function offline_get_manifest_version($version) {
+    $dir = dirname($_SERVER['SCRIPT_FILENAME']);
+    $handle = opendir($dir);
+    while ($file = readdir($handle)) {
+        if (file_exists("$dir/$file")) {
+            $v = filemtime("$dir/$file");
+            if ($v > $version) {
+                $version = $v;
+            }
+        }
+    }
+    return $version;
+}
+
+/**
+ * This output function will display a menu for offline mode.
+ *
+ * @param string $menu The original menu 
+ * @return string $menu The modified menu with the offline option
+ */
+function offline_output_menu($menu) {
+    
+    global $PAGE, $CFG, $USER;
+
+    $PAGE->requires->yui_lib('animation');
+    $PAGE->requires->yui_lib('element');
+    $PAGE->requires->yui_lib('connection');
+    //$PAGE->requires->yui_lib('container');
+
+    $PAGE->requires->js('lib/offline/progressbar-debug.js');
+    $PAGE->requires->css('lib/offline/progressbar.css');
+
+
+    $PAGE->requires->string_for_js('gooffline', 'moodle');
+    $PAGE->requires->string_for_js('goofflinetitle', 'moodle');
+    $PAGE->requires->string_for_js('goonline', 'moodle');
+    $PAGE->requires->string_for_js('goonlinetitle', 'moodle');
+    $PAGE->requires->string_for_js('pleasewait', 'moodle');
+    $PAGE->requires->string_for_js('cantdetectconnection', 'moodle');
+    $PAGE->requires->string_for_js('mustinstallgears', 'moodle');
+    $PAGE->requires->string_for_js('unavailableextlink', 'moodle');
+    $PAGE->requires->string_for_js('unavailablefeature', 'moodle');
+
+    $menu = '<div id="content" style="visibility:hidden"></div><div id="pb" style="float:left; margin-top:5px; margin-right:0em;"></div><font size="-1"><span id="pb-percentage"></span></font> <span id="offline-message"></span> <span id="offline-img"></span> <span id="offline-status"></span>'.$menu;
+    
+    $PAGE->requires->js('lib/offline/gears_init.js');
+    $PAGE->requires->js('lib/offline/go_offline.js');
+    $PAGE->requires->js_function_call('offline_init')->on_dom_ready();
+   
+    return $menu;
+}
+
diff --git a/lib/offline/manifest.php b/lib/offline/manifest.php
new file mode 100644
index 0000000..f77dc6d
--- /dev/null
+++ b/lib/offline/manifest.php
@@ -0,0 +1,24 @@
+<?php
+
+require_once('../../config.php');
+require_once($CFG->libdir .'/offline/lib.php');
+
+header('Content-type: text/plain');
+
+$version = offline_get_manifest_version(0);
+
+$files   = array_merge(offline_get_dynamic_files());
+
+$entries = array();
+foreach ($files as $file) {
+    array_push($entries, "    {\"url\": \"$file\"}");
+}
+?>
+{
+  "betaManifestVersion": 1,
+  "version": "<?php echo $version; ?>",
+  "entries": [
+<?php echo implode(",\n", $entries); ?>
+
+  ]
+}
diff --git a/lib/offline/manifest_init.php b/lib/offline/manifest_init.php
new file mode 100644
index 0000000..dc1520a
--- /dev/null
+++ b/lib/offline/manifest_init.php
@@ -0,0 +1,24 @@
+<?php
+
+require_once('../../config.php');
+require_once($CFG->libdir .'/offline/lib.php');
+
+header('Content-type: text/plain');
+
+$version = offline_get_manifest_version(0);
+
+$files   = array_merge(offline_get_dynamic_files(), offline_get_static_files());
+
+$entries = array();
+foreach ($files as $file) { 
+    array_push($entries, "    {\"url\": \"$file\"}");
+}
+?>
+{
+  "betaManifestVersion": 1,
+  "version": "<?php echo $version; ?>",
+  "entries": [
+<?php echo implode(",\n", $entries); ?>
+
+  ]
+}
\ No newline at end of file
diff --git a/lib/offline/progressbar-debug.js b/lib/offline/progressbar-debug.js
new file mode 100644
index 0000000..24599d8
--- /dev/null
+++ b/lib/offline/progressbar-debug.js
@@ -0,0 +1,461 @@
+/*
+People working in large organizations need this sort of statement to be able to include it in their applications, so here it goes:
+
+Copyright (c) 2008, Daniel Barreiro (a.k.a. Satyam). All rights reserved.
+satyam at satyam dot com dot ar  (yes, it ends with ar)
+It is the intention of the author to make this component freely available for use along the YAHOO User Interface Library
+so it is licensed with a BSD License: http://developer.yahoo.net/yui/license.txt
+developped along version: 2.5.2 of YUI
+*/
+/**
+ * The ProgressBar widget provides an easy way to draw a bar depicting progress of an operation.
+ * It allows for highly customized styles
+ *
+ * @module progressbar
+ * @requires yahoo, dom, event, element
+ * @optional animation
+ * @title ProgressBar Widget
+ */
+
+(function () {
+    var Dom = YAHOO.util.Dom,
+        Event = YAHOO.util.Event,
+        Lang = YAHOO.lang;
+    
+/**
+ * A Progress Bar providing various visual options, animation, it can grow vertically or horizontally in any direction
+  * and fires several events while moving.
+ * @namespace YAHOO.widget
+ * @class ProgressBar
+ * @extends YAHOO.util.Element
+ * @param oConfigs {object} An object containing any configuration attributes to be set 
+ * @constructor
+ */        
+    var Prog = function(oConfigs) {
+        
+        Prog.superclass.constructor.call(this, document.createElement('div') , oConfigs);
+    /**
+         * Fires when the value is about to change.  It reports the starting value
+         * @event startEvent
+         * @type CustomEvent
+         * @param value {Number} the current (initial) value of the node
+         */
+        this.createEvent('startEvent');
+    /**
+         * If animation is loaded, it will trigger for each frame of the animation providing partial values
+         * @event changingEvent
+         * @type CustomEvent
+         * @param  value{Number} the current (changing) value of the node
+         */
+        this.createEvent('changingEvent');
+    /**
+         * It will fire at the end of the animation or immediately upon changing values if animation is not loaded
+         * @event completeEvent
+         * @type CustomEvent
+         * @param value {Number} the current (final)  value of the node
+         */
+        this.createEvent('completeEvent');
+
+        var anim = this.get('anim');
+        // If there is an instance of Animator available, I fire the events
+        if (anim) {
+            anim.onTween.subscribe(function (ev) {
+                this.fireEvent('changingEvent',Math.floor(this._tweenFactor * anim.currentFrame + this._previousValue));
+            },this,true);
+            anim.onComplete.subscribe(function(ev) {
+                this.fireEvent('completeEvent',this._previousValue = this.get('value'));
+                Dom.removeClass(this.get('barEl'),'yui-pb-anim');
+            },this,true);
+        }
+        
+        // I'm listening to AttributeProvider's own attribute change events to adjust the bar according to the new sizes
+        this.on('minValueChange',this.redraw);
+        this.on('maxValueChange',this.redraw);
+        this.on('widthChange',  this.redraw);
+    };
+    
+    YAHOO.widget.ProgressBar = Prog;
+    
+    Lang.extend(Prog, YAHOO.util.Element, {
+/**
+ * Implementation of Element's abstract method. Sets up config values.
+ *
+ * @method initAttributes
+ * @param oConfigs {Object} (Optional) Object literal definition of configuration values.
+ * @private
+ */ 
+        initAttributes: function (oConfigs) {
+
+            Prog.superclass.initAttributes.call(this, oConfigs);
+
+            var el = this.get('element');
+            el.innerHTML =  '<div class="yui-pb-bar"></div><table class="yui-pb-mask"><tr><td class="yui-pb-tl"></td><td class="yui-pb-tr"></td></tr><tr><td class="yui-pb-bl"></td><td class="yui-pb-br"></td></tr></table>';
+            var barEl  = el.firstChild;
+            var maskEl = barEl.nextSibling;
+ /**
+    * @attribute barEl
+    * @description Reference to the HTML object that makes the moving bar (read-only)
+    * @type HTMLElement (div)
+    * @readonly
+    */          
+            this.setAttributeConfig('barEl', {
+                readOnly: true,
+                value: barEl
+            });
+/**
+    * @attribute maskEl
+    * @description Reference to the HTML object that overlays the bar providing the mask. (read-only)
+    * @type HTMLElement (table)
+    * @readonly
+    */          
+            this.setAttributeConfig('maskEl', {
+                readOnly: true,
+                value: maskEl
+            });
+            
+/**
+    * @attribute direction
+    * @description Direction of movement of the bar.  
+    *    It can be any of 'lr' (left to right), 'rl' (the reverse) , 'tb' (top to bottom) or 'bt', case-insensitive.
+    *    Can only be set once and only before rendering.
+    * @default 'lr'
+    * @type String
+    */          
+            this.setAttributeConfig('direction', {
+                writeOnce: true,
+                value:'lr',
+                validator:function(value) {
+                    switch (value) {
+                        case 'lr':
+                        case 'rl':
+                        case 'tb':
+                        case 'bt':
+                            return true;
+                        default:
+                            return false;
+                    }
+                }
+            });
+/**
+    * @attribute maxValue
+    * @description Represents the top value for the bar. 
+    *   The bar will be fully extended when reaching this value.  
+    *   Values higher than this will be ignored. 
+    * @default 100
+    * @type Number
+    */                  
+            this.setAttributeConfig('maxValue', {
+                value: 100,
+                validator: Lang.isNumber,
+                method: function (value) {
+                    this.get('element').setAttribute('aria-valuemax',value);
+                    this._recalculateConstants();
+                }
+            });
+            
+/**
+    * @attribute minValue
+    * @description Represents the lowest value for the bar. 
+    *   The bar will be totally collapsed when reaching this value.  
+    *    Values lower than this will be ignored. 
+    * @default 0
+    * @type Number
+    */              
+
+            this.setAttributeConfig('minValue', {
+                value: 0,
+                validator: Lang.isNumber,
+                method: function (value) {
+                    this.get('element').setAttribute('aria-valuemin',value);
+                    this._recalculateConstants();
+                }
+            });
+/**
+    * @attribute width
+    * @description Width of the ProgressBar.
+    *     If a number, it will be presumed to be in pixels.  
+    *     If a string it should be a valid setting for the CSS width attribute.  
+    *     It will always be returned as a string including units.
+    * @default 200px
+    * @type Number or String
+    */              
+
+            this.setAttributeConfig('width', {
+                value: '200px',
+                method: function(value) {
+                    if (Lang.isNumber(value)) {
+                        value += 'px';
+                    }
+                    YAHOO.log('Setting width: ' + value,'info','ProgressBar');
+                    this.setStyle('width',value);
+                    Dom.setStyle(maskEl,'width', value);
+                }
+            });
+
+/**
+    * @attribute height
+    * @description Height of the ProgressBar.
+    *     If a number, it will be presumed to be in pixels.  
+    *     If a string it should be a valid setting for the CSS height attribute.  
+    *     It will always be returned as a string including units.
+    * @default 20px
+    * @type Number or String
+    */              
+            this.setAttributeConfig('height', {
+                value: '20px',
+                method: function(value) {
+                    if (Lang.isNumber(value)) {
+                        value += 'px';
+                    }
+                    YAHOO.log('Setting height: ' + value,'info','ProgressBar');
+                    this.setStyle('height',value);
+                    //Dom.setStyle(maskEl,'height', value);
+                }
+            });
+            
+/**
+    * @attribute barColor
+    * @description Color for the bar.  It can be any valid CSS color value.
+    * @default 'blue'
+    * @type String - CSS color specification
+    */              
+            this.setAttributeConfig('barColor', {
+                value:'blue',
+                method: function (value) {
+                    YAHOO.log('Setting bar color: ' + value,'info','ProgressBar');
+                    Dom.setStyle(barEl,'background-color', value);
+                    Dom.setStyle(barEl,'background-image', 'none');
+                }
+            });
+
+/**
+    * @attribute backColor
+    * @description Color for the background.  It can be any valid CSS color value.
+    * @default 'white'
+    * @type String - CSS color specification
+    */              
+            this.setAttributeConfig('backColor', {
+                value:'white',
+                method: function (value) {
+                    YAHOO.log('Setting background color: ' + value,'info','ProgressBar');
+                    this.setStyle('background-color', value);
+                    this.setStyle('background-image', 'none');
+                }
+            });
+            
+/**
+    * @attribute border
+    * @description CSS attributes for the border.
+    * @default 'none'
+    * @type String - CSS border specification
+    */              
+            this.setAttributeConfig('border', {
+                value:'none',
+                method: function (value) {
+                    YAHOO.log('Setting border: ' + value,'info','ProgressBar');
+                    this.setStyle('border', value);
+                }
+            });
+            
+/**
+    * @attribute ariaText
+    * @description The text to be voiced by screen readers.  
+    *     The text should contain a vertical bar character where the current  value of the bar should be inserted.
+    * @default '|'
+    * @type String
+    */              
+            this.setAttributeConfig('ariaText', {
+                value:'|'
+            });
+/**
+    * @attribute value
+    * @description The value for the bar.  
+    *     Valid values are in between the minValue and maxValue attributes.
+    * @default 50
+    * @type Number
+    */          
+            this.setAttributeConfig('value', {
+                value: 50,
+                validator: function(value) {
+                    return Lang.isNumber(value) && value >= this.get('minValue') && value <= this.get('maxValue');
+                },
+                method:function (value) {
+                    YAHOO.log('set value: ' + value,'info','ProgressBar');
+                    var anim = this.get('anim'),
+                        pixelValue = Math.floor((value - this._mn) * this._barFactor),
+                        container = this.get('element'),
+                        barEl = this.get('barEl');
+                        
+                    container.setAttribute('aria-valuenow',value);
+                    container.setAttribute('aria-valuetext',this.get('ariaText').replace('|',value));
+                    this.fireEvent('startEvent',this._previousValue);
+                    if (anim) {
+                        Dom.addClass(this.get('barEl'),'yui-pb-anim');
+                        this._tweenFactor = (value - this._previousValue) / anim.totalFrames;
+                        switch (this.get('direction')) {
+                            case 'lr':
+                                anim.attributes = {width:{ to: pixelValue }}; 
+                                break;
+                            case 'rl':
+                                anim.attributes = {
+                                    width:{ to: pixelValue },
+                                    left:{to: this._barSpace - pixelValue}
+                                }; 
+                                break;
+                            case 'tb':
+                                anim.attributes = {height:{to: pixelValue}};
+                                break;
+                            case 'bt':
+                                anim.attributes = {
+                                    height:{to: pixelValue},
+                                    top:{to: this._barSpace - pixelValue}
+                                };
+                                break;
+                        }
+                        anim.animate();
+                    } else {
+                        switch (this.get('direction')) {
+                            case 'lr':
+                                Dom.setStyle(barEl,'width',  pixelValue + 'px');
+                                break;
+                            case 'rl':
+                                Dom.setStyle(barEl,'width',  pixelValue + 'px');
+                                Dom.setStyle(barEl,'left',(this._barSpace - pixelValue) + 'px');
+                                break;
+                            case 'tb':
+                                Dom.setStyle(barEl,'height',  pixelValue + 'px');
+                                break;
+                            case 'bt':
+                                Dom.setStyle(barEl,'height',  pixelValue + 'px');
+                                Dom.setStyle(barEl,'top',  (this._barSpace - pixelValue) + 'px');
+                                break;
+                        }
+                        // If the animation utility has not been loaded then changing the value will always complete immediately.
+                        this.fireEvent('completeEvent',value);
+                    }
+                }
+            });
+/**
+    * @attribute anim
+    * @description A reference to the YAHOO.util.Anim instance attached to the bar.  (ReadOnly)  
+    *   If the Animation utility is loaded, it will be automatically used.  
+    * @default null
+    * @type {instance of YAHOO.util.Anim}
+    */          
+            
+            this.setAttributeConfig('anim', {
+                readOnly:true,
+                value: YAHOO.util.Anim?new YAHOO.util.Anim(barEl):null
+            });
+            
+        },
+    /** 
+     *  It will render the ProgressBar into the given container.  
+     *  If the container has other content, the ProgressBar will be appended to them.
+     *  If the second argument is provided, the ProgressBar will be inserted before the given child.
+     * The method is chainable since it returns a reference to this instance.
+     * @method render
+     * @param el {HTML Element}  HTML element that will contain the ProgressBar
+     * @param before {HTML Element}  (optional) If present, the ProgressBar will be inserted before this element.
+     * @return {YAHOO.widget.ProgressBar}
+     * @chainable
+     */
+        render: function(el,before) {
+            YAHOO.log('start render','info','ProgressBar');
+
+            this.addClass('yui-pb');
+            var container = this.get('element');
+            container.tabIndex = 1;
+            container.setAttribute('role','progressbar');
+            container.setAttribute('aria-valuemin',this.get('minValue'));
+            container.setAttribute('aria-valuemax',this.get('maxValue'));
+            container.setAttribute('aria-valuenow',this.get('value'));
+            container.setAttribute('aria-valuetext',this.get('ariaText').replace('|',this.get('value')));
+
+            this.appendTo(el,before);
+            
+            this.redraw();
+            return this;
+        },
+
+    /** 
+     *  It will recalculate the bar size and position and redraw it
+     * @method redraw
+     * @return  void
+     * @private
+     */
+        redraw: function () {
+            this._recalculateConstants();
+            this.refresh('value',true);
+        },
+            
+    /** 
+     *  It will destroy the ProgressBar, related objects and unsubscribe all events
+     * @method destroy
+     * @return  void
+     */
+        destroy: function() {
+            YAHOO.log('destroy','info','ProgressBar');
+            var anim = this.get('anim');
+            if (anim) {
+                anim.onTween.unsubscribeAll();
+                anim.onComplete.unsubscribeAll();
+            }
+            this.unsubscribeAll();
+            var el = this.get('element');
+            el.parentNode.removeChild(el);
+        },
+/**
+  * The previous value setting for the bar.  Used mostly as information to event listeners
+  * @property _previousValue
+  * @type Number
+  * @private
+  * @default  100
+  */
+        _previousValue:100,
+/**
+  * The actual space (in pixels) available for the bar within the mask
+  * @property _barSpace
+  * @type Number
+  * @private
+  * @default  100
+  */
+        _barSpace:100,
+/**
+  * The factor to convert the actual value of the bar into pixels
+  * @property _barSpace
+  * @type Number
+  * @private
+  * @default  1
+  */
+        _barFactor:1,
+    /** 
+     *  Calculates some auxiliary values to make the rendering faster
+     * @method _recalculateConstants
+     * @return  void
+     * @private
+     */     
+        _recalculateConstants: function() {
+            var barEl = this.get('barEl');
+            this._mn = this.get('minValue') || 0;
+
+            switch (this.get('direction')) {
+                case 'lr':
+                case 'rl':
+                    this._barSpace = parseInt(this.getStyle('width'),10) - 
+                        parseInt(Dom.getStyle(barEl,'marginLeft'),10)  -
+                        Math.abs(parseInt(Dom.getStyle(barEl,'marginRight'),10));
+                    break;
+                case 'tb':
+                case 'bt':
+                    this._barSpace = parseInt(this.getStyle('height'),10) -
+                        parseInt(Dom.getStyle(barEl,'marginTop'),10) -
+                        parseInt(Dom.getStyle(barEl,'marginBottom'),10); 
+                    break;
+            }
+            this._barFactor = this._barSpace / (this.get('maxValue') - this._mn)  || 1;
+        }
+    });
+    
+})();
+YAHOO.register('progressbar',YAHOO.widget.ProgressBar,{version: "2.5.2", build: "0"});
\ No newline at end of file
diff --git a/lib/offline/progressbar.css b/lib/offline/progressbar.css
new file mode 100644
index 0000000..e5ce3da
--- /dev/null
+++ b/lib/offline/progressbar.css
@@ -0,0 +1,26 @@
+.yui-pb {
+	text-align:left;
+	vertical-align:bottom;
+
+	padding:0;
+	border:none;
+	margin:0;
+
+	width:200px;
+	height:20px;
+
+	position:relative;
+	top:0;
+	left:0;
+}
+
+.yui-pb-bar {
+	background-color: blue;
+	width:100%;
+	height:100%;
+	margin:0;
+
+	position:absolute;
+	left:0;
+	top:0;
+}
diff --git a/lib/offline/test.txt b/lib/offline/test.txt
new file mode 100644
index 0000000..30d74d2
--- /dev/null
+++ b/lib/offline/test.txt
@@ -0,0 +1 @@
+test
\ No newline at end of file
diff --git a/lib/offline/turbo_manifest.php b/lib/offline/turbo_manifest.php
new file mode 100644
index 0000000..3cd5784
--- /dev/null
+++ b/lib/offline/turbo_manifest.php
@@ -0,0 +1,24 @@
+<?php
+
+require_once('../../config.php');
+require_once($CFG->libdir .'/offline/lib.php');
+
+header('Content-type: text/plain');
+
+$version = offline_get_manifest_version(0);
+
+$files   = array_merge(offline_get_turbo_files(), offline_get_static_files());
+
+$entries = array();
+foreach ($files as $file) { 
+    array_push($entries, "    {\"url\": \"$file\"}");
+}
+?>
+{
+  "betaManifestVersion": 1,
+  "version": "<?php echo $version; ?>",
+  "entries": [
+<?php echo implode(",\n", $entries); ?>
+
+  ]
+}
\ No newline at end of file
diff --git a/lib/offline/update_db.php b/lib/offline/update_db.php
new file mode 100644
index 0000000..328c7a2
--- /dev/null
+++ b/lib/offline/update_db.php
@@ -0,0 +1,16 @@
+<?php 
+
+require_once('../../config.php');
+
+$log = $_POST;
+$result = $DB->insert_record_raw('log', $log, false);
+print_r($log);
+
+if ($result) {
+    print_r($result);
+}
+else {
+    print_r('Error: Could not insert a new entry to the Moodle log during offline synchronization');
+}
+
+?>
\ No newline at end of file
diff --git a/lib/offline/worker.js b/lib/offline/worker.js
new file mode 100644
index 0000000..ba30e2b
--- /dev/null
+++ b/lib/offline/worker.js
@@ -0,0 +1,26 @@
+google.gears.workerPool.onmessage = function(messageText, senderId, message) {
+    
+    var response = "Please wait (turbo)";   
+    //google.gears.workerPool.sendMessage(response, message.sender);
+    
+    localServer = google.gears.factory.create("beta.localserver");
+    turboStore = localServer.createManagedStore(message.body.tsn);
+    turboStore.manifestUrl = message.body.tmf;
+    turboStore.checkForUpdate();
+
+    
+    
+    var timer = google.gears.factory.create('beta.timer');
+    var timerId = timer.setInterval(function() { 
+        if (turboStore.currentVersion) {
+            timer.clearInterval(timerId);
+            response = 'Turbo updated. Version ' + turboStore.currentVersion;
+            //google.gears.workerPool.sendMessage(response, message.sender);
+        } else if (turboStore.updateStatus == 3) {
+            response = "Error: " + turboStore.lastErrorMessage + " ";
+            //google.gears.workerPool.sendMessage(response, message.sender);            
+        }
+    }, 500);
+};
+
+
diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php
index 6213bd9..7f5120e 100644
--- a/lib/outputrenderers.php
+++ b/lib/outputrenderers.php
@@ -695,6 +695,12 @@ class moodle_core_renderer extends moodle_renderer_base {
         // TODO remove $navigation and $menu arguments - replace with $PAGE->navigation
         global $USER, $CFG;
 
+        $offline = TRUE;
+        if($offline && isloggedin()){
+            include($CFG->libdir .'/offline/lib.php');
+            $menu = offline_output_menu($menu);
+        }
+
         $this->page->set_state(moodle_page::STATE_PRINTING_HEADER);
 
         // Find the appropriate page template, based on $this->page->generaltype.
