From 6995089c2d2d4203cd5a12558351ca93c690d660 Mon Sep 17 00:00:00 2001
From: ajv <ajv>
Date: Sun, 14 Jun 2009 19:52:22 +1000
Subject: [PATCH] First part of offline caching integration

This patch adds the homepage, accessible course pages and static files (JS, CSS, images) to the localserver of Google Gears. In includes a dynamic manifest to retrieve all relevant content.
---
 lang/en_utf8/moodle.php   |    3 +
 lib/offline/gears_init.js |   87 ++++++++++++++++++++++++++
 lib/offline/go_offline.js |  148 +++++++++++++++++++++++++++++++++++++++++++++
 lib/offline/manifest.php  |  120 ++++++++++++++++++++++++++++++++++++
 lib/offline/test.txt      |    1 +
 lib/weblib.php            |   15 +++++
 6 files changed, 374 insertions(+), 0 deletions(-)
 create mode 100644 lib/offline/gears_init.js
 create mode 100644 lib/offline/go_offline.js
 create mode 100644 lib/offline/manifest.php
 create mode 100644 lib/offline/test.txt

diff --git a/lang/en_utf8/moodle.php b/lang/en_utf8/moodle.php
index 416c34b..b45cf89 100644
--- a/lang/en_utf8/moodle.php
+++ b/lang/en_utf8/moodle.php
@@ -735,6 +735,8 @@ $string['gdnot'] = 'GD is not installed';
 $string['general'] = 'General';
 $string['gettheselogs'] = 'Get these logs';
 $string['go'] = 'Go';
+$string['gooffline'] = 'Go offline';
+$string['goonline'] = 'Go online';
 $string['gotoyourserver'] = '(Links back to your server)';
 $string['gpl'] = 'Copyright (C) 1999 onwards  Martin Dougiamas  (http://moodle.com)
 
