Index: /trunk/FACT++/www/shift/calendar.css
===================================================================
--- /trunk/FACT++/www/shift/calendar.css	(revision 13685)
+++ /trunk/FACT++/www/shift/calendar.css	(revision 13685)
@@ -0,0 +1,124 @@
+/**********************************************************************
+*          Calendar JavaScript [DOM] v3.1 by Michael Loesler           *
+************************************************************************
+* Copyright (C) 2005-10 by Michael Loesler, http//derletztekick.com    *
+*                                                                      *
+*                                                                      *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 3 of the License, or    *
+* (at your option) any later version.                                  *
+*                                                                      *
+* This program is distributed in the hope that it will be useful,      *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of       *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
+* GNU General Public License for more details.                         *
+*                                                                      *
+* You should have received a copy of the GNU General Public License    *
+* along with this program; if not, see <http://www.gnu.org/licenses/>  *
+* or write to the                                                      *
+* Free Software Foundation, Inc.,                                      *
+* 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.            *
+*                                                                      *
+ **********************************************************************/
+
+			html, body { 
+				background-color: #ECECEC;
+				font-family: verdana, arial, sans-serif; 
+			}
+				
+			#calendar { /* Fuer IE <= 6 */
+				text-align: center;
+			}
+    
+			#calendar table thead th{ 
+				font-weight: bold; 
+				font-size: 0.75em;
+				line-height: 1.5em;				
+				color: #BFBFC1; 
+				text-align: center;
+				background-color: #112A5D;
+			}
+			
+			#calendar table thead th.weekday{ 
+				font-weight: bold; 
+				font-size: 0.66em;
+				line-height: 1.5em;
+				color: #112A5D; 
+				text-align: center;
+				background-color: #CCD2D8;
+				border: solid #112A5D 1px;
+			}
+
+			#calendar table tbody td, #calendar table tfoot td{ 
+				font-weight: normal; 
+				font-size: 0.66em;
+				line-height: 1.5em;
+				width: 2.0em;
+				padding-left: 0.6em; 
+				padding-right: 0.6em; 
+				color: #0E224B; 
+				text-align: right;
+				border: 1px solid #CCD2D8;
+                                vertical-align:top;
+                                font-weight:bolder;
+			}
+			
+			#calendar table tfoot td {
+				font-size: 0.65em;
+				border: none;
+			}
+			
+			#calendar table tfoot td.calendar_week {
+				text-align: left;
+			}
+			
+			#calendar table tbody td.saturday{ 
+				color: #9A2525;
+				font-weight: normal;
+			}
+			
+			#calendar table tbody td.sunday{ 
+				color: #9A2525;
+				font-weight: bold;
+			}
+			
+			#calendar table tbody td.weekend{ 
+				color: #9A2525;
+			}
+			
+			#calendar table tbody td.today{
+				/*background-color: #A7B5F7;*/
+                                border:3px solid #FC8298;
+			}
+
+			#calendar table tbody td.enabled{
+				background-color: #E8F5C7;
+			}
+			#calendar table tbody div.institute{
+				background-color: #C8D5A7;
+                                text-align:center;
+                                font-size:1.25em;
+			}
+			#calendar table tbody td.choosen{
+                                border:2px solid #808080;
+			}
+			
+			#calendar table thead th.prev_year, #calendar table thead th.next_year {
+				margin: 0.1em;
+				padding: 0.1em;
+				line-height: 0.75em;
+				font-size: 0.65em;
+			}
+			
+			#calendar table tbody td.last_month, #calendar table tbody td.next_month {
+				color: 	#a3afc4;
+			}
+			
+			#calendar table{
+				border-collapse: collapse;
+				border: solid #112A5D 2px;
+				padding: 0;
+				margin:0;
+				background-color: #F6F6F6;
+			}
Index: /trunk/FACT++/www/shift/calendar.js
===================================================================
--- /trunk/FACT++/www/shift/calendar.js	(revision 13685)
+++ /trunk/FACT++/www/shift/calendar.js	(revision 13685)
@@ -0,0 +1,716 @@
+/**********************************************************************
+*          Calendar JavaScript [DOM] v3.11 by Michael Loesler          *
+************************************************************************
+* Copyright (C) 2005-09 by Michael Loesler, http//derletztekick.com    *
+*                                                                      *
+*                                                                      *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 3 of the License, or    *
+* (at your option) any later version.                                  *
+*                                                                      *
+* This program is distributed in the hope that it will be useful,      *
+* but WITHOUT ANY WARRANTY; without even the implied warranty of       *
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
+* GNU General Public License for more details.                         *
+*                                                                      *
+* You should have received a copy of the GNU General Public License    *
+* along with this program; if not, see <http://www.gnu.org/licenses/>  *
+* or write to the                                                      *
+* Free Software Foundation, Inc.,                                      *
+* 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.            *
+*                                                                      *
+ **********************************************************************/
+/*
+function logout()
+{
+    var xmlHttp = new XMLHttpRequest();
+    xmlHttp.open('POST', "calendar.php?logout=1", true);
+    xmlHttp.onload = function ()
+    {
+        if (xmlHttp.status!=200)
+        {
+            alert("ERROR - HTTP request: "+xmlHttp.statusText+" ["+xmlHttp.status+"]");
+            return;
+        }
+
+        alert("Logout successful!");
+    };
+
+    xmlHttp.send();
+}
+*/
+function resize()
+{
+    var table = document.getElementById("table");
+
+    var W = window.innerWidth;
+    var H = window.innerHeight;
+
+    table.style.width =W+"px";
+    table.style.height=H+"px";
+}
+
+var institutes= ["User", "EPFL", "ETHZ", "ISDC", "UNIDO", "UNIGE", "UNIWUE" ];
+
+function CalendarJS()
+{
+    this.now       = new Date();
+    this.dayname   = ["Mo","Tu","We","Th","Fr","Sa","So"];
+    this.monthname = ["January","February","March","April","May","June","July","August","September","October","November","December"];
+    this.tooltip   = ["previous month","next month","current date","last year","next year"];
+    this.monthCell = document.createElement("th");
+    this.tableHead = null;
+    this.tableFoot = null;
+    this.parEl     = null;
+
+    this.init = function( id, initDate )
+    {
+        this.now       = initDate ? initDate : new Date();
+        this.date      = this.now.getDate();
+        this.month     = this.mm = this.now.getMonth();
+        this.year      = this.yy = this.now.getFullYear();
+        this.monthCell.appendChild(document.createTextNode( this.monthname[this.mm]+"\u00a0"+this.yy ));
+
+        this.tableHead = this.createTableHead();
+        this.tableFoot = this.createTableFoot();
+
+        this.parEl = document.getElementById( id );
+        this.show();
+
+        if (!initDate)
+            this.checkDate();
+    };
+
+    this.checkDate = function()
+    {
+        var self  = this;
+        var today = new Date();
+
+        if (this.date != today.getDate())
+        {
+            this.tableHead = this.createTableHead();
+            this.tableFoot = this.createTableFoot();
+
+            this.date = today.getDate();
+            if (this.mm == this.month && this.yy == this.year)
+                this.switchMonth("current");
+
+            this.month = today.getMonth();
+            if (this.mm == this.month && this.yy == this.year)
+                this.switchMonth("current");
+
+            this.year  = today.getFullYear();
+            if (this.mm == this.month && this.yy == this.year)
+                this.switchMonth("current");
+        }
+        window.setTimeout(function() { self.checkDate(); }, Math.abs(new Date(this.year, this.month, this.date, 24, 0, 0)-this.now));
+    },
+
+    this.removeElements = function( Obj )
+    {
+        while( Obj.childNodes.length > 0)
+            Obj.removeChild(Obj.lastChild);
+
+        return Obj;
+    };
+
+    this.show = function()
+    {
+        this.parEl = this.removeElements( this.parEl );
+        this.monthCell.firstChild.replaceData(0, this.monthCell.firstChild.nodeValue.length, this.monthname[this.mm]+"\u00a0"+this.yy);
+
+        var table = document.createElement("table");
+        table.id = "table";
+
+        this.parEl.appendChild( table );
+
+        table.appendChild( this.tableHead );
+        table.appendChild( this.tableFoot );
+
+        table.appendChild( this.createTableBody(window.innerHeight-table.offsetHeight) );
+ 
+        resize();
+    };
+
+    this.createTableFoot = function()
+    {
+        var tfoot = document.createElement("tfoot");
+
+        var tr = document.createElement("tr");
+        var td = document.createElement("td");
+        td.height = "1%";
+        td.colSpan = 7;
+        td.style.padding="3px 3px";
+        tfoot.appendChild(tr);
+        tr.appendChild(td);
+        var table = document.createElement("table");
+        table.width="100%";
+        td.appendChild(table);
+        tr = document.createElement("tr");
+        table.appendChild(tr);
+        for (var i=0; i<institutes.length; i++)
+        {
+            td = document.createElement("td");
+            td.width=100/institutes.length+"%";
+            td.setAttribute("style", "text-align:center;font-size:1em;border:solid #112A5D 2px;padding:3px 3px;");
+            td.changeUser = this.changeUser;
+            td.onclick = function(e) { this.changeUser(); }
+            td.appendChild(document.createTextNode(institutes[i]));
+            tr.appendChild(td);
+
+            if (i==0)
+                td.style.backgroundColor = "yellow";
+        }
+        document.getElementById("body").setAttribute("data-user", institutes[0]);
+
+
+        tr = document.createElement("tr");
+        td = document.createElement("td");
+        td.colSpan = 7;
+        td.style.paddingLeft = "0px";
+        td.style.paddingTop  = "0px";
+        td.height = "1%";
+        var form  = document.createElement("form");
+        var input = document.createElement("textarea");
+        input.overflow    = "auto";
+        input.wrap        = "virtual";
+        input.id          = "comment";
+        input.value       = "enter comment here";
+        input.style.color = "#888";
+        input.style.width = "100%";
+        input.rows        = 5;
+        input.title       = "Enter a comment. Click somewhere in the calender to store the comment.";
+        input.onchange    = function() { pushComment(); };
+        input.onfocus     = function() { if (input.value=="enter comment here" && input.style.color!="black") input.value=""; input.style.color="black"; };
+        input.onblur      = function() { input.style.color="#888"; if (input.value=="") input.value="enter comment here"; };
+        form.appendChild(input);
+        td.appendChild( form );
+        tr.appendChild( td );
+        tfoot.appendChild(tr);
+
+        tr = document.createElement("tr");
+        td = this.getCell( "td", "logout", "calendar_week");
+        td.onclick = function(e) { logout(); }
+        td.height="1%";
+        td.colSpan = 1;
+        tr.appendChild( td );
+
+        var td = document.createElement("td");
+        td.height="1%";
+        td.colSpan = 5;
+        td.style.textAlign="center";
+        var a = document.createElement("a");
+        a.href = "overview.png";
+        a.appendChild(document.createTextNode("click here for help"));
+        td.appendChild(a);
+        tr.appendChild( td );
+
+        td = this.getCell( "td", this.timeTrigger(), "clock" );
+        td.colSpan = 1;
+        td.height="1%";
+        tr.appendChild( td );
+        tfoot.appendChild( tr );
+
+        var self = this;
+        window.setInterval(function() { td.firstChild.nodeValue = self.timeTrigger(); }, 500);
+        return tfoot;
+    }
+
+    this.createTableHead = function()
+    {
+        var thead = document.createElement("thead");
+        thead.style.height="1%";
+        var tr = document.createElement("tr");
+        var th = this.getCell( "th", "\u00AB", "prev_month" );
+
+        th.rowSpan = 2;
+        th.Instanz = this;
+        th.onclick = function() { this.Instanz.switchMonth("prev"); };
+        th.title = this.tooltip[0];
+
+        try { th.style.cursor = "pointer"; } catch(e){ th.style.cursor = "hand"; }
+        tr.appendChild( th );
+
+        this.monthCell.Instanz = this;
+        this.monthCell.rowSpan = 2;
+        this.monthCell.colSpan = 4;
+        this.monthCell.onclick = function() { this.Instanz.switchMonth("current"); };
+        this.monthCell.title = this.tooltip[2];
+
+        try { this.monthCell.style.cursor = "pointer"; } catch(e){ this.monthCell.style.cursor = "hand"; }
+        tr.appendChild( this.monthCell );
+
+        th = this.getCell( "th", "\u00BB", "next_month" );
+        th.rowSpan = 2;
+        th.Instanz = this;
+        th.onclick = function() { this.Instanz.switchMonth("next"); };
+        th.title = this.tooltip[1];
+
+        try { th.style.cursor = "pointer"; } catch(e){ th.style.cursor = "hand"; }
+        tr.appendChild( th );
+
+        th = this.getCell( "th", "\u02c4", "prev_year" );
+        th.Instanz = this;
+        th.onclick = function() { this.Instanz.switchMonth("prev_year"); };
+        th.title = this.tooltip[3];
+
+        try { th.style.cursor = "pointer"; } catch(e){ th.style.cursor = "hand"; }
+        tr.appendChild( th );
+
+        thead.appendChild( tr );
+
+        tr = document.createElement("tr");
+        th = this.getCell( "th", "\u02c5", "next_year" );
+        th.Instanz = this;
+        th.onclick = function() { this.Instanz.switchMonth("next_year"); };
+        th.title = this.tooltip[4];
+
+        try { th.style.cursor = "pointer"; } catch(e){ th.style.cursor = "hand"; }
+        tr.appendChild( th );
+
+        thead.appendChild( tr );
+
+        tr = document.createElement('tr');
+        for (var i=0; i<this.dayname.length; i++)
+        {
+            var th = this.getCell("th", this.dayname[i], "weekday" );
+            th.width=100/7+"%";
+            tr.appendChild( th );
+        }
+
+        thead.appendChild( tr );
+
+        return thead;
+    },
+
+    this.createTableBody = function(height)
+    {
+        var dayspermonth = [31,28,31,30,31,30,31,31,30,31,30,31];
+        var sevendaysaweek = 0;
+        var begin = new Date(this.yy, this.mm, 1);
+        var firstday = begin.getDay()-1;
+        if (firstday < 0)
+            firstday = 6;
+        if ((this.yy%4==0) && ((this.yy%100!=0) || (this.yy%400==0)))
+            dayspermonth[1] = 29;
+
+        var tbody = document.createElement("tbody");
+        var tr    = document.createElement('tr');
+
+        tbody.height="100%";
+
+        var height="";//"20%";//100/8+"%";
+
+        if (firstday == 0)
+        {
+            for (var i=0; i<this.dayname.length; i++)
+            {
+                var prevMonth = (this.mm == 0)?11:this.mm-1;
+                var td = this.getCell( "td", dayspermonth[prevMonth]-6+i, "last_month" );
+                td.style.height=height;
+                tr.appendChild( td );
+            }
+            tbody.appendChild( tr );
+            tr = document.createElement('tr');
+        }
+
+        for (var i=0; i<firstday; i++, sevendaysaweek++)
+        {
+            var prevMonth = (this.mm == 0)?11:this.mm-1;
+            var td = this.getCell( "td", dayspermonth[prevMonth]-firstday+i+1, "last_month" );
+            td.style.height=height;
+            tr.appendChild( td );
+        }
+
+        for (var i=1; i<=dayspermonth[this.mm]; i++, sevendaysaweek++)
+        {
+            if (this.dayname.length == sevendaysaweek)
+            {
+                tbody.appendChild( tr );
+                tr = document.createElement('tr');
+                sevendaysaweek = 0;
+            }
+
+            var td = null;
+            if (i==this.date && this.mm==this.month && this.yy==this.year && (sevendaysaweek == 5 || sevendaysaweek == 6))
+                td = this.getCell( "td", i, "today weekend" );
+            else
+                if (i==this.date && this.mm==this.month && this.yy==this.year)
+                    td = this.getCell( "td", i, "today" );
+                else
+                    if (sevendaysaweek == 5 || sevendaysaweek == 6)
+                        td = this.getCell( "td", i, "weekend" );
+                    else
+                        td = this.getCell( "td", i, null);
+
+            td.setDate    = this.setDate;
+            td.chooseDate = this.chooseDate;
+            td.dd = i;
+            td.mm = this.mm;
+            td.yy = this.yy;
+            td.id = this.mm+"-"+i;
+            td.title = "Click to select this date.";
+
+            td.style.height=height;
+
+            td.onclick = function(e) {
+                this.chooseDate();
+            };
+
+            tr.appendChild( td );
+        }
+
+        var daysNextMonth = 1;
+        for (var i=sevendaysaweek; i<this.dayname.length; i++)
+            tr.appendChild( this.getCell( "td", daysNextMonth++, "next_month"  ) );
+
+        tbody.appendChild( tr );
+
+        while (tbody.getElementsByTagName("tr").length<6) {
+            tr = document.createElement('tr');
+            for (var i=0; i<this.dayname.length; i++)
+            {
+                var td = this.getCell( "td", daysNextMonth++, "next_month"  );
+                td.style.height=height;
+                tr.appendChild( td );
+            }
+            tbody.appendChild( tr );
+        }
+
+        requestAll(this.yy, this.mm);
+        if (this.year==this.yy && this.month==this.mm)
+            requestComment(this.year, this.month, this.date);
+        else
+        {
+            var c = document.getElementById("comment");
+            c.color="#888";
+            c.value="enter comment here";
+        }
+
+        return tbody;
+    };
+
+    this.getCalendarWeek = function(j,m,t)
+    {
+        var cwDate = this.now;
+        if (!t)
+        {
+            j = cwDate.getFullYear();
+            m = cwDate.getMonth();
+            t = cwDate.getDate();
+        }
+        cwDate = new Date(j,m,t);
+
+        var doDat = new Date(cwDate.getTime() + (3-((cwDate.getDay()+6) % 7)) * 86400000);
+        cwYear = doDat.getFullYear();
+
+        var doCW = new Date(new Date(cwYear,0,4).getTime() + (3-((new Date(cwYear,0,4).getDay()+6) % 7)) * 86400000);
+        cw = Math.floor(1.5+(doDat.getTime()-doCW.getTime())/86400000/7);
+        return cw;
+    };
+
+    function request(td)
+    {
+        var user = document.getElementById("body").getAttribute("data-user");
+        var uri = "calendar.php?toggle&y="+td.yy+"&m="+td.mm+"&d="+td.dd;
+        if (user!="User")
+            uri += "&u="+user;
+
+        var xmlHttp = new XMLHttpRequest();
+        xmlHttp.open('POST', uri, true);
+        xmlHttp.onload = function ()
+        {
+            if (xmlHttp.status!=200)
+            {
+                alert("ERROR - HTTP request: "+xmlHttp.statusText+" ["+xmlHttp.status+"]");
+                return;
+            }
+
+            var lines = xmlHttp.responseText.split('\n');
+            if (lines.length==0)
+                return;
+
+            while (td.childNodes.length>1)
+                td.removeChild(td.lastChild);
+
+            for (var i=0; i<lines.length; i++)
+            {
+                var x = lines[i].split('\t');
+                if (x.length!=2)
+                    continue;
+
+                var div = document.createElement("div");
+                div.style.fontWeight="normal";
+                div.appendChild(document.createTextNode(x[1]));
+                td.appendChild(div);
+
+                for (var j=0; j<institutes.length; j++)
+                    if (x[1]==institutes[j])
+                    {
+                        div.className += " institute";
+                        break;
+                    }
+            }
+
+            if (td.childNodes.length>1)
+                td.className += " enabled";
+            else
+                td.className = td.className.replace(/enabled/g, "");
+        };
+
+        xmlHttp.send();
+    }
+
+    function logout()
+    {
+        var xmlHttp = new XMLHttpRequest();
+        xmlHttp.open('POST', "calendar.php?logout", true);
+        xmlHttp.onload = function ()
+        {
+            if (xmlHttp.status!=200)
+            {
+                alert("ERROR - HTTP request: "+xmlHttp.statusText+" ["+xmlHttp.status+"]");
+                return;
+            }
+        };
+
+        xmlHttp.send();
+    }
+
+    function pushComment()
+    {
+        var c = document.getElementById("comment");
+
+        var y = c.getAttribute("data-y");
+        var m = c.getAttribute("data-m");
+        var d = c.getAttribute("data-d");
+
+        var uri = "calendar.php?y="+y+"&m="+m+"&d="+d+"&c="+encodeURIComponent(c.value);
+
+        var xmlHttp = new XMLHttpRequest();
+        xmlHttp.open('POST', uri, true);
+        xmlHttp.onload = function ()
+        {
+            if (xmlHttp.status!=200)
+            {
+                alert("ERROR - HTTP request: "+xmlHttp.statusText+" ["+xmlHttp.status+"]");
+                return;
+            }
+
+            alert("Comment inserted successfully.")
+        };
+
+        xmlHttp.send();
+    }
+
+    function requestComment(yy, mm, dd)
+    {
+        var c = document.getElementById("comment");
+
+        var y = c.getAttribute("data-y");
+        var m = c.getAttribute("data-m");
+        var d = c.getAttribute("data-d");
+
+        if (y==yy && m==mm && d==dd)
+            return;
+
+        var uri = "calendar.php?comment&y="+yy+"&m="+mm+"&d="+dd;
+        var xmlHttp = new XMLHttpRequest();
+        xmlHttp.open('POST', uri, true);
+        xmlHttp.onload = function ()
+        {
+            if (xmlHttp.status!=200)
+            {
+                alert("ERROR - HTTP request: "+xmlHttp.statusText+" ["+xmlHttp.status+"]");
+                return;
+            }
+
+            c.color="#888";
+            if (xmlHttp.responseText=="")
+                c.value="enter comment here";
+            else
+                c.value = xmlHttp.responseText;
+
+            c.setAttribute("data-y", yy);
+            c.setAttribute("data-m", mm);
+            c.setAttribute("data-d", dd);
+        };
+
+        xmlHttp.send();
+    }
+
+    function requestAll(yy, mm)
+    {
+        var uri = "calendar.php?y="+yy+"&m="+mm;
+        var xmlHttp = new XMLHttpRequest();
+        xmlHttp.open('POST', uri, true);
+        xmlHttp.onload = function ()
+        {
+            if (xmlHttp.status!=200)
+            {
+                alert("ERROR - HTTP request: "+xmlHttp.statusText+" ["+xmlHttp.status+"]");
+                return;
+            }
+
+            var lines = xmlHttp.responseText.split('\n');
+            if (lines.length==0)
+                return;
+
+            for (var i=0; i<lines.length; i++)
+            {
+                var x = lines[i].split('\t');
+                if (x.length!=2)
+                    continue;
+
+                var td = document.getElementById(mm+"-"+x[0]);
+
+                var div = document.createElement("div");
+                div.style.fontWeight="normal";
+                div.appendChild(document.createTextNode(x[1]));
+                td.appendChild(div);
+
+                for (var j=0; j<institutes.length; j++)
+                    if (x[1]==institutes[j])
+                    {
+                        div.className += " institute";
+                        break;
+                    }
+
+                td.className += " enabled";
+            }
+        };
+
+        xmlHttp.send();
+    }
+
+    this.setDate = function()
+    {
+        request(this);
+    };
+
+    this.changeUser = function()
+    {
+        var sib = this.nextSibling;
+        while (sib)
+        {
+            sib.style.backgroundColor = "";
+            sib = sib.nextSibling;
+        }
+
+        sib = this.previousSibling;
+        while (sib)
+        {
+            sib.style.backgroundColor = "";
+            sib = sib.previousSibling;
+        }
+
+        this.style.backgroundColor = "yellow";
+
+        document.getElementById("body").setAttribute("data-user", this.firstChild.textContent);
+    };
+
+    this.chooseDate = function()
+    {
+        while (document.getElementsByClassName("choosen")[0])
+        {
+            var e = document.getElementsByClassName("choosen")[0];
+            e.title = "Click to select this date.";
+            e.className = e.className.replace(/choosen/g, "");
+            e.onclick = function() {
+                this.chooseDate();
+            };
+        }
+
+        this.className += " choosen";
+        this.title = "Click again to add or remove your name.";
+
+        requestComment(this.yy, this.mm, this.dd);
+
+        this.onclick = function() {
+            this.setDate();
+        };
+    };
+
+    this.timeTrigger = function()
+    {
+        var now = new Date();
+        var ss  = (now.getSeconds()<10)?"0"+now.getSeconds():now.getSeconds();
+        var mm  = (now.getMinutes()<10)?"0"+now.getMinutes():now.getMinutes();
+        var hh  = (now.getHours()  <10)?"0"+now.getHours()  :now.getHours();
+
+        var kw = "KW\u00a0" + this.getCalendarWeek(this.year, this.month, this.date);
+        var str = hh+":"+mm+":"+ss+"\u00a0["+kw+"]";
+        return str;
+    };
+
+    this.getCell = function(tag, str, cssClass)
+    {
+        var El = document.createElement( tag );
+        El.appendChild(document.createTextNode( str ));
+        if (cssClass != null)
+            El.className = cssClass;
+        return El;
+    },
+
+    this.switchMonth = function( s )
+    {
+        switch (s)
+        {
+        case "prev":
+            this.yy = (this.mm == 0) ? this.yy-1 : this.yy;
+            this.mm = (this.mm == 0) ? 11        : this.mm-1;
+            break;
+
+        case "next":
+            this.yy = (this.mm == 11) ? this.yy+1 : this.yy;
+            this.mm = (this.mm == 11) ? 0         : this.mm+1;
+            break;
+
+        case "prev_year":
+            this.yy = this.yy-1;
+            break;
+
+        case "next_year":
+            this.yy = this.yy+1;
+            break;
+
+        case "current":
+            this.yy = this.year;
+            this.mm = this.month;
+            break;
+        }
+        this.show();
+    }
+}
+
+var DOMContentLoaded = false;
+function addContentLoadListener (func)
+{
+    if (document.addEventListener)
+    {
+        var DOMContentLoadFunction = function ()
+        {
+            window.DOMContentLoaded = true;
+            func();
+        };
+
+        document.addEventListener("DOMContentLoaded", DOMContentLoadFunction, false);
+    }
+
+    var oldfunc = (window.onload || new Function());
+
+    window.onload = function ()
+    {
+        if (!window.DOMContentLoaded)
+        {
+            oldfunc();
+            func();
+        }
+    };
+}
+
+addContentLoadListener( function() {
+new CalendarJS().init("calendar");
+//new CalendarJS().init("calendar", new Date(2009, 1, 15));
+} );
Index: /trunk/FACT++/www/shift/calendar.php
===================================================================
--- /trunk/FACT++/www/shift/calendar.php	(revision 13685)
+++ /trunk/FACT++/www/shift/calendar.php	(revision 13685)
@@ -0,0 +1,154 @@
+<?PHP
+
+require_once("config.php");
+
+function login()
+{
+    global $ldaphost;
+    global $baseDN;
+    global $groupDN;
+
+    $username = $_SERVER['PHP_AUTH_USER'];
+    $password = $_SERVER['PHP_AUTH_PW'];
+
+    $con = @ldap_connect($ldaphost);
+    if (!$con)
+        return "ldap_connect failed to ".$ldaphost;
+
+    //------------------ Look for user common name
+    $attributes = array('cn', 'mail');
+    $dn         = 'ou=People,'.$baseDN;
+    $filter     = '(uid='.$username.')';
+
+    $sr = @ldap_search($con, $dn, $filter, $attributes);
+    if (!$sr)
+        return "ldap_search failed for dn=".$dn.": ".ldap_error($con);
+
+    $srData = @ldap_get_entries($con, $sr);
+    if ($srData["count"]==0)
+        return "No results returned by ldap_get_entries for dn=".$dn.".";
+
+    $email         =$srData[0]['mail'][0];
+    $userCommonName=$srData[0]['cn'][0];
+    $userDN        =$srData[0]['dn'];
+
+    //------------------ Authenticate user
+    if (!@ldap_bind($con, $userDN, $password))
+        return "ldap_bind failed: ".ldap_error($con);
+
+    //------------------ Check if the user is in FACT ldap group
+    $attributes= array("member");
+    $filter= '(objectClass=*)';
+
+    // Get all members of the group.
+    $sr = @ldap_read($con, $groupDN, $filter, $attributes);
+    if (!$sr)
+        return "ldap_read failed for dn=".$groupDN.": ".ldap_error($con);
+
+    // retrieve the corresponding data
+    $srData = @ldap_get_entries($con, $sr);
+    if ($srData["count"]==0)
+        return "No results returned by ldap_get_entries for dn=".$dn.".";
+
+    @ldap_unbind($con);
+
+    $found = false;
+    foreach ($srData[0]['member'] as $member)
+        if (strpos($member, "cn=".$userCommonName.",")===0)
+            return "";
+
+    return "Sorry, your credentials don't match!";
+}
+
+if (isset($_GET['logout']))
+{
+    Header( "HTTP/1.0 401 Logout successfull!");
+    exit();
+}
+
+if (!isset($_GET['y']) || !isset($_GET['m']))
+    return;
+
+$y = $_GET['y'];
+$m = $_GET['m'];
+
+if (!mysql_connect($dbhost, $dbuser, $dbpass))
+    return header('HTTP/1.0 500 '.mysql_error());
+
+if (!mysql_select_db($dbname))
+    return header('HTTP/1.0 500 '.mysql_error());
+
+if (isset($_GET['comment']))
+{
+    if (!isset($_GET['d']))
+        return;
+
+    $d = $_GET['d'];
+
+    $query = "SELECT c FROM Comments WHERE y=".$y." AND m=".$m." AND d=".$d;
+
+    $result = mysql_query($query);
+    if (!$result)
+        return header('HTTP/1.0 500 '.mysql_error());
+
+    $row = mysql_fetch_array($result, MYSQL_NUM);
+
+    print($row[0]);
+
+    return;
+}
+
+if (isset($_GET['d']))
+{
+    if (!isset($_SERVER['PHP_AUTH_USER']) || !isset($_SERVER['PHP_AUTH_PW']))
+    {
+        header('WWW-Authenticate: Basic realm="Shift schedule"');
+        header('HTTP/1.0 401 Unauthorized');
+        return;
+    }
+
+    $rc = login();
+    if ($rc!="")
+    {
+        header('HTTP/1.0 401 '.$rc);
+        return;
+    }
+
+    $d = $_GET['d'];
+
+    if (isset($_GET['c']))
+    {
+        $c = $_GET['c'];
+
+        if (!mysql_query("DELETE FROM Comments WHERE y=".$y." AND m=".$m." AND d=".$d))
+            return header('HTTP/1.0 500 '.mysql_error());
+
+        if (strlen($c)>0)
+            if (!mysql_query("INSERT Comments SET y=".$y.", m=".$m.", d=".$d.", c='".$c."'"))
+                return header('HTTP/1.0 500 '.mysql_error());
+        return;
+    }
+
+    $u = isset($_GET['u']) ? $_GET['u'] : $_SERVER['PHP_AUTH_USER'];
+
+    if (!mysql_query("DELETE FROM Data WHERE y=".$y." AND m=".$m." AND d=".$d." AND u='".$u."'"))
+        return header('HTTP/1.0 500 '.mysql_error());
+
+    if (mysql_affected_rows()==0)
+    {
+        if (!mysql_query("INSERT Data SET y=".$y.", m=".$m.", d=".$d.", u='".$u."'"))
+            return header('HTTP/1.0 500 '.mysql_error());
+    }
+}
+
+$query = "SELECT d, u FROM Data WHERE y=".$y." AND m=".$m;
+if (isset($_GET['d']))
+    $query .= " AND d=".$_GET['d'];
+
+$result = mysql_query($query);
+if (!$result)
+    return header('HTTP/1.0 500 '.mysql_error());
+
+while ($row = mysql_fetch_array($result, MYSQL_NUM))
+    print($row[0]."\t".$row[1]."\n");
+?>
Index: /trunk/FACT++/www/shift/index.html
===================================================================
--- /trunk/FACT++/www/shift/index.html	(revision 13685)
+++ /trunk/FACT++/www/shift/index.html	(revision 13685)
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+   <title>FACT shift schedule</title>
+   <link rel="stylesheet" media="screen" href="./calendar.css" type="text/css" />
+   <script type="text/javascript" src="./calendar.js"></script>
+   <meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, user-scalable=0, minimum-scale=1.0, maximum-scale=1.0" />
+</head>
+<body id="body" onresize="resize();">
+<div id="calendar" style="position:fixed;top:0;left:0;overflow:hidden;"/>
+</body>
+</html>