@@ -1233,6 +1235,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 
diff --git a/lib/offline/gears_init.js b/lib/offline/gears_init.js
new file mode 100644
index 0000000..57c083b
--- /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..9b5d443
--- /dev/null
+++ b/lib/offline/go_offline.js
@@ -0,0 +1,148 @@
+// 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 STORE_NAME = "my_offline_docset";
+
+var MANIFEST_FILENAME = moodle_cfg['wwwroot']+"/lib/offline/manifest.php";
+
+var localServer;
+var store;
+var initialized = false;
+
+var numPings = 0;
+var TIME_BETWEEN_PINGS = 3*1000;
+var PING_TIMEOUT_SECONDS = 1*1000;
+var request;
+
+function pingSuccess() {
+    if(request.responseText != "" && request.responseText.indexOf("404 Page not found") == -1){
+      document.getElementById('serverStatus').innerHTML = "[Server Accessible]";
+    } else {    
+      document.getElementById('serverStatus').innerHTML = "[Server Inaccessible]";  
+    }
+  }
+
+function isServerAvailable() {  
+    var resource_to_test = moodle_cfg['wwwroot']+"test.txt";  
+    resource_to_test += "?q=" + Math.floor(Math.random() * 100000);  
+    request.open('GET', resource_to_test);  
+    window.setTimeout("pingSuccess()",PING_TIMEOUT_SECONDS);  
+    request.onreadystatechange = function() {    
+      if (request.readyState == 4) {
+        numPings++;
+        document.getElementById('pings').innerHTML = "Number of pings: " + numPings;
+      }
+    };
+    request.send();
+    window.setTimeout("isServerAvailable()",TIME_BETWEEN_PINGS);
+}
+
+
+// Called onload to initialize local server and store variables
+function init_offline() {
+	if (!window.google || !google.gears) {
+        textOut("NOTE:  You must install Gears first.", 1);
+    } else {
+        var request = google.gears.factory.create('beta.httprequest');
+        localServer = google.gears.factory.create("beta.localserver");
+        store = localServer.createManagedStore(STORE_NAME);
+        try {
+            if(localServer.canServeLocally(document.URL)) {
+                linkOut(removeStore, js_lang_string.goonline);
+            }
+        } catch (e) {
+            alert(e.message);
+        }
+    }
+}
+
+// Create the managed resource store
+function createStore() {
+    if (!window.google || !google.gears) {
+        alert("You must install Gears first.");
+        return;
+    } 
+	textOut(js_lang_string.pleasewait, 1)
+    //localServer = google.gears.factory.create("beta.localserver");
+    store = localServer.createManagedStore(STORE_NAME);
+    store.manifestUrl = MANIFEST_FILENAME;
+    store.checkForUpdate();
+
+    var timerId = window.setInterval(function() {
+        // When the currentVersion property has a value, all of the resources
+        // listed in the manifest file for that version are captured. There is
+        // an open bug to surface this state change as an event.
+        if (store.currentVersion) {
+            window.clearInterval(timerId);
+            textOut("Captured "+store.currentVersion+".",1)
+            textOut("",1)
+            linkOut(removeStore, js_lang_string.goonline);
+
+        } else if (store.updateStatus == 3) {
+            textOut("Error: " + store.lastErrorMessage,1);
+        }
+    }, 500);  
+}
+
+// Remove the managed resource store.
+function removeStore() {
+    if (!window.google || !google.gears) {
+        alert("You must install Gears first.");
+        return;
+    }
+	/*if (isServerAvailable()) {
+		alert("yes");
+    	localServer.removeManagedStore(STORE_NAME);
+    	textOut("Erased. ", 1);
+    	linkOut(createStore, js_lang_string.gooffline);
+	}
+	else {
+		alert("no");
+		
+		textOut("Cannot detect a connection. ", 1);
+	}*/
+	//alert(isServerAvailable());
+	localServer.removeManagedStore(STORE_NAME);
+	textOut("Erased. ", 1);
+	linkOut(createStore, js_lang_string.gooffline);
+
+}
+
+// Utility function to output some status text.
+function textOut(s, n) {
+    var elm = document.getElementById("offline-message");
+    elm.innerHTML = s;
+}
+
+function linkOut(functn, status) {
+    var link = document.createElement('a');
+    link.href = "#";
+    link.innerHTML = status;
+    link.onclick = functn;
+    var elm = document.getElementById("offline-status");
+    elm.innerHTML = '';
+    //elm.innerHTML += "Go ";
+    elm.appendChild(link);
+}
diff --git a/lib/offline/manifest.php b/lib/offline/manifest.php
new file mode 100644
index 0000000..46196cf
--- /dev/null
+++ b/lib/offline/manifest.php
@@ -0,0 +1,120 @@
+<?php
+require_once('../../config.php');
+require_once('../../course/lib.php');
+
+header('Content-type: text/plain');
+
+/**
+ * Retrieve recursively all the files in a directory, except
+ * .php and system files
+ *
+ * @uses $_SERVER
+ * @param string The directory path
+ * @return string[] The array of files in the directory
+ */
+function get_files_from_dir($dir){
+	$handle = opendir($dir);
+	$files = array();
+	while (false !== ($file = readdir($handle))) {
+	  if (!strchr($file,'.')) {
+        $files = array_merge($files, get_files_from_dir($dir.'/'.$file));
+	  }
+      else if ($file != "." && $file != ".." && $file != ".DS_Store" && !strchr($file,'.php')) {
+	    $files[] = $dir.'/'.$file;
+	  }
+	}
+	return $files;
+}
+
+// Determine the manifest version
+$version = 0;
+$dir = dirname($_SERVER['SCRIPT_FILENAME']);
+$handle = opendir($dir);
+while (false !== ($file = readdir($handle))) {
+  	if (file_exists("$dir/$file")) {
+    	$v = filemtime("$dir/$file");
+    	if ($v > $version) {
+      		$version = $v;
+    	}
+  	}
+}
+
+// Include homepage, static files and accessible course pages
+$files = array(
+	'.',
+	$CFG->httpswwwroot.'/',
+	$CFG->httpswwwroot.'/index.php',
+	$CFG->httpswwwroot.'/lib/javascript-static.js',
+	$CFG->httpswwwroot.'/lib/javascript-mod.php',
+	$CFG->httpswwwroot.'/lib/overlib/overlib.js',
+	$CFG->httpswwwroot.'/lib/overlib/overlib_cssstyle.js',
+	$CFG->httpswwwroot.'/lib/cookies.js',
+	$CFG->httpswwwroot.'/lib/ufo.js',
+	$CFG->httpswwwroot.'/lib/dropdown.js',
+	$CFG->httpswwwroot.'/lib/offline/go_offline.js',
+	$CFG->httpswwwroot.'/lib/offline/gears_init.js',
+  );
+
+$usehttps = (int)($CFG->httpswwwroot !== $CFG->wwwroot); 
+$files[] = $CFG->httpswwwroot.'/lib/editor/tinymce/extra/tinymce.js.php?elanguage='.current_language().'&etheme='.current_theme().'&eusehttps='.$usehttps;
+
+$tinymcefiles = get_files_from_dir($CFG->dirroot.'/lib/editor/tinymce');
+$tinymcefiles = str_replace($CFG->libdir.'/editor/tinymce', $CFG->httpswwwroot.'/lib/editor/tinymce', $tinymcefiles);
+$yuifiles = get_files_from_dir($CFG->dirroot.'/lib/yui');
+$yuifiles = str_replace($CFG->libdir.'/yui', $CFG->httpswwwroot.'/lib/yui', $yuifiles);
+$pixfiles = get_files_from_dir($CFG->dirroot.'/pix');
+$pixfiles = str_replace($CFG->dirroot,$CFG->httpswwwroot,$pixfiles);
+$themefiles = get_files_from_dir($CFG->dirroot.'/theme/'.current_theme());
+$themefiles = str_replace($CFG->dirroot.'/theme',$CFG->themewww,$themefiles);
+
+ob_start();
+style_sheet_setup();
+ob_end_clean();
+
+$files = array_merge($files, $tinymcefiles, $yuifiles, $pixfiles, $themefiles, $CFG->stylesheets);
+
+// accessible courses if logged in as admin
+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);
+}
+// all visible courses
+foreach ($courses as $course) {
+    if ($course->visible == 1
+        || has_capability('moodle/course:viewhiddencourses',$course->context)) {
+        $files[] = $CFG->wwwroot.'/course/view.php?id='.$course->id;
+	/*	foreach(get_list_of_plugins() as $module){
+			$files[] = $CFG->wwwroot.'/mod/'.$module.'/index.php?id='.$course->id;
+		}
+	*/
+    }
+}
+
+
+
+
+$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/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/weblib.php b/lib/weblib.php
index 26feb67..e4bf04e 100644
--- a/lib/weblib.php
+++ b/lib/weblib.php
@@ -2446,6 +2446,21 @@ function print_header ($title='', $heading='', $navigation='', $focus='',
         $menu = user_login_string($COURSE);
     }
 
+    $gears = TRUE;
+    if($gears && isloggedin()){
+        $lang_string = array();
+        $lang_string['gooffline'] = get_string('gooffline');
+        $lang_string['goonline'] = get_string('goonline');
+        $lang_string['pleasewait'] = get_string('pleasewait');
+        $PAGE->requires->data_for_js('js_lang_string', $lang_string);
+        
+        $PAGE->requires->js('lib/offline/gears_init.js');
+        $PAGE->requires->js('lib/offline/go_offline.js');
+        $PAGE->requires->js_function_call('init_offline');
+        $menu = '<span id="serverStatus"></span><span id="offline-message"></span><span id="offline-status"><a href="#" onclick="createStore()">'.get_string('gooffline').'</a></span>'.$menu;
+
+    }
+
     if (isset($SESSION->justloggedin)) {
         unset($SESSION->justloggedin);
         if (!empty($CFG->displayloginfailures)) {
-- 
1.6.3.1

