Index: /branches/1.0/AUTHORS
===================================================================
--- /branches/1.0/AUTHORS	(revision 825)
+++ /branches/1.0/AUTHORS	(revision 825)
@@ -0,0 +1,37 @@
+MAIN DEVELOPER(S)
+=================
+
+	* Design, code and implementation
+
+	-	Ramon Bastiaans
+		< bastiaans ( a t ) sara ( d o t ) nl >
+
+ORIGINAL CONCEPT(S)
+===================
+
+	* PBS Monitor
+
+	-	Bas van der Vlies
+		< basv ( a t ) sara ( d o t ) nl >
+
+	* PBS web software
+
+	-	ROCKS Cluster group,
+		San Diego
+
+CONTRIBUTOR(S)
+==============
+
+	* Support, testing and idea's
+
+	-	Bas van der Vlies
+		< basv ( a t ) sara ( d o t ) nl >
+
+	-	Walter de Jong
+		< walter ( a t ) sara ( d o t ) nl >
+
+	-	Jaap Dijkshoorn
+		< jaap ( a t ) sara ( d o t ) nl >
+
+	-	Maarten van Ingen
+		< maarten ( a t ) sara ( d o t ) nl >
Index: /branches/1.0/CHANGELOG
===================================================================
--- /branches/1.0/CHANGELOG	(revision 825)
+++ /branches/1.0/CHANGELOG	(revision 825)
@@ -0,0 +1,221 @@
+
+	LEGEND	f: fixed - c: changed - a: added - r: removed
+
+0.4:
+
+	jobmond)
+		a:	SGE support
+			thanks to: Dave Love - d(d.o.t)love(a.t)liverpool(d.o.t)ac(d.o.t)uk
+			for writing it!
+		a:	LSF support
+			thanks to: Mahmoud Hanafi - mhanafi(a.t)csc(d.o.t)com
+			for writing it!
+		a:	GMETRIC_TARGET is now parsed from gmond.conf
+		a:	GMETRIC_BINARY is now looked for in PATH
+		f:	queue selection support is now working
+			thanks to: Craig West - cwest(a.t)astro(d.o.t)umass(d.o.t)edu
+			for the patch
+	web)
+		a:	large graphs link for job report
+			thanks to: Craig West - cwest(a.t)astro(d.o.t)umass(d.o.t)edu
+		a:	SHOW_EMPTY_COLUMN, SHOW_EMPTY_ROW options for ClusterImage hostname parsing
+
+0.3.1:
+
+	other)
+		f:	updated INSTALL since "addons" directory is not included by default anymore in Ganglia
+			thanks to: Steven DuChene linux(d.a.s.h)clusters(a.t)mindspring(d.o.t)com
+			for reporting it
+
+	rpm)
+		f:	add "addons" directory since it's not included by default anymore in Ganglia
+		f:	properly rewrite WEBDIR path in %files when rebuilding rpms with Makefile
+
+	web)
+		f:	typo in empty_cpu variable: causing incorrect 'free cpu' count and similar errors
+			thanks to: Craig West - cwest(a.t)astro(d.o.t)umass(d.o.t)edu
+			for reporting it
+		f:	changed erroneous domain detection a little
+			thanks to: Craig West - cwest(a.t)astro(d.o.t)umass(d.o.t)edu
+			for reporting it
+		a:	now properly detects whether or not to use FQDN or short hostnames w/o domain
+			thanks to: Craig West - cwest(a.t)astro(d.o.t)umass(d.o.t)edu
+			thanks to: Jeffrey Sarlo - JSarlo(a.t)Central(d.o.t)UH(d.o.t)EDU
+			for the many testing and reporting it
+
+			SPECIAL THANKS to the University of Houston for sending me a shirt!
+
+	jobarchived)
+		f:	properly catch postgres exception
+		f:	don't use debug_message while loading config file
+
+0.3:
+
+	web)
+		a:	allow per-cluster settings/override options: see CLUSTER_CONFS option
+		a:	clusterimage can now draw nodes at x,y position parsed from hostname
+			see SORTBY_HOSTNAME for this in clusterconf/example.php
+		a:	clusterimage nodes are now clickable: has link to all jobs from that host
+		a:	clusterimage nodes now have a tooltip: displays hostname and jobids for now
+		a:	jobmonarch logo image
+			thank to: Robin Day
+			for the design
+		a:	rrd graph of running/queued jobs to overview
+		a:	per-cluster settings for archive database
+			thanks to: Alexis Michon - alexis(d.o.t)michon(a.t)ibcp(d.o.t)fr
+			for the patch
+
+		c:	host archive view is now more complete and detailed in the same manner as
+			Ganglia's own host view
+		c:	host archive view available metric list is now compiled from disk,
+			so that the detailed archive host view works even when the node is currently down.
+		c:	removed size restrictions from detailed host archive view
+
+		f:	compatibility: removed php5 call
+			thanks to: Alexis Michon - alexis(d.o.t)michon(a.t)ibcp(d.o.t)fr
+			for the patch
+		f:	prevent negative cpu/node calculation
+			thanks to: aloga(a.t)ifca(d.o.t)unican(d.o.t)es
+			for the patch
+		f:	archive search not properly resetting nodes list
+			thanks to: Alexis Michon - alexis(d.o.t)michon(a.t)ibcp(d.o.t)fr
+			for the patch 
+		f:	detailed host view from jobarchive was broken since hostbased support of 0.2
+			now host view is properly set and parsed again
+			thanks to: Alexis Michon - alexis(d.o.t)michon(a.t)ibcp(d.o.t)fr
+			for reporting the bug and suggesting a patch
+		f:	bug where jobstart redline indicator in host detail graphs was set incorrectly
+			or not at all due to a miscalculation in job times
+		f:	bug where hostimage headertext xoffset was miscalculated, causing the column names
+			to overlap their position when the columnname was longer than the columnvalues
+
+	jobmond)
+
+		a:	syslog support
+		a:	report number of running/queued jobs as seperate metrics
+		a:	native gmetric support, much faster and cleaner!
+			thanks to: Nick Galbreath - nickg(a.t)modp(d.o.t)com
+			for writing it and allowing inclusion in jobmond
+
+		f:	crashing jobmond when multiple nodes amounts are requested in
+			a queued job: numeric_node variable not initialized properly
+			thanks to: aloga(a.t)ifca(d.o.t)unican(d.o.t)es
+			for supplying the patch
+			and many others for reporting and helping debug this
+		f:	hanging/blocked, increased cpu usage and halted reporting
+			thanks to: Bas van der Vlies - basv(a.t)sara(d.o.t)nl
+			for discovering the origin of the bug
+			thanks to: Mickael Gastineau - gastineau(a.t)imcce(d.o.t)fr
+			for reporting it and testing the fix
+			thanks to: Craig West - cwest(a.t)astro(d.o.t)umass(d.o.t)edu
+			for reporting it and testing the fix
+		f:	uninitialized variable in checkGmetricVersion()
+			thanks to: Peter Kruse - pk(a.t)q-leap(d.o.t)com
+			for the patch
+		f:	undefined PBSError
+			thanks to: Peter Kruse - pk(a.t)q-leap(d.o.t)com
+			for reporting it
+
+		r:	SGE support broken
+
+	jobarchived)
+
+		a:	can now use py-rrdtool api instead of pipes, much faster!
+			install py-rrdtool to use this
+			backwards compatible fails back to pipes if module not installed
+
+		c:	all XML input was uniencoded, which could cause errors,
+			now all properly converted to normal strings
+
+		f:	when XML data source (gmetad) is unavailable parsethread didn't return correctly
+			which caused a large number of threads to spawn while consuming large amounts of memory
+		f:	autocreate clusterdirs in archivedir
+		f:	unhandled gather exception
+		f:	incorrect stop_timestamping when jobs finished
+			thanks to: Alexis Michon - alexis(d.o.t)michon(a.t)ibcp(d.o.t)fr
+			for finding and debugging/testing it
+
+0.2:
+
+	web)
+		f:	misc. optimization and bugfixes
+		f:	now fully compatible with latest PHP5 and PHP4
+
+		c:	cluster image now incorporates small text descr.
+		c:	monarch (cluster/host) images no longer displayed
+			for clusters that are not jobmond enabled
+		c:	pie chart percentages are now cpu-based instead of node-based
+
+		a:	host template for Ganglia
+			adds a extra monarch host image to Ganglia's host overview
+			which displays/links to the jobs on that host
+			NOTE!: be sure to copy/install new template from addons/templates
+		a:	(optional) nodes hostnames column
+			thanks to: Daniel Barthel - daniel(d.o.t)barthel(a.t)nottingham(d.o.t)ac(d.o.t)uk
+			for the suggestion
+
+	jobmond)
+
+		f:	when a job metric is longer than maximum metric length,
+			the info is split up amongst multiple metrics
+		f: 	no longer exit when batch server is unavailable
+			thanks to: Peter Kruse - pk(a.t)q-leap(d.o.t)com
+			for the patch
+		f:	fd closure bug causing stderr/stdout to remain open after daemonizing
+
+		c:	rearranged code to allow support for other batch systems
+
+		a:	(experimental) SGE (Sun Grid Engine) support as batch server
+			thanks to: Babu Sundaram - babu(a.t)cs(d.o.t)uh(d.o.t)edu
+			who developed it for a OSCAR's Google-SoC project
+		a:	pidfile support 
+			thanks to: Michael Jeanson - michael(a.t)ccs(d.o.t)usherbrooke(d.o.t)ca
+			for the patch
+		a:	usage display
+			thanks to: Michael Jeanson - michael(a.t)ccs(d.o.t)usherbrooke(d.o.t)ca
+			for the patch
+		a:	queue selection support: ability to specify which QUEUE's to get jobinfo from
+			thanks to: Michael Jeanson - michael(a.t)ccs(d.o.t)usherbrooke(d.o.t)ca
+			for the patch
+
+	jobarchived)
+
+		f:	XML retrieval for Ganglia version >= 3.0.3 working properly again
+		f:	database storing for Ganglia version >= 3.0.3 working properly again
+		f:	fd closure bug causing stderr/stdout to remain open after daemonizing
+
+		c:	misc. bugfixes to optimize XML connections
+		c:	misc. bugfixes for misc. minor issues
+
+		a:	cleaning of stale jobs in dbase: see JOB_TIMEOUT option
+
+0.1.1: 
+
+	web)
+
+		f:	misc. layout bugs for overview & search
+		f:	bug that occured when calculating the number of nodes when there
+			was more than one job running on a machine
+
+		c:	column requested memory is now optional through conf.php
+		c:	search and overview tables are now full screen (100%)
+		c:	overview jobnames are now cutoff at max 9 characters
+			to prevent (layout) scews in the tables
+		c:	overview graphs are no longer downsized
+
+		a:	(optional) column 'queued' (since) in overview
+		a:	search results (can) now have a SEARCH_RESULT_LIMIT
+			this increases performance of the query's significantly!
+		a:	date/time format as displayed is now configurable through conf.php
+
+	jobmond)
+
+		a:	now reports 'queued since' (or creation time) of jobs
+
+	documentation)
+
+		f:	wrong e-mail adress in INSTALL (doh!)
+
+0.1:
+
+	- First public release
Index: /branches/1.0/INSTALL
===================================================================
--- /branches/1.0/INSTALL	(revision 825)
+++ /branches/1.0/INSTALL	(revision 825)
@@ -0,0 +1,230 @@
+DESCRIPTION
+===========
+
+	Job Monarch is a set of tools to monitor and optionally archive (batch)job information.
+
+	It is a addon for the Ganglia monitoring system and plugs in to a existing Ganglia setup.
+
+	To view a operational setup with Job Monarch, have a look here: http://ganglia.sara.nl/
+
+
+	Job Monarch stands for 'Job Monitoring and Archiving' tool and consists of three (3) components:
+
+	* jobmond
+
+		The Job Monitoring Daemon.
+		  
+		Gathers PBS/Torque batch statistics on jobs/nodes and submits them into
+		Ganglia's XML stream.
+
+		Through this daemon, users are able to view the PBS/Torque batch system and the
+		jobs/nodes that are in it (be it either running or queued).
+
+	* jobarchived (optionally)
+
+		The Job Archiving Daemon.
+
+		Listens to Ganglia's XML stream and archives the job and node statistics.
+		It stores the job statistics in a Postgres SQL database and the node statistics
+		in RRD files.
+		
+		Through this daemon, users are able to lookup a old/finished job
+		and view all it's statistics.
+
+		Optionally: You can either choose to use this daemon if your users have use for it.
+		As it can be a heavy application to run and not everyone may have a need for it.
+
+		- Multithreaded:	Will not miss any data regardless of (slow) storage
+
+		- Staged writing:	Spread load over bigger time periods
+
+		- High precision RRDs:	Allow for zooming on old periods with large precision
+
+		- Timeperiod RRDs:	Allow for smaller number of files while still keeping advantage
+					of small disk space
+		
+	* web
+
+		The Job Monarch web interface.
+
+		This interfaces with the jobmond data and (optionally) the jobarchived and presents the
+		data and graphs.
+
+		It does this in a similar layout/setup as Ganglia itself, so the navigation and usage is intuitive.
+
+		- Graphical usage:	Displays graphical cluster overview so you can see the cluster (job) state
+					in one view/image and additional pie chart with relevant information on your
+					current view
+
+		- Filters:		Ability to filter output to limit information displayed (usefull for those
+					clusters with 500+ jobs). This also filters the graphical overview images output
+					and pie chart so you only see the filter relevant data
+
+		- Archive:		When enabling jobarchived, users can go back as far as recorded in the database
+					or archived RRDs to find out what happened to a crashed or old job
+
+		- Zoom ability:		Users can zoom into a timepriod as small as the smallest grain of the RRDS
+					(typically up to 10 seconds) when a jobarchived is present
+
+REQUIREMENTS
+============
+
+	all:
+
+		- Python 2.3 or higher
+
+	jobmond:
+
+		- pbs_python v2.8.2 or higher
+		  https://subtrac.sara.nl/oss/pbs_python/
+
+		- gmond v3.0.1 or higher
+		  http://www.ganglia.info/
+
+	jobarchived:
+
+		- Postgres SQL v7.xx
+		  http://www.postgres.org/
+
+		- rrdtool v1.xx
+		  http://people.ee.ethz.ch/~oetiker/webtools/rrdtool/
+
+		- py-rrdtool
+		  http://sourceforge.net/projects/py-rrdtool/
+
+		- python-pgsql v4.x.x
+		  http://sourceforge.net/projects/pypgsql/
+
+		- gmetad v3.x.x
+		  http://www.ganglia.info/
+
+	web:
+
+		- PHP v4.1 or higher
+		  http://www.php.net
+
+		- php-pgsql v4.x.x
+		  (should come with Postgres)
+
+		- GD v2.x
+		  http://www.boutell.com/gd/
+
+		- Ganglia web frontend v3.x.x
+		  http://www.ganglia.info
+
+
+INSTALLATION
+============
+
+	Prior to installing the software make sure you meet the necessary requirements as
+	mentioned above.
+
+	NOTE: You can choose to install to other path/directories if your setup is different.
+
+	* jobmond
+
+		1. Copy jobmond.py:
+
+		 > cp jobmond/jobmond.py /usr/local/sbin/jobmond.py
+
+		2. Copy jobmond.conf:
+		
+		 > cp jobmond/jobmond.conf /etc/jobmond.conf
+
+	* jobarchived
+
+		1. Create a Postgres SQL database for jobarchived:
+
+		 > createdb jobarchive
+
+		2. Setup jobarchived's tables:
+
+		 > psql -f jobarchived/job_dbase.sql jobarchive
+
+		3. Copy jobarchived/jobarchived.conf:
+
+		 > cp jobarchived/jobarchived.conf /etc/jobarchived.conf
+
+		4. Copy jobarchived.py:
+
+		 > cp jobarchived/jobarchived.py /usr/local/sbin/jobarchived.py
+
+	* web
+
+		1. Copy the Job Monarch Template to your Ganglia installation
+
+		 > cp -a web/templates/job_monarch /var/www/ganglia/templates
+
+		2. Copy the web interface files to the addon directory in Ganglia
+
+		 > mkdir -p /var/www/ganglia/addons
+		 > cp -a web/addons/job_monarch /var/www/ganglia/addons
+
+CONFIGURATION
+=============
+
+	After installation each component requires additional configuration.
+
+	* jobmond
+	
+		1. Edit Jobmond's config to reflect your settings:
+
+		 - In /etc/jobmond.conf
+
+		   ( see config comments for syntax and explanation )
+
+	* jobarchived
+
+		1. Edit Jobarchived's config to reflect your settings:
+
+		 - In /etc/jobarchived.conf
+
+		   ( see config comments for syntax and explanation )
+
+	* web
+
+		1. Change your Ganglia's web template to Job Monarch
+
+		 - In /var/www/ganglia/conf.php:
+
+		 > $template_name = "job_monarch";
+
+		2. Change Job Monarch's config to reflect your settings:
+
+		 - In /var/www/ganglia/addons/job_monarch/conf.php
+
+		   ( see config comments for syntax and explanation )
+
+START
+=====
+
+	* jobmond
+
+		The Job Monitor has to be run on a machine that is allowed to
+		query the PBS/Torque server.
+		Make sure that if you have 'acl_hosts' enabled on your PBS/Torque
+		server that jobmond's machine is in it.
+
+		1. Start the Job Monitor:
+
+		 > /usr/local/sbin/jobmond.py -c /etc/jobmond.conf
+
+	* jobarchived
+
+		1. Start the Job Archiver:
+
+		 > /usr/local/sbin/jobarchived.py -c /etc/jobarchived.conf
+
+	* web
+
+		Doesn't require you to (re)start anything.
+		( make sure the Postgres database is running though )
+
+CONTACT
+=======
+
+	To contact the author for anything from bugfixes to flame/hate mail:
+
+	* Ramon Bastiaans
+
+	  <bastiaans ( a t ) sara ( d o t ) nl>
Index: /branches/1.0/LICENSE
===================================================================
--- /branches/1.0/LICENSE	(revision 825)
+++ /branches/1.0/LICENSE	(revision 825)
@@ -0,0 +1,345 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    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 2 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, write to the Free Software
+    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
Index: /branches/1.0/LICENSE.gmetric
===================================================================
--- /branches/1.0/LICENSE.gmetric	(revision 825)
+++ /branches/1.0/LICENSE.gmetric	(revision 825)
@@ -0,0 +1,23 @@
+# This is the MIT License
+# http://www.opensource.org/licenses/mit-license.php
+#
+# Copyright (c) 2007 Nick Galbreath
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
Index: /branches/1.0/Makefile
===================================================================
--- /branches/1.0/Makefile	(revision 825)
+++ /branches/1.0/Makefile	(revision 825)
@@ -0,0 +1,171 @@
+# Where to build
+#
+TMPDIR = /tmp
+
+# Clear this if you don't want to use ${FAKEROOT}
+#
+FAKEROOT = fakeroot
+
+# What is the location of the Ganglia web frontend
+# i.e.: where to we install Job Monarch's web frontend addon
+# 
+WEBDIR = /var/www/ganglia
+
+VERSION = 0.4SVN
+RELEASE = 1
+
+REQUIRED = ./jobarchived ./jobmond ./web
+
+deb:	deb-jobmond deb-jobarchived deb-webfrontend
+rpm:	rpm-jobmond rpm-jobarchived rpm-webfrontend
+
+all:	tarball deb rpm
+
+tarball:	tarball-gzip tarball-bzip
+
+tarball-gzip:	${REQUIRED}
+	mkdir -p ${TMPDIR}/.monarch_buildroot/ganglia_jobmonarch-${VERSION}
+	( rsync -a --exclude=.svn --exclude=*_test* --exclude=*-example.php \
+	. ${TMPDIR}/.monarch_buildroot/ganglia_jobmonarch-${VERSION} )
+	( cd ${TMPDIR}/.monarch_buildroot; tar zcvf ganglia_jobmonarch-${VERSION}.tar.gz ./ganglia_jobmonarch-${VERSION} )
+	mv ${TMPDIR}/.monarch_buildroot/ganglia_jobmonarch-${VERSION}.tar.gz ..
+
+tarball-bzip:	${REQUIRED}
+	mkdir -p ${TMPDIR}/.monarch_buildroot/ganglia_jobmonarch-${VERSION}
+	( rsync -a --exclude=.svn --exclude=*_test* --exclude=*-example.php \
+	. ${TMPDIR}/.monarch_buildroot/ganglia_jobmonarch-${VERSION} )
+	( cd ${TMPDIR}/.monarch_buildroot; tar jcvf ganglia_jobmonarch-${VERSION}.tar.bz2 ./ganglia_jobmonarch-${VERSION} )
+	mv ${TMPDIR}/.monarch_buildroot/ganglia_jobmonarch-${VERSION}.tar.bz2 ..
+
+deb-webfrontend:	${REQUIRED}
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend_${VERSION}-${RELEASE}/DEBIAN >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend_${VERSION}-${RELEASE}/${WEBDIR} >/dev/null
+	( cd web; rsync -a --exclude=.svn --exclude=*_test* --exclude=*-example.php \
+	. ${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend_${VERSION}-${RELEASE}/${WEBDIR} )
+	( cd pkg/deb/web/DEBIAN; \
+	rsync -a --exclude=.svn --exclude=*_test* --exclude=*-example.php \
+	. ${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend_${VERSION}-${RELEASE}/DEBIAN )
+	( cd ${TMPDIR}/.monarch_buildroot/; cat jobmonarch-webfrontend_${VERSION}-${RELEASE}/DEBIAN/control \
+	| sed "s/^Version:.*$//Version: ${VERSION}-${RELEASE}/g" >jobmonarch-webfrontend_${VERSION}-${RELEASE}/DEBIAN/control.new; \
+	mv jobmonarch-webfrontend_${VERSION}-${RELEASE}/DEBIAN/control.new \
+	jobmonarch-webfrontend_${VERSION}-${RELEASE}/DEBIAN/control )
+	( cd ${TMPDIR}/.monarch_buildroot/; ${FAKEROOT} dpkg -b jobmonarch-webfrontend_${VERSION}-${RELEASE} )
+	mv ${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend_${VERSION}-${RELEASE}.deb ..
+
+deb-jobarchived:	${REQUIRED}
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/DEBIAN >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/etc/init.d >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/etc/default >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/usr/sbin >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/usr/share/jobarchived >/dev/null
+	install -m 755 jobarchived/jobarchived.py ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/usr/sbin
+	( cd ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/usr/sbin; \
+	ln -s jobarchived.py jobarchived || true)
+	install jobarchived/jobarchived.conf ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/etc
+	install pkg/deb/init.d/jobarchived ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/etc/init.d
+	install pkg/deb/default/jobarchived ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/etc/default
+	install jobarchived/job_dbase.sql \
+	${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/usr/share/jobarchived
+	( cd pkg/deb/jobarchived/DEBIAN; \
+	rsync -a --exclude=.svn --exclude=*_test* --exclude=*-example.php \
+	. ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}/DEBIAN )
+	( cd ${TMPDIR}/.monarch_buildroot/; cat jobmonarch-jobarchived_${VERSION}-${RELEASE}/DEBIAN/control \
+	| sed "s/^Version:.*$//Version: ${VERSION}-${RELEASE}/g" >jobmonarch-jobarchived_${VERSION}-${RELEASE}/DEBIAN/control.new; \
+	mv jobmonarch-jobarchived_${VERSION}-${RELEASE}/DEBIAN/control.new \
+	jobmonarch-jobarchived_${VERSION}-${RELEASE}/DEBIAN/control )
+	( cd ${TMPDIR}/.monarch_buildroot/; ${FAKEROOT} dpkg -b jobmonarch-jobarchived_${VERSION}-${RELEASE} )
+	mv ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived_${VERSION}-${RELEASE}.deb ..
+
+deb-jobmond:	${REQUIRED}
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/DEBIAN >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/etc/init.d >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/etc/default >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/usr/sbin >/dev/null
+	install -m 755 jobmond/jobmond.py ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/usr/sbin
+	( cd ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/usr/sbin; \
+	ln -s jobmond.py jobmond || true)
+	install jobmond/jobmond.conf ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/etc
+	install pkg/deb/init.d/jobmond ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/etc/init.d
+	install pkg/deb/default/jobmond ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/etc/default
+	( cd pkg/deb/jobmond/DEBIAN; \
+	rsync -a --exclude=.svn --exclude=*_test* --exclude=*-example.php \
+	. ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}/DEBIAN )
+	( cd ${TMPDIR}/.monarch_buildroot/; cat jobmonarch-jobmond_${VERSION}-${RELEASE}/DEBIAN/control \
+	| sed "s/^Version:.*$//Version: ${VERSION}-${RELEASE}/g" >jobmonarch-jobmond_${VERSION}-${RELEASE}/DEBIAN/control.new; \
+	mv jobmonarch-jobmond_${VERSION}-${RELEASE}/DEBIAN/control.new jobmonarch-jobmond_${VERSION}-${RELEASE}/DEBIAN/control )
+	( cd ${TMPDIR}/.monarch_buildroot/; ${FAKEROOT} dpkg -b jobmonarch-jobmond_${VERSION}-${RELEASE} )
+	mv ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond_${VERSION}-${RELEASE}.deb ..
+
+rpm-jobmond:	${REQUIRED}
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}/etc/init.d >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}/etc/sysconfig >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}/usr/sbin >/dev/null
+	install -m 755 jobmond/jobmond.py ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}/usr/sbin
+	( cd ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}/usr/sbin; \
+	ln -s jobmond.py jobmond || true)
+	install jobmond/jobmond.conf ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}/etc
+	install pkg/rpm/init.d/jobmond ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}/etc/init.d
+	install pkg/rpm/sysconfig/jobmond ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}/etc/sysconfig
+	cp pkg/rpm/jobmonarch-jobmond.spec \
+	${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}/jobmonarch-jobmond-${VERSION}-${RELEASE}.spec
+	( cd ${TMPDIR}/.monarch_buildroot/; \
+	cat jobmonarch-jobmond-${VERSION}-${RELEASE}/jobmonarch-jobmond-${VERSION}-${RELEASE}.spec \
+	| sed "s/^Buildroot:.*$//Buildroot: \${TMPDIR}\/\.monarch_buildroot\/jobmonarch-jobmond-${VERSION}-${RELEASE}/g" \
+	| sed "s/^Version:.*$//Version: ${VERSION}/g" \
+	| sed "s/^Release:.*$//Release: ${RELEASE}/g" \
+	>jobmonarch-jobmond-${VERSION}-${RELEASE}/jobmonarch-jobmond-${VERSION}-${RELEASE}.spec.new; \
+	mv jobmonarch-jobmond-${VERSION}-${RELEASE}/jobmonarch-jobmond-${VERSION}-${RELEASE}.spec.new \
+	jobmonarch-jobmond-${VERSION}-${RELEASE}/jobmonarch-jobmond-${VERSION}-${RELEASE}.spec )
+	( cd ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}; \
+	${FAKEROOT} rpmbuild -bb jobmonarch-jobmond-${VERSION}-${RELEASE}.spec )
+	mv ${TMPDIR}/.monarch_buildroot/jobmonarch-jobmond-${VERSION}-${RELEASE}.rpm ..
+
+rpm-jobarchived:	${REQUIRED}
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/etc/init.d >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/etc/sysconfig >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/usr/sbin >/dev/null
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/usr/share/jobarchived >/dev/null
+	install -m 755 jobarchived/jobarchived.py ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/usr/sbin
+	( cd ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/usr/sbin; \
+	ln -s jobarchived.py jobarchived || true)
+	install jobarchived/jobarchived.conf ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/etc
+	install pkg/rpm/init.d/jobarchived ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/etc/init.d
+	install pkg/rpm/sysconfig/jobarchived ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/etc/sysconfig
+	install jobarchived/job_dbase.sql \
+	${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/usr/share/jobarchived
+	cp pkg/rpm/jobmonarch-jobarchived.spec \
+	${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}/jobmonarch-jobarchived-${VERSION}-${RELEASE}.spec
+	( cd ${TMPDIR}/.monarch_buildroot/; \
+	cat jobmonarch-jobarchived-${VERSION}-${RELEASE}/jobmonarch-jobarchived-${VERSION}-${RELEASE}.spec \
+	| sed "s/^Buildroot:.*$//Buildroot: \${TMPDIR}\/\.monarch_buildroot\/jobmonarch-jobarchived-${VERSION}-${RELEASE}/g" \
+	| sed "s/^Version:.*$//Version: ${VERSION}/g" \
+	| sed "s/^Release:.*$//Release: ${RELEASE}/g" \
+	>jobmonarch-jobarchived-${VERSION}-${RELEASE}/jobmonarch-jobarchived-${VERSION}-${RELEASE}.spec.new; \
+	mv jobmonarch-jobarchived-${VERSION}-${RELEASE}/jobmonarch-jobarchived-${VERSION}-${RELEASE}.spec.new \
+	jobmonarch-jobarchived-${VERSION}-${RELEASE}/jobmonarch-jobarchived-${VERSION}-${RELEASE}.spec )
+	( cd ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}; \
+	${FAKEROOT} rpmbuild -bb jobmonarch-jobarchived-${VERSION}-${RELEASE}.spec )
+	mv ${TMPDIR}/.monarch_buildroot/jobmonarch-jobarchived-${VERSION}-${RELEASE}.rpm ..
+
+rpm-webfrontend:	${REQUIRED}
+	mkdir -p ${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend-${VERSION}-${RELEASE}/${WEBDIR} >/dev/null
+	( cd web; \
+	rsync -a --exclude=.svn --exclude=*_test* --exclude=*-example.php \
+	. ${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend-${VERSION}-${RELEASE}/${WEBDIR} )
+	cp pkg/rpm/jobmonarch-webfrontend.spec \
+	${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend-${VERSION}-${RELEASE}/jobmonarch-webfrontend-${VERSION}-${RELEASE}.spec
+	( cd ${TMPDIR}/.monarch_buildroot/; \
+	cat jobmonarch-webfrontend-${VERSION}-${RELEASE}/jobmonarch-webfrontend-${VERSION}-${RELEASE}.spec \
+	| sed "s/^Buildroot:.*$//Buildroot: \${TMPDIR}\/\.monarch_buildroot\/jobmonarch-webfrontend-${VERSION}-${RELEASE}/g" \
+	| sed "s/^Version:.*$//Version: ${VERSION}/g" \
+	| sed "s/^Release:.*$//Release: ${RELEASE}/g" \
+	| sed "s+/var/www/ganglia+${WEBDIR}+g" \
+	>jobmonarch-webfrontend-${VERSION}-${RELEASE}/jobmonarch-webfrontend-${VERSION}-${RELEASE}.spec.new; \
+	mv jobmonarch-webfrontend-${VERSION}-${RELEASE}/jobmonarch-webfrontend-${VERSION}-${RELEASE}.spec.new \
+	jobmonarch-webfrontend-${VERSION}-${RELEASE}/jobmonarch-webfrontend-${VERSION}-${RELEASE}.spec )
+	( cd ${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend-${VERSION}-${RELEASE}; \
+	${FAKEROOT} rpmbuild -bb jobmonarch-webfrontend-${VERSION}-${RELEASE}.spec )
+	mv ${TMPDIR}/.monarch_buildroot/jobmonarch-webfrontend-${VERSION}-${RELEASE}.rpm ..
+
+clean:	${TMPDIR}/.monarch_buildroot
+	rm -rf ${TMPDIR}/.monarch_buildroot
Index: /branches/1.0/README
===================================================================
--- /branches/1.0/README	(revision 825)
+++ /branches/1.0/README	(revision 825)
@@ -0,0 +1,30 @@
+DESCRIPTION
+===========
+
+        Job Monarch is a set of tools to monitor batch jobs on a website.
+
+	This is achieved as a addon to Ganglia.
+
+	Job Monarch consists of three parts:
+
+	*	jobmond)
+
+		* Reports job statistics through Ganglia's gmetric/gmond
+
+	*	jobarchived)
+
+		* Optional
+		* Allows the archiving of jobs statistics in a database and more detailed
+		  RRDs, with the purpose of providing users or admins with the ability to
+		  lookup a node's behaviour and environment at the time of a finished job.
+
+	*	web)
+
+		* Frontend that displays the statistics in a intuitive manner
+
+WEBSITE
+=======
+
+For more details, go to the project website:
+
+	* https://subtrac.sara.nl/oss/jobmonarch
Index: /branches/1.0/TODO
===================================================================
--- /branches/1.0/TODO	(revision 825)
+++ /branches/1.0/TODO	(revision 825)
@@ -0,0 +1,17 @@
+Functionality TODO (wish) list
+==============================
+
+	*	Improved config file and arguments usage for daemons
+		(also --help and possibly a man page)
+
+	*	Usage of a node's (Ganglia) location in the ClusterImage
+		construction (placement by x,y,z coordinates)
+  
+	*	More advanced and detailed graph's, etc
+
+	*	Usage of regexp's or wildcards at jobsearch
+		i.e. for job name/user/queue
+
+	*	Ability to search by certain node properties
+		instead of job properties. I.e.: a certain node
+		hostname and/or number of nodes/cpus
Index: /branches/1.0/UPGRADE
===================================================================
--- /branches/1.0/UPGRADE	(revision 825)
+++ /branches/1.0/UPGRADE	(revision 825)
@@ -0,0 +1,95 @@
+Most Job Monarch releases (and their configs) should be backwards compatible with
+previous version. However, should there be any changes that has significant effect on the
+operation, tips and suggestions will be given here.
+
+0.2 -> 0.3
+============
+
+web)
+	* Now supports per-cluster specific settings
+
+	This enables you to override any setting of the webfrontend's conf.php on a per-cluster basis.
+	You can also specify a different sql database per cluster this way, of specify cluster image
+	hostname parsing (see below).
+
+	You can do this by including separate conf files as follows:
+
+	$CLUSTER_CONFS["My Cluster"]          = "/path/to/clusterconf/mycluster.php";
+
+	* Now supports parsing hostname location for building the ClusterImage as seen on the overview
+
+	This enables you to graphically display your cluster as it might be physically located in the racks
+	or computer floor, assuming that this is part of the hostname somehow.
+
+	If for example, your hosts are named: r<racknumber>n<nodenumber>
+
+	You can configure the webfrontend to parse racknumber as a X coordinate and nodenumber as Y coordinate.
+
+	Since your Grid/Cluster hostnames will probably be different per-cluster, you will probably only want to
+	configure this using a cluster-specific config file, as mentioned above.
+
+	Then you can create the following configuration options that will specify from which position in the hostname
+	the X and Y coordinate will be parsed:
+
+	<?php
+	//
+	$SORTBY_HOSTNAME = "r{x}n{y}.mydomain.tld";
+	$SORT_ORDER = "asc";
+
+	$SORT_XLABEL = "rack";
+	$SORT_YLABEL = "node";
+	?>
+
+jobmond)
+	* Now supports native Gmetric support, without the use of any external libraries or binaries.
+
+	This enables jobmond to transmit it's data much faster and efficient. To make use of this, you
+	have to set where jobmond should send it's metric's to. This is defined in jobmond.conf:
+
+	# Target of Gmetric's: where should we report to
+	# (usually: your udp_send_channel from gmond)
+	#
+	# Syntax: <ip>:<port>
+	#
+	GMETRIC_TARGET          : 239.2.11.71:8649
+
+	Should you choose not to configure this, jobmond will fail back to it's old binary style submission
+	of Gmetric's using Ganglia.
+
+	* Now supports the use of syslog for errors.
+
+	Should jobmond by freak accident crash, you can find in the syslog why this happened.
+
+	To configure this you have to enable it in your jobmond.conf:
+
+	 
+	# Enable logging to syslog? 
+	# 
+	USE_SYSLOG                      : 1 
+
+	# What level msg'es should be logged to syslog? 
+	# 
+	# usually: lvl 0 (errors) 
+	# 
+	SYSLOG_LEVEL                    : 0 
+
+	# Which facility to use in syslog 
+	# 
+	# Known: 
+	#       KERN, USER, MAIL, DAEMON, AUTH, LPR, 
+	#       NEWS, UUCP, CRON and LOCAL0 through LOCAL7 
+	# 
+	SYSLOG_FACILITY                 : DAEMON 
+
+jobarchived)
+
+	- Now supports the py-rrdtool library, in stead of rrd pipes.
+
+	This enables Jobarchived to write it's archive to disk MUCH MUCH faster and efficient than before.
+	It's HIGHLY RECOMMENDED to do this, since this will increase performance significantly!
+
+	To make use of this, simply install the 'py-rrdtool' Python module as mentioned in the README.
+	Jobarchived will automatically detect that py-rrdtool is available and make use of it.
+
+	Should you choose not to install it, Jobarchived fails back to it's old rrd pipe based writing of data.
+
Index: /branches/1.0/jobarchived/examples/jobarchived.conf
===================================================================
--- /branches/1.0/jobarchived/examples/jobarchived.conf	(revision 825)
+++ /branches/1.0/jobarchived/examples/jobarchived.conf	(revision 825)
@@ -0,0 +1,13 @@
+[DEFAULT]
+DAEMONIZE			: 1
+DEBUG_LEVEL			: 1
+USE_SYSLOG			: 1
+SYSLOG_LEVEL			: 0
+SYSLOG_FACILITY			: DAEMON
+GMETAD_CONF			: /etc/gmetad.conf
+ARCHIVE_XMLSOURCE		: localhost:8651
+ARCHIVE_DATASOURCES		: "LISA Cluster"
+ARCHIVE_HOURS_PER_RRD		: 12
+ARCHIVE_EXCLUDE_METRICS		: "^Temp_.*_.*", ".*_Temp_.*", ".*_RPM_.*", ".*_Battery_.*"
+ARCHIVE_PATH			: /var/lib/jobarch/rrds
+JOB_SQL_DBASE			: localhost/jobarch
Index: /branches/1.0/jobarchived/job_dbase.sql
===================================================================
--- /branches/1.0/jobarchived/job_dbase.sql	(revision 825)
+++ /branches/1.0/jobarchived/job_dbase.sql	(revision 825)
@@ -0,0 +1,34 @@
+DROP TABLE jobs CASCADE;
+DROP TABLE nodes CASCADE;
+DROP TABLE job_nodes CASCADE;
+
+CREATE TABLE jobs (
+	job_id			VARCHAR(20) NOT NULL UNIQUE PRIMARY KEY,
+	job_name		VARCHAR(255),
+	job_queue		VARCHAR(50),
+	job_owner		VARCHAR(50),
+	job_requested_time	VARCHAR(10),
+	job_requested_memory	VARCHAR(10),
+	job_ppn			INT,
+	job_status		VARCHAR(2),
+	job_start_timestamp	VARCHAR(10),
+	job_stop_timestamp	VARCHAR(10)
+);
+
+CREATE TABLE nodes (
+	node_id			SERIAL NOT NULL UNIQUE PRIMARY KEY,
+	node_hostname		VARCHAR(100),
+	node_location		VARCHAR(10)
+);
+
+CREATE TABLE job_nodes (
+	job_id			VARCHAR(20) NOT NULL REFERENCES jobs ON DELETE CASCADE,
+	node_id			INT NOT NULL REFERENCES nodes ON DELETE RESTRICT,
+	PRIMARY KEY ( job_id, node_id )
+);
+
+# modify me: set a password
+#CREATE USER jobarchive WITH PASSWORD '';
+
+GRANT ALL ON jobs,nodes,job_nodes TO "jobarchive";
+GRANT ALL ON nodes_node_id_seq TO "jobarchive";
Index: /branches/1.0/jobarchived/jobarchived.conf
===================================================================
--- /branches/1.0/jobarchived/jobarchived.conf	(revision 825)
+++ /branches/1.0/jobarchived/jobarchived.conf	(revision 825)
@@ -0,0 +1,91 @@
+[DEFAULT]
+# Wether or not to run as a daemon in background
+#
+DAEMONIZE			: 0
+
+# Specify debugging level here (only when _not_ DAEMONIZE)
+#
+# 11 = XML: metrics
+# 10 = XML: host, cluster, grid, ganglia
+# 9  = RRD activity, gmetad config parsing
+# 8  = RRD file activity
+# 6  = SQL
+# 1  = daemon threading
+# 0  = errors
+#
+# default: 0
+#
+DEBUG_LEVEL			: 1
+
+# Enable logging to syslog?
+#
+USE_SYSLOG			: 1
+
+# What level msg'es should be logged to syslog?
+#
+# usually: lvl 0 (errors)
+#
+SYSLOG_LEVEL			: 0
+
+# Which facility to use in syslog
+#
+# Known:
+#       KERN, USER, MAIL, DAEMON, AUTH, LPR,
+#       NEWS, UUCP, CRON and LOCAL0 through LOCAL7
+#
+SYSLOG_FACILITY			: DAEMON
+
+# Where is the gmetad.conf located
+#
+GMETAD_CONF			: /etc/ganglia/gmetad.conf
+
+# Where to grab XML data from
+# Usually: local gmetad (port 8651)
+#
+# Syntax: <hostname>:<port>
+#
+ARCHIVE_XMLSOURCE		: localhost:8651
+
+# List of data_source names to archive for
+#
+# Syntax: [ "<clustername>", "<clustername>" ]
+#
+ARCHIVE_DATASOURCES		: [ "My Cluster" ]
+
+# Amount of hours to store in one single archived rrd
+#
+# If you would like less files you can set this bigger
+# but could degrade performance
+#
+# For now 12 hours seems to work: 2 periods per day
+#
+ARCHIVE_HOURS_PER_RRD		: 12
+
+# Which metrics to exclude from archiving
+# NOTE: This can be a regexp or a string
+#
+ARCHIVE_EXCLUDE_METRICS		: ".*Temp.*", ".*RPM.*", ".*Version.*", ".*Tag$", "boottime", "gexec", "os.*", "machine_type"
+
+# Where to store the archived rrd's
+#
+ARCHIVE_PATH			: /data/jobarch
+
+# Archive's SQL dbase to use
+#
+# Syntax: <hostname>/<database>
+#
+JOB_SQL_DBASE			: localhost/jobarchive
+JOB_SQL_USER            : jobarchive
+
+#JOB_SQL_PASSWORD        : 
+
+# Timeout for jobs in archive
+#
+# Assume job has already finished while jobarchived was not running
+# after this amount of hours: the it will be finished anyway in the database
+#
+JOB_TIMEOUT			: 168
+
+# Location of rrdtool binary
+#
+RRDTOOL				: /usr/bin/rrdtool
Index: /branches/1.0/jobarchived/jobarchived.py
===================================================================
--- /branches/1.0/jobarchived/jobarchived.py	(revision 825)
+++ /branches/1.0/jobarchived/jobarchived.py	(revision 825)
@@ -0,0 +1,2156 @@
+#!/usr/bin/env python
+#
+# This file is part of Jobmonarch
+#
+# Copyright (C) 2006-2013  Ramon Bastiaans
+#
+# Jobmonarch 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 2 of the License, or
+# (at your option) any later version.
+#
+# Jobmonarch 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+#
+# SVN $Id$
+#
+
+import getopt, syslog, ConfigParser, sys
+
+VERSION='0.4+SVN'
+
+def usage( ver ):
+
+    print 'jobarchived %s' %VERSION
+
+    if ver:
+        return 0
+
+    print
+    print 'Purpose:'
+    print '  The Job Archive Daemon (jobarchived) stores batch job information in a SQL database'
+    print '  and node statistics in a RRD archive'
+    print
+    print 'Usage:    jobarchived [OPTIONS]'
+    print
+    print '  -c, --config=FILE    The configuration file to use (default: /etc/jobarchived.conf)'
+    print '  -p, --pidfile=FILE    Use pid file to store the process id'
+    print '  -h, --help        Print help and exit'
+    print '  -v, --version        Print version and exit'
+    print
+
+def processArgs( args ):
+
+    SHORT_L    = 'p:hvc:'
+    LONG_L    = [ 'help', 'config=', 'pidfile=', 'version' ]
+
+    config_filename = '/etc/jobarchived.conf'
+
+    global PIDFILE
+
+    PIDFILE    = None
+
+    try:
+
+        opts, args = getopt.getopt( args, SHORT_L, LONG_L )
+
+    except getopt.error, detail:
+
+        print detail
+        sys.exit(1)
+
+    for opt, value in opts:
+
+        if opt in [ '--config', '-c' ]:
+
+            config_filename = value
+
+        if opt in [ '--pidfile', '-p' ]:
+
+            PIDFILE         = value
+
+        if opt in [ '--help', '-h' ]:
+
+            usage( False )
+            sys.exit( 0 )
+
+        if opt in [ '--version', '-v' ]:
+
+            usage( True )
+            sys.exit( 0 )
+
+    try:
+        return loadConfig( config_filename )
+
+    except ConfigParser.NoOptionError, detail:
+
+        print detail
+        sys.exit( 1 )
+
+def loadConfig( filename ):
+
+    def getlist( cfg_string ):
+
+        my_list = [ ]
+
+        for item_txt in cfg_string.split( ',' ):
+
+            sep_char = None
+
+            item_txt = item_txt.strip()
+
+            for s_char in [ "'", '"' ]:
+
+                if item_txt.find( s_char ) != -1:
+
+                    if item_txt.count( s_char ) != 2:
+
+                        print 'Missing quote: %s' %item_txt
+                        sys.exit( 1 )
+
+                    else:
+
+                        sep_char = s_char
+                        break
+
+            if sep_char:
+
+                item_txt = item_txt.split( sep_char )[1]
+
+            my_list.append( item_txt )
+
+        return my_list
+
+    cfg = ConfigParser.ConfigParser()
+
+    cfg.read( filename )
+
+    global DEBUG_LEVEL, USE_SYSLOG, SYSLOG_LEVEL, SYSLOG_FACILITY, GMETAD_CONF, ARCHIVE_XMLSOURCE
+    global ARCHIVE_DATASOURCES, ARCHIVE_PATH, ARCHIVE_HOURS_PER_RRD, ARCHIVE_EXCLUDE_METRICS
+    global JOB_SQL_DBASE, DAEMONIZE, RRDTOOL, JOB_TIMEOUT, MODRRDTOOL, JOB_SQL_PASSWORD, JOB_SQL_USER
+
+    ARCHIVE_PATH        = cfg.get( 'DEFAULT', 'ARCHIVE_PATH' )
+
+    ARCHIVE_HOURS_PER_RRD    = cfg.getint( 'DEFAULT', 'ARCHIVE_HOURS_PER_RRD' )
+
+    DEBUG_LEVEL        = cfg.getint( 'DEFAULT', 'DEBUG_LEVEL' )
+
+    USE_SYSLOG        = cfg.getboolean( 'DEFAULT', 'USE_SYSLOG' )
+
+    SYSLOG_LEVEL        = cfg.getint( 'DEFAULT', 'SYSLOG_LEVEL' )
+
+    MODRRDTOOL        = False
+
+    try:
+        global rrdtool
+        import rrdtool
+
+        MODRRDTOOL        = True
+
+    except ImportError:
+
+        MODRRDTOOL        = False
+
+        print "ERROR: py-rrdtool import FAILED: failing back to DEPRECATED use of rrdtool binary. This will slow down jobarchived significantly!"
+
+        RRDTOOL            = cfg.get( 'DEFAULT', 'RRDTOOL' )
+
+    try:
+
+        SYSLOG_FACILITY    = eval( 'syslog.LOG_' + cfg.get( 'DEFAULT', 'SYSLOG_FACILITY' ) )
+
+    except AttributeError, detail:
+
+        print 'Unknown syslog facility'
+        sys.exit( 1 )
+
+    GMETAD_CONF        = cfg.get( 'DEFAULT', 'GMETAD_CONF' )
+
+    ARCHIVE_XMLSOURCE    = cfg.get( 'DEFAULT', 'ARCHIVE_XMLSOURCE' )
+
+    ARCHIVE_DATASOURCES    = getlist( cfg.get( 'DEFAULT', 'ARCHIVE_DATASOURCES' ) )
+
+    ARCHIVE_EXCLUDE_METRICS    = getlist( cfg.get( 'DEFAULT', 'ARCHIVE_EXCLUDE_METRICS' ) )
+
+    JOB_SQL_DBASE        = cfg.get( 'DEFAULT', 'JOB_SQL_DBASE' )
+    JOB_SQL_USER        = cfg.get( 'DEFAULT', 'JOB_SQL_USER' )
+    JOB_SQL_PASSWORD        = cfg.get( 'DEFAULT', 'JOB_SQL_PASSWORD' )
+
+    JOB_TIMEOUT        = cfg.getint( 'DEFAULT', 'JOB_TIMEOUT' )
+
+    DAEMONIZE        = cfg.getboolean( 'DEFAULT', 'DAEMONIZE' )
+
+
+    return True
+
+# What XML data types not to store
+#
+UNSUPPORTED_ARCHIVE_TYPES = [ 'string' ]
+
+# Maximum time (in seconds) a parsethread may run
+#
+PARSE_TIMEOUT = 60
+
+# Maximum time (in seconds) a storethread may run
+#
+STORE_TIMEOUT = 360
+
+"""
+The Job Archiving Daemon
+"""
+
+from types import *
+
+import xml.sax, xml.sax.handler, socket, string, os, os.path, time, thread, threading, random, re
+
+try:
+    import psycopg2
+
+except ImportError, details:
+
+    print "FATAL ERROR: psycopg2 python module not found"
+    sys.exit( 1 )
+
+class InitVars:
+        Vars = {}
+        
+        def __init__(self, **key_arg):
+                for (key, value) in key_arg.items():
+                        if value:
+                                self.Vars[key] = value
+                        else:   
+                                self.Vars[key] = None
+                                
+        def __call__(self, *key):
+                key = "%s" % key
+                return self.Vars[key]
+                
+        def __getitem__(self, key):
+                return self.Vars[key]
+                
+        def __repr__(self):
+                return repr(self.Vars)
+                
+        def keys(self):
+                barf =  map(None, self.Vars.keys())
+                return barf
+                
+        def values(self):
+                barf =  map(None, self.Vars.values())
+                return barf
+                
+        def has_key(self, key):
+                if self.Vars.has_key(key):
+                        return 1
+                else:   
+                        return 0
+                        
+class DBError(Exception):
+        def __init__(self, msg=''):
+                self.msg = msg
+                Exception.__init__(self, msg)
+        def __repr__(self):
+                return self.msg
+        __str__ = __repr__
+
+#
+# Class to connect to a database
+# and return the queury in a list or dictionairy.
+#
+class DB:
+    def __init__(self, db_vars):
+
+        self.dict = db_vars
+
+        if self.dict.has_key('User'):
+            self.user = self.dict['User']
+        else:
+            self.user = 'postgres'
+
+        if self.dict.has_key('Host'):
+            self.host = self.dict['Host']
+        else:
+            self.host = 'localhost'
+
+        if self.dict.has_key('Password'):
+            self.passwd = self.dict['Password']
+        else:
+            self.passwd = ''
+
+        if self.dict.has_key('DataBaseName'):
+            self.db = self.dict['DataBaseName']
+        else:
+            self.db = 'jobarchive'
+
+        # connect_string = 'host:port:database:user:password:
+        dsn = "host='%s' dbname='%s' user='%s' password='%s'" %(self.host, self.db, self.user, self.passwd)
+
+        try:
+            self.SQL = psycopg2.connect(dsn)
+        except psycopg2.Error, details:
+            str = "%s" %details
+            raise DBError(str)
+
+    def __repr__(self):
+        return repr(self.result)
+
+    def __nonzero__(self):
+        return not(self.result == None)
+
+    def __len__(self):
+        return len(self.result)
+
+    def __getitem__(self,i):
+        return self.result[i]
+
+    def __getslice__(self,i,j):
+        return self.result[i:j]
+
+    def Get(self, q_str):
+        c = self.SQL.cursor()
+        try:
+            c.execute(q_str)
+            result = c.fetchall()
+        except psycopg2.Error, details:
+            c.close()
+            str = "%s" %details
+            raise DBError(str)
+
+        c.close()
+        return result
+
+    def Set(self, q_str):
+        c = self.SQL.cursor()
+        try:
+            c.execute(q_str)
+
+        except psycopg2.Error, details:
+            c.close()
+            str = "%s" %details
+            raise DBError(str)
+
+        c.close()
+        return True
+
+    def Commit(self):
+
+        return self.SQL.commit()
+
+    def Rollback( self ):
+
+        return self.SQL.rollback()
+
+class DataSQLStore:
+
+    db_vars = None
+    dbc = None
+
+    def __init__( self, hostname, database ):
+
+        global JOB_SQL_USER, JOB_SQL_PASSWORD
+
+        self.db_vars = InitVars(DataBaseName=database,
+                User=JOB_SQL_USER,
+                Host=hostname,
+                Password=JOB_SQL_PASSWORD,
+                Dictionary='true')
+
+        try:
+            self.dbc     = DB(self.db_vars)
+        except DBError, details:
+            debug_msg( 0, 'FATAL ERROR: Unable to connect to database!: ' +str(details) )
+            sys.exit(1)
+
+    def setDatabase(self, statement):
+
+        ret = self.doDatabase('set', statement)
+        return ret
+        
+    def getDatabase(self, statement):
+
+        ret = self.doDatabase('get', statement)
+        return ret
+
+    def doCommit( self ):
+
+        return self.dbc.Commit()
+
+    def doRollback( self ):
+
+        return self.dbc.Rollback()
+
+    def doDatabase(self, type, statement):
+
+        debug_msg( 10, 'doDatabase(): %s: %s' %(type, statement) )
+        try:
+            if type == 'set':
+                result = self.dbc.Set( statement )
+            elif type == 'get':
+                result = self.dbc.Get( statement )
+                
+        except DBError, detail:
+            operation = statement.split(' ')[0]
+            debug_msg( 0, 'ERROR: ' +operation+ ' on database failed while doing ['+statement+'] full msg: '+str(detail) )
+            return False
+
+        debug_msg( 10, 'doDatabase(): result: %s' %(result) )
+        return result
+
+    def getJobNodeId( self, job_id, node_id ):
+
+        id = self.getDatabase( "SELECT job_id,node_id FROM job_nodes WHERE job_id = '%s' AND node_id = '%s'" %(job_id, node_id) )
+        if not id:
+            return False
+
+        if len( id ) > 0:
+
+            if len( id[0] ) > 0 and id[0] != '':
+            
+                return True
+
+        return False
+
+    def getNodeId( self, hostname ):
+
+        id = self.getDatabase( "SELECT node_id FROM nodes WHERE node_hostname = '%s'" %hostname )
+
+        if len( id ) > 0:
+
+            id = id[0][0]
+
+            return id
+        else:
+            return None
+
+    def getNodeIds( self, hostnames ):
+
+        ids = [ ]
+
+        for node in hostnames:
+
+            id = self.getNodeId( node )
+
+            if id:
+                ids.append( id )
+
+        return ids
+
+    def getJobId( self, jobid ):
+
+        id = self.getDatabase( "SELECT job_id FROM jobs WHERE job_id = '%s'" %jobid )
+
+        if id:
+            id = id[0][0]
+
+            return id
+        else:
+            return None
+
+    def addJob( self, job_id, jobattrs ):
+
+        if not self.getJobId( job_id ):
+
+            return self.mutateJob( 'insert', job_id, jobattrs ) 
+        else:
+            return self.mutateJob( 'update', job_id, jobattrs )
+
+    def mutateJob( self, action, job_id, jobattrs ):
+
+        job_values     = [ 'name', 'queue', 'owner', 'requested_time', 'requested_memory', 'ppn', 'status', 'start_timestamp', 'stop_timestamp' ]
+
+        insert_col_str = 'job_id'
+        insert_val_str = "'%s'" %job_id
+        update_str     = None
+
+        debug_msg( 10, 'mutateJob(): %s %s' %(action,job_id))
+
+        ids = [ ]
+
+        for valname, value in jobattrs.items():
+
+            if valname in job_values and value != '':
+
+                column_name = 'job_' + valname
+
+                if action == 'insert':
+
+                    if not insert_col_str:
+                        insert_col_str = column_name
+                    else:
+                        insert_col_str = insert_col_str + ',' + column_name
+
+                    if not insert_val_str:
+                        insert_val_str = value
+                    else:
+                        insert_val_str = insert_val_str + ",'%s'" %value
+
+                elif action == 'update':
+                    
+                    if not update_str:
+                        update_str = "%s='%s'" %(column_name, value)
+                    else:
+                        update_str = update_str + ",%s='%s'" %(column_name, value)
+
+            elif valname == 'nodes' and value:
+
+                node_valid = 1
+
+                if len(value) == 1:
+                
+                    if jobattrs['status'] == 'Q':
+
+                        node_valid = 0
+
+                    else:
+
+                        node_valid = 0
+
+                        for node_char in str(value[0]):
+
+                            if string.find( string.digits, node_char ) != -1 and not node_valid:
+
+                                node_valid = 1
+
+                if node_valid:
+
+                    ids = self.addNodes( value, jobattrs['domain'] )
+
+        if action == 'insert':
+
+            db_ret = self.setDatabase( "INSERT INTO jobs ( %s ) VALUES ( %s )" %( insert_col_str, insert_val_str ) )
+
+        elif action == 'update':
+
+            db_ret = self.setDatabase( "UPDATE jobs SET %s WHERE job_id='%s'" %(update_str, job_id) )
+
+        if len( ids ) > 0:
+            self.addJobNodes( job_id, ids )
+
+        return db_ret
+
+    def addNodes( self, hostnames, domain ):
+
+        ids = [ ]
+
+        for node in hostnames:
+
+            node    = '%s.%s' %( node, domain )
+            id    = self.getNodeId( node )
+    
+            if not id:
+                self.setDatabase( "INSERT INTO nodes ( node_hostname ) VALUES ( '%s' )" %node )
+                id = self.getNodeId( node )
+
+            ids.append( id )
+
+        return ids
+
+    def addJobNodes( self, jobid, nodes ):
+
+        for node in nodes:
+
+            if not self.getJobNodeId( jobid, node ):
+
+                self.addJobNode( jobid, node )
+
+    def addJobNode( self, jobid, nodeid ):
+
+        self.setDatabase( "INSERT INTO job_nodes (job_id,node_id) VALUES ( '%s',%s )" %(jobid, nodeid) )
+
+    def storeJobInfo( self, jobid, jobattrs ):
+
+        return self.addJob( jobid, jobattrs )
+
+    def checkStaleJobs( self ):
+
+        # Locate all jobs in the database that are not set to finished
+        #
+        q = "SELECT * from jobs WHERE job_status != 'F'"
+
+        r = self.getDatabase( q )
+
+        if len( r ) == 0:
+
+            return None
+
+        cleanjobs    = [ ]
+        timeoutjobs    = [ ]
+
+        jobtimeout_sec    = JOB_TIMEOUT * (60 * 60)
+        cur_time    = time.time()
+
+        for row in r:
+
+            job_id            = row[0]
+            job_requested_time    = row[4]
+            job_status        = row[7]
+            job_start_timestamp    = row[8]
+
+            # If it was set to queued and we didn't see it started
+            # there's not point in keeping it around
+            #
+            if job_status == 'Q' or not job_start_timestamp:
+
+                cleanjobs.append( job_id )
+
+            else:
+
+                start_timestamp = int( job_start_timestamp )
+
+                # If it was set to running longer than JOB_TIMEOUT 
+                # close the job: it probably finished while we were not running
+                #
+                if ( cur_time - start_timestamp ) > jobtimeout_sec:
+
+                    if job_requested_time:
+
+                        rtime_epoch    = reqtime2epoch( job_requested_time )
+                    else:
+                        rtime_epoch    = None
+                    
+                    timeoutjobs.append( (job_id, job_start_timestamp, rtime_epoch) )
+
+        debug_msg( 1, 'Found ' + str( len( cleanjobs ) ) + ' stale jobs in database: deleting entries' )
+
+        # Purge these from database
+        #
+        for j in cleanjobs:
+
+            q = "DELETE FROM jobs WHERE job_id = '" + str( j ) + "'"
+            self.setDatabase( q )
+
+        debug_msg( 1, 'Found ' + str( len( timeoutjobs ) ) + ' timed out jobs in database: closing entries' )
+
+        # Close these jobs in the database
+        # update the stop_timestamp to: start_timestamp + requested wallclock
+        # and set state: finished
+        #
+        for j in timeoutjobs:
+
+            ( i, s, r )        = j
+
+            if r:
+                new_end_timestamp    = int( s ) + r
+
+            q = "UPDATE jobs SET job_status='F',job_stop_timestamp = '" + str( new_end_timestamp ) + "' WHERE job_id = '" + str(i) + "'"
+            self.setDatabase( q )
+
+class RRDMutator:
+    """A class for performing RRD mutations"""
+
+    binary = None
+
+    def __init__( self, binary=None ):
+        """Set alternate binary if supplied"""
+
+        if binary:
+            self.binary = binary
+
+    def create( self, filename, args ):
+        """Create a new rrd with args"""
+
+        global MODRRDTOOL
+
+        if MODRRDTOOL:
+            return self.perform( 'create', filename, args )
+        else:
+            return self.perform( 'create', '"' + filename + '"', args )
+
+    def update( self, filename, args ):
+        """Update a rrd with args"""
+
+        global MODRRDTOOL
+
+        if MODRRDTOOL:
+            return self.perform( 'update', filename, args )
+        else:
+            return self.perform( 'update', '"' + filename + '"', args )
+
+    def grabLastUpdate( self, filename ):
+        """Determine the last update time of filename rrd"""
+
+        global MODRRDTOOL
+
+        last_update = 0
+
+        # Use the py-rrdtool module if it's available on this system
+        #
+        if MODRRDTOOL:
+
+            debug_msg( 8, 'rrdtool.info( ' + filename + ' )' )
+
+            rrd_header     = { }
+
+            try:
+                rrd_header    = rrdtool.info( filename )
+            except rrdtool.error, msg:
+                debug_msg( 8, str( msg ) )
+
+            if rrd_header.has_key( 'last_update' ):
+                return last_update
+            else:
+                return 0
+
+        # For backwards compatiblity: use the rrdtool binary if py-rrdtool is unavailable
+        # DEPRECATED (slow!)
+        #
+        else:
+            debug_msg( 8, self.binary + ' info ' + filename )
+
+            my_pipe        = os.popen( self.binary + ' info "' + filename + '"' )
+
+            for line in my_pipe.readlines():
+
+                if line.find( 'last_update') != -1:
+
+                    last_update = line.split( ' = ' )[1]
+
+            if my_pipe:
+
+                my_pipe.close()
+
+            if last_update:
+                return last_update
+            else:
+                return 0
+
+
+    def perform( self, action, filename, args ):
+        """Perform action on rrd filename with args"""
+
+        global MODRRDTOOL
+
+        arg_string = None
+
+        if type( args ) is not ListType:
+            debug_msg( 8, 'Arguments needs to be of type List' )
+            return 1
+
+        for arg in args:
+
+            if not arg_string:
+
+                arg_string = arg
+            else:
+                arg_string = arg_string + ' ' + arg
+
+        if MODRRDTOOL:
+
+            debug_msg( 8, 'rrdtool.' + action + "( " + filename + ' ' + arg_string + ")" )
+
+            try:
+                debug_msg( 8, "filename '" + str(filename) + "' type "+ str(type(filename)) + " args " + str( args ) )
+
+                if action == 'create':
+
+                    rrdtool.create( str( filename ), *args )
+
+                elif action == 'update':
+
+                    rrdtool.update( str( filename ), *args )
+
+            except rrdtool.error, msg:
+
+                error_msg = str( msg )
+                debug_msg( 8, error_msg )
+                return 1
+
+        else:
+
+            debug_msg( 8, self.binary + ' ' + action + ' ' + filename + ' ' + arg_string  )
+
+            cmd     = os.popen( self.binary + ' ' + action + ' ' + filename + ' ' + arg_string )
+            lines   = cmd.readlines()
+
+            cmd.close()
+
+            for line in lines:
+
+                if line.find( 'ERROR' ) != -1:
+
+                    error_msg = string.join( line.split( ' ' )[1:] )
+                    debug_msg( 8, error_msg )
+                    return 1
+
+        return 0
+
+class XMLProcessor:
+    """Skeleton class for XML processor's"""
+
+    def run( self ):
+        """Do main processing of XML here"""
+
+        pass
+
+class JobXMLProcessor( XMLProcessor ):
+    """Main class for processing XML and acting with it"""
+
+    def __init__( self, XMLSource, DataStore ):
+        """Setup initial XML connection and handlers"""
+
+        self.myXMLSource  = XMLSource
+        self.myXMLHandler = JobXMLHandler( DataStore )
+        self.myXMLError   = XMLErrorHandler()
+
+        self.config       = GangliaConfigParser( GMETAD_CONF )
+
+        self.kill_thread  = False
+
+    def killThread( self ):
+
+        self.kill_thread  = True
+
+    def run( self ):
+        """Main XML processing"""
+
+        debug_msg( 1, 'job_xml_thread(): started.' )
+
+        while( 1 ):
+
+            debug_msg( 1, 'job_xml_thread(): Retrieving XML data..' )
+
+            my_data    = self.myXMLSource.getData()
+
+            debug_msg( 1, 'job_xml_thread(): Done retrieving: data size %d' %len(my_data) )
+
+            if my_data:
+                debug_msg( 1, 'job_xml_thread(): Parsing XML..' )
+
+                xml.sax.parseString( my_data, self.myXMLHandler, self.myXMLError )
+
+                debug_msg( 1, 'job_xml_thread(): Done parsing.' )
+            else:
+                debug_msg( 1, 'job_xml_thread(): Got no data.' )
+
+            if self.kill_thread:
+
+                debug_msg( 1, 'job_xml_thread(): killed.' )
+                return None
+                
+            debug_msg( 1, 'job_xml_thread(): Sleeping.. (%ss)' %(str( self.config.getLowestInterval() ) ) )
+            time.sleep( self.config.getLowestInterval() )
+
+class JobXMLHandler( xml.sax.handler.ContentHandler ):
+    """Parse Job's jobinfo XML from our plugin"""
+
+    def __init__( self, datastore ):
+
+        self.ds              = datastore
+        self.jobs_processed  = [ ]
+        self.jobs_to_store   = [ ]
+        self.jobAttrs        = { }
+        self.jobAttrsSaved   = { }
+
+        debug_msg( 1, "XML: Handler created" )
+
+    def startDocument( self ):
+
+        self.jobs_processed = [ ]
+        self.heartbeat      = 0
+        self.elementct      = 0
+
+        debug_msg( 1, "XML: Start document" )
+
+    def startElement( self, name, attrs ):
+        """
+        This XML will be all gmetric XML
+        so there will be no specific start/end element
+        just one XML statement with all info
+        """
+
+        jobinfo = { }
+
+        self.elementct    += 1
+
+        if name == 'CLUSTER':
+
+            self.clustername = str( attrs.get( 'NAME', "" ) )
+
+        elif name == 'METRIC' and self.clustername in ARCHIVE_DATASOURCES:
+
+            metricname = str( attrs.get( 'NAME', "" ) )
+
+            if metricname == 'zplugin_monarch_heartbeat':
+
+                self.heartbeat = str( attrs.get( 'VAL', "" ) )
+
+            elif metricname.find( 'zplugin_monarch_job' ) != -1:
+
+                job_id  = metricname.split( 'zplugin_monarch_job_' )[1].split( '_' )[1]
+                val     = str( attrs.get( 'VAL', "" ) )
+
+                valinfo = val.split( ' ' )
+
+                for myval in valinfo:
+
+                    if len( myval.split( '=' ) ) > 1:
+
+                        valname = myval.split( '=' )[0]
+                        value   = myval.split( '=' )[1]
+
+                        if valname == 'nodes':
+
+                            value = value.split( ';' )
+
+                        jobinfo[ valname ] = value
+
+                self.jobAttrs[ job_id ] = jobinfo
+
+                self.jobs_processed.append( job_id )
+                    
+    def endDocument( self ):
+        """When all metrics have gone, check if any jobs have finished"""
+
+        jobs_finished = [ ]
+
+        debug_msg( 1, "XML: Processed "+str(self.elementct)+ " elements - found "+str(len(self.jobs_processed))+" jobs" )
+
+        if self.heartbeat == 0:
+            return None
+
+        for jobid, jobinfo in self.jobAttrs.items():
+
+            if jobinfo['reported'] != self.heartbeat:
+
+                if (jobinfo['status'] != 'R'):
+                    debug_msg( 1, 'job %s report time %s does not match current heartbeat %s : ignoring job' %(jobid, jobinfo['reported'], self.heartbeat ) )
+                    del self.jobAttrs[ jobid ]
+
+                    if jobid in self.jobs_to_store:
+                        del self.jobs_to_store[ jobid ]
+
+                    continue
+
+                elif jobid not in self.jobs_processed:
+
+                    # Was running previous heartbeat but not anymore: must be finished
+                    self.jobAttrs[ jobid ]['status'] = 'F'
+                    self.jobAttrs[ jobid ]['stop_timestamp'] = str( self.heartbeat )
+                    debug_msg( 1, 'job %s appears to have finished' %jobid )
+
+                    jobs_finished.append( jobid )
+
+                    if not jobid in self.jobs_to_store:
+                        self.jobs_to_store.append( jobid )
+
+                    continue
+
+            elif self.jobAttrsSaved.has_key( jobid ):
+
+                # This should pretty much never happen, but hey let's be careful
+                # Perhaps if someone altered their job while in queue with qalter
+
+                if self.jobinfoChanged( jobid, jobinfo ):
+
+                    self.jobAttrs[ jobid ]['stop_timestamp'] = ''
+                    self.jobAttrs[ jobid ]                   = self.setJobAttrs( self.jobAttrs[ jobid ], jobinfo )
+
+                    if not jobid in self.jobs_to_store:
+
+                        self.jobs_to_store.append( jobid )
+
+                    debug_msg( 10, 'jobinfo for job %s has changed' %jobid )
+            else:
+                debug_msg( 1, 'new job %s' %jobid )
+
+                if not jobid in self.jobs_to_store:
+
+                    self.jobs_to_store.append( jobid )
+
+        debug_msg( 1, 'job_xml_thread(): Found %s updated jobs.' %len(self.jobs_to_store) )
+
+        failed_store = [ ]
+        succes_store = [ ]
+
+        if len( self.jobs_to_store ) > 0:
+
+            debug_msg( 1, 'job_xml_thread(): Storing jobs to database..' )
+
+            for n in range( 0, len(self.jobs_to_store ) ):
+
+                if len( self.jobs_to_store ) == 0:
+                    break
+
+                jobid = self.jobs_to_store.pop( 0 )
+
+                db_ok = self.ds.storeJobInfo( jobid, self.jobAttrs[ jobid ] )
+
+                if not db_ok:
+
+                    self.ds.doRollback()
+                    failed_store.append( jobid )
+                    continue
+
+                self.ds.doCommit()
+                succes_store.append( jobid )
+
+                if not jobid in jobs_finished:
+
+                    self.jobAttrsSaved[ jobid ] = self.jobAttrs[ jobid ]
+
+                elif self.jobAttrsSaved.has_key( jobid ):
+
+                    del self.jobAttrsSaved[ jobid ]
+
+                if self.jobAttrs[ jobid ]['status'] == 'F':
+
+                    del self.jobAttrs[ jobid ]
+
+            result_str = 'succesfully stored: %s jobs' %str(len(succes_store))
+
+            if len( failed_store ) > 0:
+                result_str = result_str + ' - failed to store: %s jobs - deferred to next interval' %str(len(failed_store))
+
+            debug_msg( 1, 'job_xml_thread(): Done storing. %s' %result_str )
+
+        else:
+            debug_msg( 1, 'job_xml_thread(): No jobs to store.' )
+
+        self.jobs_processed = [ ]
+
+        # TODO: once in while check database AND self.jobAttrsSaved for stale jobs
+
+    def setJobAttrs( self, old, new ):
+        """
+        Set new job attributes in old, but not lose existing fields
+        if old attributes doesn't have those
+        """
+
+        for valname, value in new.items():
+            old[ valname ] = value
+
+        return old
+        
+
+    def jobinfoChanged( self, jobid, jobinfo ):
+        """
+        Check if jobinfo has changed from jobattrs[jobid]
+        if it's report time is bigger than previous one
+        and it is report time is recent (equal to heartbeat)
+        """
+
+        ignore_changes = [ 'reported' ]
+
+        if self.jobAttrsSaved.has_key( jobid ):
+
+            for valname, value in jobinfo.items():
+
+                if valname not in ignore_changes:
+
+                    if self.jobAttrsSaved[ jobid ].has_key( valname ):
+
+                        if value != self.jobAttrsSaved[ jobid ][ valname ]:
+
+                            if jobinfo['reported'] > self.jobAttrsSaved[ jobid ][ 'reported' ]:
+
+                                debug_msg( 1, "job %s field '%s' changed since saved from: %s to: %s" %( jobid, valname, value, self.jobAttrsSaved[ jobid ][ valname ] ) )
+
+                                return True
+
+                    else:
+                        debug_msg( 1, "job %s did not have field '%s'" %( jobid, valname )  )
+                        return True
+
+        return False
+
+class GangliaXMLHandler( xml.sax.handler.ContentHandler ):
+    """Parse Ganglia's XML"""
+
+    def __init__( self, config, datastore ):
+        """Setup initial variables and gather info on existing rrd archive"""
+
+        self.config    = config
+        self.clusters    = { }
+        self.ds        = datastore
+
+        debug_msg( 1, 'Checking database..' )
+
+        global DEBUG_LEVEL
+
+        if DEBUG_LEVEL <= 2:
+            self.ds.checkStaleJobs()
+
+        debug_msg( 1, 'Check done.' )
+        debug_msg( 1, 'Checking rrd archive..' )
+        self.gatherClusters()
+        debug_msg( 1, 'Check done.' )
+
+    def gatherClusters( self ):
+        """Find all existing clusters in archive dir"""
+
+        archive_dir    = check_dir(ARCHIVE_PATH)
+
+        hosts        = [ ]
+
+        if os.path.exists( archive_dir ):
+
+            dirlist    = os.listdir( archive_dir )
+
+            for cfgcluster in ARCHIVE_DATASOURCES:
+
+                if cfgcluster not in dirlist:
+
+                    # Autocreate a directory for this cluster
+                    # assume it is new
+                    #
+                    cluster_dir = '%s/%s' %( check_dir(ARCHIVE_PATH), cfgcluster )
+
+                    os.mkdir( cluster_dir )
+
+                    dirlist.append( cfgcluster )
+
+            for item in dirlist:
+
+                clustername = item
+
+                if not self.clusters.has_key( clustername ) and clustername in ARCHIVE_DATASOURCES:
+
+                    self.clusters[ clustername ] = RRDHandler( self.config, clustername )
+
+        debug_msg( 9, "Found "+str(len(self.clusters.keys()))+" clusters" )
+
+    def startElement( self, name, attrs ):
+        """Memorize appropriate data from xml start tags"""
+
+        if name == 'GANGLIA_XML':
+
+            self.XMLSource      = str( attrs.get( 'SOURCE',  "" ) )
+            self.gangliaVersion = str( attrs.get( 'VERSION', "" ) )
+
+            debug_msg( 10, 'Found XML data: source %s version %s' %( self.XMLSource, self.gangliaVersion ) )
+
+        elif name == 'GRID':
+
+            self.gridName    = str( attrs.get( 'NAME', "" ) )
+            self.time    = str( attrs.get( 'LOCALTIME', "" ) )
+
+            debug_msg( 10, '`-Grid found: %s' %( self.gridName ) )
+
+        elif name == 'CLUSTER':
+
+            self.clusterName = str( attrs.get( 'NAME',      "" ) )
+            self.time        = str( attrs.get( 'LOCALTIME', "" ) )
+
+            if not self.clusters.has_key( self.clusterName ) and self.clusterName in ARCHIVE_DATASOURCES:
+
+                self.clusters[ self.clusterName ] = RRDHandler( self.config, self.clusterName )
+
+                debug_msg( 10, ' |-Cluster found: %s' %( self.clusterName ) )
+
+        elif name == 'HOST' and self.clusterName in ARCHIVE_DATASOURCES:     
+
+            self.hostName     = str( attrs.get( 'NAME',     "" ) )
+            self.hostIp       = str( attrs.get( 'IP',       "" ) )
+            self.hostReported = str( attrs.get( 'REPORTED', "" ) )
+
+            debug_msg( 10, ' | |-Host found: %s - ip %s reported %s' %( self.hostName, self.hostIp, self.hostReported ) )
+
+        elif name == 'METRIC' and self.clusterName in ARCHIVE_DATASOURCES:
+
+            type = str( attrs.get( 'TYPE', "" ) )
+            
+            exclude_metric = False
+            
+            for ex_metricstr in ARCHIVE_EXCLUDE_METRICS:
+
+                orig_name = str( attrs.get( 'NAME', "" ) )
+
+                if string.lower( orig_name ) == string.lower( ex_metricstr ):
+                
+                    exclude_metric = True
+
+                elif re.match( ex_metricstr, orig_name ):
+
+                    exclude_metric = True
+
+            if type not in UNSUPPORTED_ARCHIVE_TYPES and not exclude_metric:
+
+                myMetric         = { }
+                myMetric['name'] = str( attrs.get( 'NAME', "" ) )
+                myMetric['val']  = str( attrs.get( 'VAL',  "" ) )
+                myMetric['time'] = self.hostReported
+
+                self.clusters[ self.clusterName ].memMetric( self.hostName, myMetric )
+
+                debug_msg( 11, ' | | |-metric: %s:%s' %( myMetric['name'], myMetric['val'] ) )
+
+    def storeMetrics( self ):
+        """Store metrics of each cluster rrd handler"""
+
+        for clustername, rrdh in self.clusters.items():
+
+            ret = rrdh.storeMetrics()
+
+            if ret:
+                debug_msg( 9, 'An error occured while storing metrics for cluster %s' %clustername )
+                return 1
+
+        return 0
+
+class XMLErrorHandler( xml.sax.handler.ErrorHandler ):
+
+    def error( self, exception ):
+        """Recoverable error"""
+
+        debug_msg( 0, 'Recoverable XML error ' + str( exception ) + ' ignored.' )
+
+    def fatalError( self, exception ):
+        """Non-recoverable error"""
+
+        exception_str = str( exception )
+
+        # Ignore 'no element found' errors
+        if exception_str.find( 'no element found' ) != -1:
+            debug_msg( 0, 'No XML data found: Socket not (re)connected or datasource not available.' )
+            return 0
+
+        debug_msg( 0, 'FATAL ERROR: Non-recoverable XML error ' + str( exception ) )
+        sys.exit( 1 )
+
+    def warning( self, exception ):
+        """Warning"""
+
+        debug_msg( 0, 'Warning ' + str( exception ) )
+
+class XMLGatherer:
+    """Setup a connection and file object to Ganglia's XML"""
+
+    s        = None
+    fd        = None
+    data        = None
+    slot        = None
+
+    # Time since the last update
+    #
+    LAST_UPDATE    = 0
+
+    # Minimum interval between updates
+    #
+    MIN_UPDATE_INT    = 10
+
+    # Is a update occuring now
+    #
+    update_now    = False
+
+    def __init__( self, host, port ):
+        """Store host and port for connection"""
+
+        self.host    = host
+        self.port    = port
+        self.slot    = threading.Lock()
+
+        self.retrieveData()
+
+    def retrieveData( self ):
+        """Setup connection to XML source"""
+
+        self.update_now    = True
+
+        self.slot.acquire()
+
+        self.data    = None
+
+        for res in socket.getaddrinfo( self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM ):
+
+            af, socktype, proto, canonname, sa = res
+
+            try:
+
+                self.s = socket.socket( af, socktype, proto )
+
+            except ( socket.error, socket.gaierror, socket.herror, socket.timeout ), msg:
+
+                self.s = None
+                continue
+
+            try:
+
+                self.s.connect( sa )
+
+            except ( socket.error, socket.gaierror, socket.herror, socket.timeout ), msg:
+
+                self.disconnect()
+                continue
+
+            break
+
+        if self.s is None:
+
+            debug_msg( 0, 'FATAL ERROR: Could not open socket or unable to connect to datasource!' )
+            self.update_now    = False
+            #sys.exit( 1 )
+
+        else:
+            #self.s.send( '\n' )
+
+            my_fp            = self.s.makefile( 'r' )
+            my_data          = my_fp.readlines()
+            my_data          = string.join( my_data, '' )
+
+            self.data        = my_data
+
+            self.LAST_UPDATE = time.time()
+
+        self.slot.release()
+
+        self.update_now    = False
+
+    def disconnect( self ):
+        """Close socket"""
+
+        if self.s:
+            #self.s.shutdown( 2 )
+            self.s.close()
+            self.s = None
+
+    def __del__( self ):
+        """Kill the socket before we leave"""
+
+        self.disconnect()
+
+    def reGetData( self ):
+        """Reconnect"""
+
+        while self.update_now:
+
+            # Must be another update in progress:
+            # Wait until the update is complete
+            #
+            time.sleep( 1 )
+
+        if self.s:
+            self.disconnect()
+
+        self.retrieveData()
+
+    def getData( self ):
+
+        """Return the XML data"""
+
+        # If more than MIN_UPDATE_INT seconds passed since last data update
+        # update the XML first before returning it
+        #
+
+        cur_time    = time.time()
+
+        if ( cur_time - self.LAST_UPDATE ) > self.MIN_UPDATE_INT:
+
+            self.reGetData()
+
+        while self.update_now:
+
+            # Must be another update in progress:
+            # Wait until the update is complete
+            #
+            time.sleep( 1 )
+            
+        return self.data
+
+    def makeFileDescriptor( self ):
+        """Make file descriptor that points to our socket connection"""
+
+        self.reconnect()
+
+        if self.s:
+            self.fd = self.s.makefile( 'r' )
+
+    def getFileObject( self ):
+        """Connect, and return a file object"""
+
+        self.makeFileDescriptor()
+
+        if self.fd:
+            return self.fd
+
+class GangliaXMLProcessor( XMLProcessor ):
+    """Main class for processing XML and acting with it"""
+
+    def __init__( self, XMLSource, DataStore ):
+        """Setup initial XML connection and handlers"""
+
+        self.config       = GangliaConfigParser( GMETAD_CONF )
+        self.myXMLSource  = XMLSource
+        self.ds           = DataStore
+        self.myXMLHandler = GangliaXMLHandler( self.config, self.ds )
+        self.myXMLError   = XMLErrorHandler()
+
+    def run( self ):
+        """Main XML processing; start a xml and storethread"""
+
+        xml_thread   = threading.Thread( None, self.processXML,   'xmlthread' )
+        store_thread = threading.Thread( None, self.storeMetrics, 'storethread' )
+
+        while( 1 ):
+
+            if not xml_thread.isAlive():
+                # Gather XML at the same interval as gmetad
+
+                # threaded call to: self.processXML()
+                #
+                try:
+                    xml_thread = threading.Thread( None, self.processXML, 'xml_thread' )
+                    xml_thread.start()
+                except thread.error, msg:
+                    debug_msg( 0, 'ERROR: Unable to start xml_thread!: '+str(msg))
+                    #return 1
+
+            if not store_thread.isAlive():
+                # Store metrics every .. sec
+
+                # threaded call to: self.storeMetrics()
+                #
+                try:
+                    store_thread = threading.Thread( None, self.storeMetrics, 'store_thread' )
+                    store_thread.start()
+                except thread.error, msg:
+                    debug_msg( 0, 'ERROR: Unable to start store_thread!: '+str(msg))
+                    #return 1
+        
+            # Just sleep a sec here, to prevent daemon from going mad. We're all threads here anyway
+            time.sleep( 1 )    
+
+    def storeMetrics( self ):
+        """Store metrics retained in memory to disk"""
+
+        global DEBUG_LEVEL
+
+        # Store metrics somewhere between every 360 and 640 seconds
+        #
+        if DEBUG_LEVEL >= 1:
+            STORE_INTERVAL = 60
+        else:
+            STORE_INTERVAL = random.randint( 360, 640 )
+
+        try:
+            store_metric_thread = threading.Thread( None, self.storeThread, 'store_metric_thread' )
+            store_metric_thread.start()
+        except thread.error, msg:
+            debug_msg( 0, 'ERROR: Unable to start ganglia_store_thread()!: '+str(msg) )
+            return 1
+
+        debug_msg( 1, 'ganglia_store_thread(): started.' )
+
+        debug_msg( 1, 'ganglia_store_thread(): Sleeping.. (%ss)' %STORE_INTERVAL )
+        time.sleep( STORE_INTERVAL )
+        debug_msg( 1, 'ganglia_store_thread(): Done sleeping.' )
+
+        if store_metric_thread.isAlive():
+
+            debug_msg( 1, 'ganglia_store_thread(): storemetricthread() still running, waiting to finish..' )
+            store_metric_thread.join( STORE_TIMEOUT ) # Maximum time is for storing thread to finish
+
+            if store_metric_thread.isAlive():
+
+                debug_msg( 1, 'ganglia_store_thread(): Done waiting: storemetricthread() still running :( now what?' )
+            else:
+                debug_msg( 1, 'ganglia_store_thread(): Done waiting: storemetricthread() has finished' )
+
+        debug_msg( 1, 'ganglia_store_thread(): finished.' )
+
+        return 0
+
+    def storeThread( self ):
+        """Actual metric storing thread"""
+
+        debug_msg( 1, 'ganglia_store_metric_thread(): started.' )
+        debug_msg( 1, 'ganglia_store_metric_thread(): Storing data..' )
+
+        ret = self.myXMLHandler.storeMetrics()
+        if ret > 0:
+            debug_msg( 0, 'ganglia_store_metric_thread(): UNKNOWN ERROR %s while storing Metrics!' %str(ret) )
+
+        debug_msg( 1, 'ganglia_store_metric_thread(): Done storing.' )
+        debug_msg( 1, 'ganglia_store_metric_thread(): finished.' )
+        
+        return 0
+
+    def processXML( self ):
+        """Process XML"""
+
+        try:
+            parsethread = threading.Thread( None, self.parseThread, 'parsethread' )
+            parsethread.start()
+        except thread.error, msg:
+            debug_msg( 0, 'ERROR: Unable to start ganglia_xml_thread()!: ' + str(msg) )
+            return 1
+
+        debug_msg( 1, 'ganglia_xml_thread(): started.' )
+
+        debug_msg( 1, 'ganglia_xml_thread(): Sleeping.. (%ss)' %self.config.getLowestInterval() )
+        time.sleep( float( self.config.getLowestInterval() ) )    
+        debug_msg( 1, 'ganglia_xml_thread(): Done sleeping.' )
+
+        if parsethread.isAlive():
+
+            debug_msg( 1, 'ganglia_xml_thread(): parsethread() still running, waiting (%ss) to finish..' %PARSE_TIMEOUT )
+            parsethread.join( PARSE_TIMEOUT ) # Maximum time for XML thread to finish
+
+            if parsethread.isAlive():
+                debug_msg( 1, 'ganglia_xml_thread(): Done waiting: parsethread() still running :( now what?' )
+            else:
+                debug_msg( 1, 'ganglia_xml_thread(): Done waiting: parsethread() finished' )
+
+        debug_msg( 1, 'ganglia_xml_thread(): finished.' )
+
+        return 0
+
+    def parseThread( self ):
+        """Actual parsing thread"""
+
+        debug_msg( 1, 'ganglia_parse_thread(): started.' )
+        debug_msg( 1, 'ganglia_parse_thread(): Retrieving XML data..' )
+        
+        my_data    = self.myXMLSource.getData()
+
+        debug_msg( 1, 'ganglia_parse_thread(): Done retrieving: data size %d' %len(my_data) )
+
+        if my_data:
+            debug_msg( 1, 'ganglia_parse_thread(): Parsing XML..' )
+            xml.sax.parseString( my_data, self.myXMLHandler, self.myXMLError )
+            debug_msg( 1, 'ganglia_parse_thread(): Done parsing.' )
+
+        debug_msg( 1, 'ganglia_parse_thread(): finished.' )
+
+        return 0
+
+class GangliaConfigParser:
+
+    sources = [ ]
+
+    def __init__( self, config ):
+        """Parse some stuff from our gmetad's config, such as polling interval"""
+
+        self.config = config
+        self.parseValues()
+
+    def parseValues( self ):
+        """Parse certain values from gmetad.conf"""
+
+        readcfg = open( self.config, 'r' )
+
+        for line in readcfg.readlines():
+
+            if line.count( '"' ) > 1:
+
+                if line.find( 'data_source' ) != -1 and line[0] != '#':
+
+                    source        = { }
+                    source['name']    = line.split( '"' )[1]
+                    source_words    = line.split( '"' )[2].split( ' ' )
+
+                    for word in source_words:
+
+                        valid_interval = 1
+
+                        for letter in word:
+
+                            if letter not in string.digits:
+
+                                valid_interval = 0
+
+                        if valid_interval and len(word) > 0:
+
+                            source['interval'] = word
+                            debug_msg( 9, 'polling interval for %s = %s' %(source['name'], source['interval'] ) )
+    
+                    # No interval found, use Ganglia's default    
+                    if not source.has_key( 'interval' ):
+                        source['interval'] = 15
+                        debug_msg( 9, 'polling interval for %s defaulted to 15' %(source['name']) )
+
+                    self.sources.append( source )
+
+    def getInterval( self, source_name ):
+        """Return interval for source_name"""
+
+        for source in self.sources:
+
+            if source['name'] == source_name:
+
+                return source['interval']
+
+        return None
+
+    def getLowestInterval( self ):
+        """Return the lowest interval of all clusters"""
+
+        lowest_interval = 0
+
+        for source in self.sources:
+
+            if not lowest_interval or source['interval'] <= lowest_interval:
+
+                lowest_interval = source['interval']
+
+        # Return 15 when nothing is found, so that the daemon won't go insane with 0 sec delays
+        if lowest_interval:
+            return lowest_interval
+        else:
+            return 15
+
+class RRDHandler:
+    """Class for handling RRD activity"""
+
+    myMetrics = { }
+    lastStored = { }
+    timeserials = { }
+    slot = None
+
+    def __init__( self, config, cluster ):
+        """Setup initial variables"""
+
+        global MODRRDTOOL
+
+        self.block    = 0
+        self.cluster    = cluster
+        self.config    = config
+        self.slot    = threading.Lock()
+
+        if MODRRDTOOL:
+
+            self.rrdm    = RRDMutator()
+        else:
+            self.rrdm    = RRDMutator( RRDTOOL )
+
+        global DEBUG_LEVEL
+
+        if DEBUG_LEVEL <= 2:
+            self.gatherLastUpdates()
+
+    def gatherLastUpdates( self ):
+        """Populate the lastStored list, containing timestamps of all last updates"""
+
+        cluster_dir = '%s/%s' %( check_dir(ARCHIVE_PATH), self.cluster )
+
+        hosts = [ ]
+
+        if os.path.exists( cluster_dir ):
+
+            dirlist = os.listdir( cluster_dir )
+
+            for dir in dirlist:
+
+                hosts.append( dir )
+
+        for host in hosts:
+
+            host_dir    = cluster_dir + '/' + host
+            dirlist        = os.listdir( host_dir )
+
+            for dir in dirlist:
+
+                if not self.timeserials.has_key( host ):
+
+                    self.timeserials[ host ] = [ ]
+
+                self.timeserials[ host ].append( dir )
+
+            last_serial = self.getLastRrdTimeSerial( host )
+
+            if last_serial:
+
+                metric_dir = cluster_dir + '/' + host + '/' + last_serial
+
+                if os.path.exists( metric_dir ):
+
+                    dirlist = os.listdir( metric_dir )
+
+                    for file in dirlist:
+
+                        metricname = file.split( '.rrd' )[0]
+
+                        if not self.lastStored.has_key( host ):
+
+                            self.lastStored[ host ] = { }
+
+                        self.lastStored[ host ][ metricname ] = self.rrdm.grabLastUpdate( metric_dir + '/' + file )
+
+    def getClusterName( self ):
+        """Return clustername"""
+
+        return self.cluster
+
+    def memMetric( self, host, metric ):
+        """Store metric from host in memory"""
+
+        # <ATOMIC>
+        #
+        self.slot.acquire()
+        
+        if self.myMetrics.has_key( host ):
+
+            if self.myMetrics[ host ].has_key( metric['name'] ):
+
+                for mymetric in self.myMetrics[ host ][ metric['name'] ]:
+
+                    if mymetric['time'] == metric['time']:
+
+                        # Allready have this metric, abort
+                        self.slot.release()
+                        return 1
+            else:
+                self.myMetrics[ host ][ metric['name'] ] = [ ]
+        else:
+            self.myMetrics[ host ]                = { }
+            self.myMetrics[ host ][ metric['name'] ]    = [ ]
+
+        # Push new metric onto stack
+        # atomic code; only 1 thread at a time may access the stack
+
+        self.myMetrics[ host ][ metric['name'] ].append( metric )
+
+        self.slot.release()
+        #
+        # </ATOMIC>
+
+    def makeUpdateList( self, host, metriclist ):
+        """
+        Make a list of update values for rrdupdate
+        but only those that we didn't store before
+        """
+
+        update_list    = [ ]
+        metric        = None
+
+        while len( metriclist ) > 0:
+
+            metric = metriclist.pop( 0 )
+
+            if self.checkStoreMetric( host, metric ):
+
+                u_val    = str( metric['time'] ) + ':' + str( metric['val'] )
+                #update_list.append( str('%s:%s') %( metric['time'], metric['val'] ) )
+                update_list.append( u_val )
+
+        return update_list
+
+    def checkStoreMetric( self, host, metric ):
+        """Check if supplied metric if newer than last one stored"""
+
+        if self.lastStored.has_key( host ):
+
+            if self.lastStored[ host ].has_key( metric['name'] ):
+
+                if metric['time'] <= self.lastStored[ host ][ metric['name'] ]:
+
+                    # This is old
+                    return 0
+
+        return 1
+
+    def memLastUpdate( self, host, metricname, metriclist ):
+        """
+        Memorize the time of the latest metric from metriclist
+        but only if it wasn't allready memorized
+        """
+
+        if not self.lastStored.has_key( host ):
+            self.lastStored[ host ] = { }
+
+        last_update_time = 0
+
+        for metric in metriclist:
+
+            if metric['name'] == metricname:
+
+                if metric['time'] > last_update_time:
+
+                    last_update_time = metric['time']
+
+        if self.lastStored[ host ].has_key( metricname ):
+            
+            if last_update_time <= self.lastStored[ host ][ metricname ]:
+                return 1
+
+        self.lastStored[ host ][ metricname ] = last_update_time
+
+    def storeMetrics( self ):
+        """
+        Store all metrics from memory to disk
+        and do it to the RRD's in appropriate timeperiod directory
+        """
+
+        debug_msg( 5, "Entering storeMetrics()")
+
+        count_values  = 0
+        count_metrics = 0
+        count_bits    = 0
+
+        for hostname, mymetrics in self.myMetrics.items():    
+
+            for metricname, mymetric in mymetrics.items():
+
+                count_metrics += 1
+
+                for dmetric in mymetric:
+
+                    count_values += 1
+
+                    count_bits   += len( dmetric['time'] )
+                    count_bits   += len( dmetric['val'] )
+
+        count_bytes    = count_bits / 8
+
+        debug_msg( 5, "size of cluster '" + self.cluster + "': " + 
+            str( len( self.myMetrics.keys() ) ) + " hosts " + 
+            str( count_metrics ) + " metrics " + str( count_values ) + " values " +
+            str( count_bits ) + " bits " + str( count_bytes ) + " bytes " )
+
+        for hostname, mymetrics in self.myMetrics.items():    
+
+            for metricname, mymetric in mymetrics.items():
+
+                metrics_to_store = [ ]
+
+                # Pop metrics from stack for storing until none is left
+                # atomic code: only 1 thread at a time may access myMetrics
+
+                # <ATOMIC>
+                #
+                self.slot.acquire() 
+
+                while len( self.myMetrics[ hostname ][ metricname ] ) > 0:
+
+                    if len( self.myMetrics[ hostname ][ metricname ] ) > 0:
+
+                        try:
+                            metrics_to_store.append( self.myMetrics[ hostname ][ metricname ].pop( 0 ) )
+                        except IndexError, msg:
+
+                            # Somehow sometimes myMetrics[ hostname ][ metricname ]
+                            # is still len 0 when the statement is executed.
+                            # Just ignore indexerror's..
+                            pass
+
+                self.slot.release()
+                #
+                # </ATOMIC>
+
+                # Create a mapping table, each metric to the period where it should be stored
+                #
+                metric_serial_table = self.determineSerials( hostname, metricname, metrics_to_store )
+
+                update_rets = [ ]
+
+                for period, pmetric in metric_serial_table.items():
+
+                    create_ret = self.createCheck( hostname, metricname, period )    
+
+                    update_ret = self.update( hostname, metricname, period, pmetric )
+
+                    if update_ret == 0:
+
+                        debug_msg( 9, 'stored metric %s for %s' %( hostname, metricname ) )
+                    else:
+                        debug_msg( 9, 'metric update failed' )
+
+                    update_rets.append( create_ret )
+                    update_rets.append( update_ret )
+
+                # Lets ignore errors here for now, we need to make sure last update time
+                # is correct!
+                #
+                #if not (1) in update_rets:
+
+                self.memLastUpdate( hostname, metricname, metrics_to_store )
+
+        debug_msg( 5, "Leaving storeMetrics()")
+
+    def makeTimeSerial( self ):
+        """Generate a time serial. Seconds since epoch"""
+
+        # Seconds since epoch
+        mytime = int( time.time() )
+
+        return mytime
+
+    def makeRrdPath( self, host, metricname, timeserial ):
+        """Make a RRD location/path and filename"""
+
+        rrd_dir        = '%s/%s/%s/%s'    %( check_dir(ARCHIVE_PATH), self.cluster, host, timeserial )
+        rrd_file    = '%s/%s.rrd'    %( rrd_dir, metricname )
+
+        return rrd_dir, rrd_file
+
+    def getLastRrdTimeSerial( self, host ):
+        """Find the last timeserial (directory) for this host"""
+
+        newest_timeserial = 0
+
+        for dir in self.timeserials[ host ]:
+
+            valid_dir = 1
+
+            for letter in dir:
+                if letter not in string.digits:
+                    valid_dir = 0
+
+            if valid_dir:
+                timeserial = dir
+                if timeserial > newest_timeserial:
+                    newest_timeserial = timeserial
+
+        if newest_timeserial:
+            return newest_timeserial
+        else:
+            return 0
+
+    def determinePeriod( self, host, check_serial ):
+        """Determine to which period (directory) this time(serial) belongs"""
+
+        period_serial = 0
+
+        if self.timeserials.has_key( host ):
+
+            for serial in self.timeserials[ host ]:
+
+                if check_serial >= serial and period_serial < serial:
+
+                    period_serial = serial
+
+        return period_serial
+
+    def determineSerials( self, host, metricname, metriclist ):
+        """
+        Determine the correct serial and corresponding rrd to store
+        for a list of metrics
+        """
+
+        metric_serial_table = { }
+
+        for metric in metriclist:
+
+            if metric['name'] == metricname:
+
+                period        = self.determinePeriod( host, metric['time'] )    
+
+                archive_secs    = ARCHIVE_HOURS_PER_RRD * (60 * 60)
+
+                if (int( metric['time'] ) - int( period ) ) > archive_secs:
+
+                    # This one should get it's own new period
+                    period = metric['time']
+
+                    if not self.timeserials.has_key( host ):
+                        self.timeserials[ host ] = [ ]
+
+                    self.timeserials[ host ].append( period )
+
+                if not metric_serial_table.has_key( period ):
+
+                    metric_serial_table[ period ] = [ ]
+
+                metric_serial_table[ period ].append( metric )
+
+        return metric_serial_table
+
+    def createCheck( self, host, metricname, timeserial ):
+        """Check if an rrd allready exists for this metric, create if not"""
+
+        debug_msg( 9, 'rrdcreate: using timeserial %s for %s/%s' %( timeserial, host, metricname ) )
+        
+        rrd_dir, rrd_file = self.makeRrdPath( host, metricname, timeserial )
+
+        if not os.path.exists( rrd_dir ):
+
+            try:
+                os.makedirs( rrd_dir )
+
+            except os.OSError, msg:
+
+                if msg.find( 'File exists' ) != -1:
+
+                    # Ignore exists errors
+                    pass
+
+                else:
+
+                    print msg
+                    return
+
+            debug_msg( 9, 'created dir %s' %( str(rrd_dir) ) )
+
+        if not os.path.exists( rrd_file ):
+
+            interval    = self.config.getInterval( self.cluster )
+            heartbeat    = 8 * int( interval )
+
+            params        = [ ]
+
+            params.append( '--step' )
+            params.append( str( interval ) )
+
+            params.append( '--start' )
+            params.append( str( int( timeserial ) - 1 ) )
+
+            params.append( 'DS:sum:GAUGE:%d:U:U' %heartbeat )
+            params.append( 'RRA:AVERAGE:0.5:1:%s' %(ARCHIVE_HOURS_PER_RRD * 240) )
+
+            self.rrdm.create( str(rrd_file), params )
+
+            debug_msg( 9, 'created rrd %s' %( str(rrd_file) ) )
+
+    def update( self, host, metricname, timeserial, metriclist ):
+        """
+        Update rrd file for host with metricname
+        in directory timeserial with metriclist
+        """
+
+        debug_msg( 9, 'rrdupdate: using timeserial %s for %s/%s' %( timeserial, host, metricname ) )
+
+        rrd_dir, rrd_file    = self.makeRrdPath( host, metricname, timeserial )
+
+        update_list        = self.makeUpdateList( host, metriclist )
+
+        if len( update_list ) > 0:
+            ret = self.rrdm.update( str(rrd_file), update_list )
+
+            if ret:
+                return 1
+        
+            debug_msg( 9, 'updated rrd %s with %s' %( str(rrd_file), string.join( update_list ) ) )
+
+        return 0
+
+def daemon():
+    """daemonized threading"""
+
+    # Fork the first child
+    #
+    pid = os.fork()
+
+    if pid > 0:
+
+        sys.exit(0)  # end parent
+
+    # creates a session and sets the process group ID 
+    #
+    os.setsid()
+
+    # Fork the second child
+    #
+    pid = os.fork()
+
+    if pid > 0:
+
+        sys.exit(0)  # end parent
+
+    write_pidfile()
+
+    # Go to the root directory and set the umask
+    #
+    os.chdir('/')
+    os.umask(0)
+
+    sys.stdin.close()
+    sys.stdout.close()
+    sys.stderr.close()
+
+    os.open('/dev/null', os.O_RDWR)
+    os.dup2(0, 1)
+    os.dup2(0, 2)
+
+    run()
+
+def run():
+    """Threading start"""
+
+    config             = GangliaConfigParser( GMETAD_CONF )
+    s_timeout          = int( config.getLowestInterval() - 1 )
+
+    socket.setdefaulttimeout( s_timeout )
+
+    myXMLSource        = XMLGatherer( ARCHIVE_XMLSOURCE.split( ':' )[0], ARCHIVE_XMLSOURCE.split( ':' )[1] )
+    myDataStore        = DataSQLStore( JOB_SQL_DBASE.split( '/' )[0], JOB_SQL_DBASE.split( '/' )[1] )
+
+    myJobProcessor     = JobXMLProcessor( myXMLSource, myDataStore )
+    myGangliaProcessor = GangliaXMLProcessor( myXMLSource, myDataStore )
+
+    try:
+        job_xml_thread     = threading.Thread( None, myJobProcessor.run, 'job_proc_thread' )
+        ganglia_xml_thread = threading.Thread( None, myGangliaProcessor.run, 'ganglia_proc_thread' )
+
+        job_xml_thread.start()
+        ganglia_xml_thread.start()
+        
+    except thread.error, msg:
+        debug_msg( 0, 'FATAL ERROR: Unable to start main threads!: '+ str(msg) )
+        syslog.closelog()
+        sys.exit(1)
+        
+    debug_msg( 0, 'main threading started.' )
+
+def main():
+    """Program startup"""
+
+    global DAEMONIZE, USE_SYSLOG
+
+    if not processArgs( sys.argv[1:] ):
+        sys.exit( 1 )
+
+    if( DAEMONIZE and USE_SYSLOG ):
+        syslog.openlog( 'jobarchived', syslog.LOG_NOWAIT, SYSLOG_FACILITY )
+
+    if DAEMONIZE:
+        daemon()
+    else:
+        run()
+
+#
+# Global functions
+#
+
+def check_dir( directory ):
+    """Check if directory is a proper directory. I.e.: Does _not_ end with a '/'"""
+
+    if directory[-1] == '/':
+        directory = directory[:-1]
+
+    return directory
+
+def reqtime2epoch( rtime ):
+
+    (hours, minutes, seconds )    = rtime.split( ':' )
+
+    etime    = int(seconds)
+    etime    = etime + ( int(minutes) * 60 )
+    etime    = etime + ( int(hours) * 60 * 60 )
+
+    return etime
+
+def debug_msg( level, msg ):
+    """Only print msg if correct levels"""
+
+    if (not DAEMONIZE and DEBUG_LEVEL >= level):
+        sys.stderr.write( printTime() + ' - ' + msg + '\n' )
+    
+    if (DAEMONIZE and USE_SYSLOG and SYSLOG_LEVEL >= level):
+        syslog.syslog( msg )
+
+def printTime( ):
+    """Print current time in human readable format"""
+
+    return time.strftime("%a %d %b %Y %H:%M:%S")
+
+def write_pidfile():
+
+    # Write pidfile if PIDFILE exists
+    if PIDFILE:
+
+        pid     = os.getpid()
+
+        pidfile = open(PIDFILE, 'w')
+
+        pidfile.write( str( pid ) )
+        pidfile.close()
+
+# Ooohh, someone started me! Let's go..
+#
+if __name__ == '__main__':
+    main()
Index: /branches/1.0/jobmond/examples/lisa_production.conf
===================================================================
--- /branches/1.0/jobmond/examples/lisa_production.conf	(revision 825)
+++ /branches/1.0/jobmond/examples/lisa_production.conf	(revision 825)
@@ -0,0 +1,9 @@
+[DEFAULT]
+DEBUG_LEVEL		: 0
+DAEMONIZE		: 1
+BATCH_API		: pbs
+BATCH_SERVER		: batch-ng.irc.sara.nl
+BATCH_POLL_INTERVAL	: 30
+GMOND_CONF		: /etc/gmond/lisa_production.conf
+DETECT_TIME_DIFFS	: 1
+BATCH_HOST_TRANSLATE	: /gb2/gb/, /ib/gb/
Index: /branches/1.0/jobmond/examples/lisa_test.conf
===================================================================
--- /branches/1.0/jobmond/examples/lisa_test.conf	(revision 825)
+++ /branches/1.0/jobmond/examples/lisa_test.conf	(revision 825)
@@ -0,0 +1,9 @@
+[DEFAULT]
+DEBUG_LEVEL		: 0
+DAEMONIZE		: 1
+BATCH_API		: pbs
+BATCH_SERVER		: gb-r7n15.irc.sara.nl
+BATCH_POLL_INTERVAL	: 10
+GMOND_CONF		: /etc/gmond/lisa_test.conf
+DETECT_TIME_DIFFS	: 1
+BATCH_HOST_TRANSLATE    :
Index: /branches/1.0/jobmond/jobmond.conf
===================================================================
--- /branches/1.0/jobmond/jobmond.conf	(revision 825)
+++ /branches/1.0/jobmond/jobmond.conf	(revision 825)
@@ -0,0 +1,91 @@
+[DEFAULT]
+# Specify debugging level here;
+#
+# 10 = gemtric cmd's
+#
+DEBUG_LEVEL		: 0
+
+# Wether or not to run as a daemon in background
+#
+DAEMONIZE		: 1
+
+# What Batch type is the system
+# 
+# Currently supported: pbs, sge (experimental)
+#
+BATCH_API		: pbs
+
+# Which Batch server to monitor
+#
+BATCH_SERVER		: localhost
+
+# Which queue(s) to report jobs of
+# (optional)
+#
+#QUEUE			: long, short
+
+# How many seconds interval for polling of jobs
+#
+# this will effect directly how accurate the
+# end time of a job can be determined
+#
+BATCH_POLL_INTERVAL	: 30
+
+# Location of gmond.conf
+#
+# Default: /etc/gmond.conf
+#
+# DEPRECATED!:		use GMETRIC_TARGET!
+#
+#GMOND_CONF		: /etc/gmond.conf
+#
+# Location of gmetric binary
+#
+# Default: /usr/bin/gmetric
+#
+# DEPRECATED!:		use GMETRIC_TARGET!
+#
+#GMETRIC_BINARY		: /usr/bin/gmetric
+
+# Target of Gmetric's: where should we report to
+# (usually: your udp_send_channel from gmond)
+#
+# Syntax: <ip>:<port>
+#
+GMETRIC_TARGET		: 239.2.11.71:8649
+
+# Enable logging to syslog?
+#
+USE_SYSLOG                      : 1
+
+# What level msg'es should be logged to syslog?
+#
+# usually: lvl 0 (errors)
+#
+SYSLOG_LEVEL                    : 0
+
+# Which facility to use in syslog
+#
+# Known:
+#       KERN, USER, MAIL, DAEMON, AUTH, LPR,
+#       NEWS, UUCP, CRON and LOCAL0 through LOCAL7
+#
+SYSLOG_FACILITY                 : DAEMON
+
+
+# Wether or not to detect differences in
+# time from Torque server and local time.
+#
+# Ideally both machines (if not the same)
+# should have the same time (via ntp or whatever)
+#
+DETECT_TIME_DIFFS	: 1
+
+# Regexp style hostname translation
+#
+# Usefull if your Batch hostnames are not the same as your
+# Ganglia hostnames (different network interfaces)
+#
+# Syntax: /orig/new/, /orig/new/
+#
+BATCH_HOST_TRANSLATE	: 
Index: /branches/1.0/jobmond/jobmond.py
===================================================================
--- /branches/1.0/jobmond/jobmond.py	(revision 825)
+++ /branches/1.0/jobmond/jobmond.py	(revision 825)
@@ -0,0 +1,1964 @@
+#!/usr/bin/env python
+#
+# This file is part of Jobmonarch
+#
+# Copyright (C) 2006-2013  Ramon Bastiaans
+# Copyright (C) 2007, 2009  Dave Love  (SGE code)
+# 
+# Jobmonarch 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 2 of the License, or
+# (at your option) any later version.
+# 
+# Jobmonarch 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+# 
+# SVN $Id$
+#
+
+# vi :set ts=4
+
+import sys, getopt, ConfigParser, time, os, socket, string, re
+import xdrlib, socket, syslog, xml, xml.sax, shlex, os.path
+from xml.sax.handler import feature_namespaces
+from collections import deque
+from glob import glob
+
+VERSION='0.4+SVN'
+
+def usage( ver ):
+
+    print 'jobmond %s' %VERSION
+
+    if ver:
+        return 0
+
+    print
+    print 'Purpose:'
+    print '  The Job Monitoring Daemon (jobmond) reports batch jobs information and statistics'
+    print '  to Ganglia, which can be viewed with Job Monarch web frontend'
+    print
+    print 'Usage:    jobmond [OPTIONS]'
+    print
+    print '  -c, --config=FILE    The configuration file to use (default: /etc/jobmond.conf)'
+    print '  -p, --pidfile=FILE    Use pid file to store the process id'
+    print '  -h, --help        Print help and exit'
+    print '  -v, --version      Print version and exit'
+    print
+
+def processArgs( args ):
+
+    SHORT_L      = 'p:hvc:'
+    LONG_L       = [ 'help', 'config=', 'pidfile=', 'version' ]
+
+    global PIDFILE, JOBMOND_CONF
+    PIDFILE      = None
+
+    JOBMOND_CONF = '/etc/jobmond.conf'
+
+    try:
+
+        opts, args    = getopt.getopt( args, SHORT_L, LONG_L )
+
+    except getopt.GetoptError, detail:
+
+        print detail
+        usage()
+        sys.exit( 1 )
+
+    for opt, value in opts:
+
+        if opt in [ '--config', '-c' ]:
+        
+            JOBMOND_CONF = value
+
+        if opt in [ '--pidfile', '-p' ]:
+
+            PIDFILE      = value
+        
+        if opt in [ '--help', '-h' ]:
+ 
+            usage( False )
+            sys.exit( 0 )
+
+        if opt in [ '--version', '-v' ]:
+
+            usage( True )
+            sys.exit( 0 )
+
+    return loadConfig( JOBMOND_CONF )
+
+class GangliaConfigParser:
+
+    def __init__( self, filename ):
+
+        self.conf_lijst   = [ ]
+        self.conf_dict    = { }
+        self.filename     = filename
+        self.file_pointer = file( filename, 'r' )
+        self.lexx         = shlex.shlex( self.file_pointer )
+        self.lexx.whitespace_split = True
+
+        self.parse()
+
+    def __del__( self ):
+
+        """
+        Cleanup: close file descriptor
+        """
+
+        self.file_pointer.close()
+        del self.lexx
+        del self.conf_lijst
+
+    def removeQuotes( self, value ):
+
+        clean_value = value
+        clean_value = clean_value.replace( "'", "" )
+        clean_value = clean_value.replace( '"', '' )
+        clean_value = clean_value.strip()
+
+        return clean_value
+
+    def removeBraces( self, value ):
+
+        clean_value = value
+        clean_value = clean_value.replace( "(", "" )
+        clean_value = clean_value.replace( ')', '' )
+        clean_value = clean_value.strip()
+
+        return clean_value
+
+    def parse( self ):
+
+        """
+        Parse self.filename using shlex scanning.
+        - Removes /* comments */
+        - Traverses (recursively) through all include () statements
+        - Stores complete valid config tokens in self.conf_list
+
+        i.e.:
+            ['globals',
+             '{',
+             'daemonize',
+             '=',
+             'yes',
+             'setuid',
+             '=',
+             'yes',
+             'user',
+             '=',
+             'ganglia',
+             'debug_level',
+             '=',
+             '0', 
+             <etc> ]
+        """
+
+        t = 'bogus'
+        c = False
+        i = False
+
+        while t != self.lexx.eof:
+            #print 'get token'
+            t = self.lexx.get_token()
+
+            if len( t ) >= 2:
+
+                if len( t ) >= 4:
+
+                    if t[:2] == '/*' and t[-2:] == '*/':
+
+                        #print 'comment line'
+                        #print 'skipping: %s' %t
+                        continue
+
+                if t == '/*' or t[:2] == '/*':
+                    c = True
+                    #print 'comment start'
+                    #print 'skipping: %s' %t
+                    continue
+
+                if t == '*/' or t[-2:] == '*/':
+                    c = False
+                    #print 'skipping: %s' %t
+                    #print 'comment end'
+                    continue
+
+            if c:
+                #print 'skipping: %s' %t
+                continue
+
+            if t == 'include':
+                i = True
+                #print 'include start'
+                #print 'skipping: %s' %t
+                continue
+
+            if i:
+
+                #print 'include start: %s' %t
+
+                t2 = self.removeQuotes( t )
+                t2 = self.removeBraces( t )
+
+                for in_file in glob( self.removeQuotes(t2) ):
+
+                    #print 'including file: %s' %in_file
+                    parse_infile = GangliaConfigParser( in_file )
+
+                    self.conf_lijst = self.conf_lijst + parse_infile.getConfLijst()
+
+                    del parse_infile
+
+                i = False
+                #print 'include end'
+                #print 'skipping: %s' %t
+                continue
+
+            #print 'keep: %s' %t
+            self.conf_lijst.append( self.removeQuotes(t) )
+
+    def getConfLijst( self ):
+
+        return self.conf_lijst
+
+    def confListToDict( self, parent_list=None ):
+
+        """
+        Recursively traverses a conf_list and creates dictionary from it
+        """
+
+        new_dict = { }
+        count    = 0
+        skip     = 0
+
+        if not parent_list:
+            parent_list = self.conf_lijst
+
+        #print 'entering confListToDict(): (parent) list size %s' %len(parent_list)
+
+        for n, c in enumerate( parent_list ):
+
+            count = count + 1
+
+            #print 'CL: n %d c %s' %(n, c)
+
+            if skip > 0:
+
+                #print '- skipped'
+                skip = skip - 1
+                continue
+
+            if (n+1) <= (len( parent_list )-1):
+
+                if parent_list[(n+1)] == '{':
+
+                    if not new_dict.has_key( c ):
+                        new_dict[ c ] = [ ]
+
+                    (temp_new_dict, skip) = self.confListToDict( parent_list[(n+2):] )
+                    new_dict[ c ].append( temp_new_dict )
+
+                if parent_list[(n+1)] == '=' and (n+2) <= (len( parent_list )-1):
+
+                    if not new_dict.has_key( c ):
+                        new_dict[ c ] = [ ]
+
+                    new_dict[ c ].append( parent_list[ (n+2) ] )
+
+                    skip = 2
+
+                if parent_list[n] == '}':
+
+                    #print 'leaving confListToDict(): new dict = %s' %new_dict
+                    return (new_dict, count)
+
+    def makeConfDict( self ):
+
+        """
+        Walks through self.conf_list and creates a dictionary based upon config values
+
+        i.e.:
+            'tcp_accept_channel': [{'acl': [{'access': [{'action': ['"allow"'],
+                                                         'ip': ['"127.0.0.1"'],
+                                                         'mask': ['32']}]}],
+                                    'port': ['8649']}],
+            'udp_recv_channel': [{'port': ['8649']}],
+            'udp_send_channel': [{'host': ['145.101.32.3'],
+                                  'port': ['8649']},
+                                 {'host': ['145.101.32.207'],
+                                  'port': ['8649']}]}
+        """
+
+        new_dict = { }
+        skip     = 0
+
+        #print 'entering makeConfDict()'
+
+        for n, c in enumerate( self.conf_lijst ):
+
+            #print 'M: n %d c %s' %(n, c)
+
+            if skip > 0:
+
+                #print '- skipped'
+                skip = skip - 1
+                continue
+
+            if (n+1) <= (len( self.conf_lijst )-1):
+
+                if self.conf_lijst[(n+1)] == '{':
+
+                    if not new_dict.has_key( c ):
+                        new_dict[ c ] = [ ]
+
+                    ( temp_new_dict, skip ) = self.confListToDict( self.conf_lijst[(n+2):] )
+                    new_dict[ c ].append( temp_new_dict )
+
+                if self.conf_lijst[(n+1)] == '=' and (n+2) <= (len( self.conf_lijst )-1):
+
+                    if not new_dict.has_key( c ):
+                        new_dict[ c ] = [ ]
+
+                    new_dict[ c ].append( self.conf_lijst[ (n+2) ] )
+
+                    skip = 2
+
+        self.conf_dict = new_dict
+        #print 'leaving makeConfDict(): conf dict size %d' %len( self.conf_dict )
+
+    def checkConfDict( self ):
+
+        if len( self.conf_lijst ) == 0:
+
+            raise Exception("Something went wrong generating conf list for %s" %self.file_name )
+
+        if len( self.conf_dict ) == 0:
+
+            self.makeConfDict()
+
+    def getConfDict( self ):
+
+        self.checkConfDict()
+        return self.conf_dict
+
+    def getUdpSendChannels( self ):
+
+        self.checkConfDict()
+
+        udp_send_channels = [ ] # IP:PORT
+
+        if not self.conf_dict.has_key( 'udp_send_channel' ):
+            return None
+
+        for u in self.conf_dict[ 'udp_send_channel' ]:
+
+            if u.has_key( 'mcast_join' ):
+
+                ip = u['mcast_join'][0]
+
+            elif u.has_key( 'host' ):
+
+                ip = u['host'][0]
+
+            port = u['port'][0]
+
+            udp_send_channels.append( ( ip, port ) )
+
+        if len( udp_send_channels ) == 0:
+            return None
+
+        return udp_send_channels
+
+    def getSectionLastOption( self, section, option ):
+
+        """
+        Get last option set in a config section that could be set multiple times in multiple (include) files.
+
+        i.e.: getSectionLastOption( 'globals', 'send_metadata_interval' )
+        """
+
+        self.checkConfDict()
+        value = None
+
+        if not self.conf_dict.has_key( section ):
+
+            return None
+
+        # Could be set multiple times in multiple (include) files: get last one set
+        for c in self.conf_dict[ section ]:
+
+                if c.has_key( option ):
+
+                    value = c[ option ][0]
+
+        return value
+
+    def getClusterName( self ):
+
+        return self.getSectionLastOption( 'cluster', 'name' )
+
+    def getVal( self, section, option ):
+
+        return self.getSectionLastOption( section, option )
+
+    def getInt( self, section, valname ):
+
+        value    = self.getVal( section, valname )
+
+        if not value:
+            return None
+
+        return int( value )
+
+    def getStr( self, section, valname ):
+
+        value    = self.getVal( section, valname )
+
+        if not value:
+            return None
+
+        return str( value )
+
+def findGmetric():
+
+    for dir in os.path.expandvars( '$PATH' ).split( ':' ):
+
+        guess    = '%s/%s' %( dir, 'gmetric' )
+
+        if os.path.exists( guess ):
+
+            return guess
+
+    return False
+
+def loadConfig( filename ):
+
+    def getlist( cfg_string ):
+
+        my_list = [ ]
+
+        for item_txt in cfg_string.split( ',' ):
+
+            sep_char = None
+
+            item_txt = item_txt.strip()
+
+            for s_char in [ "'", '"' ]:
+
+                if item_txt.find( s_char ) != -1:
+
+                    if item_txt.count( s_char ) != 2:
+
+                        print 'Missing quote: %s' %item_txt
+                        sys.exit( 1 )
+
+                    else:
+
+                        sep_char = s_char
+                        break
+
+            if sep_char:
+
+                item_txt = item_txt.split( sep_char )[1]
+
+            my_list.append( item_txt )
+
+        return my_list
+
+    if not os.path.isfile( JOBMOND_CONF ):
+
+        print "Is not a file or does not exist: '%s'" %JOBMOND_CONF
+        sys.exit( 1 )
+
+    try:
+        f = open( JOBMOND_CONF, 'r' )
+    except IOError, detail:
+        print "Cannot read config file: '%s'" %JOBMOND_CONF
+        sys.exit( 1 )
+    else:
+        f.close()
+
+    cfg        = ConfigParser.ConfigParser()
+
+    cfg.read( filename )
+
+    global DEBUG_LEVEL, DAEMONIZE, BATCH_SERVER, BATCH_POLL_INTERVAL
+    global GMOND_CONF, DETECT_TIME_DIFFS, BATCH_HOST_TRANSLATE
+    global BATCH_API, QUEUE, GMETRIC_TARGET, USE_SYSLOG
+    global SYSLOG_LEVEL, SYSLOG_FACILITY, GMETRIC_BINARY
+    global METRIC_MAX_VAL_LEN, GMOND_UDP_SEND_CHANNELS
+
+    DEBUG_LEVEL = cfg.getint( 'DEFAULT', 'DEBUG_LEVEL' )
+
+    DAEMONIZE   = cfg.getboolean( 'DEFAULT', 'DAEMONIZE' )
+
+    SYSLOG_LEVEL    = -1
+    SYSLOG_FACILITY = None
+
+    try:
+        USE_SYSLOG  = cfg.getboolean( 'DEFAULT', 'USE_SYSLOG' )
+
+    except ConfigParser.NoOptionError:
+
+        USE_SYSLOG  = True
+
+        debug_msg( 0, 'ERROR: no option USE_SYSLOG found: assuming yes' )
+
+    if USE_SYSLOG:
+
+        try:
+            SYSLOG_LEVEL = cfg.getint( 'DEFAULT', 'SYSLOG_LEVEL' )
+
+        except ConfigParser.NoOptionError:
+
+            debug_msg( 0, 'ERROR: no option SYSLOG_LEVEL found: assuming level 0' )
+            SYSLOG_LEVEL = 0
+
+        try:
+
+            SYSLOG_FACILITY = eval( 'syslog.LOG_' + cfg.get( 'DEFAULT', 'SYSLOG_FACILITY' ) )
+
+        except ConfigParser.NoOptionError:
+
+            SYSLOG_FACILITY = syslog.LOG_DAEMON
+
+            debug_msg( 0, 'ERROR: no option SYSLOG_FACILITY found: assuming facility DAEMON' )
+
+    try:
+
+        BATCH_SERVER = cfg.get( 'DEFAULT', 'BATCH_SERVER' )
+
+    except ConfigParser.NoOptionError:
+
+        # Backwards compatibility for old configs
+        #
+
+        BATCH_SERVER = cfg.get( 'DEFAULT', 'TORQUE_SERVER' )
+        api_guess    = 'pbs'
+    
+    try:
+    
+        BATCH_POLL_INTERVAL = cfg.getint( 'DEFAULT', 'BATCH_POLL_INTERVAL' )
+
+    except ConfigParser.NoOptionError:
+
+        # Backwards compatibility for old configs
+        #
+
+        BATCH_POLL_INTERVAL = cfg.getint( 'DEFAULT', 'TORQUE_POLL_INTERVAL' )
+        api_guess           = 'pbs'
+    
+    try:
+
+        GMOND_CONF          = cfg.get( 'DEFAULT', 'GMOND_CONF' )
+
+    except ConfigParser.NoOptionError:
+
+        # Not specified: assume /etc/ganglia/gmond.conf
+        #
+        GMOND_CONF          = '/etc/ganglia/gmond.conf'
+
+    ganglia_cfg             = GangliaConfigParser( GMOND_CONF )
+    GMETRIC_TARGET          = None
+
+    GMOND_UDP_SEND_CHANNELS = ganglia_cfg.getUdpSendChannels()
+
+    if not GMOND_UDP_SEND_CHANNELS:
+
+        debug_msg( 0, "WARNING: Can't parse udp_send_channel from: '%s' - Trying: %s" %( GMOND_CONF, JOBMOND_CONF ) )
+
+        # Couldn't figure it out: let's see if it's in our jobmond.conf
+        #
+        try:
+
+            GMETRIC_TARGET    = cfg.get( 'DEFAULT', 'GMETRIC_TARGET' )
+
+        # Guess not: now just give up
+        
+        except ConfigParser.NoOptionError:
+
+            GMETRIC_TARGET    = None
+
+            debug_msg( 0, "ERROR: GMETRIC_TARGET not set: internal Gmetric handling aborted. Failing back to DEPRECATED use of gmond.conf/gmetric binary. This will slow down jobmond significantly!" )
+
+            gmetric_bin    = findGmetric()
+
+            if gmetric_bin:
+
+                GMETRIC_BINARY     = gmetric_bin
+            else:
+                debug_msg( 0, "WARNING: Can't find gmetric binary anywhere in $PATH" )
+
+                try:
+
+                    GMETRIC_BINARY = cfg.get( 'DEFAULT', 'GMETRIC_BINARY' )
+
+                except ConfigParser.NoOptionError:
+
+                    print "FATAL ERROR: GMETRIC_BINARY not set and not in $PATH"
+                    sys.exit( 1 )
+
+    #TODO: is this really still needed or should be automatic
+    DETECT_TIME_DIFFS    = cfg.getboolean( 'DEFAULT', 'DETECT_TIME_DIFFS' )
+
+    BATCH_HOST_TRANSLATE = getlist( cfg.get( 'DEFAULT', 'BATCH_HOST_TRANSLATE' ) )
+
+    try:
+
+        BATCH_API    = cfg.get( 'DEFAULT', 'BATCH_API' )
+
+    except ConfigParser.NoOptionError, detail:
+
+        if BATCH_SERVER and api_guess:
+
+            BATCH_API    = api_guess
+        else:
+            print "FATAL ERROR: BATCH_API not set and can't make guess"
+            sys.exit( 1 )
+
+    try:
+
+        QUEUE        = getlist( cfg.get( 'DEFAULT', 'QUEUE' ) )
+
+    except ConfigParser.NoOptionError, detail:
+
+        QUEUE        = None
+
+    METRIC_MAX_VAL_LEN = ganglia_cfg.getInt( 'globals', 'max_udp_msg_len' )
+
+    return True
+
+def fqdn_parts (fqdn):
+
+    """Return pair of host and domain for fully-qualified domain name arg."""
+
+    parts = fqdn.split (".")
+
+    return (parts[0], string.join(parts[1:], "."))
+
+class DataProcessor:
+
+    """Class for processing of data"""
+
+    binary = None
+
+    def __init__( self, binary=None ):
+
+        """Remember alternate binary location if supplied"""
+
+        global GMETRIC_BINARY, GMOND_CONF
+
+        if binary:
+            self.binary = binary
+
+        if not self.binary and not GMETRIC_TARGET and not GMOND_UDP_SEND_CHANNELS:
+            self.binary = GMETRIC_BINARY
+
+        # Timeout for XML
+        #
+        # From ganglia's documentation:
+        #
+        # 'A metric will be deleted DMAX seconds after it is received, and
+        # DMAX=0 means eternal life.'
+
+        self.dmax = str( int( int( BATCH_POLL_INTERVAL ) * 2 ) )
+
+        if GMOND_CONF and not GMETRIC_TARGET and not GMOND_UDP_SEND_CHANNELS:
+
+            incompatible = self.checkGmetricVersion()
+
+            if incompatible:
+
+                print 'Ganglia/Gmetric version not compatible, please upgrade to at least 3.4.0'
+                sys.exit( 1 )
+
+    def checkGmetricVersion( self ):
+
+        """
+        Check version of gmetric is at least 3.4.0
+        for the syntax we use
+        """
+
+        global METRIC_MAX_VAL_LEN, GMETRIC_TARGET
+
+        incompatible    = 0
+
+        gfp        = os.popen( self.binary + ' --version' )
+        lines      = gfp.readlines()
+
+        gfp.close()
+
+        for line in lines:
+
+            line = line.split( ' ' )
+
+            if len( line ) == 2 and str( line ).find( 'gmetric' ) != -1:
+            
+                gmetric_version    = line[1].split( '\n' )[0]
+
+                version_major    = int( gmetric_version.split( '.' )[0] )
+                version_minor    = int( gmetric_version.split( '.' )[1] )
+                version_patch    = int( gmetric_version.split( '.' )[2] )
+
+                incompatible    = 0
+
+                if version_major < 3:
+
+                    incompatible = 1
+                
+                elif version_major == 3:
+
+                    if version_minor < 4:
+
+                        incompatible = 1
+
+        return incompatible
+
+    def multicastGmetric( self, metricname, metricval, valtype='string', units='' ):
+
+        """Call gmetric binary and multicast"""
+
+        cmd = self.binary
+
+        if GMOND_UDP_SEND_CHANNELS:
+
+            for c_ip, c_port  in GMOND_UDP_SEND_CHANNELS:
+
+                metric_debug        = "[gmetric %s:%s] name: %s - val: %s - dmax: %s" %( str(c_ip), str(c_port), str( metricname ), str( metricval ), str( self.dmax ) )
+
+                debug_msg( 10, printTime() + ' ' + metric_debug)
+
+                gm = Gmetric( c_ip, c_port )
+
+                gm.send( str( metricname ), str( metricval ), str( self.dmax ), valtype, units )
+
+        elif GMETRIC_TARGET:
+
+            GMETRIC_TARGET_HOST    = GMETRIC_TARGET.split( ':' )[0]
+            GMETRIC_TARGET_PORT    = GMETRIC_TARGET.split( ':' )[1]
+
+            metric_debug        = "[gmetric] name: %s - val: %s - dmax: %s" %( str( metricname ), str( metricval ), str( self.dmax ) )
+
+            debug_msg( 10, printTime() + ' ' + metric_debug)
+
+            gm = Gmetric( GMETRIC_TARGET_HOST, GMETRIC_TARGET_PORT )
+
+            gm.send( str( metricname ), str( metricval ), str( self.dmax ), valtype, units )
+
+        else:
+            try:
+                cmd = cmd + ' -c' + GMOND_CONF
+
+            except NameError:
+
+                debug_msg( 10, 'Assuming /etc/ganglia/gmond.conf for gmetric cmd' )
+
+            cmd = cmd + ' -n' + str( metricname )+ ' -v"' + str( metricval )+ '" -t' + str( valtype ) + ' -d' + str( self.dmax )
+
+            if len( units ) > 0:
+
+                cmd = cmd + ' -u"' + units + '"'
+
+            debug_msg( 10, printTime() + ' ' + cmd )
+
+            os.system( cmd )
+
+class DataGatherer:
+
+    """Skeleton class for batch system DataGatherer"""
+
+    def printJobs( self, jobs ):
+
+        """Print a jobinfo overview"""
+
+        for name, attrs in self.jobs.items():
+
+            print 'job %s' %(name)
+
+            for name, val in attrs.items():
+
+                print '\t%s = %s' %( name, val )
+
+    def printJob( self, jobs, job_id ):
+
+        """Print job with job_id from jobs"""
+
+        print 'job %s' %(job_id)
+
+        for name, val in jobs[ job_id ].items():
+
+            print '\t%s = %s' %( name, val )
+
+    def getAttr( self, attrs, name ):
+
+        """Return certain attribute from dictionary, if exists"""
+
+        if attrs.has_key( name ):
+
+            return attrs[ name ]
+        else:
+            return ''
+
+    def jobDataChanged( self, jobs, job_id, attrs ):
+
+        """Check if job with attrs and job_id in jobs has changed"""
+
+        if jobs.has_key( job_id ):
+
+            oldData = jobs[ job_id ]    
+        else:
+            return 1
+
+        for name, val in attrs.items():
+
+            if oldData.has_key( name ):
+
+                if oldData[ name ] != attrs[ name ]:
+
+                    return 1
+
+            else:
+                return 1
+
+        return 0
+
+    def submitJobData( self ):
+
+        """Submit job info list"""
+
+        global BATCH_API
+
+        self.dp.multicastGmetric( 'zplugin_monarch_heartbeat', str( int( int( self.cur_time ) + int( self.timeoffset ) ) ) )
+
+        running_jobs = 0
+        queued_jobs  = 0
+
+        # Count how many running/queued jobs we found
+        #
+        for jobid, jobattrs in self.jobs.items():
+
+            if jobattrs[ 'status' ] == 'Q':
+
+                queued_jobs += 1
+
+            elif jobattrs[ 'status' ] == 'R':
+
+                running_jobs += 1
+
+        # Report running/queued jobs as seperate metric for a nice RRD graph
+        #
+        self.dp.multicastGmetric( 'zplugin_monarch_rj', str( running_jobs ), 'uint32', 'jobs' )
+        self.dp.multicastGmetric( 'zplugin_monarch_qj', str( queued_jobs ), 'uint32', 'jobs' )
+
+        # Report down/offline nodes in batch (PBS only ATM)
+        #
+        if BATCH_API == 'pbs':
+
+            domain        = fqdn_parts( socket.getfqdn() )[1]
+
+            downed_nodes  = list()
+            offline_nodes = list()
+        
+            l        = ['state']
+
+            nodelist = self.getNodeData()
+
+            for name, node in nodelist.items():
+
+                if ( node[ 'state' ].find( "down" ) != -1 ):
+
+                    downed_nodes.append( name )
+
+                if ( node[ 'state' ].find( "offline" ) != -1 ):
+
+                    offline_nodes.append( name )
+
+            downnodeslist    = do_nodelist( downed_nodes )
+            offlinenodeslist = do_nodelist( offline_nodes )
+
+            down_str    = 'nodes=%s domain=%s reported=%s' %( string.join( downnodeslist, ';' ), domain, str( int( int( self.cur_time ) + int( self.timeoffset ) ) ) )
+            offl_str    = 'nodes=%s domain=%s reported=%s' %( string.join( offlinenodeslist, ';' ), domain, str( int( int( self.cur_time ) + int( self.timeoffset ) ) ) )
+            self.dp.multicastGmetric( 'zplugin_monarch_down'   , down_str )
+            self.dp.multicastGmetric( 'zplugin_monarch_offline', offl_str )
+
+        # Now let's spread the knowledge
+        #
+        for jobid, jobattrs in self.jobs.items():
+
+            # Make gmetric values for each job: respect max gmetric value length
+            #
+            gmetric_val        = self.compileGmetricVal( jobid, jobattrs )
+            metric_increment    = 0
+
+            # If we have more job info than max gmetric value length allows, split it up
+            # amongst multiple metrics
+            #
+            for val in gmetric_val:
+
+                metric_name = 'zplugin_monarch_job_%s_%s' %( str(metric_increment) , str( jobid ) )
+                self.dp.multicastGmetric( metric_name, val )
+
+                # Increase follow number if this jobinfo is split up amongst more than 1 gmetric
+                #
+                metric_increment    = metric_increment + 1
+
+    def compileGmetricVal( self, jobid, jobattrs ):
+
+        """Create a val string for gmetric of jobinfo"""
+
+        gval_lists    = [ ]
+        val_list    = { }
+
+        for val_name, val_value in jobattrs.items():
+
+            # These are our own metric names, i.e.: status, start_timestamp, etc
+            #
+            val_list_names_len    = len( string.join( val_list.keys() ) ) + len(val_list.keys())
+
+            # These are their corresponding values
+            #
+            val_list_vals_len    = len( string.join( val_list.values() ) ) + len(val_list.values())
+
+            if val_name == 'nodes' and jobattrs['status'] == 'R':
+
+                node_str = None
+
+                for node in val_value:
+
+                    if node_str:
+
+                        node_str = node_str + ';' + node
+                    else:
+                        node_str = node
+
+                    # Make sure if we add this new info, that the total metric's value length does not exceed METRIC_MAX_VAL_LEN
+                    #
+                    if (val_list_names_len + len(val_name) ) + (val_list_vals_len + len(node_str) ) > METRIC_MAX_VAL_LEN:
+
+                        # It's too big, we need to make a new gmetric for the additional info
+                        #
+                        val_list[ val_name ]    = node_str
+
+                        gval_lists.append( val_list )
+
+                        val_list        = { }
+                        node_str        = None
+
+                val_list[ val_name ]    = node_str
+
+                gval_lists.append( val_list )
+
+                val_list        = { }
+
+            elif val_value != '':
+
+                # Make sure if we add this new info, that the total metric's value length does not exceed METRIC_MAX_VAL_LEN
+                #
+                if (val_list_names_len + len(val_name) ) + (val_list_vals_len + len(str(val_value)) ) > METRIC_MAX_VAL_LEN:
+
+                    # It's too big, we need to make a new gmetric for the additional info
+                    #
+                    gval_lists.append( val_list )
+
+                    val_list        = { }
+
+                val_list[ val_name ]    = val_value
+
+        if len( val_list ) > 0:
+
+            gval_lists.append( val_list )
+
+        str_list    = [ ]
+
+        # Now append the value names and values together, i.e.: stop_timestamp=value, etc
+        #
+        for val_list in gval_lists:
+
+            my_val_str    = None
+
+            for val_name, val_value in val_list.items():
+
+                if type(val_value) == list:
+
+                    val_value    = val_value.join( ',' )
+
+                if my_val_str:
+
+                    try:
+                        # fixme: It's getting
+                        # ('nodes', None) items
+                        my_val_str = my_val_str + ' ' + val_name + '=' + val_value
+                    except:
+                        pass
+
+                else:
+                    my_val_str = val_name + '=' + val_value
+
+            str_list.append( my_val_str )
+
+        return str_list
+
+    def daemon( self ):
+
+        """Run as daemon forever"""
+
+        # Fork the first child
+        #
+        pid = os.fork()
+        if pid > 0:
+            sys.exit(0)  # end parent
+
+        # creates a session and sets the process group ID
+        #
+        os.setsid()
+
+        # Fork the second child
+        #
+        pid = os.fork()
+        if pid > 0:
+            sys.exit(0)  # end parent
+
+        write_pidfile()
+
+        # Go to the root directory and set the umask
+        #
+        os.chdir('/')
+        os.umask(0)
+
+        sys.stdin.close()
+        sys.stdout.close()
+        sys.stderr.close()
+
+        os.open('/dev/null', os.O_RDWR)
+        os.dup2(0, 1)
+        os.dup2(0, 2)
+
+        self.run()
+
+    def run( self ):
+
+        """Main thread"""
+
+        while ( 1 ):
+        
+            self.getJobData()
+            self.submitJobData()
+            time.sleep( BATCH_POLL_INTERVAL )    
+
+# SGE code by Dave Love <fx@gnu.org>.  Tested with SGE 6.0u8 and 6.0u11.  May
+# work with SGE 6.1 (else should be easily fixable), but definitely doesn't
+# with 6.2.  See also the fixmes.
+
+class NoJobs (Exception):
+    """Exception raised by empty job list in qstat output."""
+    pass
+
+class SgeQstatXMLParser(xml.sax.handler.ContentHandler):
+    """SAX handler for XML output from Sun Grid Engine's `qstat'."""
+
+    def __init__(self):
+        self.value = ""
+        self.joblist = []
+        self.job = {}
+        self.queue = ""
+        self.in_joblist = False
+        self.lrequest = False
+        self.eltq = deque()
+        xml.sax.handler.ContentHandler.__init__(self)
+
+    # The structure of the output is as follows (for SGE 6.0).  It's
+    # similar for 6.1, but radically different for 6.2, and is
+    # undocumented generally.  Unfortunately it's voluminous, and probably
+    # doesn't scale to large clusters/queues.
+
+    # <detailed_job_info  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+    #   <djob_info>
+    #     <qmaster_response>  <!-- job -->
+    #       ...
+    #       <JB_ja_template>  
+    #     <ulong_sublist>
+    #     ...         <!-- start_time, state ... -->
+    #     </ulong_sublist>
+    #       </JB_ja_template>  
+    #       <JB_ja_tasks>
+    #     <ulong_sublist>
+    #       ...       <!-- task info
+    #     </ulong_sublist>
+    #     ...
+    #       </JB_ja_tasks>
+    #       ...
+    #     </qmaster_response>
+    #   </djob_info>
+    #   <messages>
+    #   ...
+
+    # NB.  We might treat each task as a separate job, like
+    # straight qstat output, but the web interface expects jobs to
+    # be identified by integers, not, say, <job number>.<task>.
+
+    # So, I lied.  If the job list is empty, we get invalid XML
+    # like this, which we need to defend against:
+
+    # <unknown_jobs  xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+    #   <>
+    #     <ST_name>*</ST_name>
+    #   </>
+    # </unknown_jobs>
+
+    def startElement(self, name, attrs):
+        self.value = ""
+        if name == "djob_info":    # job list
+            self.in_joblist = True
+        # The job container is "qmaster_response" in SGE 6.0
+        # and 6.1, but "element" in 6.2.  This is only the very
+        # start of what's necessary for 6.2, though (sigh).
+        elif (name == "qmaster_response" or name == "element") \
+                and self.eltq[-1] == "djob_info": # job
+            self.job = {"job_state": "U", "slots": 0,
+                    "nodes": [], "queued_timestamp": "",
+                    "queued_timestamp": "", "queue": "",
+                    "ppn": "0", "RN_max": 0,
+                    # fixme in endElement
+                    "requested_memory": 0, "requested_time": 0
+                    }
+            self.joblist.append(self.job)
+        elif name == "qstat_l_requests": # resource request
+            self.lrequest = True
+        elif name == "unknown_jobs":
+            raise NoJobs
+        self.eltq.append (name)
+
+    def characters(self, ch):
+        self.value += ch
+
+    def endElement(self, name): 
+        """Snarf job elements contents into job dictionary.
+           Translate keys if appropriate."""
+
+        name_trans = {
+          "JB_job_number": "number",
+          "JB_job_name": "name", "JB_owner": "owner",
+          "queue_name": "queue", "JAT_start_time": "start_timestamp",
+          "JB_submission_time": "queued_timestamp"
+          }
+        value = self.value
+        self.eltq.pop ()
+
+        if name == "djob_info":
+            self.in_joblist = False
+            self.job = {}
+        elif name == "JAT_master_queue":
+            self.job["queue"] = value.split("@")[0]
+        elif name == "JG_qhostname":
+            if not (value in self.job["nodes"]):
+                self.job["nodes"].append(value)
+        elif name == "JG_slots": # slots in use
+            self.job["slots"] += int(value)
+        elif name == "RN_max": # requested slots (tasks or parallel)
+            self.job["RN_max"] = max (self.job["RN_max"],
+                          int(value))
+        elif name == "JAT_state": # job state (bitwise or)
+            value = int (value)
+            # Status values from sge_jobL.h
+            #define JIDLE           0x00000000
+            #define JHELD           0x00000010
+            #define JMIGRATING          0x00000020
+            #define JQUEUED         0x00000040
+            #define JRUNNING        0x00000080
+            #define JSUSPENDED          0x00000100
+            #define JTRANSFERING        0x00000200
+            #define JDELETED        0x00000400
+            #define JWAITING        0x00000800
+            #define JEXITING        0x00001000
+            #define JWRITTEN        0x00002000
+            #define JSUSPENDED_ON_THRESHOLD 0x00010000
+            #define JFINISHED           0x00010000
+            if value & 0x80:
+                self.job["status"] = "R"
+            elif value & 0x40:
+                self.job["status"] = "Q"
+            else:
+                self.job["status"] = "O" # `other'
+        elif name == "CE_name" and self.lrequest and self.value in \
+                ("h_cpu", "s_cpu", "cpu", "h_core", "s_core"):
+            # We're in a container for an interesting resource
+            # request; record which type.
+            self.lrequest = self.value
+        elif name == "CE_doubleval" and self.lrequest:
+            # if we're in a container for an interesting
+            # resource request, use the maxmimum of the hard
+            # and soft requests to record the requested CPU
+            # or core.  Fixme:  I'm not sure if this logic is
+            # right.
+            if self.lrequest in ("h_core", "s_core"):
+                self.job["requested_memory"] = \
+                    max (float (value),
+                     self.job["requested_memory"])
+            # Fixme:  Check what cpu means, c.f [hs]_cpu.
+            elif self.lrequest in ("h_cpu", "s_cpu", "cpu"):
+                self.job["requested_time"] = \
+                    max (float (value),
+                     self.job["requested_time"])
+        elif name == "qstat_l_requests":
+            self.lrequest = False
+        elif self.job and self.in_joblist:
+            if name in name_trans:
+                name = name_trans[name]
+                self.job[name] = value
+
+# Abstracted from PBS original.
+# Fixme:  Is it worth (or appropriate for PBS) sorting the result?
+#
+def do_nodelist( nodes ):
+
+    """Translate node list as appropriate."""
+
+    nodeslist        = [ ]
+    my_domain        = fqdn_parts( socket.getfqdn() )[1]
+
+    for node in nodes:
+
+        host        = node.split( '/' )[0] # not relevant for SGE
+        h, host_domain    = fqdn_parts(host)
+
+        if host_domain == my_domain:
+
+            host    = h
+
+        if nodeslist.count( host ) == 0:
+
+            for translate_pattern in BATCH_HOST_TRANSLATE:
+
+                if translate_pattern.find( '/' ) != -1:
+
+                    translate_orig    = \
+                        translate_pattern.split( '/' )[1]
+                    translate_new    = \
+                        translate_pattern.split( '/' )[2]
+                    host = re.sub( translate_orig,
+                               translate_new, host )
+            if not host in nodeslist:
+                nodeslist.append( host )
+    return nodeslist
+
+class SgeDataGatherer(DataGatherer):
+
+    jobs = {}
+
+    def __init__( self ):
+        self.jobs = {}
+        self.timeoffset = 0
+        self.dp = DataProcessor()
+
+    def getJobData( self ):
+        """Gather all data on current jobs in SGE"""
+
+        import popen2
+
+        self.cur_time = 0
+        queues = ""
+        if QUEUE:    # only for specific queues
+            # Fixme:  assumes queue names don't contain single
+            # quote or comma.  Don't know what the SGE rules are.
+            queues = " -q '" + string.join (QUEUE, ",") + "'"
+        # Note the comment in SgeQstatXMLParser about scaling with
+        # this method of getting data.  I haven't found better one.
+        # Output with args `-xml -ext -f -r' is easier to parse
+        # in some ways, harder in others, but it doesn't provide
+        # the submission time (at least SGE 6.0).  The pipeline
+        # into sed corrects bogus XML observed with a configuration
+        # of SGE 6.0u8, which otherwise causes the parsing to hang.
+        piping = popen2.Popen3("qstat -u '*' -j '*' -xml | \
+sed -e 's/reported usage>/reported_usage>/g' -e 's;<\/*JATASK:.*>;;'" \
+                           + queues, True)
+        qstatparser = SgeQstatXMLParser()
+        parse_err = 0
+        try:
+            xml.sax.parse(piping.fromchild, qstatparser)
+        except NoJobs:
+            pass
+        except:
+            parse_err = 1
+        if piping.wait():
+            debug_msg(10, "qstat error, skipping until next polling interval: " + piping.childerr.readline())
+            return None
+        elif parse_err:
+            debug_msg(10, "Bad XML output from qstat"())
+            exit (1)
+        for f in piping.fromchild, piping.tochild, piping.childerr:
+            f.close()
+        self.cur_time = time.time()
+        jobs_processed = []
+        for job in qstatparser.joblist:
+            job_id = job["number"]
+            if job["status"] in [ 'Q', 'R' ]:
+                jobs_processed.append(job_id)
+            if job["status"] == "R":
+                job["nodes"] = do_nodelist (job["nodes"])
+                # Fixme: why is job["nodes"] sometimes null?
+                try:
+                    # Fixme: Is this sensible?  The
+                    # PBS-type PPN isn't something you use
+                    # with SGE.
+                    job["ppn"] = float(job["slots"]) / len(job["nodes"])
+                except:
+                    job["ppn"] = 0
+                if DETECT_TIME_DIFFS:
+                    # If a job start is later than our
+                    # current date, that must mean
+                    # the SGE server's time is later
+                    # than our local time.
+                    start_timestamp = int (job["start_timestamp"])
+                    if start_timestamp > int(self.cur_time) + int(self.timeoffset):
+
+                        self.timeoffset    = start_timestamp - int(self.cur_time)
+            else:
+                # fixme: Note sure what this should be:
+                job["ppn"] = job["RN_max"]
+                job["nodes"] = "1"
+
+            myAttrs = {}
+            for attr in ["name", "queue", "owner",
+                     "requested_time", "status",
+                     "requested_memory", "ppn",
+                     "start_timestamp", "queued_timestamp"]:
+                myAttrs[attr] = str(job[attr])
+            myAttrs["nodes"] = job["nodes"]
+            myAttrs["reported"] = str(int(self.cur_time) + int(self.timeoffset))
+            myAttrs["domain"] = fqdn_parts(socket.getfqdn())[1]
+            myAttrs["poll_interval"] = str(BATCH_POLL_INTERVAL)
+
+            if self.jobDataChanged(self.jobs, job_id, myAttrs) and myAttrs["status"] in ["R", "Q"]:
+                self.jobs[job_id] = myAttrs
+        for id, attrs in self.jobs.items():
+            if id not in jobs_processed:
+                del self.jobs[id]
+
+# LSF code by Mahmoud Hanafi <hanafim@users.sourceforge.nt>
+# Requres LSFObject http://sourceforge.net/projects/lsfobject
+#
+class LsfDataGatherer(DataGatherer):
+
+    """This is the DataGatherer for LSf"""
+
+    global lsfObject
+
+    def __init__( self ):
+
+        self.jobs = { }
+        self.timeoffset = 0
+        self.dp = DataProcessor()
+        self.initLsfQuery()
+
+    def _countDuplicatesInList( self, dupedList ):
+
+        countDupes    = { }
+
+        for item in dupedList:
+
+            if not countDupes.has_key( item ):
+
+                countDupes[ item ]    = 1
+            else:
+                countDupes[ item ]    = countDupes[ item ] + 1
+
+        dupeCountList    = [ ]
+
+        for item, count in countDupes.items():
+
+            dupeCountList.append( ( item, count ) )
+
+        return dupeCountList
+#
+#lst = ['I1','I2','I1','I3','I4','I4','I7','I7','I7','I7','I7']
+#print _countDuplicatesInList(lst)
+#[('I1', 2), ('I3', 1), ('I2', 1), ('I4', 2), ('I7', 5)]
+########################
+
+    def initLsfQuery( self ):
+        self.pq = None
+        self.pq = lsfObject.jobInfoEntObject()
+
+    def getJobData( self, known_jobs="" ):
+        """Gather all data on current jobs in LSF"""
+        if len( known_jobs ) > 0:
+            jobs = known_jobs
+        else:
+            jobs = { }
+        joblist = {}
+        joblist = self.pq.getJobInfo()
+        nodelist = ''
+
+        self.cur_time = time.time()
+
+        jobs_processed = [ ]
+
+        for name, attrs in joblist.items():
+            job_id = str(name)
+            jobs_processed.append( job_id )
+            name = self.getAttr( attrs, 'jobName' )
+            queue = self.getAttr( self.getAttr( attrs, 'submit') , 'queue' )
+            owner = self.getAttr( attrs, 'user' )
+
+### THIS IS THE rLimit List index values
+#define LSF_RLIMIT_CPU      0        /* cpu time in milliseconds */
+#define LSF_RLIMIT_FSIZE    1        /* maximum file size */
+#define LSF_RLIMIT_DATA     2        /* data size */
+#define LSF_RLIMIT_STACK    3        /* stack size */
+#define LSF_RLIMIT_CORE     4        /* core file size */
+#define LSF_RLIMIT_RSS      5        /* resident set size */
+#define LSF_RLIMIT_NOFILE   6        /* open files */
+#define LSF_RLIMIT_OPEN_MAX 7        /* (from HP-UX) */
+#define LSF_RLIMIT_VMEM     8        /* maximum swap mem */
+#define LSF_RLIMIT_SWAP     8
+#define LSF_RLIMIT_RUN      9        /* max wall-clock time limit */
+#define LSF_RLIMIT_PROCESS  10       /* process number limit */
+#define LSF_RLIMIT_THREAD   11       /* thread number limit (introduced in LSF6.0) */
+#define LSF_RLIM_NLIMITS    12       /* number of resource limits */
+
+            requested_time = self.getAttr( self.getAttr( attrs, 'submit') , 'rLimits' )[9]
+            if requested_time == -1: 
+                requested_time = ""
+            requested_memory = self.getAttr( self.getAttr( attrs, 'submit') , 'rLimits' )[8]
+            if requested_memory == -1: 
+                requested_memory = ""
+# This tries to get proc per node. We don't support this right now
+            ppn = 0 #self.getAttr( self.getAttr( attrs, 'SubmitList') , 'numProessors' )
+            requested_cpus = self.getAttr( self.getAttr( attrs, 'submit') , 'numProcessors' )
+            if requested_cpus == None or requested_cpus == "":
+                requested_cpus = 1
+
+            if QUEUE:
+                for q in QUEUE:
+                    if q == queue:
+                        display_queue = 1
+                        break
+                    else:
+                        display_queue = 0
+                        continue
+            if display_queue == 0:
+                continue
+
+            runState = self.getAttr( attrs, 'status' )
+            if runState == 4:
+                status = 'R'
+            else:
+                status = 'Q'
+            queued_timestamp = self.getAttr( attrs, 'submitTime' )
+
+            if status == 'R':
+                start_timestamp = self.getAttr( attrs, 'startTime' )
+                nodesCpu =  dict(self._countDuplicatesInList(self.getAttr( attrs, 'exHosts' )))
+                nodelist = nodesCpu.keys()
+
+                if DETECT_TIME_DIFFS:
+
+                    # If a job start if later than our current date,
+                    # that must mean the Torque server's time is later
+                    # than our local time.
+
+                    if int(start_timestamp) > int( int(self.cur_time) + int(self.timeoffset) ):
+
+                        self.timeoffset = int( int(start_timestamp) - int(self.cur_time) )
+
+            elif status == 'Q':
+                start_timestamp = ''
+                count_mynodes = 0
+                numeric_node = 1
+                nodelist = ''
+
+            myAttrs = { }
+            if name == "":
+                myAttrs['name'] = "none"
+            else:
+                myAttrs['name'] = name
+
+            myAttrs[ 'owner' ]        = owner
+            myAttrs[ 'requested_time' ]    = str(requested_time)
+            myAttrs[ 'requested_memory' ]    = str(requested_memory)
+            myAttrs[ 'requested_cpus' ]    = str(requested_cpus)
+            myAttrs[ 'ppn' ]        = str( ppn )
+            myAttrs[ 'status' ]        = status
+            myAttrs[ 'start_timestamp' ]    = str(start_timestamp)
+            myAttrs[ 'queue' ]        = str(queue)
+            myAttrs[ 'queued_timestamp' ]    = str(queued_timestamp)
+            myAttrs[ 'reported' ]        = str( int( int( self.cur_time ) + int( self.timeoffset ) ) )
+            myAttrs[ 'nodes' ]        = do_nodelist( nodelist )
+            myAttrs[ 'domain' ]        = fqdn_parts( socket.getfqdn() )[1]
+            myAttrs[ 'poll_interval' ]    = str(BATCH_POLL_INTERVAL)
+
+            if self.jobDataChanged( jobs, job_id, myAttrs ) and myAttrs['status'] in [ 'R', 'Q' ]:
+                jobs[ job_id ] = myAttrs
+
+                debug_msg( 10, printTime() + ' job %s state changed' %(job_id) )
+
+        for id, attrs in jobs.items():
+            if id not in jobs_processed:
+                # This one isn't there anymore
+                #
+                del jobs[ id ]
+        self.jobs=jobs
+
+
+class PbsDataGatherer( DataGatherer ):
+
+    """This is the DataGatherer for PBS and Torque"""
+
+    global PBSQuery, PBSError
+
+    def __init__( self ):
+
+        """Setup appropriate variables"""
+
+        self.jobs       = { }
+        self.timeoffset = 0
+        self.dp         = DataProcessor()
+
+        self.initPbsQuery()
+
+    def initPbsQuery( self ):
+
+        self.pq = None
+
+        try:
+
+            if( BATCH_SERVER ):
+
+                self.pq = PBSQuery( BATCH_SERVER )
+            else:
+                self.pq = PBSQuery()
+
+        except PBSError, details:
+            print 'Cannot connect to pbs server'
+            print details
+            sys.exit( 1 )
+
+        try:
+            # TODO: actually use new data structure
+            self.pq.old_data_structure()
+
+        except AttributeError:
+
+            # pbs_query is older
+            #
+            pass
+
+    def getNodeData( self ):
+
+        nodedict = { }
+
+        try:
+            nodedict = self.pq.getnodes()
+
+        except PBSError, detail:
+
+            debug_msg( 10, "PBS server unavailable, skipping until next polling interval: " + str( detail ) )
+
+        return nodedict
+
+    def getJobData( self ):
+
+        """Gather all data on current jobs in Torque"""
+
+        joblist            = {}
+        self.cur_time      = 0
+
+        try:
+            joblist        = self.pq.getjobs()
+            self.cur_time  = time.time()
+
+        except PBSError, detail:
+
+            debug_msg( 10, "PBS server unavailable, skipping until next polling interval: " + str( detail ) )
+            return None
+
+        jobs_processed    = [ ]
+
+        for name, attrs in joblist.items():
+            display_queue = 1
+            job_id        = name.split( '.' )[0]
+
+            name          = self.getAttr( attrs, 'Job_Name' )
+            queue         = self.getAttr( attrs, 'queue' )
+
+            if QUEUE:
+                for q in QUEUE:
+                    if q == queue:
+                        display_queue = 1
+                        break
+                    else:
+                        display_queue = 0
+                        continue
+            if display_queue == 0:
+                continue
+
+
+            owner            = self.getAttr( attrs, 'Job_Owner' ).split( '@' )[0]
+            requested_time   = self.getAttr( attrs, 'Resource_List.walltime' )
+            requested_memory = self.getAttr( attrs, 'Resource_List.mem' )
+
+            mynoderequest    = self.getAttr( attrs, 'Resource_List.nodes' )
+
+            ppn = ''
+
+            if mynoderequest.find( ':' ) != -1 and mynoderequest.find( 'ppn' ) != -1:
+
+                mynoderequest_fields = mynoderequest.split( ':' )
+
+                for mynoderequest_field in mynoderequest_fields:
+
+                    if mynoderequest_field.find( 'ppn' ) != -1:
+
+                        ppn = mynoderequest_field.split( 'ppn=' )[1]
+
+            status = self.getAttr( attrs, 'job_state' )
+
+            if status in [ 'Q', 'R' ]:
+
+                jobs_processed.append( job_id )
+
+            queued_timestamp = self.getAttr( attrs, 'ctime' )
+
+            if status == 'R':
+
+                start_timestamp = self.getAttr( attrs, 'mtime' )
+                nodes           = self.getAttr( attrs, 'exec_host' ).split( '+' )
+
+                nodeslist       = do_nodelist( nodes )
+
+                if DETECT_TIME_DIFFS:
+
+                    # If a job start if later than our current date,
+                    # that must mean the Torque server's time is later
+                    # than our local time.
+                
+                    if int( start_timestamp ) > int( int( self.cur_time ) + int( self.timeoffset ) ):
+
+                        self.timeoffset = int( int(start_timestamp) - int(self.cur_time) )
+
+            elif status == 'Q':
+
+                # 'mynodequest' can be a string in the following syntax according to the
+                # Torque Administator's manual:
+                # 
+                # {<node_count> | <hostname>}[:ppn=<ppn>][:<property>[:<property>]...][+ ...]
+                # {<node_count> | <hostname>}[:ppn=<ppn>][:<property>[:<property>]...][+ ...]
+                # etc
+                #
+
+                #
+                # For now we only count the amount of nodes request and ignore properties
+                #
+
+                start_timestamp = ''
+                count_mynodes   = 0
+
+                for node in mynoderequest.split( '+' ):
+
+                    # Just grab the {node_count|hostname} part and ignore properties
+                    #
+                    nodepart     = node.split( ':' )[0]
+
+                    # Let's assume a node_count value
+                    #
+                    numeric_node = 1
+
+                    # Chop the value up into characters
+                    #
+                    for letter in nodepart:
+
+                        # If this char is not a digit (0-9), this must be a hostname
+                        #
+                        if letter not in string.digits:
+
+                            numeric_node = 0
+
+                    # If this is a hostname, just count this as one (1) node
+                    #
+                    if not numeric_node:
+
+                        count_mynodes = count_mynodes + 1
+                    else:
+
+                        # If this a number, it must be the node_count
+                        # and increase our count with it's value
+                        #
+                        try:
+                            count_mynodes = count_mynodes + int( nodepart )
+
+                        except ValueError, detail:
+
+                            # When we arrive here I must be bugged or very confused
+                            # THIS SHOULD NOT HAPPEN!
+                            #
+                            debug_msg( 10, str( detail ) )
+                            debug_msg( 10, "Encountered weird node in Resources_List?!" )
+                            debug_msg( 10, 'nodepart = ' + str( nodepart ) )
+                            debug_msg( 10, 'job = ' + str( name ) )
+                            debug_msg( 10, 'attrs = ' + str( attrs ) )
+                        
+                nodeslist       = str( count_mynodes )
+            else:
+                start_timestamp = ''
+                nodeslist       = ''
+
+            myAttrs                = { }
+
+            myAttrs[ 'name' ]             = str( name )
+            myAttrs[ 'queue' ]            = str( queue )
+            myAttrs[ 'owner' ]            = str( owner )
+            myAttrs[ 'requested_time' ]   = str( requested_time )
+            myAttrs[ 'requested_memory' ] = str( requested_memory )
+            myAttrs[ 'ppn' ]              = str( ppn )
+            myAttrs[ 'status' ]           = str( status )
+            myAttrs[ 'start_timestamp' ]  = str( start_timestamp )
+            myAttrs[ 'queued_timestamp' ] = str( queued_timestamp )
+            myAttrs[ 'reported' ]         = str( int( int( self.cur_time ) + int( self.timeoffset ) ) )
+            myAttrs[ 'nodes' ]            = nodeslist
+            myAttrs[ 'domain' ]           = fqdn_parts( socket.getfqdn() )[1]
+            myAttrs[ 'poll_interval' ]    = str( BATCH_POLL_INTERVAL )
+
+            if self.jobDataChanged( self.jobs, job_id, myAttrs ) and myAttrs['status'] in [ 'R', 'Q' ]:
+
+                self.jobs[ job_id ] = myAttrs
+
+        for id, attrs in self.jobs.items():
+
+            if id not in jobs_processed:
+
+                # This one isn't there anymore; toedeledoki!
+                #
+                del self.jobs[ id ]
+
+GMETRIC_DEFAULT_TYPE    = 'string'
+GMETRIC_DEFAULT_HOST    = '127.0.0.1'
+GMETRIC_DEFAULT_PORT    = '8649'
+GMETRIC_DEFAULT_UNITS   = ''
+
+class Gmetric:
+
+    global GMETRIC_DEFAULT_HOST, GMETRIC_DEFAULT_PORT
+
+    slope           = { 'zero' : 0, 'positive' : 1, 'negative' : 2, 'both' : 3, 'unspecified' : 4 }
+    type            = ( '', 'string', 'uint16', 'int16', 'uint32', 'int32', 'float', 'double', 'timestamp' )
+    protocol        = ( 'udp', 'multicast' )
+
+    def __init__( self, host=GMETRIC_DEFAULT_HOST, port=GMETRIC_DEFAULT_PORT ):
+                
+        global GMETRIC_DEFAULT_TYPE
+
+        self.prot       = self.checkHostProtocol( host )
+        self.data_msg   = xdrlib.Packer()
+        self.meta_msg   = xdrlib.Packer()
+        self.socket     = socket.socket( socket.AF_INET, socket.SOCK_DGRAM )
+
+        if self.prot not in self.protocol:
+
+            raise ValueError( "Protocol must be one of: " + str( self.protocol ) )
+
+        if self.prot == 'multicast':
+
+            # Set multicast options
+            #
+            self.socket.setsockopt( socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 20 )
+
+        self.hostport   = ( host, int( port ) )
+        self.slopestr   = 'both'
+        self.tmax       = 60
+
+    def checkHostProtocol( self, ip ):
+
+        """Detect if a ip adress is a multicast address"""
+
+        MULTICAST_ADDRESS_MIN   = ( "224", "0", "0", "0" )
+        MULTICAST_ADDRESS_MAX   = ( "239", "255", "255", "255" )
+
+        ip_fields               = ip.split( '.' )
+
+        if ip_fields >= MULTICAST_ADDRESS_MIN and ip_fields <= MULTICAST_ADDRESS_MAX:
+
+            return 'multicast'
+        else:
+            return 'udp'
+
+    def send( self, name, value, dmax, typestr = '', units = '' ):
+
+        if len( units ) == 0:
+            units       = GMETRIC_DEFAULT_UNITS
+
+        if len( typestr ) == 0:
+            typestr     = GMETRIC_DEFAULT_TYPE
+
+        (meta_msg, data_msg) = self.makexdr( name, value, typestr, units, self.slopestr, self.tmax, dmax )
+
+        meta_rt = self.socket.sendto( meta_msg, self.hostport )
+        data_rt = self.socket.sendto( data_msg, self.hostport )
+
+        return ( meta_rt, data_rt )
+
+    def makexdr( self, name, value, typestr, unitstr, slopestr, tmax, dmax, group=None, spoof=None ):
+
+        hostname = "unset"
+
+        if slopestr not in self.slope:
+
+            raise ValueError( "Slope must be one of: " + str( self.slope.keys() ) )
+
+        if typestr not in self.type:
+
+            raise ValueError( "Type must be one of: " + str( self.type ) )
+
+        if len( name ) == 0:
+
+            raise ValueError( "Name must be non-empty" )
+
+        self.meta_msg.reset()
+        self.meta_msg.pack_int( 128 )
+
+        if not spoof:
+            self.meta_msg.pack_string( hostname )
+        else:
+            self.meta_msg.pack_string( spoof )
+
+        self.meta_msg.pack_string( name )
+
+        if not spoof:
+            self.meta_msg.pack_int( 0 )
+        else:
+            self.meta_msg.pack_int( 1 )
+            
+        self.meta_msg.pack_string( typestr )
+        self.meta_msg.pack_string( name )
+        self.meta_msg.pack_string( unitstr )
+        self.meta_msg.pack_int( self.slope[ slopestr ] )
+        self.meta_msg.pack_uint( int( tmax ) )
+        self.meta_msg.pack_uint( int( dmax ) )
+
+        if not group:
+            self.meta_msg.pack_int( 0 )
+        else:
+            self.meta_msg.pack_int( 1 )
+            self.meta_msg.pack_string( "GROUP" )
+            self.meta_msg.pack_string( group )
+
+        self.data_msg.reset()
+        self.data_msg.pack_int( 128+5 )
+
+        if not spoof:
+            self.data_msg.pack_string( hostname )
+        else:
+            self.data_msg.pack_string( spoof )
+
+        self.data_msg.pack_string( name )
+
+        if not spoof:
+            self.data_msg.pack_int( 0 )
+        else:
+            self.data_msg.pack_int( 1 )
+
+        self.data_msg.pack_string( "%s" )
+        self.data_msg.pack_string( str( value ) )
+
+        return ( self.meta_msg.get_buffer(), self.data_msg.get_buffer() )
+
+def printTime( ):
+
+    """Print current time/date in human readable format for log/debug"""
+
+    return time.strftime("%a, %d %b %Y %H:%M:%S")
+
+def debug_msg( level, msg ):
+
+    """Print msg if at or above current debug level"""
+
+    global DAEMONIZE, DEBUG_LEVEL, SYSLOG_LEVEL
+
+    if (not DAEMONIZE and DEBUG_LEVEL >= level):
+        sys.stderr.write( msg + '\n' )
+
+    if (DAEMONIZE and USE_SYSLOG and SYSLOG_LEVEL >= level):
+        syslog.syslog( msg )
+
+def write_pidfile():
+
+    # Write pidfile if PIDFILE is set
+    #
+    if PIDFILE:
+
+        pid    = os.getpid()
+
+        pidfile    = open( PIDFILE, 'w' )
+
+        pidfile.write( str( pid ) )
+        pidfile.close()
+
+def main():
+
+    """Application start"""
+
+    global PBSQuery, PBSError, lsfObject
+    global SYSLOG_FACILITY, USE_SYSLOG, BATCH_API, DAEMONIZE
+
+    if not processArgs( sys.argv[1:] ):
+
+        sys.exit( 1 )
+
+    # Load appropriate DataGatherer depending on which BATCH_API is set
+    # and any required modules for the Gatherer
+    #
+    if BATCH_API == 'pbs':
+
+        try:
+            from PBSQuery import PBSQuery, PBSError
+
+        except ImportError, details:
+
+            print "FATAL ERROR: BATCH_API set to 'pbs' but python module 'pbs_python' cannot be loaded"
+            print details
+            sys.exit( 1 )
+
+        gather = PbsDataGatherer()
+
+    elif BATCH_API == 'sge':
+
+        # Tested with SGE 6.0u11.
+        #
+        gather = SgeDataGatherer()
+
+    elif BATCH_API == 'lsf':
+
+        try:
+            from lsfObject import lsfObject
+        except:
+            print "FATAL ERROR: BATCH_API set to 'lsf' but python module is not found or installed"
+            sys.exit( 1)
+
+        gather = LsfDataGatherer()
+
+    else:
+        print "FATAL ERROR: unknown BATCH_API '" + BATCH_API + "' is not supported"
+
+        sys.exit( 1 )
+
+    if( DAEMONIZE and USE_SYSLOG ):
+
+        syslog.openlog( 'jobmond', syslog.LOG_NOWAIT, SYSLOG_FACILITY )
+
+    if DAEMONIZE:
+
+        gather.daemon()
+    else:
+        gather.run()
+
+# wh00t? someone started me! :)
+#
+if __name__ == '__main__':
+    main()
Index: /branches/1.0/parse_ganglia.py
===================================================================
--- /branches/1.0/parse_ganglia.py	(revision 825)
+++ /branches/1.0/parse_ganglia.py	(revision 825)
@@ -0,0 +1,331 @@
+#!/usr/bin/env python
+
+import shlex, sys, pprint
+from glob import glob
+
+class GangliaConfigParser:
+
+    def __init__( self, filename ):
+
+        self.conf_lijst   = [ ]
+        self.conf_dict    = { }
+        self.filename     = filename
+        self.file_pointer = file( filename, 'r' )
+        self.lexx         = shlex.shlex( self.file_pointer )
+        self.lexx.whitespace_split = True
+
+        self.parse()
+
+    def __del__( self ):
+
+        """
+        Cleanup: close file descriptor
+        """
+
+        self.file_pointer.close()
+        del self.lexx
+        del self.conf_lijst
+
+    def removeQuotes( self, value ):
+
+        clean_value = value
+        clean_value = clean_value.replace( "'", "" )
+        clean_value = clean_value.replace( '"', '' )
+        clean_value = clean_value.strip()
+    
+        return clean_value
+
+    def removeBraces( self, value ):
+
+        clean_value = value
+        clean_value = clean_value.replace( "(", "" )
+        clean_value = clean_value.replace( ')', '' )
+        clean_value = clean_value.strip()
+    
+        return clean_value
+
+    def parse( self ):
+
+        """
+        Parse self.filename using shlex scanning.
+        - Removes /* comments */
+        - Traverses (recursively) through all include () statements
+        - Stores complete valid config tokens in self.conf_list
+
+        i.e.:
+            ['globals',
+             '{',
+             'daemonize',
+             '=',
+             'yes',
+             'setuid',
+             '=',
+             'yes',
+             'user',
+             '=',
+             'ganglia',
+             'debug_level',
+             '=',
+             '0', 
+             <etc> ]
+        """
+
+        t = 'bogus'
+        c = False
+        i = False
+
+        while t != self.lexx.eof:
+            #print 'get token'
+            t = self.lexx.get_token()
+
+            if len( t ) >= 2:
+
+                if len( t ) >= 4:
+
+                    if t[:2] == '/*' and t[-2:] == '*/':
+
+                        #print 'comment line'
+                        #print 'skipping: %s' %t
+                        continue
+
+                if t == '/*' or t[:2] == '/*':
+                    c = True
+                    #print 'comment start'
+                    #print 'skipping: %s' %t
+                    continue
+
+                if t == '*/' or t[-2:] == '*/':
+                    c = False
+                    #print 'skipping: %s' %t
+                    #print 'comment end'
+                    continue
+
+            if c:
+                #print 'skipping: %s' %t
+                continue
+
+            if t == 'include':
+                i = True
+                #print 'include start'
+                #print 'skipping: %s' %t
+                continue
+
+            if i:
+
+                #print 'include start: %s' %t
+
+                t2 = self.removeQuotes( t )
+                t2 = self.removeBraces( t )
+
+                for in_file in glob( self.removeQuotes(t2) ):
+
+                    #print 'including file: %s' %in_file
+                    parse_infile = GangliaConfigParser( in_file )
+
+                    self.conf_lijst = self.conf_lijst + parse_infile.getConfLijst()
+
+                    del parse_infile
+
+                i = False
+                #print 'include end'
+                #print 'skipping: %s' %t
+                continue
+
+            #print 'keep: %s' %t
+            self.conf_lijst.append( self.removeQuotes(t) )
+
+    def getConfLijst( self ):
+
+        return self.conf_lijst
+
+    def confListToDict( self, parent_list=None ):
+
+        """
+        Recursively traverses a conf_list and creates dictionary from it
+        """
+
+        new_dict = { }
+        count    = 0
+        skip     = 0
+
+        if not parent_list:
+            parent_list = self.conf_lijst
+
+        #print 'entering confListToDict(): (parent) list size %s' %len(parent_list)
+
+        for n, c in enumerate( parent_list ):
+
+            count = count + 1
+
+            #print 'CL: n %d c %s' %(n, c)
+
+            if skip > 0:
+
+                #print '- skipped'
+                skip = skip - 1
+                continue
+
+            if (n+1) <= (len( parent_list )-1):
+
+                if parent_list[(n+1)] == '{':
+
+                    if not new_dict.has_key( c ):
+                        new_dict[ c ] = [ ]
+
+                    (temp_new_dict, skip) = self.confListToDict( parent_list[(n+2):] )
+                    new_dict[ c ].append( temp_new_dict )
+
+                if parent_list[(n+1)] == '=' and (n+2) <= (len( parent_list )-1):
+
+                    if not new_dict.has_key( c ):
+                        new_dict[ c ] = [ ]
+
+                    new_dict[ c ].append( parent_list[ (n+2) ] )
+
+                    skip = 2
+
+                if parent_list[n] == '}':
+
+                    #print 'leaving confListToDict(): new dict = %s' %new_dict
+                    return (new_dict, count)
+
+
+    def makeConfDict( self ):
+
+        """
+        Walks through self.conf_list and creates a dictionary based upon config values
+
+        i.e.:
+            'tcp_accept_channel': [{'acl': [{'access': [{'action': ['"allow"'],
+                                                         'ip': ['"127.0.0.1"'],
+                                                         'mask': ['32']}]}],
+                                    'port': ['8649']}],
+            'udp_recv_channel': [{'port': ['8649']}],
+            'udp_send_channel': [{'host': ['145.101.32.3'],
+                                  'port': ['8649']},
+                                 {'host': ['145.101.32.207'],
+                                  'port': ['8649']}]}
+        """
+
+        new_dict = { }
+        skip     = 0
+
+        #print 'entering makeConfDict()'
+
+        for n, c in enumerate( self.conf_lijst ):
+
+            #print 'M: n %d c %s' %(n, c)
+
+            if skip > 0:
+
+                #print '- skipped'
+                skip = skip - 1
+                continue
+
+            if (n+1) <= (len( self.conf_lijst )-1):
+
+                if self.conf_lijst[(n+1)] == '{':
+
+                    if not new_dict.has_key( c ):
+                        new_dict[ c ] = [ ]
+
+                    ( temp_new_dict, skip ) = self.confListToDict( self.conf_lijst[(n+2):] )
+                    new_dict[ c ].append( temp_new_dict )
+
+                if self.conf_lijst[(n+1)] == '=' and (n+2) <= (len( self.conf_lijst )-1):
+
+                    if not new_dict.has_key( c ):
+                        new_dict[ c ] = [ ]
+
+                    new_dict[ c ].append( self.conf_lijst[ (n+2) ] )
+
+                    skip = 2
+
+        self.conf_dict = new_dict
+        #print 'leaving makeConfDict(): conf dict size %d' %len( self.conf_dict )
+
+    def checkConfDict( self ):
+
+        if len( self.conf_lijst ) == 0:
+
+            raise Exception("Something went wrong generating conf list for %s" %self.file_name )
+
+        if len( self.conf_dict ) == 0:
+
+            self.makeConfDict()
+
+    def getConfDict( self ):
+
+        self.checkConfDict()
+        return self.conf_dict
+
+    def getUdpSendChannels( self ):
+
+        self.checkConfDict()
+        return self.conf_dict[ 'udp_send_channel' ]
+
+    def getSectionLastOption( self, section, option ):
+
+        """
+        Get last option set in a config section that could be set multiple times in multiple (include) files.
+
+        i.e.: getSectionLastOption( 'globals', 'send_metadata_interval' )
+        """
+
+        self.checkConfDict()
+        value = None
+
+        if not self.conf_dict.has_key( section ):
+
+            return None
+
+        # Could be set multiple times in multiple (include) files: get last one set
+        for c in self.conf_dict[ section ]:
+
+                if c.has_key( option ):
+
+                    cluster_name = c[ option ][0]
+
+        return cluster_name
+
+    def getClusterName( self ):
+
+        return self.getSectionLastOption( 'cluster', 'name' )
+
+    def getVal( self, section, option ):
+
+        return self.getSectionLastOption( section, option )
+
+    def getInt( self, section, valname ):
+
+        value    = self.getVal( section, valname )
+
+        if not value:
+            return None
+
+        return int( value )
+
+    def getStr( self, section, valname ):
+
+        value    = self.getVal( section, valname )
+
+        if not value:
+            return None
+
+        return str( value )
+
+GMOND_LOCATION = '/etc/ganglia/gmond.conf'
+
+g = GangliaConfigParser( GMOND_LOCATION )
+
+pprint.pprint( g.getConfLijst(), width=1 )
+
+g.makeConfDict()
+
+pprint.pprint( g.getConfDict(), width=1 )
+
+print g.getClusterName()
+print g.getUdpSendChannels()
+
+print 'exiting..'
+sys.exit(0)
Index: /branches/1.0/pkg/deb/default/jobarchived
===================================================================
--- /branches/1.0/pkg/deb/default/jobarchived	(revision 825)
+++ /branches/1.0/pkg/deb/default/jobarchived	(revision 825)
@@ -0,0 +1,1 @@
+OPTIONS=""
Index: /branches/1.0/pkg/deb/default/jobmond
===================================================================
--- /branches/1.0/pkg/deb/default/jobmond	(revision 825)
+++ /branches/1.0/pkg/deb/default/jobmond	(revision 825)
@@ -0,0 +1,1 @@
+OPTIONS=""
Index: /branches/1.0/pkg/deb/init.d/jobarchived
===================================================================
--- /branches/1.0/pkg/deb/init.d/jobarchived	(revision 825)
+++ /branches/1.0/pkg/deb/init.d/jobarchived	(revision 825)
@@ -0,0 +1,41 @@
+#! /bin/sh
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/sbin/jobarchived
+NAME=jobarchived
+DESC="Job Archiving Daemon"
+
+PIDFILE="/var/run/$NAME.pid"
+OPTIONS=""
+
+test -x $DAEMON || exit 0
+
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+case "$1" in
+  start)
+	echo -n "Starting $DESC: "
+	start-stop-daemon --start --quiet --startas "$DAEMON" --pidfile "$PIDFILE" -- \
+		--pidfile="$PIDFILE" $OPTIONS || { echo "start failed."; exit 0; }
+	echo "$NAME."
+	exit 0
+	;;
+  stop)
+	echo -n "Stopping $DESC: "
+        start-stop-daemon --stop  --quiet --pidfile "$PIDFILE" \
+                --signal 9 2>&1 > /dev/null || { rm -f "$PIDFILE"; echo "not running."; exit 0; }
+	echo "$NAME."
+	rm -f "$PIDFILE"
+	exit 0
+	;;
+  restart|force-reload)
+	$0 stop
+	$0 start
+	;;
+  *)
+	echo "Usage: $N {start|stop|restart}" >&2
+	exit 1
+	;;
+esac
+
+exit 0
Index: /branches/1.0/pkg/deb/init.d/jobmond
===================================================================
--- /branches/1.0/pkg/deb/init.d/jobmond	(revision 825)
+++ /branches/1.0/pkg/deb/init.d/jobmond	(revision 825)
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
+DAEMON=/usr/sbin/jobmond
+NAME=jobmond
+DESC="Job Monitor Daemon"
+
+PIDFILE="/var/run/$NAME.pid"
+
+test -x $DAEMON || return 0
+
+OPTIONS=""
+
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+case "$1" in
+  start)
+	echo -n "Starting $DESC: "
+	start-stop-daemon --start --quiet --startas "$DAEMON" --pidfile "$PIDFILE" -- \
+		--pidfile="$PIDFILE" $OPTIONS || { echo "start failed."; exit 0; }
+	echo "$NAME."
+	exit 0
+	;;
+  stop)
+	echo -n "Stopping $DESC: "
+        start-stop-daemon --stop --quiet --pidfile "$PIDFILE" \
+                --signal 9 2>&1 > /dev/null || { rm -f "$PIDFILE"; echo "not running."; exit 0; }
+	echo "$NAME."
+	rm -f "$PIDFILE"
+	exit 0
+	;;
+  restart|force-reload)
+	$0 stop
+	$0 start
+	;;
+  *)
+	echo "Usage: $N {start|stop|restart}" >&2
+	exit 1
+	;;
+esac
+
+exit 0
Index: /branches/1.0/pkg/deb/jobarchived/DEBIAN/conffiles
===================================================================
--- /branches/1.0/pkg/deb/jobarchived/DEBIAN/conffiles	(revision 825)
+++ /branches/1.0/pkg/deb/jobarchived/DEBIAN/conffiles	(revision 825)
@@ -0,0 +1,1 @@
+/etc/jobarchived.conf
Index: /branches/1.0/pkg/deb/jobarchived/DEBIAN/control
===================================================================
--- /branches/1.0/pkg/deb/jobarchived/DEBIAN/control	(revision 825)
+++ /branches/1.0/pkg/deb/jobarchived/DEBIAN/control	(revision 825)
@@ -0,0 +1,10 @@
+Package: jobmonarch-jobarchived
+Version: 0.2-1
+Section: misc
+Priority: optional
+Architecture: all
+Essential: no
+Depends: python, gmetad, rrdtool, postgresql, ganglia-webfrontend
+Installed-Size: 76K
+Maintainer: Ramon Bastiaans <bastiaans@sara.nl>
+Description: Job Archiving Daemon
Index: /branches/1.0/pkg/deb/jobarchived/DEBIAN/postinst
===================================================================
--- /branches/1.0/pkg/deb/jobarchived/DEBIAN/postinst	(revision 825)
+++ /branches/1.0/pkg/deb/jobarchived/DEBIAN/postinst	(revision 825)
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+PATH=/bin:/sbin:/usr/bin:/usr/sbin
+
+if [ -x /etc/init.d/jobarchived ]
+	then
+
+		update-rc.d -f jobarchived defaults
+fi
+
+echo ""
+echo "Additional manual changes are required to setup jobarchived:"
+echo ""
+echo "1) Edit /etc/jobarchived.conf to reflect your local settings and setup:"
+echo "   - ARCHIVE_DATASOURCES and ARCHIVE_PATH"
+echo ""
+echo "2) Create a 'jobarchive' database and create jobarchived's tables:" 
+echo "   - createdb jobarchive"
+echo "   - psql -f /usr/share/jobarchived/job_dbase.sql jobarchive"
+echo ""
Index: /branches/1.0/pkg/deb/jobarchived/DEBIAN/prerm
===================================================================
--- /branches/1.0/pkg/deb/jobarchived/DEBIAN/prerm	(revision 825)
+++ /branches/1.0/pkg/deb/jobarchived/DEBIAN/prerm	(revision 825)
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+PATH=/bin:/sbin:/usr/bin:/usr/sbin
+
+/etc/init.d/jobarchived stop || true
+update-rc.d -f jobarchived remove || true
+
+exit 0
Index: /branches/1.0/pkg/deb/jobmond/DEBIAN/conffiles
===================================================================
--- /branches/1.0/pkg/deb/jobmond/DEBIAN/conffiles	(revision 825)
+++ /branches/1.0/pkg/deb/jobmond/DEBIAN/conffiles	(revision 825)
@@ -0,0 +1,1 @@
+/etc/jobmond.conf
Index: /branches/1.0/pkg/deb/jobmond/DEBIAN/control
===================================================================
--- /branches/1.0/pkg/deb/jobmond/DEBIAN/control	(revision 825)
+++ /branches/1.0/pkg/deb/jobmond/DEBIAN/control	(revision 825)
@@ -0,0 +1,10 @@
+Package: jobmonarch-jobmond
+Version: 0.2-1
+Section: misc
+Priority: optional
+Architecture: all
+Essential: no
+Depends: python, pbs-python, ganglia-monitor
+Installed-Size: 44K
+Maintainer: Ramon Bastiaans <bastiaans@sara.nl>
+Description: Job Monitoring Daemon
Index: /branches/1.0/pkg/deb/jobmond/DEBIAN/postinst
===================================================================
--- /branches/1.0/pkg/deb/jobmond/DEBIAN/postinst	(revision 825)
+++ /branches/1.0/pkg/deb/jobmond/DEBIAN/postinst	(revision 825)
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+PATH=/bin:/sbin:/usr/bin:/usr/sbin
+
+if [ -x /etc/init.d/jobmond ] 
+	then
+
+		update-rc.d -f jobmond defaults
+
+fi
+
+/etc/init.d/jobmond restart
Index: /branches/1.0/pkg/deb/jobmond/DEBIAN/prerm
===================================================================
--- /branches/1.0/pkg/deb/jobmond/DEBIAN/prerm	(revision 825)
+++ /branches/1.0/pkg/deb/jobmond/DEBIAN/prerm	(revision 825)
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+PATH=/bin:/sbin:/usr/bin:/usr/sbin
+
+/etc/init.d/jobmond stop || true
+update-rc.d -f jobmond remove || true
Index: /branches/1.0/pkg/deb/web/DEBIAN/conffiles
===================================================================
--- /branches/1.0/pkg/deb/web/DEBIAN/conffiles	(revision 825)
+++ /branches/1.0/pkg/deb/web/DEBIAN/conffiles	(revision 825)
@@ -0,0 +1,1 @@
+/var/www/ganglia/addons/job_monarch/conf.php
Index: /branches/1.0/pkg/deb/web/DEBIAN/control
===================================================================
--- /branches/1.0/pkg/deb/web/DEBIAN/control	(revision 825)
+++ /branches/1.0/pkg/deb/web/DEBIAN/control	(revision 825)
@@ -0,0 +1,10 @@
+Package: jobmonarch-webfrontend
+Version: 0.2-1
+Section: misc
+Priority: optional
+Architecture: all
+Essential: no
+Depends: php4 | php5, php4-gd | php5-gd, rrdtool, ganglia-webfrontend
+Installed-Size: 304K
+Maintainer: Ramon Bastiaans <bastiaans@sara.nl>
+Description: Job MonArch's Web Frontend
Index: /branches/1.0/pkg/deb/web/DEBIAN/postinst
===================================================================
--- /branches/1.0/pkg/deb/web/DEBIAN/postinst	(revision 825)
+++ /branches/1.0/pkg/deb/web/DEBIAN/postinst	(revision 825)
@@ -0,0 +1,6 @@
+#!/bin/sh
+
+echo "Make sure to set your Ganglia template to job_monarch now"
+echo "" 
+echo "In your Ganglia conf.php, set this line:"
+echo "$template_name = \"job_monarch\";"
Index: /branches/1.0/pkg/deb/web/DEBIAN/postrm
===================================================================
--- /branches/1.0/pkg/deb/web/DEBIAN/postrm	(revision 825)
+++ /branches/1.0/pkg/deb/web/DEBIAN/postrm	(revision 825)
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+echo "Dont forget to set your Ganglia template back to default"
Index: /branches/1.0/pkg/rpm/init.d/jobarchived
===================================================================
--- /branches/1.0/pkg/rpm/init.d/jobarchived	(revision 825)
+++ /branches/1.0/pkg/rpm/init.d/jobarchived	(revision 825)
@@ -0,0 +1,77 @@
+#!/bin/sh
+#
+# Init file for the Job Archiving Daemon
+#
+# chkconfig: 2345 20 80
+# description: jobarchived archives information on nodes in RRDS and batch jobs in SQL
+#
+# processname: jobarchived
+# config: /etc/jobarchived.conf
+# config: /etc/sysconfig/jobarchived
+# pidfile: /var/run/jobarchived
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+RETVAL=0
+NAME=jobarchived
+DAEMON=/usr/sbin/jobarchived
+DESC="Job Archiving Daemon"
+
+PIDFILE=/var/run/$NAME.pid
+
+OPTIONS=""
+
+if [ -r /etc/sysconfig/$NAME ]
+	then
+		source /etc/sysconfig/$NAME
+fi
+
+start() {
+	echo -n $"Starting $DESC: "
+	daemon $DAEMON --pidfile="$PIDFILE" $OPTIONS
+	RETVAL=$?
+	echo
+	[ $RETVAL -eq 0 ] && touch /var/lock/subsys/$NAME
+	return $RETVAL
+}
+
+stop() {
+	echo -n $"Stopping $DESC: "
+	killproc $DAEMON
+	RETVAL=$?
+	echo
+	rm -f $PIDFILE
+	[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$NAME
+	return $RETVAL
+}
+
+restart() {
+	stop
+	start
+}
+
+case "$1" in
+	start)
+		start
+		;;
+	stop)
+		stop
+		;;
+	restart)
+		restart
+		;;
+	condrestart)
+		[ -e /var/lock/subsys/$NAME ] && restart
+		RETVAL=$?
+		;;
+	status)
+		status python
+		RETVAL=$?
+		;;
+	*)
+		echo $"Usage: $0 {start|stop|restart|condrestart|status}"
+		RETVAL=1
+esac
+
+exit $RETVAL
Index: /branches/1.0/pkg/rpm/init.d/jobmond
===================================================================
--- /branches/1.0/pkg/rpm/init.d/jobmond	(revision 825)
+++ /branches/1.0/pkg/rpm/init.d/jobmond	(revision 825)
@@ -0,0 +1,76 @@
+#!/bin/sh
+#
+# Init file for the Job Monitoring Daemon
+#
+# chkconfig: 2345 20 80
+# description: jobmond reports information on batch jobs as Ganglia metrics
+#
+# processname: jobmond
+# config: /etc/jobmond.conf
+# config: /etc/sysconfig/jobmond
+# pidfile: /var/run/jobmond
+
+# Source function library.
+. /etc/rc.d/init.d/functions
+
+RETVAL=0
+NAME=jobmond
+DAEMON=/usr/sbin/jobmond
+DESC="Job Monitoring Daemon"
+
+PIDFILE=/var/run/$NAME.pid
+OPTIONS=""
+
+if [ -r /etc/sysconfig/$name ]
+	then
+		source /etc/sysconfig/$NAME
+fi
+
+start() {
+	echo -n $"Starting $DESC: "
+	daemon $DAEMON --pidfile="$PIDFILE" $OPTIONS
+	RETVAL=$?
+	echo
+	[ $RETVAL -eq 0 ] && touch /var/lock/subsys/$NAME
+	return $RETVAL
+}
+
+stop() {
+	echo -n $"Stopping $DESC: "
+	killproc $DAEMON
+	RETVAL=$?
+	echo
+	rm -f $PIDFILE
+	[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/$NAME
+	return $RETVAL
+}
+
+restart() {
+	stop
+	start
+}
+
+case "$1" in
+	start)
+		start
+		;;
+	stop)
+		stop
+		;;
+	restart)
+		restart
+		;;
+	condrestart)
+		[ -e /var/lock/subsys/$NAME ] && restart
+		RETVAL=$?
+		;;
+	status)
+		status python
+		RETVAL=$?
+		;;
+	*)
+		echo $"Usage: $0 {start|stop|restart|condrestart|status}"
+		RETVAL=1
+esac
+
+exit $RETVAL
Index: /branches/1.0/pkg/rpm/jobmonarch-jobarchived.spec
===================================================================
--- /branches/1.0/pkg/rpm/jobmonarch-jobarchived.spec	(revision 825)
+++ /branches/1.0/pkg/rpm/jobmonarch-jobarchived.spec	(revision 825)
@@ -0,0 +1,59 @@
+Buildroot: 
+Name: jobmonarch-jobarchived
+Version: 
+Release: 
+Summary: Job Archiving Daemon
+License: GPL
+Distribution: Fedora
+Group: Applications/System
+
+%define _rpmdir ../
+%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.rpm
+%define _unpackaged_files_terminate_build 0
+
+%post
+#!/bin/sh
+
+PATH=/bin:/sbin:/usr/bin:/usr/sbin
+
+if [ -x /etc/init.d/jobarchived ]
+	then
+
+                chkconfig --add jobarchived
+		chkconfig jobarchived on
+
+fi
+
+echo ""
+echo "Additional manual changes are required to setup jobarchived:"
+echo ""
+echo "1) Edit /etc/jobarchived.conf to reflect your local settings and setup:"
+echo "   - ARCHIVE_DATASOURCES and ARCHIVE_PATH"
+echo ""
+echo "2) Create a 'jobarchive' database and create jobarchived's tables:" 
+echo "   - createdb jobarchive"
+echo "   - psql -f /usr/share/jobarchived/job_dbase.sql jobarchive"
+echo ""
+
+
+%preun
+#!/bin/sh
+
+/etc/init.d/jobarchived stop
+chkconfig jobarchived off
+chkconfig --del jobarchived
+
+
+%description
+
+
+Job MonArch's archive daemon
+
+%files
+"/usr/sbin/jobarchived.py"
+"/usr/sbin/jobarchived"
+"/etc/init.d/jobarchived"
+%config "/etc/jobarchived.conf"
+%config "/etc/sysconfig/jobarchived"
+%dir "/usr/share/jobarchived/"
+"/usr/share/jobarchived/job_dbase.sql"
Index: /branches/1.0/pkg/rpm/jobmonarch-jobmond.spec
===================================================================
--- /branches/1.0/pkg/rpm/jobmonarch-jobmond.spec	(revision 825)
+++ /branches/1.0/pkg/rpm/jobmonarch-jobmond.spec	(revision 825)
@@ -0,0 +1,46 @@
+Buildroot: 
+Name: jobmonarch-jobmond
+Version: 
+Release: 
+Summary: Job Monitoring Daemon
+License: GPL
+Distribution: Fedora
+Group: Applications/System
+
+%define _rpmdir ../
+%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.rpm
+%define _unpackaged_files_terminate_build 0
+
+%post
+#!/bin/sh
+
+PATH=/bin:/sbin:/usr/bin:/usr/sbin
+
+if [ -x /etc/init.d/jobmond ]
+	then
+
+		chkconfig --add jobmond
+		chkconfig jobmond on
+		/etc/init.d/jobmond restart
+
+fi
+
+%preun
+#!/bin/sh
+
+/etc/init.d/jobmond stop
+chkconfig jobmond off
+chkconfig --del jobmond
+
+
+%description
+
+
+Job MonArch's monitoring daemon.
+
+%files
+"/etc/init.d/jobmond"
+%config "/etc/sysconfig/jobmond"
+"/usr/sbin/jobmond.py"
+"/usr/sbin/jobmond"
+%config "/etc/jobmond.conf"
Index: /branches/1.0/pkg/rpm/jobmonarch-webfrontend.spec
===================================================================
--- /branches/1.0/pkg/rpm/jobmonarch-webfrontend.spec	(revision 825)
+++ /branches/1.0/pkg/rpm/jobmonarch-webfrontend.spec	(revision 825)
@@ -0,0 +1,72 @@
+Buildroot: 
+Name: jobmonarch-webfrontend
+Version: 
+Release: 
+Summary: Job MonArch Web Frontend
+License: GPL
+Distribution: Fedora
+Group: Applications/Internet
+
+%define _rpmdir ../
+%define _rpmfilename %%{NAME}-%%{VERSION}-%%{RELEASE}.rpm
+%define _unpackaged_files_terminate_build 0
+
+%post
+#!/bin/sh
+
+echo "Make sure to set your Ganglia template to job_monarch now"
+echo "" 
+echo "In your Ganglia conf.php, set this line:"
+echo "$template_name = \"job_monarch\";"
+
+
+%postun
+#!/bin/sh
+
+echo "Dont forget to set your Ganglia template back to default"
+
+
+%description
+
+
+Job Monarch's web frontend.
+
+%files
+%dir "/var/www/ganglia/templates/job_monarch/"
+%dir "/var/www/ganglia/templates/job_monarch/images/"
+"/var/www/ganglia/templates/job_monarch/cluster_extra.tpl"
+"/var/www/ganglia/templates/job_monarch/host_extra.tpl"
+%dir "/var/www/ganglia/addons/"
+%dir "/var/www/ganglia/addons/job_monarch/"
+%dir "/var/www/ganglia/addons/job_monarch/clusterconf/"
+"/var/www/ganglia/addons/job_monarch/clusterconf/example.php"
+%dir "/var/www/ganglia/addons/job_monarch/templates/"
+"/var/www/ganglia/addons/job_monarch/templates/overview.tpl"
+"/var/www/ganglia/addons/job_monarch/templates/search.tpl"
+"/var/www/ganglia/addons/job_monarch/templates/footer.tpl"
+"/var/www/ganglia/addons/job_monarch/templates/header.tpl"
+"/var/www/ganglia/addons/job_monarch/templates/host_view.tpl"
+"/var/www/ganglia/addons/job_monarch/templates/index.tpl"
+%config "/var/www/ganglia/addons/job_monarch/conf.php"
+"/var/www/ganglia/addons/job_monarch/search.php"
+"/var/www/ganglia/addons/job_monarch/libtoga.php"
+"/var/www/ganglia/addons/job_monarch/version.php"
+"/var/www/ganglia/addons/job_monarch/cal.gif"
+"/var/www/ganglia/addons/job_monarch/document_archive.jpg"
+"/var/www/ganglia/addons/job_monarch/graph.php"
+"/var/www/ganglia/addons/job_monarch/header.php"
+"/var/www/ganglia/addons/job_monarch/host_view.php"
+"/var/www/ganglia/addons/job_monarch/image.php"
+"/var/www/ganglia/addons/job_monarch/libtoga.js"
+"/var/www/ganglia/addons/job_monarch/logo_ned.gif"
+"/var/www/ganglia/addons/job_monarch/next.gif"
+"/var/www/ganglia/addons/job_monarch/prev.gif"
+"/var/www/ganglia/addons/job_monarch/redcross.jpg"
+"/var/www/ganglia/addons/job_monarch/ts_picker.js"
+"/var/www/ganglia/addons/job_monarch/ts_validatetime.js"
+"/var/www/ganglia/addons/job_monarch/footer.php"
+"/var/www/ganglia/addons/job_monarch/styles.css"
+"/var/www/ganglia/addons/job_monarch/index.php"
+"/var/www/ganglia/addons/job_monarch/overview.php"
+"/var/www/ganglia/addons/job_monarch/jobmonarch.gif"
+"/var/www/ganglia/templates/job_monarch/images/logo.jpg"
Index: /branches/1.0/pkg/rpm/sysconfig/jobarchived
===================================================================
--- /branches/1.0/pkg/rpm/sysconfig/jobarchived	(revision 825)
+++ /branches/1.0/pkg/rpm/sysconfig/jobarchived	(revision 825)
@@ -0,0 +1,1 @@
+OPTIONS=""
Index: /branches/1.0/pkg/rpm/sysconfig/jobmond
===================================================================
--- /branches/1.0/pkg/rpm/sysconfig/jobmond	(revision 825)
+++ /branches/1.0/pkg/rpm/sysconfig/jobmond	(revision 825)
@@ -0,0 +1,1 @@
+OPTIONS=""
Index: /branches/1.0/web/addons/job_monarch/clusterconf/example.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/clusterconf/example.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/clusterconf/example.php	(revision 825)
@@ -0,0 +1,21 @@
+<?php
+// Will parse {x} (must be numeric) from hostname
+// and use as X (horizontal) coordinate of nodes
+//
+// Will parse {y} (must be numeric) from hostname
+// and use as Y (vertical) coordinate of nodes
+//
+// You can use both X and Y, or only one of them
+//
+$SORTBY_HOSTNAME = "rack{x}node{y}.mydomain.mytld";
+
+// Should we organize the nodes based on above X,Y coordinates
+// ascending or descending
+//
+$SORT_ORDER = "asc";
+
+// What is the label of X and/or Y
+//
+$SORT_XLABEL = "rack";
+$SORT_YLABEL = "node";
+?>
Index: /branches/1.0/web/addons/job_monarch/conf.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/conf.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/conf.php	(revision 825)
@@ -0,0 +1,105 @@
+<?php
+// Show hosts in a jobview by default?
+//
+$default_showhosts = true;
+
+// Stop displaying archive search results after SEARCH_RESULT_LIMIT
+//
+$SEARCH_RESULT_LIMIT = 20;
+
+// Show the column job attribute 'requested memory'?
+//
+$COLUMN_REQUESTED_MEMORY = false;
+
+// Show the column job attribute 'queued' (since)?
+//
+$COLUMN_QUEUED = true;
+
+// Show the column job attribute 'nodes' hostnames?
+//
+$COLUMN_NODES = true;
+
+// Path to Ganglia's web frontend root
+//
+$GANGLIA_PATH = "/var/www/ganglia/";
+
+// Format of how to display a date and time in human readable format
+//
+$DATETIME_FORMAT = "%a %d %b %Y %H:%M:%S";
+
+// Max size of small clusterimage
+// (250 pixels is same width as Ganglia's pie chart)
+//
+$SMALL_CLUSTERIMAGE_MAXWIDTH = 300;
+
+// The size of a single node in the small clusterimage
+//
+$SMALL_CLUSTERIMAGE_NODEWIDTH = 7;
+
+// Max size of small clusterimage
+// (250 pixels is same width as Ganglia's pie chart)
+//
+$BIG_CLUSTERIMAGE_MAXWIDTH = 500;
+
+// The size of a single node in the small clusterimage
+//
+$BIG_CLUSTERIMAGE_NODEWIDTH = 11;
+
+// Max size of small host image
+//
+$SMALL_HOSTIMAGE_MAXWIDTH = 450;
+
+// How to mark nodes with a job in clusterimage
+//
+$JOB_NODE_MARKING = "J";
+
+// How to mark nodes that are 'down' in the batch
+//
+$NODE_DOWN_MARKING = "X";
+
+// How to mark nodes that are 'offline' in the batch
+//
+$NODE_OFFLINE_MARKING = ".";
+
+// Show empty columns in the ClusterImage?
+//
+$SHOW_EMPTY_COLUMN = false;
+
+// Show empty rows in the ClusterImage?
+//
+$SHOW_EMPTY_ROW = true;
+
+// XML Datasource for Job Monarch
+// by default localhost's gmetad
+// [syntax: <ip>:<port>]
+//
+$DATA_SOURCE = '127.0.0.1:8652';
+
+// Is there a jobarchive?
+//
+$JOB_ARCHIVE = false;
+
+// Path to the job archive rrd files
+//
+$JOB_ARCHIVE_DIR = "/path/to/my/archive";
+
+// Location of the job archive (postgres) database
+// [syntax: <ip>/<dbase>]
+//
+$JOB_ARCHIVE_DBASE = "127.0.0.1/jobarchive";
+
+// Username to connect to the SQL database
+//
+$JOB_ARCHIVE_SQL_USER = "jobarchive";
+
+// Password to connect to the SQL database: change me
+//
+//$JOB_ARCHIVE_SQL_PASSWORD = "";
+
+// Include cluster specific settings here, 
+// they will override any (global) settings above
+// on a per-cluster basis, where available.
+//
+//$CLUSTER_CONFS["Example Cluster"]	= "./clusterconf/example.php";
+//
+?>
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo.compiled.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo.compiled.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo.compiled.php	(revision 825)
@@ -0,0 +1,1 @@
+<?php  define('DWOO_DIRECTORY', dirname(__FILE__) . DIRECTORY_SEPARATOR); class Dwoo_Core { const VERSION = '1.1.1'; const RELEASE_TAG = 17; const CLASS_PLUGIN = 1; const FUNC_PLUGIN = 2; const NATIVE_PLUGIN = 4; const BLOCK_PLUGIN = 8; const COMPILABLE_PLUGIN = 16; const CUSTOM_PLUGIN = 32; const SMARTY_MODIFIER = 64; const SMARTY_BLOCK = 128; const SMARTY_FUNCTION = 256; const PROXY_PLUGIN = 512; const TEMPLATE_PLUGIN = 1024; protected $charset = 'utf-8'; public $globals; protected $compileDir; protected $cacheDir; protected $cacheTime = 0; protected $securityPolicy = null; protected $plugins = array(); protected $filters = array(); protected $resources = array ( 'file' => array ( 'class' => 'Dwoo_Template_File', 'compiler' => null ), 'string' => array ( 'class' => 'Dwoo_Template_String', 'compiler' => null ) ); protected $loader = null; protected $template = null; protected $runtimePlugins; protected $returnData; public $data; public $scope; protected $scopeTree; protected $stack; protected $curBlock; protected $buffer; protected $pluginProxy; public function __construct($compileDir = null, $cacheDir = null) { if ($compileDir !== null) { $this->setCompileDir($compileDir); } if ($cacheDir !== null) { $this->setCacheDir($cacheDir); } $this->initGlobals(); } public function __clone() { $this->template = null; unset($this->data); unset($this->returnData); } public function output($tpl, $data = array(), Dwoo_ICompiler $compiler = null) { return $this->get($tpl, $data, $compiler, true); } public function get($_tpl, $data = array(), $_compiler = null, $_output = false) { if ($this->template instanceof Dwoo_ITemplate) { return clone $this; } if ($_tpl instanceof Dwoo_ITemplate) { } elseif (is_string($_tpl) && file_exists($_tpl)) { $_tpl = new Dwoo_Template_File($_tpl); } else { throw new Dwoo_Exception('Dwoo->get/Dwoo->output\'s first argument must be a Dwoo_ITemplate (i.e. Dwoo_Template_File) or a valid path to a template file', E_USER_NOTICE); } $this->template = $_tpl; if ($data instanceof Dwoo_IDataProvider) { $this->data = $data->getData(); } elseif (is_array($data)) { $this->data = $data; } else { throw new Dwoo_Exception('Dwoo->get/Dwoo->output\'s data argument must be a Dwoo_IDataProvider object (i.e. Dwoo_Data) or an associative array', E_USER_NOTICE); } $this->globals['template'] = $_tpl->getName(); $this->initRuntimeVars($_tpl); $file = $_tpl->getCachedTemplate($this); $doCache = $file === true; $cacheLoaded = is_string($file); if ($cacheLoaded === true) { if ($_output === true) { include $file; $this->template = null; } else { ob_start(); include $file; $this->template = null; return ob_get_clean(); } } else { if ($doCache === true) { $dynamicId = uniqid(); } $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler); $out = include $compiledTemplate; if ($out === false) { $_tpl->forceCompilation(); $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler); $out = include $compiledTemplate; } if ($doCache === true) { $out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*'.$dynamicId.'*/ echo \'$1\'; ?>', $out); if (!class_exists('Dwoo_plugin_dynamic', false)) { $this->getLoader()->loadPlugin('dynamic'); } $out = Dwoo_Plugin_dynamic::unescape($out, $dynamicId, $compiledTemplate); } foreach ($this->filters as $filter) { if (is_array($filter) && $filter[0] instanceof Dwoo_Filter) { $out = call_user_func($filter, $out); } else { $out = call_user_func($filter, $this, $out); } } if ($doCache === true) { $file = $_tpl->cache($this, $out); if ($_output === true) { include $file; $this->template = null; } else { ob_start(); include $file; $this->template = null; return ob_get_clean(); } } else { $this->template = null; if ($_output === true) { echo $out; } return $out; } } } protected function initGlobals() { $this->globals = array ( 'version' => self::VERSION, 'ad' => '<a href="http://dwoo.org/">Powered by Dwoo</a>', 'now' => $_SERVER['REQUEST_TIME'], 'charset' => $this->charset, ); } protected function initRuntimeVars(Dwoo_ITemplate $tpl) { $this->runtimePlugins = array(); $this->scope =& $this->data; $this->scopeTree = array(); $this->stack = array(); $this->curBlock = null; $this->buffer = ''; $this->returnData = array(); } public function addPlugin($name, $callback, $compilable = false) { $compilable = $compilable ? self::COMPILABLE_PLUGIN : 0; if (is_array($callback)) { if (is_subclass_of(is_object($callback[0]) ? get_class($callback[0]) : $callback[0], 'Dwoo_Block_Plugin')) { $this->plugins[$name] = array('type'=>self::BLOCK_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>(is_object($callback[0]) ? get_class($callback[0]) : $callback[0])); } else { $this->plugins[$name] = array('type'=>self::CLASS_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>(is_object($callback[0]) ? get_class($callback[0]) : $callback[0]), 'function'=>$callback[1]); } } elseif (class_exists($callback, false)) { if (is_subclass_of($callback, 'Dwoo_Block_Plugin')) { $this->plugins[$name] = array('type'=>self::BLOCK_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>$callback); } else { $this->plugins[$name] = array('type'=>self::CLASS_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>$callback, 'function'=>'process'); } } elseif (function_exists($callback)) { $this->plugins[$name] = array('type'=>self::FUNC_PLUGIN | $compilable, 'callback'=>$callback); } else { throw new Dwoo_Exception('Callback could not be processed correctly, please check that the function/class you used exists'); } } public function removePlugin($name) { if (isset($this->plugins[$name])) { unset($this->plugins[$name]); } } public function addFilter($callback, $autoload = false) { if ($autoload) { $class = 'Dwoo_Filter_'.$callback; if (!class_exists($class, false) && !function_exists($class)) { try { $this->getLoader()->loadPlugin($callback); } catch (Dwoo_Exception $e) { if (strstr($callback, 'Dwoo_Filter_')) { throw new Dwoo_Exception('Wrong filter name : '.$callback.', the "Dwoo_Filter_" prefix should not be used, please only use "'.str_replace('Dwoo_Filter_', '', $callback).'"'); } else { throw new Dwoo_Exception('Wrong filter name : '.$callback.', when using autoload the filter must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Filter_name"'); } } } if (class_exists($class, false)) { $callback = array(new $class($this), 'process'); } elseif (function_exists($class)) { $callback = $class; } else { throw new Dwoo_Exception('Wrong filter name : '.$callback.', when using autoload the filter must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Filter_name"'); } $this->filters[] = $callback; } else { $this->filters[] = $callback; } } public function removeFilter($callback) { if (($index = array_search('Dwoo_Filter_'.$callback, $this->filters, true)) !== false) { unset($this->filters[$index]); } elseif (($index = array_search($callback, $this->filters, true)) !== false) { unset($this->filters[$index]); } else { $class = 'Dwoo_Filter_' . $callback; foreach ($this->filters as $index=>$filter) { if (is_array($filter) && $filter[0] instanceof $class) { unset($this->filters[$index]); break; } } } } public function addResource($name, $class, $compilerFactory = null) { if (strlen($name) < 2) { throw new Dwoo_Exception('Resource names must be at least two-character long to avoid conflicts with Windows paths'); } if (!class_exists($class)) { throw new Dwoo_Exception('Resource class does not exist'); } $interfaces = class_implements($class); if (in_array('Dwoo_ITemplate', $interfaces) === false) { throw new Dwoo_Exception('Resource class must implement Dwoo_ITemplate'); } $this->resources[$name] = array('class'=>$class, 'compiler'=>$compilerFactory); } public function removeResource($name) { unset($this->resources[$name]); if ($name==='file') { $this->resources['file'] = array('class'=>'Dwoo_Template_File', 'compiler'=>null); } } public function setLoader(Dwoo_ILoader $loader) { $this->loader = $loader; } public function getLoader() { if ($this->loader === null) { $this->loader = new Dwoo_Loader($this->getCompileDir()); } return $this->loader; } public function getCustomPlugins() { return $this->plugins; } public function getCacheDir() { if ($this->cacheDir === null) { $this->setCacheDir(DWOO_DIRECTORY.'cache'.DIRECTORY_SEPARATOR); } return $this->cacheDir; } public function setCacheDir($dir) { $this->cacheDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR; if (is_writable($this->cacheDir) === false) { throw new Dwoo_Exception('The cache directory must be writable, chmod "'.$this->cacheDir.'" to make it writable'); } } public function getCompileDir() { if ($this->compileDir === null) { $this->setCompileDir(DWOO_DIRECTORY.'compiled'.DIRECTORY_SEPARATOR); } return $this->compileDir; } public function setCompileDir($dir) { $this->compileDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR; if (is_writable($this->compileDir) === false) { throw new Dwoo_Exception('The compile directory must be writable, chmod "'.$this->compileDir.'" to make it writable'); } } public function getCacheTime() { return $this->cacheTime; } public function setCacheTime($seconds) { $this->cacheTime = (int) $seconds; } public function getCharset() { return $this->charset; } public function setCharset($charset) { $this->charset = strtolower((string) $charset); } public function getTemplate() { return $this->template; } public function setTemplate(Dwoo_ITemplate $tpl) { $this->template = $tpl; } public function setDefaultCompilerFactory($resourceName, $compilerFactory) { $this->resources[$resourceName]['compiler'] = $compilerFactory; } public function getDefaultCompilerFactory($resourceName) { return $this->resources[$resourceName]['compiler']; } public function setSecurityPolicy(Dwoo_Security_Policy $policy = null) { $this->securityPolicy = $policy; } public function getSecurityPolicy() { return $this->securityPolicy; } public function setPluginProxy(Dwoo_IPluginProxy $pluginProxy) { $this->pluginProxy = $pluginProxy; } public function getPluginProxy() { return $this->pluginProxy; } public function isCached(Dwoo_ITemplate $tpl) { return is_string($tpl->getCachedTemplate($this)); } public function clearCache($olderThan=-1) { $cacheDirs = new RecursiveDirectoryIterator($this->getCacheDir()); $cache = new RecursiveIteratorIterator($cacheDirs); $expired = time() - $olderThan; $count = 0; foreach ($cache as $file) { if ($cache->isDot() || $cache->isDir() || substr($file, -5) !== '.html') { continue; } if ($cache->getCTime() < $expired) { $count += unlink((string) $file) ? 1 : 0; } } return $count; } public function templateFactory($resourceName, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null) { if (isset($this->resources[$resourceName])) { return call_user_func(array($this->resources[$resourceName]['class'], 'templateFactory'), $this, $resourceId, $cacheTime, $cacheId, $compileId, $parentTemplate); } else { throw new Dwoo_Exception('Unknown resource type : '.$resourceName); } } public function isArray($value, $checkIsEmpty=false) { if (is_array($value) === true || $value instanceof ArrayAccess) { if ($checkIsEmpty === false) { return true; } else { return $this->count($value); } } } public function isTraversable($value, $checkIsEmpty=false) { if (is_array($value) === true) { if ($checkIsEmpty === false) { return true; } else { return count($value) > 0; } } elseif ($value instanceof Traversable) { if ($checkIsEmpty === false) { return true; } else { return $this->count($value); } } return false; } public function count($value) { if (is_array($value) === true || $value instanceof Countable) { return count($value); } elseif ($value instanceof ArrayAccess) { if ($value->offsetExists(0)) { return true; } } elseif ($value instanceof Iterator) { $value->rewind(); if ($value->valid()) { return true; } } elseif ($value instanceof Traversable) { foreach ($value as $dummy) { return true; } } return 0; } public function triggerError($message, $level=E_USER_NOTICE) { if (!($tplIdentifier = $this->template->getResourceIdentifier())) { $tplIdentifier = $this->template->getResourceName(); } trigger_error('Dwoo error (in '.$tplIdentifier.') : '.$message, $level); } public function addStack($blockName, array $args=array()) { if (isset($this->plugins[$blockName])) { $class = $this->plugins[$blockName]['class']; } else { $class = 'Dwoo_Plugin_'.$blockName; } if ($this->curBlock !== null) { $this->curBlock->buffer(ob_get_contents()); ob_clean(); } else { $this->buffer .= ob_get_contents(); ob_clean(); } $block = new $class($this); $cnt = count($args); if ($cnt===0) { $block->init(); } elseif ($cnt===1) { $block->init($args[0]); } elseif ($cnt===2) { $block->init($args[0], $args[1]); } elseif ($cnt===3) { $block->init($args[0], $args[1], $args[2]); } elseif ($cnt===4) { $block->init($args[0], $args[1], $args[2], $args[3]); } else { call_user_func_array(array($block,'init'), $args); } $this->stack[] = $this->curBlock = $block; return $block; } public function delStack() { $args = func_get_args(); $this->curBlock->buffer(ob_get_contents()); ob_clean(); $cnt = count($args); if ($cnt===0) { $this->curBlock->end(); } elseif ($cnt===1) { $this->curBlock->end($args[0]); } elseif ($cnt===2) { $this->curBlock->end($args[0], $args[1]); } elseif ($cnt===3) { $this->curBlock->end($args[0], $args[1], $args[2]); } elseif ($cnt===4) { $this->curBlock->end($args[0], $args[1], $args[2], $args[3]); } else { call_user_func_array(array($this->curBlock, 'end'), $args); } $tmp = array_pop($this->stack); if (count($this->stack) > 0) { $this->curBlock = end($this->stack); $this->curBlock->buffer($tmp->process()); } else { $this->curBlock = null; echo $tmp->process(); } unset($tmp); } public function getParentBlock(Dwoo_Block_Plugin $block) { $index = array_search($block, $this->stack, true); if ($index !== false && $index > 0) { return $this->stack[$index-1]; } return false; } public function findBlock($type) { if (isset($this->plugins[$type])) { $type = $this->plugins[$type]['class']; } else { $type = 'Dwoo_Plugin_'.str_replace('Dwoo_Plugin_', '', $type); } $keys = array_keys($this->stack); while (($key = array_pop($keys)) !== false) { if ($this->stack[$key] instanceof $type) { return $this->stack[$key]; } } return false; } public function getObjectPlugin($class) { if (isset($this->runtimePlugins[$class])) { return $this->runtimePlugins[$class]; } return $this->runtimePlugins[$class] = new $class($this); } public function classCall($plugName, array $params = array()) { $class = 'Dwoo_Plugin_'.$plugName; $plugin = $this->getObjectPlugin($class); $cnt = count($params); if ($cnt===0) { return $plugin->process(); } elseif ($cnt===1) { return $plugin->process($params[0]); } elseif ($cnt===2) { return $plugin->process($params[0], $params[1]); } elseif ($cnt===3) { return $plugin->process($params[0], $params[1], $params[2]); } elseif ($cnt===4) { return $plugin->process($params[0], $params[1], $params[2], $params[3]); } else { return call_user_func_array(array($plugin, 'process'), $params); } } public function arrayMap($callback, array $params) { if ($params[0] === $this) { $addThis = true; array_shift($params); } if ((is_array($params[0]) || ($params[0] instanceof Iterator && $params[0] instanceof ArrayAccess))) { if (empty($params[0])) { return $params[0]; } $out = array(); $cnt = count($params); if (isset($addThis)) { array_unshift($params, $this); $items = $params[1]; $keys = array_keys($items); if (is_string($callback) === false) { while (($i = array_shift($keys)) !== null) { $out[] = call_user_func_array($callback, array(1=>$items[$i]) + $params); } } elseif ($cnt===1) { while (($i = array_shift($keys)) !== null) { $out[] = $callback($this, $items[$i]); } } elseif ($cnt===2) { while (($i = array_shift($keys)) !== null) { $out[] = $callback($this, $items[$i], $params[2]); } } elseif ($cnt===3) { while (($i = array_shift($keys)) !== null) { $out[] = $callback($this, $items[$i], $params[2], $params[3]); } } else { while (($i = array_shift($keys)) !== null) { $out[] = call_user_func_array($callback, array(1=>$items[$i]) + $params); } } } else { $items = $params[0]; $keys = array_keys($items); if (is_string($callback) === false) { while (($i = array_shift($keys)) !== null) { $out[] = call_user_func_array($callback, array($items[$i]) + $params); } } elseif ($cnt===1) { while (($i = array_shift($keys)) !== null) { $out[] = $callback($items[$i]); } } elseif ($cnt===2) { while (($i = array_shift($keys)) !== null) { $out[] = $callback($items[$i], $params[1]); } } elseif ($cnt===3) { while (($i = array_shift($keys)) !== null) { $out[] = $callback($items[$i], $params[1], $params[2]); } } elseif ($cnt===4) { while (($i = array_shift($keys)) !== null) { $out[] = $callback($items[$i], $params[1], $params[2], $params[3]); } } else { while (($i = array_shift($keys)) !== null) { $out[] = call_user_func_array($callback, array($items[$i]) + $params); } } } return $out; } else { return $params[0]; } } public function readVarInto($varstr, $data, $safeRead = false) { if ($data === null) { return null; } if (is_array($varstr) === false) { preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m); } else { $m = $varstr; } unset($varstr); while (list($k, $sep) = each($m[1])) { if ($sep === '.' || $sep === '[' || $sep === '') { if ((is_array($data) || $data instanceof ArrayAccess) && ($safeRead === false || isset($data[$m[2][$k]]))) { $data = $data[$m[2][$k]]; } else { return null; } } else { if (is_object($data) && ($safeRead === false || isset($data->$m[2][$k]) || is_callable(array($data, '__get')))) { $data = $data->$m[2][$k]; } else { return null; } } } return $data; } public function readParentVar($parentLevels, $varstr = null) { $tree = $this->scopeTree; $cur = $this->data; while ($parentLevels--!==0) { array_pop($tree); } while (($i = array_shift($tree)) !== null) { if (is_object($cur)) { $cur = $cur->$i; } else { $cur = $cur[$i]; } } if ($varstr!==null) { return $this->readVarInto($varstr, $cur); } else { return $cur; } } public function readVar($varstr) { if (is_array($varstr)===true) { $m = $varstr; unset($varstr); } else { if (strstr($varstr, '.') === false && strstr($varstr, '[') === false && strstr($varstr, '->') === false) { if ($varstr === 'dwoo') { return $this->globals; } elseif ($varstr === '__' || $varstr === '_root' ) { return $this->data; $varstr = substr($varstr, 6); } elseif ($varstr === '_' || $varstr === '_parent') { $varstr = '.'.$varstr; $tree = $this->scopeTree; $cur = $this->data; array_pop($tree); while (($i = array_shift($tree)) !== null) { if (is_object($cur)) { $cur = $cur->$i; } else { $cur = $cur[$i]; } } return $cur; } $cur = $this->scope; if (isset($cur[$varstr])) { return $cur[$varstr]; } else { return null; } } if (substr($varstr, 0, 1) === '.') { $varstr = 'dwoo'.$varstr; } preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m); } $i = $m[2][0]; if ($i === 'dwoo') { $cur = $this->globals; array_shift($m[2]); array_shift($m[1]); switch ($m[2][0]) { case 'get': $cur = $_GET; break; case 'post': $cur = $_POST; break; case 'session': $cur = $_SESSION; break; case 'cookies': case 'cookie': $cur = $_COOKIE; break; case 'server': $cur = $_SERVER; break; case 'env': $cur = $_ENV; break; case 'request': $cur = $_REQUEST; break; case 'const': array_shift($m[2]); if (defined($m[2][0])) { return constant($m[2][0]); } else { return null; } } if ($cur !== $this->globals) { array_shift($m[2]); array_shift($m[1]); } } elseif ($i === '__' || $i === '_root') { $cur = $this->data; array_shift($m[2]); array_shift($m[1]); } elseif ($i === '_' || $i === '_parent') { $tree = $this->scopeTree; $cur = $this->data; while (true) { array_pop($tree); array_shift($m[2]); array_shift($m[1]); if (current($m[2]) === '_' || current($m[2]) === '_parent') { continue; } while (($i = array_shift($tree)) !== null) { if (is_object($cur)) { $cur = $cur->$i; } else { $cur = $cur[$i]; } } break; } } else { $cur = $this->scope; } while (list($k, $sep) = each($m[1])) { if ($sep === '.' || $sep === '[' || $sep === '') { if ((is_array($cur) || $cur instanceof ArrayAccess) && isset($cur[$m[2][$k]])) { $cur = $cur[$m[2][$k]]; } else { return null; } } elseif ($sep === '->') { if (is_object($cur)) { $cur = $cur->$m[2][$k]; } else { return null; } } else { return null; } } return $cur; } public function assignInScope($value, $scope) { $tree =& $this->scopeTree; $data =& $this->data; if (!is_string($scope)) { return $this->triggerError('Assignments must be done into strings, ('.gettype($scope).') '.var_export($scope, true).' given', E_USER_ERROR); } if (strstr($scope, '.') === false && strstr($scope, '->') === false) { $this->scope[$scope] = $value; } else { preg_match_all('#(\[|->|\.)?([^.[\]-]+)\]?#i', $scope, $m); $cur =& $this->scope; $last = array(array_pop($m[1]), array_pop($m[2])); while (list($k, $sep) = each($m[1])) { if ($sep === '.' || $sep === '[' || $sep === '') { if (is_array($cur) === false) { $cur = array(); } $cur =& $cur[$m[2][$k]]; } elseif ($sep === '->') { if (is_object($cur) === false) { $cur = new stdClass; } $cur =& $cur->$m[2][$k]; } else { return false; } } if ($last[0] === '.' || $last[0] === '[' || $last[0] === '') { if (is_array($cur) === false) { $cur = array(); } $cur[$last[1]] = $value; } elseif ($last[0] === '->') { if (is_object($cur) === false) { $cur = new stdClass; } $cur->$last[1] = $value; } else { return false; } } } public function setScope($scope, $absolute = false) { $old = $this->scopeTree; if (is_string($scope)===true) { $scope = explode('.', $scope); } if ($absolute===true) { $this->scope =& $this->data; $this->scopeTree = array(); } while (($bit = array_shift($scope)) !== null) { if ($bit === '_' || $bit === '_parent') { array_pop($this->scopeTree); $this->scope =& $this->data; $cnt = count($this->scopeTree); for ($i=0;$i<$cnt;$i++) $this->scope =& $this->scope[$this->scopeTree[$i]]; } elseif ($bit === '__' || $bit === '_root') { $this->scope =& $this->data; $this->scopeTree = array(); } elseif (isset($this->scope[$bit])) { $this->scope =& $this->scope[$bit]; $this->scopeTree[] = $bit; } else { unset($this->scope); $this->scope = null; } } return $old; } public function getData() { return $this->data; } public function setReturnValue($name, $value) { $this->returnData[$name] = $value; } public function getReturnValues() { return $this->returnData; } public function &getScope() { return $this->scope; } public function __call($method, $args) { $proxy = $this->getPluginProxy(); if (!$proxy) { throw new Dwoo_Exception('Call to undefined method '.__CLASS__.'::'.$method.'()'); } return call_user_func_array($proxy->getCallback($method), $args); } } class Dwoo extends Dwoo_Core { } interface Dwoo_IPluginProxy { public function handles($name); public function getCode($name, $params); public function getCallback($name); public function getLoader($name); } interface Dwoo_IElseable { } interface Dwoo_ILoader { public function loadPlugin($class, $forceRehash = true); } class Dwoo_Loader implements Dwoo_ILoader { protected $paths = array(); protected $classPath = array(); protected $cacheDir; protected $corePluginDir; public function __construct($cacheDir) { $this->corePluginDir = DWOO_DIRECTORY . 'plugins'; $this->cacheDir = rtrim($cacheDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; $cacheFile = $this->cacheDir.'classpath.cache.d'.Dwoo_Core::RELEASE_TAG.'.php'; if (file_exists($cacheFile)) { $classpath = file_get_contents($cacheFile); $this->classPath = unserialize($classpath) + $this->classPath; } else { $this->rebuildClassPathCache($this->corePluginDir, $cacheFile); } } protected function rebuildClassPathCache($path, $cacheFile) { if ($cacheFile!==false) { $tmp = $this->classPath; $this->classPath = array(); } $list = glob(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*'); if (is_array($list)) { foreach ($list as $f) { if (is_dir($f)) { $this->rebuildClassPathCache($f, false); } else { $this->classPath[str_replace(array('function.','block.','modifier.','outputfilter.','filter.','prefilter.','postfilter.','pre.','post.','output.','shared.','helper.'), '', basename($f, '.php'))] = $f; } } } if ($cacheFile!==false) { if (!file_put_contents($cacheFile, serialize($this->classPath))) { throw new Dwoo_Exception('Could not write into '.$cacheFile.', either because the folder is not there (create it) or because of the chmod configuration (please ensure this directory is writable by php), alternatively you can change the directory used with $dwoo->setCompileDir() or provide a custom loader object with $dwoo->setLoader()'); } $this->classPath += $tmp; } } public function loadPlugin($class, $forceRehash = true) { if (!isset($this->classPath[$class]) || !(include $this->classPath[$class])) { if ($forceRehash) { $this->rebuildClassPathCache($this->corePluginDir, $this->cacheDir . 'classpath.cache.d'.Dwoo_Core::RELEASE_TAG.'.php'); foreach ($this->paths as $path=>$file) { $this->rebuildClassPathCache($path, $file); } if (isset($this->classPath[$class])) { include $this->classPath[$class]; } else { throw new Dwoo_Exception('Plugin <em>'.$class.'</em> can not be found, maybe you forgot to bind it if it\'s a custom plugin ?', E_USER_NOTICE); } } else { throw new Dwoo_Exception('Plugin <em>'.$class.'</em> can not be found, maybe you forgot to bind it if it\'s a custom plugin ?', E_USER_NOTICE); } } } public function addDirectory($pluginDirectory) { $pluginDir = realpath($pluginDirectory); if (!$pluginDir) { throw new Dwoo_Exception('Plugin directory does not exist or can not be read : '.$pluginDirectory); } $cacheFile = $this->cacheDir . 'classpath-'.substr(strtr($pluginDir, '/\\:'.PATH_SEPARATOR, '----'), strlen($pluginDir) > 80 ? -80 : 0).'.d'.Dwoo_Core::RELEASE_TAG.'.php'; $this->paths[$pluginDir] = $cacheFile; if (file_exists($cacheFile)) { $classpath = file_get_contents($cacheFile); $this->classPath = unserialize($classpath) + $this->classPath; } else { $this->rebuildClassPathCache($pluginDir, $cacheFile); } } } class Dwoo_Exception extends Exception { } class Dwoo_Security_Policy { const PHP_ENCODE = 1; const PHP_REMOVE = 2; const PHP_ALLOW = 3; const CONST_DISALLOW = false; const CONST_ALLOW = true; protected $allowedPhpFunctions = array ( 'str_repeat' => true, 'number_format' => true, 'htmlentities' => true, 'htmlspecialchars' => true, 'long2ip' => true, 'strlen' => true, 'list' => true, 'empty' => true, 'count' => true, 'sizeof' => true, 'in_array' => true, 'is_array' => true, ); protected $allowedMethods = array(); protected $allowedDirectories = array(); protected $phpHandling = self::PHP_REMOVE; protected $constHandling = self::CONST_DISALLOW; public function allowPhpFunction($func) { if (is_array($func)) foreach ($func as $fname) $this->allowedPhpFunctions[strtolower($fname)] = true; else $this->allowedPhpFunctions[strtolower($func)] = true; } public function disallowPhpFunction($func) { if (is_array($func)) foreach ($func as $fname) unset($this->allowedPhpFunctions[strtolower($fname)]); else unset($this->allowedPhpFunctions[strtolower($func)]); } public function getAllowedPhpFunctions() { return $this->allowedPhpFunctions; } public function allowMethod($class, $method = null) { if (is_array($class)) foreach ($class as $elem) $this->allowedMethods[strtolower($elem[0])][strtolower($elem[1])] = true; else $this->allowedMethods[strtolower($class)][strtolower($method)] = true; } public function disallowMethod($class, $method = null) { if (is_array($class)) foreach ($class as $elem) unset($this->allowedMethods[strtolower($elem[0])][strtolower($elem[1])]); else unset($this->allowedMethods[strtolower($class)][strtolower($method)]); } public function getAllowedMethods() { return $this->allowedMethods; } public function allowDirectory($path) { if (is_array($path)) foreach ($path as $dir) $this->allowedDirectories[realpath($dir)] = true; else $this->allowedDirectories[realpath($path)] = true; } public function disallowDirectory($path) { if (is_array($path)) foreach ($path as $dir) unset($this->allowedDirectories[realpath($dir)]); else unset($this->allowedDirectories[realpath($path)]); } public function getAllowedDirectories() { return $this->allowedDirectories; } public function setPhpHandling($level = self::PHP_REMOVE) { $this->phpHandling = $level; } public function getPhpHandling() { return $this->phpHandling; } public function setConstantHandling($level = self::CONST_DISALLOW) { $this->constHandling = $level; } public function getConstantHandling() { return $this->constHandling; } public function callMethod(Dwoo_Core $dwoo, $obj, $method, $args) { foreach ($this->allowedMethods as $class => $methods) { if (!isset($methods[$method])) { continue; } if ($obj instanceof $class) { return call_user_func_array(array($obj, $method), $args); } } $dwoo->triggerError('The current security policy prevents you from calling '.get_class($obj).'::'.$method.'()'); return null; } public function isMethodAllowed($class, $method = null) { if (is_array($class)) { list($class, $method) = $class; } foreach ($this->allowedMethods as $allowedClass => $methods) { if (!isset($methods[$method])) { continue; } if ($class === $allowedClass || is_subclass_of($class, $allowedClass)) { return true; } } return false; } } class Dwoo_Security_Exception extends Dwoo_Exception { } interface Dwoo_ICompilable { } interface Dwoo_ICompiler { public function compile(Dwoo_Core $dwoo, Dwoo_ITemplate $template); public function setCustomPlugins(array $customPlugins); public function setSecurityPolicy(Dwoo_Security_Policy $policy = null); } interface Dwoo_IDataProvider { public function getData(); } interface Dwoo_ITemplate { public function getCacheTime(); public function setCacheTime($seconds = null); public function getCachedTemplate(Dwoo_Core $dwoo); public function cache(Dwoo_Core $dwoo, $output); public function clearCache(Dwoo_Core $dwoo, $olderThan = -1); public function getCompiledTemplate(Dwoo_Core $dwoo, Dwoo_ICompiler $compiler = null); public function getName(); public function getResourceName(); public function getResourceIdentifier(); public function getSource(); public function getUid(); public function getCompiler(); public function getIsModifiedCode(); public static function templateFactory(Dwoo_Core $dwoo, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null); } interface Dwoo_ICompilable_Block { } abstract class Dwoo_Plugin { protected $dwoo; public function __construct(Dwoo_Core $dwoo) { $this->dwoo = $dwoo; } public static function paramsToAttributes(array $params, $delim = '\'') { if (isset($params['*'])) { $params = array_merge($params, $params['*']); unset($params['*']); } $out = ''; foreach ($params as $attr=>$val) { $out .= ' '.$attr.'='; if (trim($val, '"\'')=='' || $val=='null') { $out .= str_replace($delim, '\\'.$delim, '""'); } elseif (substr($val, 0, 1) === $delim && substr($val, -1) === $delim) { $out .= str_replace($delim, '\\'.$delim, '"'.substr($val, 1, -1).'"'); } else { $out .= str_replace($delim, '\\'.$delim, '"') . $delim . '.'.$val.'.' . $delim . str_replace($delim, '\\'.$delim, '"'); } } return ltrim($out); } } abstract class Dwoo_Block_Plugin extends Dwoo_Plugin { protected $buffer = ''; public function buffer($input) { $this->buffer .= $input; } public function end() { } public function process() { return $this->buffer; } public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type) { return Dwoo_Compiler::PHP_OPEN.$prepend.'$this->addStack("'.$type.'", array('.Dwoo_Compiler::implode_r($compiler->getCompiledParams($params)).'));'.$append.Dwoo_Compiler::PHP_CLOSE; } public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content) { return $content . Dwoo_Compiler::PHP_OPEN.$prepend.'$this->delStack();'.$append.Dwoo_Compiler::PHP_CLOSE; } } abstract class Dwoo_Filter { protected $dwoo; public function __construct(Dwoo_Core $dwoo) { $this->dwoo = $dwoo; } abstract public function process($input); } abstract class Dwoo_Processor { protected $compiler; public function __construct(Dwoo_Compiler $compiler) { $this->compiler = $compiler; } abstract public function process($input); } class Dwoo_Template_String implements Dwoo_ITemplate { protected $name; protected $compileId; protected $cacheId; protected $cacheTime; protected $compilationEnforced; protected static $cache = array('cached'=>array(), 'compiled'=>array()); protected $compiler; protected $chmod = 0777; public function __construct($templateString, $cacheTime = null, $cacheId = null, $compileId = null) { $this->template = $templateString; if (function_exists('hash')) { $this->name = hash('md4', $templateString); } else { $this->name = md5($templateString); } $this->cacheTime = $cacheTime; if ($compileId !== null) { $this->compileId = str_replace('../', '__', strtr($compileId, '\\%?=!:;'.PATH_SEPARATOR, '/-------')); } if ($cacheId !== null) { $this->cacheId = str_replace('../', '__', strtr($cacheId, '\\%?=!:;'.PATH_SEPARATOR, '/-------')); } } public function getCacheTime() { return $this->cacheTime; } public function setCacheTime($seconds = null) { $this->cacheTime = $seconds; } public function getChmod() { return $this->chmod; } public function setChmod($mask = null) { $this->chmod = $mask; } public function getName() { return $this->name; } public function getResourceName() { return 'string'; } public function getResourceIdentifier() { return false; } public function getSource() { return $this->template; } public function getUid() { return $this->name; } public function getCompiler() { return $this->compiler; } public function forceCompilation() { $this->compilationEnforced = true; } public function getCachedTemplate(Dwoo_Core $dwoo) { if ($this->cacheTime !== null) { $cacheLength = $this->cacheTime; } else { $cacheLength = $dwoo->getCacheTime(); } if ($cacheLength === 0) { return false; } $cachedFile = $this->getCacheFilename($dwoo); if (isset(self::$cache['cached'][$this->cacheId]) === true && file_exists($cachedFile)) { return $cachedFile; } elseif ($this->compilationEnforced !== true && file_exists($cachedFile) && ($cacheLength === -1 || filemtime($cachedFile) > ($_SERVER['REQUEST_TIME'] - $cacheLength)) && $this->isValidCompiledFile($this->getCompiledFilename($dwoo))) { self::$cache['cached'][$this->cacheId] = true; return $cachedFile; } else { return true; } } public function cache(Dwoo_Core $dwoo, $output) { $cacheDir = $dwoo->getCacheDir(); $cachedFile = $this->getCacheFilename($dwoo); $temp = tempnam($cacheDir, 'temp'); if (!($file = @fopen($temp, 'wb'))) { $temp = $cacheDir . uniqid('temp'); if (!($file = @fopen($temp, 'wb'))) { trigger_error('Error writing temporary file \''.$temp.'\'', E_USER_WARNING); return false; } } fwrite($file, $output); fclose($file); $this->makeDirectory(dirname($cachedFile), $cacheDir); if (!@rename($temp, $cachedFile)) { @unlink($cachedFile); @rename($temp, $cachedFile); } if ($this->chmod !== null) { chmod($cachedFile, $this->chmod); } self::$cache['cached'][$this->cacheId] = true; return $cachedFile; } public function clearCache(Dwoo_Core $dwoo, $olderThan = -1) { $cachedFile = $this->getCacheFilename($dwoo); return !file_exists($cachedFile) || (filectime($cachedFile) < (time() - $olderThan) && unlink($cachedFile)); } public function getCompiledTemplate(Dwoo_Core $dwoo, Dwoo_ICompiler $compiler = null) { $compiledFile = $this->getCompiledFilename($dwoo); if ($this->compilationEnforced !== true && isset(self::$cache['compiled'][$this->compileId]) === true) { } elseif ($this->compilationEnforced !== true && $this->isValidCompiledFile($compiledFile)) { self::$cache['compiled'][$this->compileId] = true; } else { $this->compilationEnforced = false; if ($compiler === null) { $compiler = $dwoo->getDefaultCompilerFactory($this->getResourceName()); if ($compiler === null || $compiler === array('Dwoo_Compiler', 'compilerFactory')) { if (class_exists('Dwoo_Compiler', false) === false) { include DWOO_DIRECTORY . 'Dwoo/Compiler.php'; } $compiler = Dwoo_Compiler::compilerFactory(); } else { $compiler = call_user_func($compiler); } } $this->compiler = $compiler; $compiler->setCustomPlugins($dwoo->getCustomPlugins()); $compiler->setSecurityPolicy($dwoo->getSecurityPolicy()); $this->makeDirectory(dirname($compiledFile), $dwoo->getCompileDir()); file_put_contents($compiledFile, $compiler->compile($dwoo, $this)); if ($this->chmod !== null) { chmod($compiledFile, $this->chmod); } self::$cache['compiled'][$this->compileId] = true; } return $compiledFile; } protected function isValidCompiledFile($file) { return file_exists($file); } public static function templateFactory(Dwoo_Core $dwoo, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null) { return new self($resourceId, $cacheTime, $cacheId, $compileId); } protected function getCompiledFilename(Dwoo_Core $dwoo) { if ($this->compileId===null) { $this->compileId = $this->name; } return $dwoo->getCompileDir() . $this->compileId.'.d'.Dwoo_Core::RELEASE_TAG.'.php'; } protected function getCacheFilename(Dwoo_Core $dwoo) { if ($this->cacheId === null) { if (isset($_SERVER['REQUEST_URI']) === true) { $cacheId = $_SERVER['REQUEST_URI']; } elseif (isset($_SERVER['SCRIPT_FILENAME']) && isset($_SERVER['argv'])) { $cacheId = $_SERVER['SCRIPT_FILENAME'].'-'.implode('-', $_SERVER['argv']); } else { $cacheId = ''; } $this->getCompiledFilename($dwoo); $this->cacheId = str_replace('../', '__', $this->compileId . strtr($cacheId, '\\%?=!:;'.PATH_SEPARATOR, '/-------')); } return $dwoo->getCacheDir() . $this->cacheId.'.html'; } public function getIsModifiedCode() { return null; } protected function makeDirectory($path, $baseDir = null) { if (is_dir($path) === true) { return; } if ($this->chmod === null) { $chmod = 0777; } else { $chmod = $this->chmod; } mkdir($path, $chmod, true); if (strpos(PHP_OS, 'WIN') !== 0 && $baseDir !== null) { $path = strtr(str_replace($baseDir, '', $path), '\\', '/'); $folders = explode('/', trim($path, '/')); foreach ($folders as $folder) { $baseDir .= $folder . DIRECTORY_SEPARATOR; chmod($baseDir, $chmod); } } } } class Dwoo_Template_File extends Dwoo_Template_String { protected $file; protected $includePath = null; protected $resolvedPath = null; public function __construct($file, $cacheTime = null, $cacheId = null, $compileId = null, $includePath = null) { $this->file = $file; $this->name = basename($file); $this->cacheTime = $cacheTime; if ($compileId !== null) { $this->compileId = str_replace('../', '__', strtr($compileId, '\\%?=!:;'.PATH_SEPARATOR, '/-------')); } if ($cacheId !== null) { $this->cacheId = str_replace('../', '__', strtr($cacheId, '\\%?=!:;'.PATH_SEPARATOR, '/-------')); } if (is_string($includePath)) { $this->includePath = array($includePath); } elseif (is_array($includePath)) { $this->includePath = $includePath; } } public function setIncludePath($paths) { if (is_array($paths) === false) { $paths = array($paths); } $this->includePath = $paths; $this->resolvedPath = null; } public function getIncludePath() { return $this->includePath; } protected function isValidCompiledFile($file) { return parent::isValidCompiledFile($file) && (int)$this->getUid() <= filemtime($file); } public function getSource() { return file_get_contents($this->getResourceIdentifier()); } public function getResourceName() { return 'file'; } public function getResourceIdentifier() { if ($this->resolvedPath !== null) { return $this->resolvedPath; } elseif ($this->includePath === null) { return $this->file; } else { foreach ($this->includePath as $path) { $path = rtrim($path, DIRECTORY_SEPARATOR); if (file_exists($path.DIRECTORY_SEPARATOR.$this->file) === true) { $this->resolvedPath = $path . DIRECTORY_SEPARATOR . $this->file; return $this->resolvedPath; } } throw new Dwoo_Exception('Template "'.$this->file.'" could not be found in any of your include path(s)'); } } public function getUid() { return (string) filemtime($this->getResourceIdentifier()); } public static function templateFactory(Dwoo_Core $dwoo, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null) { if (DIRECTORY_SEPARATOR === '\\') { $resourceId = str_replace(array("\t", "\n", "\r", "\f", "\v"), array('\\t', '\\n', '\\r', '\\f', '\\v'), $resourceId); } $resourceId = strtr($resourceId, '\\', '/'); $includePath = null; if (file_exists($resourceId) === false) { if ($parentTemplate === null) { $parentTemplate = $dwoo->getTemplate(); } if ($parentTemplate instanceof Dwoo_Template_File) { if ($includePath = $parentTemplate->getIncludePath()) { if (strstr($resourceId, '../')) { throw new Dwoo_Exception('When using an include path you can not reference a template into a parent directory (using ../)'); } } else { $resourceId = dirname($parentTemplate->getResourceIdentifier()).DIRECTORY_SEPARATOR.$resourceId; if (file_exists($resourceId) === false) { return null; } } } else { return null; } } if ($policy = $dwoo->getSecurityPolicy()) { while (true) { if (preg_match('{^([a-z]+?)://}i', $resourceId)) { throw new Dwoo_Security_Exception('The security policy prevents you to read files from external sources : <em>'.$resourceId.'</em>.'); } if ($includePath) { break; } $resourceId = realpath($resourceId); $dirs = $policy->getAllowedDirectories(); foreach ($dirs as $dir=>$dummy) { if (strpos($resourceId, $dir) === 0) { break 2; } } throw new Dwoo_Security_Exception('The security policy prevents you to read <em>'.$resourceId.'</em>'); } } $class = 'Dwoo_Template_File'; if ($parentTemplate) { $class = get_class($parentTemplate); } return new $class($resourceId, $cacheTime, $cacheId, $compileId, $includePath); } protected function getCompiledFilename(Dwoo_Core $dwoo) { if ($this->compileId===null) { $this->compileId = str_replace('../', '__', strtr($this->getResourceIdentifier(), '\\:', '/-')); } return $dwoo->getCompileDir() . $this->compileId.'.d'.Dwoo_Core::RELEASE_TAG.'.php'; } public function getIsModifiedCode() { return '"'.$this->getUid().'" == filemtime('.var_export($this->getResourceIdentifier(), true).')'; } } class Dwoo_Data implements Dwoo_IDataProvider { protected $data = array(); public function getData() { return $this->data; } public function clear($name = null) { if ($name === null) { $this->data = array(); } elseif (is_array($name)) { foreach ($name as $index) unset($this->data[$index]); } else { unset($this->data[$name]); } } public function setData(array $data) { $this->data = $data; } public function mergeData(array $data) { $args = func_get_args(); while (list(,$v) = each($args)) { if (is_array($v)) { $this->data = array_merge($this->data, $v); } } } public function assign($name, $val = null) { if (is_array($name)) { reset($name); while (list($k,$v) = each($name)) $this->data[$k] = $v; } else { $this->data[$name] = $val; } } public function __set($name, $value) { $this->assign($name, $value); } public function assignByRef($name, &$val) { $this->data[$name] =& $val; } public function append($name, $val = null, $merge = false) { if (is_array($name)) { foreach ($name as $key=>$val) { if (isset($this->data[$key]) && !is_array($this->data[$key])) { settype($this->data[$key], 'array'); } if ($merge === true && is_array($val)) { $this->data[$key] = $val + $this->data[$key]; } else { $this->data[$key][] = $val; } } } elseif ($val !== null) { if (isset($this->data[$name]) && !is_array($this->data[$name])) { settype($this->data[$name], 'array'); } elseif (!isset($this->data[$name])) { $this->data[$name] = array(); } if ($merge === true && is_array($val)) { $this->data[$name] = $val + $this->data[$name]; } else { $this->data[$name][] = $val; } } } public function appendByRef($name, &$val, $merge = false) { if (isset($this->data[$name]) && !is_array($this->data[$name])) { settype($this->data[$name], 'array'); } if ($merge === true && is_array($val)) { foreach ($val as $key => &$val) { $this->data[$name][$key] =& $val; } } else { $this->data[$name][] =& $val; } } public function isAssigned($name) { return isset($this->data[$name]); } public function __isset($name) { return isset($this->data[$name]); } public function unassign($name) { unset($this->data[$name]); } public function __unset($name) { unset($this->data[$name]); } public function get($name) { return $this->__get($name); } public function __get($name) { if (isset($this->data[$name])) { return $this->data[$name]; } else { throw new Dwoo_Exception('Tried to read a value that was not assigned yet : "'.$name.'"'); } } } 
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo.php	(revision 825)
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * micro-class that extends Dwoo_Core for compatibility purposes
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2010-02-28
+ * @package    Dwoo
+ * @see Dwoo_Core
+ */
+class Dwoo extends Dwoo_Core
+{
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/DwooRenderer.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/DwooRenderer.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/DwooRenderer.php	(revision 825)
@@ -0,0 +1,204 @@
+<?php
+
+/**
+ * Dwoo adapter for Agavi
+ *
+ * Install instructions :
+ *  - download dwoo from dwoo.org and unzip it somewhere in your agavi app
+ *  - add a renderer to your output_types.xml as such :
+ *     <renderer name="dwoo" class="DwooRenderer">
+ *        <parameter name="assigns">
+ *           <parameter name="routing">ro</parameter>
+ *           <parameter name="request">rq</parameter>
+ *           <parameter name="controller">ct</parameter>
+ *           <parameter name="user">us</parameter>
+ *           <parameter name="translation_manager">tm</parameter>
+ *           <parameter name="request_data">rd</parameter>
+ *        </parameter>
+ *        <parameter name="extract_vars">true</parameter>
+ *        <parameters name="plugin_dir">
+ *           <parameter>%core.lib_dir%/dwoo_plugins</parameter>
+ *           <parameter>%core.lib_dir%/other_dwoo_plugins</parameter>
+ *        </parameters>
+ *     </renderer>
+ *
+ *  - add dwoo's directory to your include path or include dwooAutoload.php yourself
+ *    either through agavi's autoload.xml (with name="Dwoo") or through your index.php
+ *
+ * Notes:
+ *  - you can copy the /Dwoo/Adapters/Agavi/dwoo_plugins directory to your agavi app's
+ *    lib directory, or change the plugin_dir parameter in the output_types.xml file.
+ *    these plugins are agavi-specific helpers that shortens the syntax to call common
+ *    agavi helpers (i18n, routing, ..)
+ *  - in the previous versions of this adapter, only a single value could be passed to
+ *    plugin_dir parameter; now you can pass several plugin directories (see example)
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the
+ * use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.1
+ * @date       2010-04-06
+ * @package    Dwoo
+ */
+class DwooRenderer extends AgaviRenderer implements AgaviIReusableRenderer
+{
+	/**
+	 * @constant   string The directory inside the cache dir where templates will
+	 *                    be stored in compiled form.
+	 */
+	const COMPILE_DIR = 'templates';
+
+	/**
+	 * @constant   string The subdirectory inside the compile dir where templates
+	 *                    will be stored in compiled form.
+	 */
+	const COMPILE_SUBDIR = 'dwoo';
+
+	/**
+	 * @constant   string The directory inside the cache dir where cached content
+	 *                    will be stored.
+	 */
+	const CACHE_DIR = 'dwoo';
+
+	/**
+	 * @var        Dwoo Dwoo template engine.
+	 */
+	protected $dwoo = null;
+
+	/**
+	 * @var        string A string with the default template file extension,
+	 *                    including the dot.
+	 */
+	protected $defaultExtension = '.html';
+
+	/**
+	 * stores the (optional) plugin directories to add to the Dwoo_Loader
+	 */
+	protected $plugin_dir = null;
+
+	/**
+	 * Pre-serialization callback.
+	 *
+	 * Excludes the Dwoo instance to prevent excessive serialization load.
+	 */
+	public function __sleep()
+	{
+		$keys = parent::__sleep();
+		unset($keys[array_search('dwoo', $keys)]);
+		return $keys;
+	}
+
+	/**
+	 * Initialize this Renderer.
+	 *
+	 * @param      AgaviContext The current application context.
+	 * @param      array        An associative array of initialization parameters.
+	 */
+	public function initialize(AgaviContext $context, array $parameters = array())
+	{
+		parent::initialize($context, $parameters);
+
+		$this->plugin_dir = $this->getParameter('plugin_dir', $this->plugin_dir);
+	}
+
+	/**
+	 * provides a custom compiler to the dwoo renderer with optional settings
+	 * you can set in the agavi output_types.xml config file
+	 *
+	 * @return Dwoo_Compiler
+	 */
+	public function compilerFactory()
+	{
+		if (class_exists('Dwoo_Compiler', false) === false) {
+			include DWOO_DIRECTORY . 'Dwoo/Compiler.php';
+		}
+		$compiler = Dwoo_Compiler::compilerFactory();
+		$compiler->setAutoEscape((bool) $this->getParameter('auto_escape', false));
+		return $compiler;
+	}
+
+	/**
+	 * Grab a cleaned up dwoo instance.
+	 *
+	 * @return     Dwoo A Dwoo instance.
+	 */
+	protected function getEngine()
+	{
+		if($this->dwoo) {
+			return $this->dwoo;
+		}
+
+		// this triggers Agavi autoload
+		if(!class_exists('Dwoo')) {
+			if (file_exists(dirname(__FILE__).'/../../../dwooAutoload.php')) {
+				// file was dropped with the entire dwoo package
+				require dirname(__FILE__).'/../../../dwooAutoload.php';
+			} else {
+				// assume the dwoo package is in the include path
+				require 'dwooAutoload.php';
+			}
+		}
+
+		$parentMode = fileperms(AgaviConfig::get('core.cache_dir'));
+
+		$compileDir = AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::COMPILE_DIR . DIRECTORY_SEPARATOR . self::COMPILE_SUBDIR;
+		AgaviToolkit::mkdir($compileDir, $parentMode, true);
+
+		$cacheDir = AgaviConfig::get('core.cache_dir') . DIRECTORY_SEPARATOR . self::CACHE_DIR;
+		AgaviToolkit::mkdir($cacheDir, $parentMode, true);
+
+		$this->dwoo = new Dwoo_Core($compileDir, $cacheDir);
+
+		if (!empty($this->plugin_dir)) {
+			foreach ((array) $this->plugin_dir as $dir) {
+				$this->dwoo->getLoader()->addDirectory($dir);
+			}
+		}
+
+		$this->dwoo->setDefaultCompilerFactory('file', array($this, 'compilerFactory'));
+
+		return $this->dwoo;
+	}
+
+	/**
+	 * Render the presentation and return the result.
+	 *
+	 * @param      AgaviTemplateLayer The template layer to render.
+	 * @param      array              The template variables.
+	 * @param      array              The slots.
+	 * @param      array              Associative array of additional assigns.
+	 *
+	 * @return     string A rendered result.
+	 */
+	public function render(AgaviTemplateLayer $layer, array &$attributes = array(), array &$slots = array(), array &$moreAssigns = array())
+	{
+		$engine = $this->getEngine();
+
+		$data = array();
+		if($this->extractVars) {
+			$data = $attributes;
+		} else {
+			$data[$this->varName] = &$attributes;
+		}
+
+		$data[$this->slotsVarName] =& $slots;
+
+		foreach($this->assigns as $key => $getter) {
+			$data[$key] = $this->getContext()->$getter();
+		}
+
+		foreach($moreAssigns as $key => &$value) {
+			if(isset($this->moreAssignNames[$key])) {
+				$key = $this->moreAssignNames[$key];
+			}
+			$data[$key] =& $value;
+		}
+
+		return $engine->get($layer->getResourceStreamIdentifier(), $data);
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/README
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/README	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/README	(revision 825)
@@ -0,0 +1,27 @@
+// ------------------------
+// Install notes :
+// ------------------------
+
+ - drop dwoo's directory in app/lib/renderer (create if needed)
+
+ - add a renderer to app/config/output_types.xml as such :
+    <renderer name="dwoo" class="DwooRenderer">
+       <parameter name="assigns">
+          <parameter name="routing">ro</parameter>
+          <parameter name="request">rq</parameter>
+          <parameter name="controller">ct</parameter>
+          <parameter name="user">us</parameter>
+          <parameter name="translation_manager">tm</parameter>
+          <parameter name="request_data">rd</parameter>
+       </parameter>
+       <parameter name="extract_vars">true</parameter>
+       <parameter name="plugin_dir">%core.lib_dir%/dwoo_plugins</parameter>
+    </renderer>
+
+ - add the renderer to app/config/autoload.xml as such :
+    <autoload name="DwooRenderer">%core.lib_dir%/renderer/dwoo/Dwoo/Adapter/Agavi/DwooRenderer.php</autoload>
+
+ - you can copy the /Dwoo/Adapters/Agavi/dwoo_plugins directory to your agavi app's
+   lib directory, or change the plugin_dir parameter in the output_types.xml file.
+   these plugins are agavi-specific helpers that shortens the syntax to call common
+   agavi helpers (i18n, routing, ..)
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/dwoo_plugins/t.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/dwoo_plugins/t.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/dwoo_plugins/t.php	(revision 825)
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * <strong>Agavi specific plugin</strong>
+ *
+ * uses AgaviTranslationManager to localize a string
+ *
+ * <pre>
+ *  * string : the string to localize
+ * </pre>
+ *
+ * Examples:
+ * <code>
+ * {t "Hello"}
+ * {t $header}
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_t_compile(Dwoo_Compiler $compiler, $string)
+{
+	return '$this->data[\'tm\']->_('.$string.')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/dwoo_plugins/url.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/dwoo_plugins/url.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/Agavi/dwoo_plugins/url.php	(revision 825)
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * <strong>Agavi specific plugin</strong>
+ *
+ * uses AgaviRouting to create an url
+ *
+ * <pre>
+ *  * route : the route name, optional (by default the current url is returned)
+ *  * params : an array with variables to build the route, optional
+ *  * options : an array of options to pass to the routing object, optional
+ *  * rest : for convenience, you can just pass named parameters that will be used as
+ *           the params array, but you must not provide the params array in this case
+ * </pre>
+ *
+ * Examples:
+ * <code>
+ * {a url("route.name" array(param="Value", param2=$otherVal))}Here is a link{/a}
+ * <form action="{url}"> {* without any parameter it just returns the current url *}
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_url_compile(Dwoo_Compiler $compiler, $route = null, $params = null, $options = null, array $rest = array())
+{
+	if ($params == 'null') {
+		if (count($rest)) {
+			$params = array();
+			foreach ($rest as $k=>$v) {
+				if (is_numeric($k)) {
+					$params[] = $k.'=>'.$v;
+				} else {
+					$params[] = '"'.$k.'"=>'.$v;
+				}
+			}
+			$params = 'array('.implode(', ', $params).')';
+		} else {
+			$params = 'array()';
+		}
+	}
+	if ($options == 'null') {
+		$options = 'array()';
+	}
+	return '$this->data[\'ro\']->gen('.$route.', '.$params.', '.$options.')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CakePHP/README
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CakePHP/README	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CakePHP/README	(revision 825)
@@ -0,0 +1,33 @@
+// CakePHP Dwoo bridge  -  v0.2 
+// ------------------------
+// Installation :
+// ------------------------
+// 1. Download and install the dwoo library, preferably on the
+// 'vendors' directory of CakePHP. However you can place it
+// anywhere you want; if you do, make sure to change the App::import
+// line in dwoo.php to include the dwoo library properly.
+//
+// 2. Place this file in the  app/views directory, or on cake/libs/view.
+// 
+// 3. Create the app/tmp/dwoo/cache and app/tmp/dwoo/compile directories 
+// and make sure they are writable.
+// ------------------------
+// Usage example :
+// ------------------------
+
+// In your controller class you need to change the view property to
+// use Dwoo at some point in the execution using :
+
+$this->view = 'Dwoo';
+
+// Or you can also override the view property in your AppController class as such :
+
+class AppController extends Controller {
+    public $view = 'Dwoo';
+}
+
+// If you want another template extension (default is .tpl) you must
+// edit the dwoo.php file at line 44 and change it to :
+$this->ext = ".html";
+
+//{include $templatedir."index.tpl"}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CakePHP/dwoo.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CakePHP/dwoo.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CakePHP/dwoo.php	(revision 825)
@@ -0,0 +1,143 @@
+<?php
+
+App::import('vendor', 'dwoo', array("file" => 'dwoo/dwooAutoload.php'));
+
+/**
+ * Dwoo adapter for CakePHP
+ *
+ * Based on SmartyView by Mark John S. Buenconsejo <mjwork@simpleteq.com>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * This file is released under the LGPL
+ * "GNU Lesser General Public License"
+ * More information can be found here:
+ * {@link http://www.gnu.org/copyleft/lesser.html}
+ *
+ * @author     Mark John S. Buenconsejo <mjwork@simpleteq.com>
+ * @author     Giangi <giangi@qwerg.com>
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://www.gnu.org/copyleft/lesser.html  GNU Lesser General Public License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class DwooView extends View
+{
+	protected $_sv_template_dir;
+	protected $_sv_layout_dir;
+	protected $_sv_compile_dir;
+	protected $_sv_cache_dir;
+	protected $_sv_compile_id;
+
+	protected $_dwoo;
+
+	public $sv_processedTpl;
+
+ 	public function __construct(&$controller)
+	{
+		parent::__construct($controller);
+
+		$this->ext = '.tpl';
+
+		$this->_sv_template_dir = array
+		(
+			VIEWS . $this->viewPath . DS . $this->subDir,
+			VIEWS . $this->viewPath,
+			VIEWS
+		);
+
+		$this->_sv_layout_dir = array
+		(
+			LAYOUTS . $this->subDir,
+			VIEWS
+		);
+
+		$this->_sv_compile_dir = TMP . 'dwoo' . DS . 'compile';
+		$this->_sv_cache_dir = TMP . 'dwoo' . DS . 'cache';
+
+		$this->_dwoo = new Dwoo_Core($this->_sv_compile_dir, $this->_sv_cache_dir);
+
+		$this->_sv_compile_id = $controller->name;
+
+		$this->_dwoo->sv_this = $this;
+        $this->_dwoo->setSecurityPolicy();
+
+		return;
+	}
+
+	/**
+	 * changes the template directory
+	 */
+	public function setTemplateDir($path = VIEW) {
+		$old = $this->_sv_template_dir;
+		$this->_sv_template_dir  = $path;
+
+		return $old;
+	}
+
+	public function getTemplateDir() {
+		return $this->_sv_template_dir ;
+	}
+
+	public function _render($___viewFn, $___data_for_view, $___play_safe = true, $loadHelpers = true)
+	{
+		// let's determine if this is a layout call or a template call
+		// and change the template dir accordingly
+		$layout = false;
+		if(isset($___data_for_view['content_for_layout'])) {
+			$this->_sv_template_dir = $this->_sv_layout_dir;
+			$layout = true;
+		}
+
+		$tpl  = new Dwoo_Template_File($___viewFn);
+		$data = $___data_for_view;
+
+		$data['view'] = $this;
+
+		if ($this->helpers != false && $loadHelpers === true) {
+			$loadedHelpers = array();
+			$loadedHelpers = $this->_loadHelpers($loadedHelpers, $this->helpers);
+
+			foreach (array_keys($loadedHelpers) as $helper) {
+				$camelBackedHelper = strtolower(substr($helper, 0, 1)) . substr($helper, 1);
+
+				${$camelBackedHelper} = $loadedHelpers[$helper];
+
+				if (is_array(${$camelBackedHelper}->helpers) && !empty(${$camelBackedHelper}->helpers)) {
+					$subHelpers = ${$camelBackedHelper}->helpers;
+					foreach ($subHelpers as $subHelper) {
+						${$camelBackedHelper}->{$subHelper} = $loadedHelpers[$subHelper];
+					}
+				}
+
+				if(isset($this->passedArgs)) {
+					${$camelBackedHelper}->passedArgs = $this->passedArgs;
+				}
+
+				$this->loaded[$camelBackedHelper] = ${$camelBackedHelper};
+
+				$data[$camelBackedHelper] = ${$camelBackedHelper};
+			}
+		}
+
+		if ($this->helpers != false && $loadHelpers === true) {
+			foreach ($loadedHelpers as $helper) {
+				if (is_object($helper)) {
+					if (is_subclass_of($helper, 'Helper') || is_subclass_of($helper, 'helper')) {
+						$helper->beforeRender();
+					}
+				}
+			}
+		}
+
+		return $this->_dwoo->get($tpl, $data);
+	}
+
+	public function get(){
+		return $this->_dwoo;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/README
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/README	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/README	(revision 825)
@@ -0,0 +1,36 @@
+CodeIgniter/Dwoo adapater
+-------------------------
+Integration of Dwoo into Codeigniter (1.7.0 >)
+
+Links:
+Dwoo - http://dwoo.org
+CodeIgniter - http://codeigniter.com
+
+Installation:
+1) Extract package into your application directory (i.e. $webroot/application or 
+   $webroot/system/application)
+2) Change the parameters in config/dwootemplate.php
+3) Create the compile and cache directory you set in your config file in step 2
+   and give it the correct rights (chmod 777 when on *nix)
+4) Extract/copy the Dwoo package into application/libraries/dwoo
+5) Browse to : http://[yoururl]/dwoowelcome
+
+
+Version info
+1.0.2 [11-03-2009] Fixed a problem with $data attribute (which Dwoo also used)
+1.0.1 [12-11-2008] Removed some & in the dwootemplate
+                   Changed licencse
+                   Changed 'output' in 'get' in the dwootemplate (line 122)
+1.0.0 [11-11-2008] Initial release
+
+
+Questions/Remarks?
+mail to:        stefan.verstege@newmedia.nl
+IM me on GTALK: verstege@gmail.com
+Contact me on the Dwoo forums: stefanv
+
+---------[ copyright notice ]-----------------------------------------------------------------------
+This software is provided 'as-is', without any express or implied warranty.
+In no event will the authors be held liable for any damages arising from the use of this software.
+
+License    http://dwoo.org/LICENSE   Modified BSD License
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/config/dwootemplate.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/config/dwootemplate.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/config/dwootemplate.php	(revision 825)
@@ -0,0 +1,12 @@
+<?php  if (!defined('BASEPATH')) exit('No direct script access allowed');
+
+// The name of the directory where templates are located.
+$config['template_dir'] = dirname(FCPATH) . '/../application/views/';
+
+// The directory where compiled templates are located
+$config['compileDir']   = dirname(FCPATH) . '/../compile/';
+
+//This tells Dwoo whether or not to cache the output of the templates to the $cache_dir.
+$config['caching']      = 0;
+$config['cacheDir']     = dirname(FCPATH) . '/../cache/';
+$config['cacheTime']    = 0;
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/controllers/dwoowelcome.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/controllers/dwoowelcome.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/controllers/dwoowelcome.php	(revision 825)
@@ -0,0 +1,16 @@
+<?php
+
+class Dwoowelcome extends Controller {
+
+    function __construct()
+    {
+        parent::Controller();
+    }
+
+    function index()
+    {
+    	$this->load->library('Dwootemplate');
+    	$this->dwootemplate->assign('itshowlate', date('H:i:s'));
+    	$this->dwootemplate->display('dwoowelcome.tpl');
+    }
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/libraries/Dwootemplate.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/libraries/Dwootemplate.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/libraries/Dwootemplate.php	(revision 825)
@@ -0,0 +1,172 @@
+<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
+
+require "dwoo/dwooAutoload.php";
+
+/**
+ * Creates an template interface from Codeigniter to DWOO.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ *
+ * @author     Stefan Verstege <stefan.verstege@newmedia.nl>
+ * @copyright  Copyright (c) 2008, Stefan Verstege
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://www.newmedia.nl/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ *
+ * @uses the dwoo package from http://dwoo.org
+ */
+class Dwootemplate extends Dwoo_Core {
+    protected $dwoo_data = array();
+
+    /**
+     * Constructor for the DwooTemplate engine
+     *
+     */
+    public function __construct() {
+        // Call parents constructor
+        parent::__construct();
+
+        // Set the config settings
+        $this->initialize();
+
+        // Assign some defaults to dwoo
+        $CI                         = get_instance();
+        $this->dwoo_data            = new Dwoo_Data();
+        $this->dwoo_data->js_files  = array();
+        $this->dwoo_data->css_files = array();
+        $this->dwoo_data->CI        = $CI;
+        $this->dwoo_data->site_url  = $CI->config->site_url(); // so we can get the full path to CI easily
+        $this->dwoo_data->uniqid    = uniqid();
+        $this->dwoo_data->timestamp = mktime();
+
+        log_message('debug', "Dwoo Template Class Initialized");
+    }
+
+
+    /**
+     * Assign data to dwoo data object
+     *
+     * @param string $key
+     * @param mixed $value
+     */
+    public function assign($key, $value) {
+        $this->dwoo_data->$key = $value;
+    }
+
+
+    /**
+     * Add Javascript files to template
+     *
+     * @param string $js
+     */
+    public function add_js($js) {
+        $current   = $this->dwoo_data->js_files;
+        $current[] = $js;
+        $this->dwoo_data->js_files = $current;
+    }
+
+
+    /**
+     * Add Css stylesheets to template
+     *
+     * @param string $css
+     */
+    public function add_css($css) {
+        $current   = $this->dwoo_data->css_files;
+        $current[] = $css;
+        $this->dwoo_data->css_files = $current;
+    }
+
+
+    /**
+     * Display or return the compiled template
+     * Since we assign the results to the standard CI output module
+     * you can also use the helper from CI in your templates!!
+     *
+     * @param string $sTemplate
+     * @param boolean $return
+     * @return mixed
+     */
+    public function display($sTemplate, $return = FALSE) {
+        // Start benchmark
+        $CI = get_instance();
+        $CI->benchmark->mark('dwoo_parse_start');
+
+        // Check if file exists
+        if ( !file_exists($this->template_dir . $sTemplate ) ) {
+            $message = sprintf('Template file \'%s\' not found.', $sTemplate);
+            show_error($message);
+            log_message('error', $message);
+        }
+
+        // Create new template
+        $tpl = new Dwoo_Template_File($this->template_dir . $sTemplate);
+
+        // render the template
+        $template = $this->get($tpl, $this->dwoo_data);
+
+        // Finish benchmark
+        $CI->benchmark->mark('dwoo_parse_end');
+
+        // Return results or not ?
+        if ($return == FALSE) {
+            $CI->output->final_output = $template;
+        } else {
+            return $template;
+        }
+    }
+
+
+    /**
+     * Toggle Codeigniter profiler on/off
+     *
+     */
+    public function enable_profiler($toggle = TRUE) {
+        $CI = get_instance();
+        $CI->output->enable_profiler($toggle);
+    }
+
+
+    /**
+     * Set http header
+     *
+     * @example $this->output->set_header("HTTP/1.1 200 OK");
+     * @example $this->output->set_header('Last-Modified: '.gmdate('D, d M Y H:i:s', $last_update).' GMT');
+     * @param string $header
+     */
+    public function set_header($header) {
+        $CI = get_instance();
+        $CI->output->set_header($header);
+    }
+
+
+    /**
+     * Set status header
+     *
+     * @example $this->output->set_status_header('401');
+     * @example // Sets the header as: Unauthorized
+     * @param string $header
+     */
+    public function set_status_header($header) {
+        $CI = get_instance();
+        $CI->output->set_status_header($header);
+    }
+
+
+    /**
+     * Assign the dwootemplate config items to the instance
+     *
+     */
+    private function initialize() {
+        $CI = get_instance();
+        $CI->config->load('dwootemplate', TRUE);
+        $config = $CI->config->item('dwootemplate');
+        foreach ($config as $key => $val) {
+                $this->$key = $val;
+        }
+    }
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/views/dwoowelcome.tpl
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/views/dwoowelcome.tpl	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/views/dwoowelcome.tpl	(revision 825)
@@ -0,0 +1,31 @@
+{extends "page.tpl"}
+
+{block "title"}
+Welcome to Dwoo-ed CodeIgniter
+{/block}
+
+{block "content"}
+<h1>Welcome to Dwoo-ed CodeIgniter!</h1>
+
+<p>The page you are looking at is being generated dynamically by <b>CodeIgniter</b> in combination with the 'Smarty-killer' <b>Dwoo</b> template engine.
+The page is rendered at {$itshowlate} by the Dwoo_compiler.</p>
+
+<p>If you would like to edit this page you'll find it located at:</p>
+<code>application/views/dwoowelcome.tpl</code>
+
+<p>The corresponding controller for this page is found at:</p>
+<code>application/controllers/dwoowelcome.php</code>
+
+<p>The library for Dwoo integration can be found at:</p>
+<code>application/libraries/Dwootemplate.php</code>
+
+<p>If you are exploring Dwoo for the very first time, you should start by reading the {anchor uri='http://dwoo.org/' title='Dwoo website'}.</p>
+<p>If you are exploring CodeIgniter for the very first time, you should start by reading the {anchor uri='http://codeigniter.com/user_guide/' title='User Guide'}.</p>
+
+<pre>
+<b>Usage</b>:
+$this->load->library('Dwootemplate');
+$this->dwootemplate->assign('test', 'test');
+$this->dwootemplate->display('dwoowelcome.tpl');
+</pre>
+{/block}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/views/page.tpl
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/views/page.tpl	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/CodeIgniter/views/page.tpl	(revision 825)
@@ -0,0 +1,57 @@
+<html>
+<head>
+<title>{block "title"}Here come the title{/block}</title>
+
+<style type="text/css">
+
+body {
+ background-color: #fff;
+ margin: 40px;
+ font-family: Lucida Grande, Verdana, Sans-serif;
+ font-size: 14px;
+ color: #4F5155;
+}
+
+a {
+ color: #003399;
+ background-color: transparent;
+ font-weight: normal;
+}
+
+h1 {
+ color: #444;
+ background-color: transparent;
+ border-bottom: 1px solid #D0D0D0;
+ font-size: 16px;
+ font-weight: bold;
+ margin: 24px 0 2px 0;
+ padding: 5px 0 6px 0;
+}
+
+code {
+ font-family: Monaco, Verdana, Sans-serif;
+ font-size: 12px;
+ background-color: #f9f9f9;
+ border: 1px solid #D0D0D0;
+ color: #002166;
+ display: block;
+ margin: 14px 0 14px 0;
+ padding: 12px 10px 12px 10px;
+}
+
+pre {
+ font-family: Monaco, Verdana, Sans-serif;
+ font-size: 12px;
+ background-color: #f9f9f9;
+ border: 1px solid #D0D0D0;
+ color: #B9BAA4;
+ display: block;
+ margin: 14px 0 14px 0;
+ padding: 12px 10px 12px 10px;
+}
+</style>
+</head>
+<body>
+{block "content"}Here comes the content{/block}
+</body>
+</html>
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/Dwoo.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/Dwoo.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/Dwoo.php	(revision 825)
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * Dwoo base class for ZendFramework
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the
+ * use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @author     Marc Hodgins <mjh@hodginsmedia.com>
+ * @copyright  Copyright (c) 2010, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.2.0
+ * @date       2010-02-28
+ * @package    Dwoo
+ */
+class Dwoo_Adapters_ZendFramework_Dwoo extends Dwoo_Core
+{
+	/**
+	 * Redirects all unknown properties to plugin proxy
+	 * to support $this->viewVariable from within templates
+	 *
+	 * @param string $name Property name
+	 * @return mixed
+	 */
+	public function __get($name)
+	{
+		if (isset($this->getPluginProxy()->view->$name)) {
+			return $this->getPluginProxy()->view->$name;
+		}
+		$trace = debug_backtrace();
+		trigger_error('Undefined property via __get(): ' . $name .
+					  ' in ' . $trace[0]['file'] .
+					  ' on line ' . $trace[0]['line'], E_USER_NOTICE);
+		return null;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/PluginProxy.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/PluginProxy.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/PluginProxy.php	(revision 825)
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * PluginProxy class for Zend View
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the
+ * use of this software.
+ *
+ * @author	   Denis Arh <denis@arh.cc>
+ * @author	   Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Denis Arh, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Adapters_ZendFramework_PluginProxy implements Dwoo_IPluginProxy
+{
+	/**
+	 * reference to the zend view owning this proxy
+	 *
+	 * @var Zend_View_Interface
+	 */
+	public $view;
+
+	/**
+	 * Dwoo_Adapters_ZendFramework_PluginProxy's constructor.
+	 *
+	 * @param Zend_View_Interface $view
+	 */
+	public function __construct(Zend_View_Interface $view) {
+		$this->view = $view;
+	}
+
+	/**
+	 * Called from Dwoo_Compiler to check if the requested plugin is available
+	 *
+	 * @param string $name
+	 * @return bool
+	 */
+	public function handles($name) {
+		try {
+			$this->view->getHelper($name);
+		} catch (Zend_Loader_PluginLoader_Exception $e) {
+			return false;
+		}
+
+		return true;
+	}
+
+	/**
+	 * returns the code (as a string) to call the plugin
+	 * (this will be executed at runtime inside the Dwoo class)
+	 *
+	 * @param string $name the plugin name
+	 * @param array $params a parameter array, array key "*" is the rest array
+	 * @return string
+	 */
+	public function getCode($name, $params) {
+		return '$this->getPluginProxy()->view->'. $name .'('.Dwoo_Compiler::implode_r($params).')';
+	}
+
+	/**
+	 * returns a callback to the plugin, this is used with the reflection API to
+	 * find out about the plugin's parameter names etc.
+	 *
+	 * should you need a rest array (i.e. for ZendFramework helpers) without the
+	 * possibility to edit the plugin's code, you can provide a callback to some
+	 * other function with the correct parameter signature, i.e. :
+	 * <code>
+	 * return array($this, "callbackHelper");
+	 * // and callbackHelper would be as such:
+	 * public function callbackHelper(array $rest=array()){}
+	 * </code>
+	 *
+	 * @param string $name the plugin name
+	 * @return callback
+	 */
+	public function getCallback($name) {
+		return array($this->view->getHelper($name), $name);
+	}
+
+	/**
+	 * returns some code that will check if the plugin is loaded and if not load it
+	 * this is optional, if your plugins are autoloaded or whatever, just return an
+	 * empty string
+	 *
+	 * @param string $name the plugin name
+	 * @return string
+	 */
+	public function getLoader($name) {
+		return '';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/README
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/README	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/README	(revision 825)
@@ -0,0 +1,34 @@
+// ------------------------
+// Usage example :
+// ------------------------
+// Note that you might need to manually include 'lib/Dwoo.php',
+// 'lib/Dwoo/Adapters/ZendFramework/View.php',
+// 'lib/Dwoo/Adapters/ZendFramework/Dwoo.php', and
+// 'lib/Dwoo/Adapters/ZendFramework/PluginProxy.php' for this to
+// work as expected, depending on your ZF setup
+//
+// If anyone writes a more advanced how-to please let me know
+// ------------------------
+
+$view = new Dwoo_Adapters_ZendFramework_View(array(
+       'compileDir' => 'path/to/compile_dir' // set to null or remove this line to use defaults
+       'cacheDir'   => 'path/to/cache_dir' // set to null or remove this line to use defaults
+));
+
+// This allows you to use ZF's helpers as if they were Dwoo plugins (i.e. {doctype} will call the doctype helper)
+// This also allows you to use $this->variable to access view variables from within templates
+
+$view->setPluginProxy(new Dwoo_Adapters_ZendFramework_PluginProxy(new Zend_View()));
+
+
+// 1. example - used with the Zend Controller
+
+$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view);
+
+Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
+
+
+// 2. example - used manually
+
+$view->assign('foo', 'bar');
+$view->display('foobar.phtml');
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/View.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/View.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Adapters/ZendFramework/View.php	(revision 825)
@@ -0,0 +1,501 @@
+<?php
+
+/**
+ * Dwoo adapter for ZendFramework
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the
+ * use of this software.
+ *
+ * @author	   Denis Arh <denis@arh.cc>
+ * @author     Stephan Wentz <stephan@wentz.it>
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Adapters_ZendFramework_View extends Zend_View_Abstract
+{
+	/**
+	 * @var Dwoo
+	 */
+	protected $_engine = null;
+
+	/**
+	 * @var Dwoo_Data
+	 */
+	protected $_dataProvider = null;
+
+	/**
+	 * @var Dwoo_Compiler
+	 */
+	protected $_compiler = null;
+
+	/**
+	 * Changing Filter's scope to play nicely
+	 *
+	 * @var array
+	 */
+	protected $_filter = array();
+
+
+	/**
+	 * @var string
+	 */
+	protected $_templateFileClass = 'Dwoo_Template_File';
+
+	/**
+	 * @var array
+	 */
+	protected $_templateFileSettings = array();
+
+	/**
+	 * @var Dwoo_IPluginProxy
+	 */
+	protected $_pluginProxy = null;
+
+	/**
+	 * Constructor method.
+	 * See setOptions for $opt details
+	 *
+	 * @see setOptions
+	 * @param array|Zend_Config List of options or Zend_Config instance
+	 */
+	public function __construct($opt = array())
+	{
+
+		if (is_array($opt)) {
+			$this->setOptions($opt);
+		} elseif ($opt instanceof Zend_Config) {
+			$this->setConfig($opt);
+		}
+
+		$this->init();
+	}
+
+	/**
+	 * Set object state from options array
+	 *  - engine        = engine class name|engine object|array of options for engine
+	 *  - dataProvider  = data provider class name|data provider object|array of options for data provider
+	 *  - compiler      = compiler class name|compiler object|array of options for compiler
+	 *  - templateFile  =
+	 *
+	 *  Array of options:
+	 *  - type class name or object for engine, dataProvider or compiler
+	 *  - any set* method (compileDir for setCompileDir ...)
+	 *
+	 * @param  array $options
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function setOptions(array $opt = array())
+	{
+		// Making sure that everything is loaded.
+		$classes = array('engine', 'dataProvider', 'compiler');
+
+		// Setting options to Dwoo objects...
+		foreach ($opt as $type => $settings) {
+			if (!method_exists($this, 'set' . $type)) {
+				throw new Dwoo_Exception("Unknown type $type");
+			}
+
+			if (is_string($settings) || is_object($settings)) {
+				call_user_func(array($this, 'set' . $type), $settings);
+			} elseif (is_array($settings)) {
+				// Set requested class
+				if (array_key_exists('type', $settings)) {
+					call_user_func(array($this, 'set' . $type), $settings['type']);
+				}
+
+				if (in_array($type, $classes)) {
+					// Call get so that the class is initialized
+					$rel = call_user_func(array($this, 'get' . $type));
+
+					// Call set*() methods so that all the settings are set.
+					foreach ($settings as $method => $value) {
+						if (method_exists($rel, 'set' . $method)) {
+							call_user_func(array($rel, 'set' . $method), $value);
+						}
+					}
+				} elseif ('templateFile' == $type) {
+					// Remember the settings for the templateFile
+					$this->_templateFileSettings = $settings;
+				}
+			}
+		}
+	}
+
+	/**
+	 * Set object state from Zend_Config object
+	 *
+	 * @param  Zend_Config $config
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function setConfig(Zend_Config $config)
+	{
+		return $this->setOptions($config->toArray());
+	}
+
+	/**
+	 * Called before template rendering
+	 *
+	 * Binds plugin proxy to the Dwoo.
+	 *
+	 * @see Dwoo_Adapters_ZendFramework_View::getPluginProxy();
+	 * @see Dwoo_Core::setPluginProxy();
+	 */
+	protected function preRender()
+	{
+		$this->getEngine()->setPluginProxy($this->getPluginProxy());
+	}
+
+	/**
+	 * Wraper for Dwoo_Data::__set()
+	 * allows to assign variables using the object syntax
+	 *
+	 * @see Dwoo_Data::__set()
+	 * @param string $name the variable name
+	 * @param string $value the value to assign to it
+	 */
+	public function __set($name, $value)
+	{
+		$this->getDataProvider()->__set($name, $value);
+	}
+
+	/**
+	 * Sraper for Dwoo_Data::__get() allows to read variables using the object
+	 * syntax
+	 *
+	 * @see Dwoo_Data::__get()
+	 * @param string $name the variable name
+	 * @return mixed
+	 */
+	public function __get($name)
+	{
+		 return $this->getDataProvider()->__get($name);
+	}
+
+	/**
+	 * Wraper for Dwoo_Data::__isset()
+	 * supports calls to isset($dwooData->var)
+	 *
+	 * @see Dwoo_Data::__isset()
+	 * @param string $name the variable name
+	 */
+	public function __isset($name)
+	{
+		return $this->getDataProvider()->__isset($name);
+	}
+
+	/**
+	 * Wraper for Dwoo_Data::_unset()
+	 * supports unsetting variables using the object syntax
+	 *
+	 * @see Dwoo_Data::__unset()
+	 * @param string $name the variable name
+	 */
+	public function __unset($name)
+	{
+		$this->getDataProvider()->__unset($name);
+	}
+
+	/**
+	 * Catches clone request and clones data provider
+	 */
+	public function __clone() {
+		$this->setDataProvider(clone $this->getDataProvider());
+	}
+
+	/**
+	 * Returns plugin proxy interface
+	 *
+	 * @return Dwoo_IPluginProxy
+	 */
+	public function getPluginProxy()
+	{
+		if (!$this->_pluginProxy) {
+			$this->_pluginProxy = new Dwoo_Adapters_ZendFramework_PluginProxy($this);
+		}
+
+		return $this->_pluginProxy;
+	}
+
+	/**
+	 * Sets plugin proxy
+	 *
+	 * @param Dwoo_IPluginProxy
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function setPluginProxy(Dwoo_IPluginProxy $pluginProxy)
+	{
+		$this->_pluginProxy = $pluginProxy;
+		return $this;
+	}
+
+	/**
+	 * Sets template engine
+	 *
+	 * @param string|Dwoo Object or name of the class
+	 */
+	public function setEngine($engine)
+	{
+		// if param given as an object
+		if ($engine instanceof Dwoo) {
+			$this->_engine = $engine;
+		}
+		//
+		elseif (is_subclass_of($engine, 'Dwoo') || 'Dwoo' === $engine) {
+			$this->_engine = new $engine();
+		}
+		else {
+			throw new Dwoo_Exception("Custom engine must be a subclass of Dwoo");
+		}
+	}
+
+	/**
+	 * Return the Dwoo template engine object
+	 *
+	 * @return Dwoo
+	 */
+	public function getEngine()
+	{
+		if (null === $this->_engine) {
+			$this->_engine = new Dwoo_Adapters_ZendFramework_Dwoo();
+		}
+
+		return $this->_engine;
+	}
+
+	/**
+	 * Sets Dwoo data object
+	 *
+	 * @param string|Dwoo_Data Object or name of the class
+	 */
+	public function setDataProvider($data)
+	{
+		if ($data instanceof Dwoo_IDataProvider) {
+			$this->_dataProvider = $data;
+		}
+		elseif (is_subclass_of($data, 'Dwoo_Data') || 'Dwoo_Data' == $data) {
+			$this->_dataProvider = new $data();
+		}
+		else {
+			throw new Dwoo_Exception("Custom data provider must be a subclass of Dwoo_Data or instance of Dwoo_IDataProvider");
+		}
+	}
+
+	/**
+	 * Return the Dwoo data object
+	 *
+	 * @return Dwoo_Data
+	 */
+	public function getDataProvider()
+	{
+		if (null === $this->_dataProvider) {
+			$this->_dataProvider = new Dwoo_Data;
+		}
+
+		return $this->_dataProvider;
+	}
+
+
+	/**
+	 * Sets Dwoo compiler
+	 *
+	 * @param string|Dwoo_Compiler Object or name of the class
+	 */
+	public function setCompiler($compiler)
+	{
+
+		// if param given as an object
+		if ($compiler instanceof Dwoo_ICompiler) {
+			$this->_compiler = $compiler;
+		}
+		// if param given as a string
+		elseif (is_subclass_of($compiler, 'Dwoo_Compiler') || 'Dwoo_Compiler' == $compiler) {
+			$this->_compiler = new $compiler;
+		}
+		else {
+			throw new Dwoo_Exception("Custom compiler must be a subclass of Dwoo_Compiler or instance of Dwoo_ICompiler");
+		}
+	}
+
+	/**
+	 * Return the Dwoo compiler object
+	 *
+	 * @return Dwoo_Data
+	 */
+	public function getCompiler()
+	{
+		if (null === $this->_compiler) {
+			$this->_compiler = Dwoo_Compiler::compilerFactory();
+		}
+
+		return $this->_compiler;
+	}
+
+	/**
+	 * Initializes Dwoo_ITemplate type of class and sets properties from _templateFileSettings
+	 *
+	 * @param  string Template location
+	 * @return Dwoo_ITemplate
+	 */
+	public function getTemplateFile($template) {
+		$templateFileClass = $this->_templateFileClass;
+
+		$dwooTemplateFile = new $templateFileClass($template);
+
+		if (!($dwooTemplateFile instanceof Dwoo_ITemplate)) {
+			throw new Dwoo_Exception("Custom templateFile class must be a subclass of Dwoo_ITemplate");
+		}
+
+		foreach ($this->_templateFileSettings as $method => $value) {
+			if (method_exists($dwooTemplateFile, 'set' . $method)) {
+				call_user_func(array($dwooTemplateFile, 'set' . $method), $value);
+			}
+		}
+
+		return $dwooTemplateFile;
+	}
+
+	/**
+	 * Dwoo_ITemplate type of class
+	 *
+	 * @param string Name of the class
+	 * @return void
+	 */
+	public function setTemplateFile($tempateFileClass) {
+		$this->_templateFileClass = $tempateFileClass;
+	}
+
+	/**
+	 * Passes data to Dwoo_Data object
+	 *
+	 * @see Dwoo_Data::assign()
+	 * @param array|string $name
+	 * @param mixed $val
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function assign($name, $val = null)
+	{
+		$this->getDataProvider()->assign($name, $val);
+		return $this;
+	}
+
+	/**
+	 * Return list of all assigned variables
+	 *
+	 * @return array
+	 */
+	public function getVars()
+	{
+		return $this->getDataProvider()->getData();
+	}
+
+	/**
+	 * Clear all assigned variables
+	 *
+	 * Clears all variables assigned to Zend_View either via {@link assign()} or
+	 * property overloading ({@link __get()}/{@link __set()}).
+	 *
+	 * @return void
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function clearVars()
+	{
+		$this->getDataProvider()->clear();
+		return $this;
+	}
+
+	/**
+	 * Wraper for parent's render method so preRender method
+	 * can be called (that will bind the plugin proxy to the
+	 * engine.
+	 *
+	 * @see Zend_View_Abstract::render
+	 * @return string The script output.
+	 */
+	public function render($name)
+	{
+		$this->preRender();
+		return parent::render($name);
+	}
+
+	/**
+	 * Processes a view script and outputs it. Output is then
+	 * passed through filters.
+	 *
+	 * @param string $name The script script name to process.
+	 * @return string The script output.
+	 */
+	public function _run()
+	{
+		echo $this->_engine->get(
+			$this->getTemplateFile(func_get_arg(0)),
+			$this->getDataProvider(),
+			$this->getCompiler()
+		);
+	}
+
+	/**
+	 * Add plugin path
+	 *
+	 * @param string $dir Directory
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function addPluginDir($dir)
+	{
+		$this->getEngine()->getLoader()->addDirectory($dir);
+		return $this;
+	}
+
+	/**
+	 * Set compile path
+	 *
+	 * @param string $dir Directory
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function setCompileDir($dir)
+	{
+		$this->getEngine()->setCompileDir($dir);
+		return $this;
+	}
+
+	/**
+	 * Set cache path
+	 *
+	 * @param string $dir Directory
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function setCacheDir($dir)
+	{
+		$this->getEngine()->setCacheDir($dir);
+		return $this;
+	}
+
+	/**
+	 * Set cache lifetime
+	 *
+	 * @param string $seconds Lifetime in seconds
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function setCacheLifetime($seconds)
+	{
+		$this->getEngine()->setCacheTime($seconds);
+		return $this;
+	}
+
+	/**
+	 * Set charset
+	 *
+	 * @param string $charset
+	 * @return Dwoo_Adapters_ZendFramework_View
+	 */
+	public function setCharset($charset)
+	{
+		$this->_engine->setCharset($charset);
+		return $this;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Block/Plugin.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Block/Plugin.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Block/Plugin.php	(revision 825)
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * base class for block plugins
+ *
+ * you have to implement the <em>init()</em> method, it will receive the parameters that
+ * are in the template code and is called when the block starts
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+abstract class Dwoo_Block_Plugin extends Dwoo_Plugin
+{
+	/**
+	 * stores the contents of the block while it runs
+	 *
+	 * @var string
+	 */
+	protected $buffer = '';
+
+	/**
+	 * buffers input, override only if necessary
+	 *
+	 * @var string $input the content that must be buffered
+	 */
+	public function buffer($input)
+	{
+		$this->buffer .= $input;
+	}
+
+	// initialization code, receives the parameters from {block param1 param2}
+	// public function init($arg, $arg, ...);
+
+	/**
+	 * called when the block ends, this is most of the time followed right away by a call
+	 * of <em>process()</em> but not always, so this should be used to do any shutdown operations on the
+	 * block object, if required.
+	 */
+	public function end()
+	{
+	}
+
+	/**
+	 * called when the block output is required by a parent block
+	 *
+	 * this must read $this->buffer and return it processed
+	 *
+	 * @return string
+	 */
+	public function process()
+	{
+		return $this->buffer;
+	}
+
+	/**
+	 * called at compile time to define what the block should output in the compiled template code, happens when the block is declared
+	 *
+	 * basically this will replace the {block arg arg arg} tag in the template
+	 *
+	 * @param Dwoo_Compiler $compiler the compiler instance that calls this function
+	 * @param array $params an array containing original and compiled parameters
+	 * @param string $prepend that is just meant to allow a child class to call
+	 * 						  parent::postProcessing($compiler, $params, "foo();") to add a command before the
+	 * 						  default commands are executed
+	 * @param string $append that is just meant to allow a child class to call
+	 * 						 parent::postProcessing($compiler, $params, null, "foo();") to add a command after the
+	 * 						 default commands are executed
+	 * @param string $type the type is the plugin class name used
+	 */
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		return Dwoo_Compiler::PHP_OPEN.$prepend.'$this->addStack("'.$type.'", array('.Dwoo_Compiler::implode_r($compiler->getCompiledParams($params)).'));'.$append.Dwoo_Compiler::PHP_CLOSE;
+	}
+
+	/**
+	 * called at compile time to define what the block should output in the compiled template code, happens when the block is ended
+	 *
+	 * basically this will replace the {/block} tag in the template
+	 *
+	 * @see preProcessing
+	 * @param Dwoo_Compiler $compiler the compiler instance that calls this function
+	 * @param array $params an array containing original and compiled parameters, see preProcessing() for more details
+	 * @param string $prepend that is just meant to allow a child class to call
+	 * 						  parent::postProcessing($compiler, $params, "foo();") to add a command before the
+	 * 						  default commands are executed
+	 * @param string $append that is just meant to allow a child class to call
+	 * 						 parent::postProcessing($compiler, $params, null, "foo();") to add a command after the
+	 * 						 default commands are executed
+	 * @param string $content the entire content of the block being closed
+	 */
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		return $content . Dwoo_Compiler::PHP_OPEN.$prepend.'$this->delStack();'.$append.Dwoo_Compiler::PHP_CLOSE;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Compilation/Exception.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Compilation/Exception.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Compilation/Exception.php	(revision 825)
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * dwoo compilation exception class
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Compilation_Exception extends Dwoo_Exception
+{
+	protected $compiler;
+	protected $template;
+
+	public function __construct(Dwoo_Compiler $compiler, $message)
+	{
+		$this->compiler = $compiler;
+		$this->template = $compiler->getDwoo()->getTemplate();
+		parent::__construct('Compilation error at line '.$compiler->getLine().' in "'.$this->template->getResourceName().':'.$this->template->getResourceIdentifier().'" : '.$message);
+	}
+
+	public function getCompiler()
+	{
+		return $this->compiler;
+	}
+
+	public function getTemplate()
+	{
+		return $this->template;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Compiler.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Compiler.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Compiler.php	(revision 825)
@@ -0,0 +1,3154 @@
+<?php
+
+include dirname(__FILE__) . '/Compilation/Exception.php';
+
+/**
+ * default dwoo compiler class, compiles dwoo templates into php
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Compiler implements Dwoo_ICompiler
+{
+	/**
+	 * constant that represents a php opening tag
+	 *
+	 * use it in case it needs to be adjusted
+	 *
+	 * @var string
+	 */
+	const PHP_OPEN = "<?php ";
+
+	/**
+	 * constant that represents a php closing tag
+	 *
+	 * use it in case it needs to be adjusted
+	 *
+	 * @var string
+	 */
+	const PHP_CLOSE = "?>";
+
+	/**
+	 * boolean flag to enable or disable debugging output
+	 *
+	 * @var bool
+	 */
+	public $debug = false;
+
+	/**
+	 * left script delimiter
+	 *
+	 * @var string
+	 */
+	protected $ld = '{';
+
+	/**
+	 * left script delimiter with escaped regex meta characters
+	 *
+	 * @var string
+	 */
+	protected $ldr = '\\{';
+
+	/**
+	 * right script delimiter
+	 *
+	 * @var string
+	 */
+	protected $rd = '}';
+
+	/**
+	 * right script delimiter with escaped regex meta characters
+	 *
+	 * @var string
+	 */
+	protected $rdr = '\\}';
+
+	/**
+	 * defines whether the nested comments should be parsed as nested or not
+	 *
+	 * defaults to false (classic block comment parsing as in all languages)
+	 *
+	 * @var bool
+	 */
+	protected $allowNestedComments = false;
+
+	/**
+	 * defines whether opening and closing tags can contain spaces before valid data or not
+	 *
+	 * turn to true if you want to be sloppy with the syntax, but when set to false it allows
+	 * to skip javascript and css tags as long as they are in the form "{ something", which is
+	 * nice. default is false.
+	 *
+	 * @var bool
+	 */
+	protected $allowLooseOpenings = false;
+
+	/**
+	 * defines whether the compiler will automatically html-escape variables or not
+	 *
+	 * default is false
+	 *
+	 * @var bool
+	 */
+	protected $autoEscape = false;
+
+	/**
+	 * security policy object
+	 *
+	 * @var Dwoo_Security_Policy
+	 */
+	protected $securityPolicy;
+
+	/**
+	 * stores the custom plugins registered with this compiler
+	 *
+	 * @var array
+	 */
+	protected $customPlugins = array();
+
+	/**
+	 * stores the template plugins registered with this compiler
+	 *
+	 * @var array
+	 */
+	protected $templatePlugins = array();
+
+	/**
+	 * stores the pre- and post-processors callbacks
+	 *
+	 * @var array
+	 */
+	protected $processors = array('pre'=>array(), 'post'=>array());
+
+	/**
+	 * stores a list of plugins that are used in the currently compiled
+	 * template, and that are not compilable. these plugins will be loaded
+	 * during the template's runtime if required.
+	 *
+	 * it is a 1D array formatted as key:pluginName value:pluginType
+	 *
+	 * @var array
+	 */
+	protected $usedPlugins;
+
+	/**
+	 * stores the template undergoing compilation
+	 *
+	 * @var string
+	 */
+	protected $template;
+
+	/**
+	 * stores the current pointer position inside the template
+	 *
+	 * @var int
+	 */
+	protected $pointer;
+
+	/**
+	 * stores the current line count inside the template for debugging purposes
+	 *
+	 * @var int
+	 */
+	protected $line;
+
+	/**
+	 * stores the current template source while compiling it
+	 *
+	 * @var string
+	 */
+	protected $templateSource;
+
+	/**
+	 * stores the data within which the scope moves
+	 *
+	 * @var array
+	 */
+	protected $data;
+
+	/**
+	 * variable scope of the compiler, set to null if
+	 * it can not be resolved to a static string (i.e. if some
+	 * plugin defines a new scope based on a variable array key)
+	 *
+	 * @var mixed
+	 */
+	protected $scope;
+
+	/**
+	 * variable scope tree, that allows to rebuild the current
+	 * scope if required, i.e. when going to a parent level
+	 *
+	 * @var array
+	 */
+	protected $scopeTree;
+
+	/**
+	 * block plugins stack, accessible through some methods
+	 *
+	 * @see findBlock
+	 * @see getCurrentBlock
+	 * @see addBlock
+	 * @see addCustomBlock
+	 * @see injectBlock
+	 * @see removeBlock
+	 * @see removeTopBlock
+	 *
+	 * @var array
+	 */
+	protected $stack = array();
+
+	/**
+	 * current block at the top of the block plugins stack,
+	 * accessible through getCurrentBlock
+	 *
+	 * @see getCurrentBlock
+	 *
+	 * @var Dwoo_Block_Plugin
+	 */
+	protected $curBlock;
+
+	/**
+	 * current dwoo object that uses this compiler, or null
+	 *
+	 * @var Dwoo
+	 */
+	protected $dwoo;
+
+	/**
+	 * holds an instance of this class, used by getInstance when you don't
+	 * provide a custom compiler in order to save resources
+	 *
+	 * @var Dwoo_Compiler
+	 */
+	protected static $instance;
+
+	/**
+	 * token types
+	 * @var int
+	 */
+	const T_UNQUOTED_STRING = 1;
+	const T_NUMERIC = 2;
+	const T_NULL = 4;
+	const T_BOOL = 8;
+	const T_MATH = 16;
+	const T_BREAKCHAR = 32;
+
+	/**
+	 * constructor
+	 *
+	 * saves the created instance so that child templates get the same one
+	 */
+	public function __construct()
+	{
+		self::$instance = $this;
+	}
+
+	/**
+	 * sets the delimiters to use in the templates
+	 *
+	 * delimiters can be multi-character strings but should not be one of those as they will
+	 * make it very hard to work with templates or might even break the compiler entirely : "\", "$", "|", ":" and finally "#" only if you intend to use config-vars with the #var# syntax.
+	 *
+	 * @param string $left left delimiter
+	 * @param string $right right delimiter
+	 */
+	public function setDelimiters($left, $right)
+	{
+		$this->ld = $left;
+		$this->rd = $right;
+		$this->ldr = preg_quote($left, '/');
+		$this->rdr = preg_quote($right, '/');
+	}
+
+	/**
+	 * returns the left and right template delimiters
+	 *
+	 * @return array containing the left and the right delimiters
+	 */
+	public function getDelimiters()
+	{
+		return array($this->ld, $this->rd);
+	}
+
+	/**
+	 * sets the way to handle nested comments, if set to true
+	 * {* foo {* some other *} comment *} will be stripped correctly.
+	 *
+	 * if false it will remove {* foo {* some other *} and leave "comment *}" alone,
+	 * this is the default behavior
+	 *
+	 * @param bool $allow allow nested comments or not, defaults to true (but the default internal value is false)
+	 */
+	public function setNestedCommentsHandling($allow = true) {
+		$this->allowNestedComments = (bool) $allow;
+	}
+
+	/**
+	 * returns the nested comments handling setting
+	 *
+	 * @see setNestedCommentsHandling
+	 * @return bool true if nested comments are allowed
+	 */
+	public function getNestedCommentsHandling() {
+		return $this->allowNestedComments;
+	}
+
+	/**
+	 * sets the tag openings handling strictness, if set to true, template tags can
+	 * contain spaces before the first function/string/variable such as { $foo} is valid.
+	 *
+	 * if set to false (default setting), { $foo} is invalid but that is however a good thing
+	 * as it allows css (i.e. #foo { color:red; }) to be parsed silently without triggering
+	 * an error, same goes for javascript.
+	 *
+	 * @param bool $allow true to allow loose handling, false to restore default setting
+	 */
+	public function setLooseOpeningHandling($allow = false)
+	{
+		$this->allowLooseOpenings = (bool) $allow;
+	}
+
+	/**
+	 * returns the tag openings handling strictness setting
+	 *
+	 * @see setLooseOpeningHandling
+	 * @return bool true if loose tags are allowed
+	 */
+	public function getLooseOpeningHandling()
+	{
+		return $this->allowLooseOpenings;
+	}
+
+	/**
+	 * changes the auto escape setting
+	 *
+	 * if enabled, the compiler will automatically html-escape variables,
+	 * unless they are passed through the safe function such as {$var|safe}
+	 * or {safe $var}
+	 *
+	 * default setting is disabled/false
+	 *
+	 * @param bool $enabled set to true to enable, false to disable
+	 */
+	public function setAutoEscape($enabled)
+	{
+		$this->autoEscape = (bool) $enabled;
+	}
+
+	/**
+	 * returns the auto escape setting
+	 *
+	 * default setting is disabled/false
+	 *
+	 * @return bool
+	 */
+	public function getAutoEscape()
+	{
+		return $this->autoEscape;
+	}
+
+	/**
+	 * adds a preprocessor to the compiler, it will be called
+	 * before the template is compiled
+	 *
+	 * @param mixed $callback either a valid callback to the preprocessor or a simple name if the autoload is set to true
+	 * @param bool $autoload if set to true, the preprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
+	 */
+	public function addPreProcessor($callback, $autoload = false)
+	{
+		if ($autoload) {
+			$name = str_replace('Dwoo_Processor_', '', $callback);
+			$class = 'Dwoo_Processor_'.$name;
+
+			if (class_exists($class, false)) {
+				$callback = array(new $class($this), 'process');
+			} elseif (function_exists($class)) {
+				$callback = $class;
+			} else {
+				$callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
+			}
+
+			$this->processors['pre'][] = $callback;
+		} else {
+			$this->processors['pre'][] = $callback;
+		}
+	}
+
+	/**
+	 * removes a preprocessor from the compiler
+	 *
+	 * @param mixed $callback either a valid callback to the preprocessor or a simple name if it was autoloaded
+	 */
+	public function removePreProcessor($callback)
+	{
+		if (($index = array_search($callback, $this->processors['pre'], true)) !== false) {
+			unset($this->processors['pre'][$index]);
+		} elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['pre'], true)) !== false) {
+			unset($this->processors['pre'][$index]);
+		} else {
+			$class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
+			foreach ($this->processors['pre'] as $index=>$proc) {
+				if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
+					unset($this->processors['pre'][$index]);
+					break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * adds a postprocessor to the compiler, it will be called
+	 * before the template is compiled
+	 *
+	 * @param mixed $callback either a valid callback to the postprocessor or a simple name if the autoload is set to true
+	 * @param bool $autoload if set to true, the postprocessor is auto-loaded from one of the plugin directories, else you must provide a valid callback
+	 */
+	public function addPostProcessor($callback, $autoload = false)
+	{
+		if ($autoload) {
+			$name = str_replace('Dwoo_Processor_', '', $callback);
+			$class = 'Dwoo_Processor_'.$name;
+
+			if (class_exists($class, false)) {
+				$callback = array(new $class($this), 'process');
+			} elseif (function_exists($class)) {
+				$callback = $class;
+			} else {
+				$callback = array('autoload'=>true, 'class'=>$class, 'name'=>$name);
+			}
+
+			$this->processors['post'][] = $callback;
+		} else {
+			$this->processors['post'][] = $callback;
+		}
+	}
+
+	/**
+	 * removes a postprocessor from the compiler
+	 *
+	 * @param mixed $callback either a valid callback to the postprocessor or a simple name if it was autoloaded
+	 */
+	public function removePostProcessor($callback)
+	{
+		if (($index = array_search($callback, $this->processors['post'], true)) !== false) {
+			unset($this->processors['post'][$index]);
+		} elseif (($index = array_search('Dwoo_Processor_'.str_replace('Dwoo_Processor_', '', $callback), $this->processors['post'], true)) !== false) {
+			unset($this->processors['post'][$index]);
+		} else	{
+			$class = 'Dwoo_Processor_' . str_replace('Dwoo_Processor_', '', $callback);
+			foreach ($this->processors['post'] as $index=>$proc) {
+				if (is_array($proc) && ($proc[0] instanceof $class) || (isset($proc['class']) && $proc['class'] == $class)) {
+					unset($this->processors['post'][$index]);
+					break;
+				}
+			}
+		}
+	}
+
+	/**
+	 * internal function to autoload processors at runtime if required
+	 *
+	 * @param string $class the class/function name
+	 * @param string $name the plugin name (without Dwoo_Plugin_ prefix)
+	 */
+	protected function loadProcessor($class, $name)
+	{
+		if (!class_exists($class, false) && !function_exists($class)) {
+			try {
+				$this->dwoo->getLoader()->loadPlugin($name);
+			} catch (Dwoo_Exception $e) {
+				throw new Dwoo_Exception('Processor '.$name.' could not be found in your plugin directories, please ensure it is in a file named '.$name.'.php in the plugin directory');
+			}
+		}
+
+		if (class_exists($class, false)) {
+			return array(new $class($this), 'process');
+		}
+
+		if (function_exists($class)) {
+			return $class;
+		}
+
+		throw new Dwoo_Exception('Wrong processor name, when using autoload the processor must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Processor_name"');
+	}
+
+	/**
+	 * adds an used plugin, this is reserved for use by the {template} plugin
+	 *
+	 * this is required so that plugin loading bubbles up from loaded
+	 * template files to the current one
+	 *
+	 * @private
+	 * @param string $name function name
+	 * @param int $type plugin type (Dwoo_Core::*_PLUGIN)
+	 */
+	public function addUsedPlugin($name, $type)
+	{
+		$this->usedPlugins[$name] = $type;
+	}
+
+	/**
+	 * returns all the plugins this template uses
+	 *
+	 * @private
+	 * @return array the list of used plugins in the parsed template
+	 */
+	public function getUsedPlugins()
+	{
+		return $this->usedPlugins;
+	}
+
+	/**
+	 * adds a template plugin, this is reserved for use by the {template} plugin
+	 *
+	 * this is required because the template functions are not declared yet
+	 * during compilation, so we must have a way of validating their argument
+	 * signature without using the reflection api
+	 *
+	 * @private
+	 * @param string $name function name
+	 * @param array $params parameter array to help validate the function call
+	 * @param string $uuid unique id of the function
+	 * @param string $body function php code
+	 */
+	public function addTemplatePlugin($name, array $params, $uuid, $body = null)
+	{
+		$this->templatePlugins[$name] = array('params'=> $params, 'body' => $body, 'uuid' => $uuid);
+	}
+
+	/**
+	 * returns all the parsed sub-templates
+	 *
+	 * @private
+	 * @return array the parsed sub-templates
+	 */
+	public function getTemplatePlugins()
+	{
+		return $this->templatePlugins;
+	}
+
+	/**
+	 * marks a template plugin as being called, which means its source must be included in the compiled template
+	 *
+	 * @param string $name function name
+	 */
+	public function useTemplatePlugin($name)
+	{
+		$this->templatePlugins[$name]['called'] = true;
+	}
+
+	/**
+	 * adds the custom plugins loaded into Dwoo to the compiler so it can load them
+	 *
+	 * @see Dwoo_Core::addPlugin
+	 * @param array $customPlugins an array of custom plugins
+	 */
+	public function setCustomPlugins(array $customPlugins)
+	{
+		$this->customPlugins = $customPlugins;
+	}
+
+	/**
+	 * sets the security policy object to enforce some php security settings
+	 *
+	 * use this if untrusted persons can modify templates,
+	 * set it on the Dwoo object as it will be passed onto the compiler automatically
+	 *
+	 * @param Dwoo_Security_Policy $policy the security policy object
+	 */
+	public function setSecurityPolicy(Dwoo_Security_Policy $policy = null)
+	{
+		$this->securityPolicy = $policy;
+	}
+
+	/**
+	 * returns the current security policy object or null by default
+	 *
+	 * @return Dwoo_Security_Policy|null the security policy object if any
+	 */
+	public function getSecurityPolicy()
+	{
+		return $this->securityPolicy;
+	}
+
+	/**
+	 * sets the pointer position
+	 *
+	 * @param int $position the new pointer position
+	 * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
+	 */
+	public function setPointer($position, $isOffset = false)
+	{
+		if ($isOffset) {
+			$this->pointer += $position;
+		} else {
+			$this->pointer = $position;
+		}
+	}
+
+	/**
+	 * returns the current pointer position, only available during compilation of a template
+	 *
+	 * @return int
+	 */
+	public function getPointer()
+	{
+		return $this->pointer;
+	}
+
+	/**
+	 * sets the line number
+	 *
+	 * @param int $number the new line number
+	 * @param bool $isOffset if set to true, the position acts as an offset and not an absolute position
+	 */
+	public function setLine($number, $isOffset = false)
+	{
+		if ($isOffset) {
+			$this->line += $number;
+		} else {
+			$this->line = $number;
+		}
+	}
+
+	/**
+	 * returns the current line number, only available during compilation of a template
+	 *
+	 * @return int
+	 */
+	public function getLine()
+	{
+		return $this->line;
+	}
+
+	/**
+	 * returns the dwoo object that initiated this template compilation, only available during compilation of a template
+	 *
+	 * @return Dwoo
+	 */
+	public function getDwoo()
+	{
+		return $this->dwoo;
+	}
+
+	/**
+	 * overwrites the template that is being compiled
+	 *
+	 * @param string $newSource the template source that must replace the current one
+	 * @param bool $fromPointer if set to true, only the source from the current pointer position is replaced
+	 * @return string the template or partial template
+	 */
+	public function setTemplateSource($newSource, $fromPointer = false)
+	{
+		if ($fromPointer === true) {
+			$this->templateSource = substr($this->templateSource, 0, $this->pointer) . $newSource;
+		} else {
+			$this->templateSource = $newSource;
+		}
+	}
+
+	/**
+	 * returns the template that is being compiled
+	 *
+	 * @param mixed $fromPointer if set to true, only the source from the current pointer
+	 * 							  position is returned, if a number is given it overrides the current pointer
+	 * @return string the template or partial template
+	 */
+	public function getTemplateSource($fromPointer = false)
+	{
+		if ($fromPointer === true) {
+			return substr($this->templateSource, $this->pointer);
+		} elseif (is_numeric($fromPointer)) {
+			return substr($this->templateSource, $fromPointer);
+		} else {
+			return $this->templateSource;
+		}
+	}
+
+	/**
+	 * resets the compilation pointer, effectively restarting the compilation process
+	 *
+	 * this is useful if a plugin modifies the template source since it might need to be recompiled
+	 */
+	public function recompile()
+	{
+		$this->setPointer(0);
+	}
+
+	/**
+	 * compiles the provided string down to php code
+	 *
+	 * @param string $tpl the template to compile
+	 * @return string a compiled php string
+	 */
+	public function compile(Dwoo_Core $dwoo, Dwoo_ITemplate $template)
+	{
+		// init vars
+		$tpl = $template->getSource();
+		$ptr = 0;
+		$this->dwoo = $dwoo;
+		$this->template = $template;
+		$this->templateSource =& $tpl;
+		$this->pointer =& $ptr;
+
+		while (true) {
+			// if pointer is at the beginning, reset everything, that allows a plugin to externally reset the compiler if everything must be reparsed
+			if ($ptr===0) {
+				// resets variables
+				$this->usedPlugins = array();
+				$this->data = array();
+				$this->scope =& $this->data;
+				$this->scopeTree = array();
+				$this->stack = array();
+				$this->line = 1;
+				$this->templatePlugins = array();
+				// add top level block
+				$compiled = $this->addBlock('topLevelBlock', array(), 0);
+				$this->stack[0]['buffer'] = '';
+
+				if ($this->debug) echo 'COMPILER INIT<br />';
+
+				if ($this->debug) echo 'PROCESSING PREPROCESSORS ('.count($this->processors['pre']).')<br>';
+
+				// runs preprocessors
+				foreach ($this->processors['pre'] as $preProc) {
+					if (is_array($preProc) && isset($preProc['autoload'])) {
+						$preProc = $this->loadProcessor($preProc['class'], $preProc['name']);
+					}
+					if (is_array($preProc) && $preProc[0] instanceof Dwoo_Processor) {
+						$tpl = call_user_func($preProc, $tpl);
+					} else {
+						$tpl = call_user_func($preProc, $this, $tpl);
+					}
+				}
+				unset($preProc);
+
+				// show template source if debug
+				if ($this->debug) echo '<pre>'.print_r(htmlentities($tpl), true).'</pre><hr />';
+
+				// strips php tags if required by the security policy
+				if ($this->securityPolicy !== null) {
+					$search = array('{<\?php.*?\?>}');
+					if (ini_get('short_open_tags')) {
+						$search = array('{<\?.*?\?>}', '{<%.*?%>}');
+					}
+					switch($this->securityPolicy->getPhpHandling()) {
+
+					case Dwoo_Security_Policy::PHP_ALLOW:
+						break;
+					case Dwoo_Security_Policy::PHP_ENCODE:
+						$tpl = preg_replace_callback($search, array($this, 'phpTagEncodingHelper'), $tpl);
+						break;
+					case Dwoo_Security_Policy::PHP_REMOVE:
+						$tpl = preg_replace($search, '', $tpl);
+
+					}
+				}
+			}
+
+			$pos = strpos($tpl, $this->ld, $ptr);
+
+			if ($pos === false) {
+				$this->push(substr($tpl, $ptr), 0);
+				break;
+			} elseif (substr($tpl, $pos-1, 1) === '\\' && substr($tpl, $pos-2, 1) !== '\\') {
+				$this->push(substr($tpl, $ptr, $pos-$ptr-1) . $this->ld);
+				$ptr = $pos+strlen($this->ld);
+			} elseif (preg_match('/^'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . 'literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', substr($tpl, $pos), $litOpen)) {
+				if (!preg_match('/'.$this->ldr . ($this->allowLooseOpenings ? '\s*' : '') . '\/literal' . ($this->allowLooseOpenings ? '\s*' : '') . $this->rdr.'/s', $tpl, $litClose, PREG_OFFSET_CAPTURE, $pos)) {
+					throw new Dwoo_Compilation_Exception($this, 'The {literal} blocks must be closed explicitly with {/literal}');
+				}
+				$endpos = $litClose[0][1];
+				$this->push(substr($tpl, $ptr, $pos-$ptr) . substr($tpl, $pos + strlen($litOpen[0]), $endpos-$pos-strlen($litOpen[0])));
+				$ptr = $endpos+strlen($litClose[0][0]);
+			} else {
+				if (substr($tpl, $pos-2, 1) === '\\' && substr($tpl, $pos-1, 1) === '\\') {
+					$this->push(substr($tpl, $ptr, $pos-$ptr-1));
+					$ptr = $pos;
+				}
+
+				$this->push(substr($tpl, $ptr, $pos-$ptr));
+				$ptr = $pos;
+
+				$pos += strlen($this->ld);
+				if ($this->allowLooseOpenings) {
+					while (substr($tpl, $pos, 1) === ' ') {
+						$pos+=1;
+					}
+				} else {
+					if (substr($tpl, $pos, 1) === ' ' || substr($tpl, $pos, 1) === "\r" || substr($tpl, $pos, 1) === "\n" || substr($tpl, $pos, 1) === "\t") {
+						$ptr = $pos;
+						$this->push($this->ld);
+						continue;
+					}
+				}
+
+				// check that there is an end tag present
+				if (strpos($tpl, $this->rd, $pos) === false) {
+					throw new Dwoo_Compilation_Exception($this, 'A template tag was not closed, started with "'.substr($tpl, $ptr, 30).'"');
+				}
+
+
+				$ptr += strlen($this->ld);
+				$subptr = $ptr;
+
+				while (true) {
+					$parsed = $this->parse($tpl, $subptr, null, false, 'root', $subptr);
+
+					// reload loop if the compiler was reset
+					if ($ptr === 0) {
+						continue 2;
+					}
+
+					$len = $subptr - $ptr;
+					$this->push($parsed, substr_count(substr($tpl, $ptr, $len), "\n"));
+					$ptr += $len;
+
+					if ($parsed === false) {
+						break;
+					}
+				}
+
+				// adds additional line breaks between php closing and opening tags because the php parser removes those if there is just a single line break
+				if (substr($this->curBlock['buffer'], -2) === '?>' && preg_match('{^(([\r\n])([\r\n]?))}', substr($tpl, $ptr, 3), $m)) {
+					if ($m[3] === '') {
+						$ptr+=1;
+						$this->push($m[1].$m[1], 1);
+					} else {
+						$ptr+=2;
+						$this->push($m[1]."\n", 2);
+					}
+				}
+			}
+		}
+
+		$compiled .= $this->removeBlock('topLevelBlock');
+
+		if ($this->debug) echo 'PROCESSING POSTPROCESSORS<br>';
+
+		foreach ($this->processors['post'] as $postProc) {
+			if (is_array($postProc) && isset($postProc['autoload'])) {
+				$postProc = $this->loadProcessor($postProc['class'], $postProc['name']);
+			}
+			if (is_array($postProc) && $postProc[0] instanceof Dwoo_Processor) {
+				$compiled = call_user_func($postProc, $compiled);
+			} else {
+				$compiled = call_user_func($postProc, $this, $compiled);
+			}
+		}
+		unset($postProc);
+
+		if ($this->debug) echo 'COMPILATION COMPLETE : MEM USAGE : '.memory_get_usage().'<br>';
+
+		$output = "<?php\n/* template head */\n";
+
+		// build plugin preloader
+		foreach ($this->usedPlugins as $plugin=>$type) {
+			if ($type & Dwoo_Core::CUSTOM_PLUGIN) {
+				continue;
+			}
+
+			switch($type) {
+
+			case Dwoo_Core::BLOCK_PLUGIN:
+			case Dwoo_Core::CLASS_PLUGIN:
+				$output .= "if (class_exists('Dwoo_Plugin_$plugin', false)===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
+				break;
+			case Dwoo_Core::FUNC_PLUGIN:
+				$output .= "if (function_exists('Dwoo_Plugin_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
+				break;
+			case Dwoo_Core::SMARTY_MODIFIER:
+				$output .= "if (function_exists('smarty_modifier_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
+				break;
+			case Dwoo_Core::SMARTY_FUNCTION:
+				$output .= "if (function_exists('smarty_function_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
+				break;
+			case Dwoo_Core::SMARTY_BLOCK:
+				$output .= "if (function_exists('smarty_block_$plugin')===false)\n\t\$this->getLoader()->loadPlugin('$plugin');\n";
+				break;
+			case Dwoo_Core::PROXY_PLUGIN:
+				$output .= $this->getDwoo()->getPluginProxy()->getPreloader($plugin);
+				break;
+			default:
+				throw new Dwoo_Compilation_Exception($this, 'Type error for '.$plugin.' with type'.$type);
+
+			}
+		}
+
+		foreach ($this->templatePlugins as $function => $attr) {
+			if (isset($attr['called']) && $attr['called'] === true && !isset($attr['checked'])) {
+				$this->resolveSubTemplateDependencies($function);
+			}
+		}
+		foreach ($this->templatePlugins as $function) {
+			if (isset($function['called']) && $function['called'] === true) {
+				$output .= $function['body'].PHP_EOL;
+			}
+		}
+
+		$output .= $compiled."\n?>";
+
+		$output = preg_replace('/(?<!;|\}|\*\/|\n|\{)(\s*'.preg_quote(self::PHP_CLOSE, '/') . preg_quote(self::PHP_OPEN, '/').')/', ";\n", $output);
+		$output = str_replace(self::PHP_CLOSE . self::PHP_OPEN, "\n", $output);
+
+		// handle <?xml tag at the beginning
+		$output = preg_replace('#(/\* template body \*/ \?>\s*)<\?xml#is', '$1<?php echo \'<?xml\'; ?>', $output);
+
+		if ($this->debug) {
+			echo '<hr><pre>';
+			$lines = preg_split('{\r\n|\n|<br />}', highlight_string(($output), true));
+			array_shift($lines);
+			foreach ($lines as $i=>$line) {
+				echo ($i+1).'. '.$line."\r\n";
+			}
+		}
+		if ($this->debug) echo '<hr></pre></pre>';
+
+		$this->template = $this->dwoo = null;
+		$tpl = null;
+
+		return $output;
+	}
+
+	/**
+	 * checks what sub-templates are used in every sub-template so that we're sure they are all compiled
+	 *
+	 * @param string $function the sub-template name
+	 */
+	protected function resolveSubTemplateDependencies($function)
+	{
+		$body = $this->templatePlugins[$function]['body'];
+		foreach ($this->templatePlugins as $func => $attr) {
+			if ($func !== $function && !isset($attr['called']) && strpos($body, 'Dwoo_Plugin_'.$func) !== false) {
+				$this->templatePlugins[$func]['called'] = true;
+				$this->resolveSubTemplateDependencies($func);
+			}
+		}
+		$this->templatePlugins[$function]['checked'] = true;
+	}
+
+	/**
+	 * adds compiled content to the current block
+	 *
+	 * @param string $content the content to push
+	 * @param int $lineCount newlines count in content, optional
+	 */
+	public function push($content, $lineCount = null)
+	{
+		if ($lineCount === null) {
+			$lineCount = substr_count($content, "\n");
+		}
+
+		if ($this->curBlock['buffer'] === null && count($this->stack) > 1) {
+			// buffer is not initialized yet (the block has just been created)
+			$this->stack[count($this->stack)-2]['buffer'] .= (string) $content;
+			$this->curBlock['buffer'] = '';
+		} else {
+			if (!isset($this->curBlock['buffer'])) {
+				throw new Dwoo_Compilation_Exception($this, 'The template has been closed too early, you probably have an extra block-closing tag somewhere');
+			}
+			// append current content to current block's buffer
+			$this->curBlock['buffer'] .= (string) $content;
+		}
+		$this->line += $lineCount;
+	}
+
+	/**
+	 * sets the scope
+	 *
+	 * set to null if the scope becomes "unstable" (i.e. too variable or unknown) so that
+	 * variables are compiled in a more evaluative way than just $this->scope['key']
+	 *
+	 * @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
+	 * @param bool $absolute if true, the scope is set from the top level scope and not from the current scope
+	 * @return array the current scope tree
+	 */
+	public function setScope($scope, $absolute = false)
+	{
+		$old = $this->scopeTree;
+
+		if ($scope===null) {
+			unset($this->scope);
+			$this->scope = null;
+		}
+
+		if (is_array($scope)===false) {
+			$scope = explode('.', $scope);
+		}
+
+		if ($absolute===true) {
+			$this->scope =& $this->data;
+			$this->scopeTree = array();
+		}
+
+		while (($bit = array_shift($scope)) !== null) {
+			if ($bit === '_parent' || $bit === '_') {
+				array_pop($this->scopeTree);
+				reset($this->scopeTree);
+				$this->scope =& $this->data;
+				$cnt = count($this->scopeTree);
+				for ($i=0;$i<$cnt;$i++)
+					$this->scope =& $this->scope[$this->scopeTree[$i]];
+			} elseif ($bit === '_root' || $bit === '__') {
+				$this->scope =& $this->data;
+				$this->scopeTree = array();
+			} elseif (isset($this->scope[$bit])) {
+				$this->scope =& $this->scope[$bit];
+				$this->scopeTree[] = $bit;
+			} else {
+				$this->scope[$bit] = array();
+				$this->scope =& $this->scope[$bit];
+				$this->scopeTree[] = $bit;
+			}
+		}
+
+		return $old;
+	}
+
+	/**
+	 * adds a block to the top of the block stack
+	 *
+	 * @param string $type block type (name)
+	 * @param array $params the parameters array
+	 * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
+	 * @return string the preProcessing() method's output
+	 */
+	public function addBlock($type, array $params, $paramtype)
+	{
+		$class = 'Dwoo_Plugin_'.$type;
+		if (class_exists($class, false) === false) {
+			$this->dwoo->getLoader()->loadPlugin($type);
+		}
+
+		$params = $this->mapParams($params, array($class, 'init'), $paramtype);
+
+		$this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
+		$this->curBlock =& $this->stack[count($this->stack)-1];
+		return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
+	}
+
+	/**
+	 * adds a custom block to the top of the block stack
+	 *
+	 * @param string $type block type (name)
+	 * @param array $params the parameters array
+	 * @param int $paramtype the parameters type (see mapParams), 0, 1 or 2
+	 * @return string the preProcessing() method's output
+	 */
+	public function addCustomBlock($type, array $params, $paramtype)
+	{
+		$callback = $this->customPlugins[$type]['callback'];
+		if (is_array($callback)) {
+			$class = is_object($callback[0]) ? get_class($callback[0]) : $callback[0];
+		} else {
+			$class = $callback;
+		}
+
+		$params = $this->mapParams($params, array($class, 'init'), $paramtype);
+
+		$this->stack[] = array('type' => $type, 'params' => $params, 'custom' => true, 'class' => $class, 'buffer' => null);
+		$this->curBlock =& $this->stack[count($this->stack)-1];
+		return call_user_func(array($class,'preProcessing'), $this, $params, '', '', $type);
+	}
+
+	/**
+	 * injects a block at the top of the plugin stack without calling its preProcessing method
+	 *
+	 * used by {else} blocks to re-add themselves after having closed everything up to their parent
+	 *
+	 * @param string $type block type (name)
+	 * @param array $params parameters array
+	 */
+	public function injectBlock($type, array $params)
+	{
+		$class = 'Dwoo_Plugin_'.$type;
+		if (class_exists($class, false) === false) {
+			$this->dwoo->getLoader()->loadPlugin($type);
+		}
+		$this->stack[] = array('type' => $type, 'params' => $params, 'custom' => false, 'class' => $class, 'buffer' => null);
+		$this->curBlock =& $this->stack[count($this->stack)-1];
+	}
+
+	/**
+	 * removes the closest-to-top block of the given type and all other
+	 * blocks encountered while going down the block stack
+	 *
+	 * @param string $type block type (name)
+	 * @return string the output of all postProcessing() method's return values of the closed blocks
+	 */
+	public function removeBlock($type)
+	{
+		$output = '';
+
+		$pluginType = $this->getPluginType($type);
+		if ($pluginType & Dwoo_Core::SMARTY_BLOCK) {
+			$type = 'smartyinterface';
+		}
+		while (true) {
+			while ($top = array_pop($this->stack)) {
+				if ($top['custom']) {
+					$class = $top['class'];
+				} else {
+					$class = 'Dwoo_Plugin_'.$top['type'];
+				}
+				if (count($this->stack)) {
+					$this->curBlock =& $this->stack[count($this->stack)-1];
+					$this->push(call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']), 0);
+				} else {
+					$null = null;
+					$this->curBlock =& $null;
+					$output = call_user_func(array($class, 'postProcessing'), $this, $top['params'], '', '', $top['buffer']);
+				}
+
+				if ($top['type'] === $type) {
+					break 2;
+				}
+			}
+
+			throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of type "'.$type.'" was closed but was not opened');
+			break;
+		}
+
+		return $output;
+	}
+
+	/**
+	 * returns a reference to the first block of the given type encountered and
+	 * optionally closes all blocks until it finds it
+	 *
+	 * this is mainly used by {else} plugins to close everything that was opened
+	 * between their parent and themselves
+	 *
+	 * @param string $type the block type (name)
+	 * @param bool $closeAlong whether to close all blocks encountered while going down the block stack or not
+	 * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
+	 * 				  'custom'=>bool defining whether it's a custom plugin or not, for internal use)
+	 */
+	public function &findBlock($type, $closeAlong = false)
+	{
+		if ($closeAlong===true) {
+			while ($b = end($this->stack)) {
+				if ($b['type']===$type) {
+					return $this->stack[key($this->stack)];
+				}
+				$this->push($this->removeTopBlock(), 0);
+			}
+		} else {
+			end($this->stack);
+			while ($b = current($this->stack)) {
+				if ($b['type']===$type) {
+					return $this->stack[key($this->stack)];
+				}
+				prev($this->stack);
+			}
+		}
+
+		throw new Dwoo_Compilation_Exception($this, 'A parent block of type "'.$type.'" is required and can not be found');
+	}
+
+	/**
+	 * returns a reference to the current block array
+	 *
+	 * @return &array the array is as such: array('type'=>pluginName, 'params'=>parameter array,
+	 * 				  'custom'=>bool defining whether it's a custom plugin or not, for internal use)
+	 */
+	public function &getCurrentBlock()
+	{
+		return $this->curBlock;
+	}
+
+	/**
+	 * removes the block at the top of the stack and calls its postProcessing() method
+	 *
+	 * @return string the postProcessing() method's output
+	 */
+	public function removeTopBlock()
+	{
+		$o = array_pop($this->stack);
+		if ($o === null) {
+			throw new Dwoo_Compilation_Exception($this, 'Syntax malformation, a block of unknown type was closed but was not opened.');
+		}
+		if ($o['custom']) {
+			$class = $o['class'];
+		} else {
+			$class = 'Dwoo_Plugin_'.$o['type'];
+		}
+
+		$this->curBlock =& $this->stack[count($this->stack)-1];
+
+		return call_user_func(array($class, 'postProcessing'), $this, $o['params'], '', '', $o['buffer']);
+	}
+
+	/**
+	 * returns the compiled parameters (for example a variable's compiled parameter will be "$this->scope['key']") out of the given parameter array
+	 *
+	 * @param array $params parameter array
+	 * @return array filtered parameters
+	 */
+	public function getCompiledParams(array $params)
+	{
+		foreach ($params as $k=>$p) {
+			if (is_array($p)) {
+				$params[$k] = $p[0];
+			}
+		}
+		return $params;
+	}
+
+	/**
+	 * returns the real parameters (for example a variable's real parameter will be its key, etc) out of the given parameter array
+	 *
+	 * @param array $params parameter array
+	 * @return array filtered parameters
+	 */
+	public function getRealParams(array $params)
+	{
+		foreach ($params as $k=>$p) {
+			if (is_array($p)) {
+				$params[$k] = $p[1];
+			}
+		}
+		return $params;
+	}
+
+	/**
+	 * returns the token of each parameter out of the given parameter array
+	 *
+	 * @param array $params parameter array
+	 * @return array tokens
+	 */
+	public function getParamTokens(array $params)
+	{
+		foreach ($params as $k=>$p) {
+			if (is_array($p)) {
+				$params[$k] = isset($p[2]) ? $p[2] : 0;
+			}
+		}
+		return $params;
+	}
+
+	/**
+	 * entry point of the parser, it redirects calls to other parse* functions
+	 *
+	 * @param string $in the string within which we must parse something
+	 * @param int $from the starting offset of the parsed area
+	 * @param int $to the ending offset of the parsed area
+	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
+	 * @param string $curBlock the current parser-block being processed
+	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
+	 * @return string parsed values
+	 */
+	protected function parse($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
+	{
+		if ($to === null) {
+			$to = strlen($in);
+		}
+		$first = substr($in, $from, 1);
+
+		if ($first === false) {
+			throw new Dwoo_Compilation_Exception($this, 'Unexpected EOF, a template tag was not closed');
+		}
+
+		while ($first===" " || $first==="\n" || $first==="\t" || $first==="\r") {
+			if ($curBlock === 'root' && substr($in, $from, strlen($this->rd)) === $this->rd) {
+				// end template tag
+				$pointer += strlen($this->rd);
+				if ($this->debug) echo 'TEMPLATE PARSING ENDED<br />';
+				return false;
+			}
+			$from++;
+			if ($pointer !== null) {
+				$pointer++;
+			}
+			if ($from >= $to) {
+				if (is_array($parsingParams)) {
+					return $parsingParams;
+				} else {
+					return '';
+				}
+			}
+			$first = $in[$from];
+		}
+
+		$substr = substr($in, $from, $to-$from);
+
+		if ($this->debug) echo '<br />PARSE CALL : PARSING "<b>'.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').'</b>" @ '.$from.':'.$to.' in '.$curBlock.' : pointer='.$pointer.'<br/>';
+		$parsed = "";
+
+		if ($curBlock === 'root' && $first === '*') {
+			$src = $this->getTemplateSource();
+			$startpos = $this->getPointer() - strlen($this->ld);
+			if (substr($src, $startpos, strlen($this->ld)) === $this->ld) {
+				if ($startpos > 0) {
+					do {
+						$char = substr($src, --$startpos, 1);
+						if ($char == "\n") {
+							$startpos++;
+							$whitespaceStart = true;
+							break;
+						}
+					} while ($startpos > 0 && ($char == ' ' || $char == "\t"));
+				}
+
+				if (!isset($whitespaceStart)) {
+					$startpos = $this->getPointer();
+				} else {
+					$pointer -= $this->getPointer() - $startpos;
+				}
+
+				if ($this->allowNestedComments && strpos($src, $this->ld.'*', $this->getPointer()) !== false) {
+					$comOpen = $this->ld.'*';
+					$comClose = '*'.$this->rd;
+					$level = 1;
+					$start = $startpos;
+					$ptr = $this->getPointer() + '*';
+
+					while ($level > 0 && $ptr < strlen($src)) {
+						$open = strpos($src, $comOpen, $ptr);
+						$close = strpos($src, $comClose, $ptr);
+
+						if ($open !== false && $close !== false) {
+							if ($open < $close) {
+								$ptr = $open + strlen($comOpen);
+								$level++;
+							} else {
+								$ptr = $close + strlen($comClose);
+								$level--;
+							}
+						} elseif ($open !== false) {
+							$ptr = $open + strlen($comOpen);
+							$level++;
+						} elseif ($close !== false) {
+							$ptr = $close + strlen($comClose);
+							$level--;
+						} else {
+							$ptr = strlen($src);
+						}
+					}
+					$endpos = $ptr - strlen('*'.$this->rd);
+				} else {
+					$endpos = strpos($src, '*'.$this->rd, $startpos);
+					if ($endpos == false) {
+						throw new Dwoo_Compilation_Exception($this, 'Un-ended comment');
+					}
+				}
+				$pointer += $endpos - $startpos + strlen('*'.$this->rd);
+				if (isset($whitespaceStart) && preg_match('#^[\t ]*\r?\n#', substr($src, $endpos+strlen('*'.$this->rd)), $m)) {
+					$pointer += strlen($m[0]);
+					$this->curBlock['buffer'] = substr($this->curBlock['buffer'], 0, strlen($this->curBlock['buffer']) - ($this->getPointer() - $startpos - strlen($this->ld)));
+				}
+				return false;
+			}
+		}
+
+		if ($first==='$') {
+			// var
+			$out = $this->parseVar($in, $from, $to, $parsingParams, $curBlock, $pointer);
+			$parsed = 'var';
+		} elseif ($first==='%' && preg_match('#^%[a-z_]#i', $substr)) {
+			// const
+			$out = $this->parseConst($in, $from, $to, $parsingParams, $curBlock, $pointer);
+		} elseif (($first==='"' || $first==="'") && !(is_array($parsingParams) && preg_match('#^([\'"])[a-z0-9_]+\1\s*=>?(?:\s+|[^=])#i', $substr))) {
+			// string
+			$out = $this->parseString($in, $from, $to, $parsingParams, $curBlock, $pointer);
+		} elseif (preg_match('/^\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?('.(is_array($parsingParams)||$curBlock!='root'?'':'\s+[^(]|').'\s*\(|\s*'.$this->rdr.'|\s*;)/i', $substr)) {
+			// func
+			$out = $this->parseFunction($in, $from, $to, $parsingParams, $curBlock, $pointer);
+			$parsed = 'func';
+		} elseif ($first === ';') {
+			// instruction end
+			if ($this->debug) echo 'END OF INSTRUCTION<br />';
+			if ($pointer !== null) {
+				$pointer++;
+			}
+			return $this->parse($in, $from+1, $to, false, 'root', $pointer);
+		} elseif ($curBlock === 'root' && preg_match('#^/([a-z_][a-z0-9_]*)?#i', $substr, $match)) {
+			// close block
+			if (!empty($match[1]) && $match[1] == 'else') {
+				throw new Dwoo_Compilation_Exception($this, 'Else blocks must not be closed explicitly, they are automatically closed when their parent block is closed');
+			}
+			if (!empty($match[1]) && $match[1] == 'elseif') {
+				throw new Dwoo_Compilation_Exception($this, 'Elseif blocks must not be closed explicitly, they are automatically closed when their parent block is closed or a new else/elseif block is declared after them');
+			}
+			if ($pointer !== null) {
+				$pointer += strlen($match[0]);
+			}
+			if (empty($match[1])) {
+				if ($this->curBlock['type'] == 'else' || $this->curBlock['type'] == 'elseif') {
+					$pointer -= strlen($match[0]);
+				}
+				if ($this->debug) echo 'TOP BLOCK CLOSED<br />';
+				return $this->removeTopBlock();
+			} else {
+				if ($this->debug) echo 'BLOCK OF TYPE '.$match[1].' CLOSED<br />';
+				return $this->removeBlock($match[1]);
+			}
+		} elseif ($curBlock === 'root' && substr($substr, 0, strlen($this->rd)) === $this->rd) {
+			// end template tag
+			if ($this->debug) echo 'TAG PARSING ENDED<br />';
+			$pointer += strlen($this->rd);
+			return false;
+		} elseif (is_array($parsingParams) && preg_match('#^(([\'"]?)[a-z0-9_]+\2\s*='.($curBlock === 'array' ? '>?':'').')(?:\s+|[^=]).*#i', $substr, $match)) {
+			// named parameter
+			if ($this->debug) echo 'NAMED PARAM FOUND<br />';
+			$len = strlen($match[1]);
+			while (substr($in, $from+$len, 1)===' ') {
+				$len++;
+			}
+			if ($pointer !== null) {
+				$pointer += $len;
+			}
+
+			$output = array(trim($match[1], " \t\r\n=>'\""), $this->parse($in, $from+$len, $to, false, 'namedparam', $pointer));
+
+			$parsingParams[] = $output;
+			return $parsingParams;
+		} elseif (preg_match('#^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*::\$[a-z0-9_]+)#i', $substr, $match)) {
+			// static member access
+			$parsed = 'var';
+			if (is_array($parsingParams)) {
+				$parsingParams[] = array($match[1], $match[1]);
+				$out = $parsingParams;
+			} else {
+				$out = $match[1];
+			}
+			$pointer += strlen($match[1]);
+		} elseif ($substr!=='' && (is_array($parsingParams) || $curBlock === 'namedparam' || $curBlock === 'condition' || $curBlock === 'expression')) {
+			// unquoted string, bool or number
+			$out = $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
+		} else {
+			// parse error
+			throw new Dwoo_Compilation_Exception($this, 'Parse error in "'.substr($in, $from, $to-$from).'"');
+		}
+
+		if (empty($out)) {
+			return '';
+		}
+
+		$substr = substr($in, $pointer, $to-$pointer);
+
+		// var parsed, check if any var-extension applies
+		if ($parsed==='var') {
+			if (preg_match('#^\s*([/%+*-])\s*([a-z0-9]|\$)#i', $substr, $match)) {
+				if($this->debug) echo 'PARSING POST-VAR EXPRESSION '.$substr.'<br />';
+				// parse expressions
+				$pointer += strlen($match[0]) - 1;
+				if (is_array($parsingParams)) {
+					if ($match[2] == '$') {
+						$expr = $this->parseVar($in, $pointer, $to, array(), $curBlock, $pointer);
+					} else {
+						$expr = $this->parse($in, $pointer, $to, array(), 'expression', $pointer);
+					}
+					$out[count($out)-1][0] .= $match[1] . $expr[0][0];
+					$out[count($out)-1][1] .= $match[1] . $expr[0][1];
+				} else {
+					if ($match[2] == '$') {
+						$expr = $this->parseVar($in, $pointer, $to, false, $curBlock, $pointer);
+					} else {
+						$expr = $this->parse($in, $pointer, $to, false, 'expression', $pointer);
+					}
+					if (is_array($out) && is_array($expr)) {
+						$out[0] .= $match[1] . $expr[0];
+						$out[1] .= $match[1] . $expr[1];
+					} elseif (is_array($out)) {
+						$out[0] .= $match[1] . $expr;
+						$out[1] .= $match[1] . $expr;
+					} elseif (is_array($expr)) {
+						$out .= $match[1] . $expr[0];
+					} else {
+						$out .= $match[1] . $expr;
+					}
+				}
+			} else if ($curBlock === 'root' && preg_match('#^(\s*(?:[+/*%-.]=|=|\+\+|--)\s*)(.*)#s', $substr, $match)) {
+				if($this->debug) echo 'PARSING POST-VAR ASSIGNMENT '.$substr.'<br />';
+				// parse assignment
+				$value = $match[2];
+				$operator = trim($match[1]);
+				if (substr($value, 0, 1) == '=') {
+					throw new Dwoo_Compilation_Exception($this, 'Unexpected "=" in <em>'.$substr.'</em>');
+				}
+
+				if ($pointer !== null) {
+					$pointer += strlen($match[1]);
+				}
+
+				if ($operator !== '++' && $operator !== '--') {
+					$parts = array();
+					$ptr = 0;
+					$parts = $this->parse($value, 0, strlen($value), $parts, 'condition', $ptr);
+					$pointer += $ptr;
+
+					// load if plugin
+					try {
+						$this->getPluginType('if');
+					} catch (Dwoo_Exception $e) {
+						throw new Dwoo_Compilation_Exception($this, 'Assignments require the "if" plugin to be accessible');
+					}
+
+					$parts = $this->mapParams($parts, array('Dwoo_Plugin_if', 'init'), 1);
+					$tokens = $this->getParamTokens($parts);
+					$parts = $this->getCompiledParams($parts);
+
+					$value = Dwoo_Plugin_if::replaceKeywords($parts['*'], $tokens['*'], $this);
+					$echo = '';
+				} else {
+					$value = array();
+					$echo = 'echo ';
+				}
+
+				if ($this->autoEscape) {
+					$out = preg_replace('#\(is_string\(\$tmp=(.+?)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $out);
+				}
+				$out = Dwoo_Compiler::PHP_OPEN. $echo . $out . $operator . implode(' ', $value) . Dwoo_Compiler::PHP_CLOSE;
+			} else if ($curBlock === 'array' && is_array($parsingParams) && preg_match('#^(\s*=>?\s*)#', $substr, $match)) {
+				// parse namedparam with var as name (only for array)
+				if ($this->debug) echo 'VARIABLE NAMED PARAM (FOR ARRAY) FOUND<br />';
+				$len = strlen($match[1]);
+				$var = $out[count($out)-1];
+				$pointer += $len;
+
+				$output = array($var[0], $this->parse($substr, $len, null, false, 'namedparam', $pointer));
+
+				$parsingParams[] = $output;
+				return $parsingParams;
+			}
+		}
+
+		if ($curBlock !== 'modifier' && ($parsed === 'func' || $parsed === 'var') && preg_match('#^\|@?[a-z0-9_]+(:.*)?#i', $substr, $match)) {
+			// parse modifier on funcs or vars
+			$srcPointer = $pointer;
+			if (is_array($parsingParams)) {
+				$tmp = $this->replaceModifiers(array(null, null, $out[count($out)-1][0], $match[0]), 'var', $pointer);
+				$out[count($out)-1][0] = $tmp;
+				$out[count($out)-1][1] .= substr($substr, $srcPointer, $srcPointer - $pointer);
+			} else {
+				$out = $this->replaceModifiers(array(null, null, $out, $match[0]), 'var', $pointer);
+			}
+		}
+
+		// func parsed, check if any func-extension applies
+		if ($parsed==='func' && preg_match('#^->[a-z0-9_]+(\s*\(.+|->[a-z_].*)?#is', $substr, $match)) {
+			// parse method call or property read
+			$ptr = 0;
+
+			if (is_array($parsingParams)) {
+				$output = $this->parseMethodCall($out[count($out)-1][1], $match[0], $curBlock, $ptr);
+
+				$out[count($out)-1][0] = $output;
+				$out[count($out)-1][1] .= substr($match[0], 0, $ptr);
+			} else {
+				$out = $this->parseMethodCall($out, $match[0], $curBlock, $ptr);
+			}
+
+			$pointer += $ptr;
+		}
+
+		if ($curBlock === 'root' && substr($out, 0, strlen(self::PHP_OPEN)) !== self::PHP_OPEN) {
+			return self::PHP_OPEN .'echo '.$out.';'. self::PHP_CLOSE;
+		} else {
+			return $out;
+		}
+	}
+
+	/**
+	 * parses a function call
+	 *
+	 * @param string $in the string within which we must parse something
+	 * @param int $from the starting offset of the parsed area
+	 * @param int $to the ending offset of the parsed area
+	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
+	 * @param string $curBlock the current parser-block being processed
+	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
+	 * @return string parsed values
+	 */
+	protected function parseFunction($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
+	{
+		$cmdstr = substr($in, $from, $to-$from);
+		preg_match('/^(\\\\?[a-z_](?:\\\\?[a-z0-9_]+)*(?:::[a-z_][a-z0-9_]*)?)(\s*'.$this->rdr.'|\s*;)?/i', $cmdstr, $match);
+
+		if (empty($match[1])) {
+			throw new Dwoo_Compilation_Exception($this, 'Parse error, invalid function name : '.substr($cmdstr, 0, 15));
+		}
+
+		$func = $match[1];
+
+		if (!empty($match[2])) {
+			$cmdstr = $match[1];
+		}
+
+		if ($this->debug) echo 'FUNC FOUND ('.$func.')<br />';
+
+		$paramsep = '';
+
+		if (is_array($parsingParams) || $curBlock != 'root') {
+			$paramspos = strpos($cmdstr, '(');
+			$paramsep = ')';
+		} elseif(preg_match_all('#^\s*[\\\\:a-z0-9_]+(\s*\(|\s+[^(])#i', $cmdstr, $match, PREG_OFFSET_CAPTURE)) {
+			$paramspos = $match[1][0][1];
+			$paramsep = substr($match[1][0][0], -1) === '(' ? ')':'';
+			if($paramsep === ')') {
+				$paramspos += strlen($match[1][0][0]) - 1;
+				if(substr($cmdstr, 0, 2) === 'if' || substr($cmdstr, 0, 6) === 'elseif') {
+					$paramsep = '';
+					if(strlen($match[1][0][0]) > 1) {
+						$paramspos--;
+					}
+				}
+			}
+		} else {
+			$paramspos = false;
+		}
+
+		$state = 0;
+
+		if ($paramspos === false) {
+			$params = array();
+
+			if ($curBlock !== 'root') {
+				return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
+			}
+		} else {
+			if ($curBlock === 'condition') {
+				// load if plugin
+				$this->getPluginType('if');
+
+				if (Dwoo_Plugin_if::replaceKeywords(array($func), array(self::T_UNQUOTED_STRING), $this) !== array($func)) {
+					return $this->parseOthers($in, $from, $to, $parsingParams, $curBlock, $pointer);
+				}
+			}
+			$whitespace = strlen(substr($cmdstr, strlen($func), $paramspos-strlen($func)));
+			$paramstr = substr($cmdstr, $paramspos+1);
+			if (substr($paramstr, -1, 1) === $paramsep) {
+				$paramstr = substr($paramstr, 0, -1);
+			}
+
+			if (strlen($paramstr)===0) {
+				$params = array();
+				$paramstr = '';
+			} else {
+				$ptr = 0;
+				$params = array();
+				if ($func === 'empty') {
+					$params = $this->parseVar($paramstr, $ptr, strlen($paramstr), $params, 'root', $ptr);
+				} else {
+					while ($ptr < strlen($paramstr)) {
+						while (true) {
+							if ($ptr >= strlen($paramstr)) {
+								break 2;
+							}
+
+							if ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === ')') {
+								if ($this->debug) echo 'PARAM PARSING ENDED, ")" FOUND, POINTER AT '.$ptr.'<br/>';
+								break 2;
+							} elseif ($paramstr[$ptr] === ';') {
+								$ptr++;
+								if ($this->debug) echo 'PARAM PARSING ENDED, ";" FOUND, POINTER AT '.$ptr.'<br/>';
+								break 2;
+							} elseif ($func !== 'if' && $func !== 'elseif' && $paramstr[$ptr] === '/') {
+								if ($this->debug) echo 'PARAM PARSING ENDED, "/" FOUND, POINTER AT '.$ptr.'<br/>';
+								break 2;
+							} elseif (substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
+								if ($this->debug) echo 'PARAM PARSING ENDED, RIGHT DELIMITER FOUND, POINTER AT '.$ptr.'<br/>';
+								break 2;
+							}
+
+							if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === ',' || $paramstr[$ptr] === "\r" || $paramstr[$ptr] === "\n" || $paramstr[$ptr] === "\t") {
+								$ptr++;
+							} else {
+								break;
+							}
+						}
+
+						if ($this->debug) echo 'FUNC START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>';
+
+						if ($func === 'if' || $func === 'elseif' || $func === 'tif') {
+							$params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'condition', $ptr);
+						} elseif ($func === 'array') {
+							$params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'array', $ptr);
+						} else {
+							$params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'function', $ptr);
+						}
+
+						if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.' ('.substr($paramstr, $ptr-1, 3).')<br/>';
+					}
+				}
+				$paramstr = substr($paramstr, 0, $ptr);
+				$state = 0;
+				foreach ($params as $k=>$p) {
+					if (is_array($p) && is_array($p[1])) {
+						$state |= 2;
+					} else {
+						if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m) && $func !== 'array') {
+							$params[$k] = array($m[2], array('true', 'true'));
+						} else {
+							if ($state & 2 && $func !== 'array') {
+								throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one');
+							}
+							$state |= 1;
+						}
+					}
+				}
+			}
+		}
+
+		if ($pointer !== null) {
+			$pointer += (isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func) + (isset($whitespace) ? $whitespace : 0);
+			if ($this->debug) echo 'FUNC ADDS '.((isset($paramstr) ? strlen($paramstr) : 0) + (')' === $paramsep ? 2 : ($paramspos === false ? 0 : 1)) + strlen($func)).' TO POINTER<br/>';
+		}
+
+		if ($curBlock === 'method' || $func === 'do' || strstr($func, '::') !== false) {
+			// handle static method calls with security policy
+			if (strstr($func, '::') !== false && $this->securityPolicy !== null && $this->securityPolicy->isMethodAllowed(explode('::', strtolower($func))) !== true) {
+				throw new Dwoo_Security_Exception('Call to a disallowed php function : '.$func);
+			}
+			$pluginType = Dwoo::NATIVE_PLUGIN;
+		} else {
+			$pluginType = $this->getPluginType($func);
+		}
+
+		// blocks
+		if ($pluginType & Dwoo_Core::BLOCK_PLUGIN) {
+			if ($curBlock !== 'root' || is_array($parsingParams)) {
+				throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments');
+			}
+			if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+				return $this->addCustomBlock($func, $params, $state);
+			} else {
+				return $this->addBlock($func, $params, $state);
+			}
+		} elseif ($pluginType & Dwoo_Core::SMARTY_BLOCK) {
+			if ($curBlock !== 'root' || is_array($parsingParams)) {
+				throw new Dwoo_Compilation_Exception($this, 'Block plugins can not be used as other plugin\'s arguments');
+			}
+
+			if ($state & 2) {
+				array_unshift($params, array('__functype', array($pluginType, $pluginType)));
+				array_unshift($params, array('__funcname', array($func, $func)));
+			} else {
+				array_unshift($params, array($pluginType, $pluginType));
+				array_unshift($params, array($func, $func));
+			}
+
+			return $this->addBlock('smartyinterface', $params, $state);
+		}
+
+		// funcs
+		if ($pluginType & Dwoo_Core::NATIVE_PLUGIN || $pluginType & Dwoo_Core::SMARTY_FUNCTION || $pluginType & Dwoo_Core::SMARTY_BLOCK) {
+			$params = $this->mapParams($params, null, $state);
+		} elseif ($pluginType & Dwoo_Core::CLASS_PLUGIN) {
+			if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+				$params = $this->mapParams($params, array($this->customPlugins[$func]['class'], $this->customPlugins[$func]['function']), $state);
+			} else {
+				$params = $this->mapParams($params, array('Dwoo_Plugin_'.$func, ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? 'compile' : 'process'), $state);
+			}
+		} elseif ($pluginType & Dwoo_Core::FUNC_PLUGIN) {
+			if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+				$params = $this->mapParams($params, $this->customPlugins[$func]['callback'], $state);
+			} else {
+				$params = $this->mapParams($params, 'Dwoo_Plugin_'.$func.(($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? '_compile' : ''), $state);
+			}
+		} elseif ($pluginType & Dwoo_Core::SMARTY_MODIFIER) {
+			$output = 'smarty_modifier_'.$func.'('.implode(', ', $params).')';
+		} elseif ($pluginType & Dwoo_Core::PROXY_PLUGIN) {
+			$params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
+		} elseif ($pluginType & Dwoo_Core::TEMPLATE_PLUGIN) {
+			// transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
+			$map = array();
+			foreach ($this->templatePlugins[$func]['params'] as $param=>$defValue) {
+				if ($param == 'rest') {
+					$param = '*';
+				}
+				$hasDefault = $defValue !== null;
+				if ($defValue === 'null') {
+					$defValue = null;
+				} elseif ($defValue === 'false') {
+					$defValue = false;
+				} elseif ($defValue === 'true') {
+					$defValue = true;
+				} elseif (preg_match('#^([\'"]).*?\1$#', $defValue)) {
+					$defValue = substr($defValue, 1, -1);
+				}
+				$map[] = array($param, $hasDefault, $defValue);
+			}
+
+			$params = $this->mapParams($params, null, $state, $map);
+		}
+
+		// only keep php-syntax-safe values for non-block plugins
+		$tokens = array();
+		foreach ($params as $k => $p) {
+			$tokens[$k] = isset($p[2]) ? $p[2] : 0;
+			$params[$k] = $p[0];
+		}
+		if ($pluginType & Dwoo_Core::NATIVE_PLUGIN) {
+			if ($func === 'do') {
+				if (isset($params['*'])) {
+					$output = implode(';', $params['*']).';';
+				} else {
+					$output = '';
+				}
+
+				if (is_array($parsingParams) || $curBlock !== 'root') {
+					throw new Dwoo_Compilation_Exception($this, 'Do can not be used inside another function or block');
+				} else {
+					return self::PHP_OPEN.$output.self::PHP_CLOSE;
+				}
+			} else {
+				if (isset($params['*'])) {
+					$output = $func.'('.implode(', ', $params['*']).')';
+				} else {
+					$output = $func.'()';
+				}
+			}
+		} elseif ($pluginType & Dwoo_Core::FUNC_PLUGIN) {
+			if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) {
+				if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+					$funcCompiler = $this->customPlugins[$func]['callback'];
+				} else {
+					$funcCompiler = 'Dwoo_Plugin_'.$func.'_compile';
+				}
+				array_unshift($params, $this);
+				if ($func === 'tif') {
+					$params[] = $tokens;
+				}
+				$output = call_user_func_array($funcCompiler, $params);
+			} else {
+				array_unshift($params, '$this');
+				$params = self::implode_r($params);
+
+				if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+					$callback = $this->customPlugins[$func]['callback'];
+					$output = 'call_user_func(\''.$callback.'\', '.$params.')';
+				} else {
+					$output = 'Dwoo_Plugin_'.$func.'('.$params.')';
+				}
+			}
+		} elseif ($pluginType & Dwoo_Core::CLASS_PLUGIN) {
+			if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) {
+				if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+					$callback = $this->customPlugins[$func]['callback'];
+					if (!is_array($callback)) {
+						if (!method_exists($callback, 'compile')) {
+							throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use');
+						}
+						if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
+							$funcCompiler = array($callback, 'compile');
+						} else {
+							$funcCompiler = array(new $callback, 'compile');
+						}
+					} else {
+						$funcCompiler = $callback;
+					}
+				} else {
+					$funcCompiler = array('Dwoo_Plugin_'.$func, 'compile');
+					array_unshift($params, $this);
+				}
+				$output = call_user_func_array($funcCompiler, $params);
+			} else {
+				$params = self::implode_r($params);
+				if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+					$callback = $this->customPlugins[$func]['callback'];
+					if (!is_array($callback)) {
+						if (!method_exists($callback, 'process')) {
+							throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "process" method to be usable, or you should provide a full callback to the method to use');
+						}
+						if (($ref = new ReflectionMethod($callback, 'process')) && $ref->isStatic()) {
+							$output = 'call_user_func(array(\''.$callback.'\', \'process\'), '.$params.')';
+						} else {
+							$output = 'call_user_func(array($this->getObjectPlugin(\''.$callback.'\'), \'process\'), '.$params.')';
+						}
+					} elseif (is_object($callback[0])) {
+						$output = 'call_user_func(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), '.$params.')';
+					} elseif (($ref = new ReflectionMethod($callback[0], $callback[1])) && $ref->isStatic()) {
+						$output = 'call_user_func(array(\''.$callback[0].'\', \''.$callback[1].'\'), '.$params.')';
+					} else {
+						$output = 'call_user_func(array($this->getObjectPlugin(\''.$callback[0].'\'), \''.$callback[1].'\'), '.$params.')';
+					}
+					if (empty($params)) {
+						$output = substr($output, 0, -3).')';
+					}
+				} else {
+					$output = '$this->classCall(\''.$func.'\', array('.$params.'))';
+				}
+			}
+		} elseif ($pluginType & Dwoo_Core::PROXY_PLUGIN) {
+			$output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
+		} elseif ($pluginType & Dwoo_Core::SMARTY_FUNCTION) {
+			if (isset($params['*'])) {
+				$params = self::implode_r($params['*'], true);
+			} else {
+				$params = '';
+			}
+
+			if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+				$callback = $this->customPlugins[$func]['callback'];
+				if (is_array($callback)) {
+					if (is_object($callback[0])) {
+						$output = 'call_user_func_array(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array(array('.$params.'), $this))';
+					} else {
+						$output = 'call_user_func_array(array(\''.$callback[0].'\', \''.$callback[1].'\'), array(array('.$params.'), $this))';
+					}
+				} else {
+					$output = $callback.'(array('.$params.'), $this)';
+				}
+			} else {
+				$output = 'smarty_function_'.$func.'(array('.$params.'), $this)';
+			}
+		} elseif ($pluginType & Dwoo_Core::TEMPLATE_PLUGIN) {
+			array_unshift($params, '$this');
+			$params = self::implode_r($params);
+			$output = 'Dwoo_Plugin_'.$func.'_'.$this->templatePlugins[$func]['uuid'].'('.$params.')';
+			$this->templatePlugins[$func]['called'] = true;
+		}
+
+		if (is_array($parsingParams)) {
+			$parsingParams[] = array($output, $output);
+			return $parsingParams;
+		} elseif ($curBlock === 'namedparam') {
+			return array($output, $output);
+		} else {
+			return $output;
+		}
+	}
+
+	/**
+	 * parses a string
+	 *
+	 * @param string $in the string within which we must parse something
+	 * @param int $from the starting offset of the parsed area
+	 * @param int $to the ending offset of the parsed area
+	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
+	 * @param string $curBlock the current parser-block being processed
+	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
+	 * @return string parsed values
+	 */
+	protected function parseString($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
+	{
+		$substr = substr($in, $from, $to-$from);
+		$first = $substr[0];
+
+		if ($this->debug) echo 'STRING FOUND (in '.htmlentities(substr($in, $from, min($to-$from, 50))).(($to-$from) > 50 ? '...':'').')<br />';
+		$strend = false;
+		$o = $from+1;
+		while ($strend === false) {
+			$strend = strpos($in, $first, $o);
+			if ($strend === false) {
+				throw new Dwoo_Compilation_Exception($this, 'Unfinished string, started with '.substr($in, $from, $to-$from));
+			}
+			if (substr($in, $strend-1, 1) === '\\') {
+				$o = $strend+1;
+				$strend = false;
+			}
+		}
+		if ($this->debug) echo 'STRING DELIMITED: '.substr($in, $from, $strend+1-$from).'<br/>';
+
+		$srcOutput = substr($in, $from, $strend+1-$from);
+
+		if ($pointer !== null) {
+			$pointer += strlen($srcOutput);
+		}
+
+		$output = $this->replaceStringVars($srcOutput, $first);
+
+		// handle modifiers
+		if ($curBlock !== 'modifier' && preg_match('#^((?:\|(?:@?[a-z0-9_]+(?::.*)*))+)#i', substr($substr, $strend+1-$from), $match)) {
+			$modstr = $match[1];
+
+			if ($curBlock === 'root' && substr($modstr, -1) === '}') {
+				$modstr = substr($modstr, 0, -1);
+			}
+			$modstr = str_replace('\\'.$first, $first, $modstr);
+			$ptr = 0;
+			$output = $this->replaceModifiers(array(null, null, $output, $modstr), 'string', $ptr);
+
+			$strend += $ptr;
+			if ($pointer !== null) {
+				$pointer += $ptr;
+			}
+			$srcOutput .= substr($substr, $strend+1-$from, $ptr);
+		}
+
+		if (is_array($parsingParams)) {
+			$parsingParams[] = array($output, substr($srcOutput, 1, -1));
+			return $parsingParams;
+		} elseif ($curBlock === 'namedparam') {
+			return array($output, substr($srcOutput, 1, -1));
+		} else {
+			return $output;
+		}
+	}
+
+	/**
+	 * parses a constant
+	 *
+	 * @param string $in the string within which we must parse something
+	 * @param int $from the starting offset of the parsed area
+	 * @param int $to the ending offset of the parsed area
+	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
+	 * @param string $curBlock the current parser-block being processed
+	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
+	 * @return string parsed values
+	 */
+	protected function parseConst($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
+	{
+		$substr = substr($in, $from, $to-$from);
+
+		if ($this->debug) {
+			echo 'CONST FOUND : '.$substr.'<br />';
+		}
+
+		if (!preg_match('#^%([a-z0-9_:]+)#i', $substr, $m)) {
+			throw new Dwoo_Compilation_Exception($this, 'Invalid constant');
+		}
+
+		if ($pointer !== null) {
+			$pointer += strlen($m[0]);
+		}
+
+		$output = $this->parseConstKey($m[1], $curBlock);
+
+		if (is_array($parsingParams)) {
+			$parsingParams[] = array($output, $m[1]);
+			return $parsingParams;
+		} elseif ($curBlock === 'namedparam') {
+			return array($output, $m[1]);
+		} else {
+			return $output;
+		}
+	}
+
+	/**
+	 * parses a constant
+	 *
+	 * @param string $key the constant to parse
+	 * @param string $curBlock the current parser-block being processed
+	 * @return string parsed constant
+	 */
+	protected function parseConstKey($key, $curBlock)
+	{
+		if ($this->securityPolicy !== null && $this->securityPolicy->getConstantHandling() === Dwoo_Security_Policy::CONST_DISALLOW) {
+			return 'null';
+		}
+
+		if ($curBlock !== 'root') {
+			$output = '(defined("'.$key.'") ? '.$key.' : null)';
+		} else {
+			$output = $key;
+		}
+
+		return $output;
+	}
+
+	/**
+	 * parses a variable
+	 *
+	 * @param string $in the string within which we must parse something
+	 * @param int $from the starting offset of the parsed area
+	 * @param int $to the ending offset of the parsed area
+	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
+	 * @param string $curBlock the current parser-block being processed
+	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
+	 * @return string parsed values
+	 */
+	protected function parseVar($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
+	{
+		$substr = substr($in, $from, $to-$from);
+
+		if (preg_match('#(\$?\.?[a-z0-9_:]*(?:(?:(?:\.|->)(?:[a-z0-9_:]+|(?R))|\[(?:[a-z0-9_:]+|(?R)|(["\'])[^\2]*?\2)\]))*)' . // var key
+			($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='expression' ? '(\(.*)?' : '()') . // method call
+			($curBlock==='root' || $curBlock==='function' || $curBlock==='namedparam' || $curBlock==='condition' || $curBlock==='variable' || $curBlock==='delimited_string' ? '((?:(?:[+/*%=-])(?:(?<!=)=?-?[$%][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|(?<!=)=?-?[0-9.,]*|[+-]))*)':'()') . // simple math expressions
+			($curBlock!=='modifier' ? '((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').*?\5|:[^`]*))*))+)?':'(())') . // modifiers
+			'#i', $substr, $match)) {
+			$key = substr($match[1], 1);
+
+			$matchedLength = strlen($match[0]);
+			$hasModifiers = !empty($match[5]);
+			$hasExpression = !empty($match[4]);
+			$hasMethodCall = !empty($match[3]);
+
+			if (substr($key, -1) == ".") {
+				$key = substr($key, 0, -1);
+				$matchedLength--;
+			}
+
+			if ($hasMethodCall) {
+				$matchedLength -= strlen($match[3]) + strlen(substr($match[1], strrpos($match[1], '->')));
+				$key = substr($match[1], 1, strrpos($match[1], '->')-1);
+				$methodCall = substr($match[1], strrpos($match[1], '->')) . $match[3];
+			}
+
+			if ($hasModifiers) {
+				$matchedLength -= strlen($match[5]);
+			}
+
+			if ($pointer !== null) {
+				$pointer += $matchedLength;
+			}
+
+			// replace useless brackets by dot accessed vars and strip enclosing quotes if present
+			$key = preg_replace('#\[(["\']?)([^$%\[.>-]+)\1\]#', '.$2', $key);
+
+			if ($this->debug) {
+				if ($hasMethodCall) {
+					echo 'METHOD CALL FOUND : $'.$key.substr($methodCall, 0, 30).'<br />';
+				} else {
+					echo 'VAR FOUND : $'.$key.'<br />';
+				}
+			}
+
+			$key = str_replace('"', '\\"', $key);
+
+			$cnt=substr_count($key, '$');
+			if ($cnt > 0) {
+				$uid = 0;
+				$parsed = array($uid => '');
+				$current =& $parsed;
+				$curTxt =& $parsed[$uid++];
+				$tree = array();
+				$chars = str_split($key, 1);
+				$inSplittedVar = false;
+				$bracketCount = 0;
+
+				while (($char = array_shift($chars)) !== null) {
+					if ($char === '[') {
+						if (count($tree) > 0) {
+							$bracketCount++;
+						} else {
+							$tree[] =& $current;
+							$current[$uid] = array($uid+1 => '');
+							$current =& $current[$uid++];
+							$curTxt =& $current[$uid++];
+							continue;
+						}
+					} elseif ($char === ']') {
+						if ($bracketCount > 0) {
+							$bracketCount--;
+						} else {
+							$current =& $tree[count($tree)-1];
+							array_pop($tree);
+							if (current($chars) !== '[' && current($chars) !== false && current($chars) !== ']') {
+								$current[$uid] = '';
+								$curTxt =& $current[$uid++];
+							}
+							continue;
+						}
+					} elseif ($char === '$') {
+						if (count($tree) == 0) {
+							$curTxt =& $current[$uid++];
+							$inSplittedVar = true;
+						}
+					} elseif (($char === '.' || $char === '-') && count($tree) == 0 && $inSplittedVar) {
+						$curTxt =& $current[$uid++];
+						$inSplittedVar = false;
+					}
+
+					$curTxt .= $char;
+				}
+				unset($uid, $current, $curTxt, $tree, $chars);
+
+				if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT : '.$key.'<br>';
+
+				$key = $this->flattenVarTree($parsed);
+
+				if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>';
+
+				$output = preg_replace('#(^""\.|""\.|\.""$|(\()""\.|\.""(\)))#', '$2$3', '$this->readVar("'.$key.'")');
+			} else {
+				$output = $this->parseVarKey($key, $hasModifiers ? 'modifier' : $curBlock);
+			}
+
+			// methods
+			if ($hasMethodCall) {
+				$ptr = 0;
+
+				$output = $this->parseMethodCall($output, $methodCall, $curBlock, $ptr);
+
+				if ($pointer !== null) {
+					$pointer += $ptr;
+				}
+				$matchedLength += $ptr;
+			}
+
+			if ($hasExpression) {
+				// expressions
+				preg_match_all('#(?:([+/*%=-])(=?-?[%$][a-z0-9.[\]>_:-]+(?:\([^)]*\))?|=?-?[0-9.,]+|\1))#i', $match[4], $expMatch);
+
+				foreach ($expMatch[1] as $k=>$operator) {
+					if (substr($expMatch[2][$k], 0, 1)==='=') {
+						$assign = true;
+						if ($operator === '=') {
+							throw new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.$substr.'</em>, can not use "==" in expressions');
+						}
+						if ($curBlock !== 'root') {
+							throw new Dwoo_Compilation_Exception($this, 'Invalid expression <em>'.$substr.'</em>, assignments can only be used in top level expressions like {$foo+=3} or {$foo="bar"}');
+						}
+						$operator .= '=';
+						$expMatch[2][$k] = substr($expMatch[2][$k], 1);
+					}
+
+					if (substr($expMatch[2][$k], 0, 1)==='-' && strlen($expMatch[2][$k]) > 1) {
+						$operator .= '-';
+						$expMatch[2][$k] = substr($expMatch[2][$k], 1);
+					}
+					if (($operator==='+'||$operator==='-') && $expMatch[2][$k]===$operator) {
+						$output = '('.$output.$operator.$operator.')';
+						break;
+					} elseif (substr($expMatch[2][$k], 0, 1) === '$') {
+						$output = '('.$output.' '.$operator.' '.$this->parseVar($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')';
+					} elseif (substr($expMatch[2][$k], 0, 1) === '%') {
+						$output = '('.$output.' '.$operator.' '.$this->parseConst($expMatch[2][$k], 0, strlen($expMatch[2][$k]), false, 'expression').')';
+					} elseif (!empty($expMatch[2][$k])) {
+						$output = '('.$output.' '.$operator.' '.str_replace(',', '.', $expMatch[2][$k]).')';
+					} else {
+						throw new Dwoo_Compilation_Exception($this, 'Unfinished expression <em>'.$substr.'</em>, missing var or number after math operator');
+					}
+				}
+			}
+
+			if ($this->autoEscape === true && $curBlock !== 'condition') {
+				$output = '(is_string($tmp='.$output.') ? htmlspecialchars($tmp, ENT_QUOTES, $this->charset) : $tmp)';
+			}
+
+			// handle modifiers
+			if ($curBlock !== 'modifier' && $hasModifiers) {
+				$ptr = 0;
+				$output = $this->replaceModifiers(array(null, null, $output, $match[5]), 'var', $ptr);
+				if ($pointer !== null) {
+					$pointer += $ptr;
+				}
+				$matchedLength += $ptr;
+			}
+
+			if (is_array($parsingParams)) {
+				$parsingParams[] = array($output, $key);
+				return $parsingParams;
+			} elseif ($curBlock === 'namedparam') {
+				return array($output, $key);
+			} elseif ($curBlock === 'string' || $curBlock === 'delimited_string') {
+				return array($matchedLength, $output);
+			} elseif ($curBlock === 'expression' || $curBlock === 'variable') {
+				return $output;
+			} elseif (isset($assign)) {
+				return self::PHP_OPEN.$output.';'.self::PHP_CLOSE;
+			} else {
+				return $output;
+			}
+		} else {
+			if ($curBlock === 'string' || $curBlock === 'delimited_string') {
+				return array(0, '');
+			} else {
+				throw new Dwoo_Compilation_Exception($this, 'Invalid variable name <em>'.$substr.'</em>');
+			}
+		}
+	}
+
+	/**
+	 * parses any number of chained method calls/property reads
+	 *
+	 * @param string $output the variable or whatever upon which the method are called
+	 * @param string $methodCall method call source, starting at "->"
+	 * @param string $curBlock the current parser-block being processed
+	 * @param int $pointer a reference to a pointer that will be increased by the amount of characters parsed
+	 * @return string parsed call(s)/read(s)
+	 */
+	protected function parseMethodCall($output, $methodCall, $curBlock, &$pointer)
+	{
+		$ptr = 0;
+		$len = strlen($methodCall);
+
+		while ($ptr < $len) {
+			if (strpos($methodCall, '->', $ptr) === $ptr) {
+				$ptr += 2;
+			}
+
+			if (in_array($methodCall[$ptr], array(';', ',', '/', ' ', "\t", "\r", "\n", ')', '+', '*', '%', '=', '-', '|')) || substr($methodCall, $ptr, strlen($this->rd)) === $this->rd) {
+				// break char found
+				break;
+			}
+
+			if(!preg_match('/^([a-z0-9_]+)(\(.*?\))?/i', substr($methodCall, $ptr), $methMatch)) {
+				throw new Dwoo_Compilation_Exception($this, 'Invalid method name : '.substr($methodCall, $ptr, 20));
+			}
+
+			if (empty($methMatch[2])) {
+				// property
+				if ($curBlock === 'root') {
+					$output .= '->'.$methMatch[1];
+				} else {
+					$output = '(($tmp = '.$output.') ? $tmp->'.$methMatch[1].' : null)';
+				}
+				$ptr += strlen($methMatch[1]);
+			} else {
+				// method
+				if (substr($methMatch[2], 0, 2) === '()') {
+					$parsedCall = $methMatch[1].'()';
+					$ptr += strlen($methMatch[1]) + 2;
+				} else {
+					$parsedCall = $this->parseFunction($methodCall, $ptr, strlen($methodCall), false, 'method', $ptr);
+				}
+				if ($this->securityPolicy !== null) {
+					$argPos = strpos($parsedCall, '(');
+					$method = strtolower(substr($parsedCall, 0, $argPos));
+					$args = substr($parsedCall, $argPos);
+					if ($curBlock === 'root') {
+						$output = '$this->getSecurityPolicy()->callMethod($this, '.$output.', '.var_export($method, true).', array'.$args.')';
+					} else {
+						$output = '(($tmp = '.$output.') ? $this->getSecurityPolicy()->callMethod($this, $tmp, '.var_export($method, true).', array'.$args.') : null)';
+					}
+				} else {
+					if ($curBlock === 'root') {
+						$output .= '->'.$parsedCall;
+					} else {
+						$output = '(($tmp = '.$output.') ? $tmp->'.$parsedCall.' : null)';
+					}
+				}
+			}
+		}
+
+		$pointer += $ptr;
+		return $output;
+	}
+
+	/**
+	 * parses a constant variable (a variable that doesn't contain another variable) and preprocesses it to save runtime processing time
+	 *
+	 * @param string $key the variable to parse
+	 * @param string $curBlock the current parser-block being processed
+	 * @return string parsed variable
+	 */
+	protected function parseVarKey($key, $curBlock)
+	{
+		if ($key === '') {
+			return '$this->scope';
+		}
+		if (substr($key, 0, 1) === '.') {
+			$key = 'dwoo'.$key;
+		}
+		if (preg_match('#dwoo\.(get|post|server|cookies|session|env|request)((?:\.[a-z0-9_-]+)+)#i', $key, $m)) {
+			$global = strtoupper($m[1]);
+			if ($global === 'COOKIES') {
+				$global = 'COOKIE';
+			}
+			$key = '$_'.$global;
+			foreach (explode('.', ltrim($m[2], '.')) as $part)
+				$key .= '['.var_export($part, true).']';
+			if ($curBlock === 'root') {
+				$output = $key;
+			} else {
+				$output = '(isset('.$key.')?'.$key.':null)';
+			}
+		} elseif (preg_match('#dwoo\.const\.([a-z0-9_:]+)#i', $key, $m)) {
+			return $this->parseConstKey($m[1], $curBlock);
+		} elseif ($this->scope !== null) {
+			if (strstr($key, '.') === false && strstr($key, '[') === false && strstr($key, '->') === false) {
+				if ($key === 'dwoo') {
+					$output = '$this->globals';
+				} elseif ($key === '_root' || $key === '__') {
+					$output = '$this->data';
+				} elseif ($key === '_parent' || $key === '_') {
+					$output = '$this->readParentVar(1)';
+				} elseif ($key === '_key') {
+					$output = '$tmp_key';
+				} else {
+					if ($curBlock === 'root') {
+						$output = '$this->scope["'.$key.'"]';
+					} else {
+						$output = '(isset($this->scope["'.$key.'"]) ? $this->scope["'.$key.'"] : null)';
+					}
+				}
+			} else {
+				preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+|(\\\?[\'"])[^\3]*?\3)\]?#i', $key, $m);
+
+				$i = $m[2][0];
+				if ($i === '_parent' || $i === '_') {
+					$parentCnt = 0;
+
+					while (true) {
+						$parentCnt++;
+						array_shift($m[2]);
+						array_shift($m[1]);
+						if (current($m[2]) === '_parent') {
+							continue;
+						}
+						break;
+					}
+
+					$output = '$this->readParentVar('.$parentCnt.')';
+				} else {
+					if ($i === 'dwoo') {
+						$output = '$this->globals';
+						array_shift($m[2]);
+						array_shift($m[1]);
+					} elseif ($i === '_root' || $i === '__') {
+						$output = '$this->data';
+						array_shift($m[2]);
+						array_shift($m[1]);
+					} elseif ($i === '_key') {
+						$output = '$tmp_key';
+					} else {
+						$output = '$this->scope';
+					}
+
+					while (count($m[1]) && $m[1][0] !== '->') {
+						$m[2][0] = preg_replace('/(^\\\([\'"])|\\\([\'"])$)/x', '$2$3', $m[2][0]);
+						if(substr($m[2][0], 0, 1) == '"' || substr($m[2][0], 0, 1) == "'") {
+							$output .= '['.$m[2][0].']';
+						} else {
+							$output .= '["'.$m[2][0].'"]';
+						}
+						array_shift($m[2]);
+						array_shift($m[1]);
+					}
+
+					if ($curBlock !== 'root') {
+						$output = '(isset('.$output.') ? '.$output.':null)';
+					}
+				}
+
+				if (count($m[2])) {
+					unset($m[0]);
+					$output = '$this->readVarInto('.str_replace("\n", '', var_export($m, true)).', '.$output.', '.($curBlock == 'root' ? 'false': 'true').')';
+				}
+			}
+		} else {
+			preg_match_all('#(\[|->|\.)?((?:[a-z0-9_]|-(?!>))+)\]?#i', $key, $m);
+			unset($m[0]);
+			$output = '$this->readVar('.str_replace("\n", '', var_export($m, true)).')';
+		}
+
+		return $output;
+	}
+
+	/**
+	 * flattens a variable tree, this helps in parsing very complex variables such as $var.foo[$foo.bar->baz].baz,
+	 * it computes the contents of the brackets first and works out from there
+	 *
+	 * @param array $tree the variable tree parsed by he parseVar() method that must be flattened
+	 * @param bool $recursed leave that to false by default, it is only for internal use
+	 * @return string flattened tree
+	 */
+	protected function flattenVarTree(array $tree, $recursed=false)
+	{
+		$out = $recursed ?  '".$this->readVarInto(' : '';
+		foreach ($tree as $bit) {
+			if (is_array($bit)) {
+				$out.='.'.$this->flattenVarTree($bit, false);
+			} else {
+				$key = str_replace('"', '\\"', $bit);
+
+				if (substr($key, 0, 1)==='$') {
+					$out .= '".'.$this->parseVar($key, 0, strlen($key), false, 'variable').'."';
+				} else {
+					$cnt = substr_count($key, '$');
+
+					if ($this->debug) echo 'PARSING SUBVARS IN : '.$key.'<br>';
+					if ($cnt > 0) {
+						while (--$cnt >= 0) {
+							if (isset($last)) {
+								$last = strrpos($key, '$', - (strlen($key) - $last + 1));
+							} else {
+								$last = strrpos($key, '$');
+							}
+							preg_match('#\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*'.
+									  '((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i', substr($key, $last), $submatch);
+
+							$len = strlen($submatch[0]);
+							$key = substr_replace(
+								$key,
+								preg_replace_callback(
+									'#(\$[a-z0-9_]+((?:(?:\.|->)(?:[a-z0-9_]+|(?R))|\[(?:[a-z0-9_]+|(?R))\]))*)'.
+									'((?:(?:[+/*%-])(?:\$[a-z0-9.[\]>_:-]+(?:\([^)]*\))?|[0-9.,]*))*)#i',
+									array($this, 'replaceVarKeyHelper'), substr($key, $last, $len)
+								),
+								$last,
+								$len
+							);
+							if ($this->debug) echo 'RECURSIVE VAR REPLACEMENT DONE : '.$key.'<br>';
+						}
+						unset($last);
+
+						$out .= $key;
+					} else {
+						$out .= $key;
+					}
+				}
+			}
+		}
+		$out .= $recursed ? ', true)."' : '';
+		return $out;
+	}
+
+	/**
+	 * helper function that parses a variable
+	 *
+	 * @param array $match the matched variable, array(1=>"string match")
+	 * @return string parsed variable
+	 */
+	protected function replaceVarKeyHelper($match)
+	{
+		return '".'.$this->parseVar($match[0], 0, strlen($match[0]), false, 'variable').'."';
+	}
+
+	/**
+	 * parses various constants, operators or non-quoted strings
+	 *
+	 * @param string $in the string within which we must parse something
+	 * @param int $from the starting offset of the parsed area
+	 * @param int $to the ending offset of the parsed area
+	 * @param mixed $parsingParams must be an array if we are parsing a function or modifier's parameters, or false by default
+	 * @param string $curBlock the current parser-block being processed
+	 * @param mixed $pointer a reference to a pointer that will be increased by the amount of characters parsed, or null by default
+	 * @return string parsed values
+	 */
+	protected function parseOthers($in, $from, $to, $parsingParams = false, $curBlock='', &$pointer = null)
+	{
+		$first = $in[$from];
+		$substr = substr($in, $from, $to-$from);
+
+		$end = strlen($substr);
+
+		if ($curBlock === 'condition') {
+			$breakChars = array('(', ')', ' ', '||', '&&', '|', '&', '>=', '<=', '===', '==', '=', '!==', '!=', '<<', '<', '>>', '>', '^', '~', ',', '+', '-', '*', '/', '%', '!', '?', ':', $this->rd, ';');
+		} elseif ($curBlock === 'modifier') {
+			$breakChars = array(' ', ',', ')', ':', '|', "\r", "\n", "\t", ";", $this->rd);
+		} elseif ($curBlock === 'expression') {
+			$breakChars = array('/', '%', '+', '-', '*', ' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
+		} else {
+			$breakChars = array(' ', ',', ')', "\r", "\n", "\t", ";", $this->rd);
+		}
+
+		$breaker = false;
+		while (list($k,$char) = each($breakChars)) {
+			$test = strpos($substr, $char);
+			if ($test !== false && $test < $end) {
+				$end = $test;
+				$breaker = $k;
+			}
+		}
+
+		if ($curBlock === 'condition') {
+			if ($end === 0 && $breaker !== false) {
+				$end = strlen($breakChars[$breaker]);
+			}
+		}
+
+		if ($end !== false) {
+			$substr = substr($substr, 0, $end);
+		}
+
+		if ($pointer !== null) {
+			$pointer += strlen($substr);
+		}
+
+		$src = $substr;
+		$substr = trim($substr);
+
+		if (strtolower($substr) === 'false' || strtolower($substr) === 'no' || strtolower($substr) === 'off') {
+			if ($this->debug) echo 'BOOLEAN(FALSE) PARSED<br />';
+			$substr = 'false';
+			$type = self::T_BOOL;
+		} elseif (strtolower($substr) === 'true' || strtolower($substr) === 'yes' || strtolower($substr) === 'on') {
+			if ($this->debug) echo 'BOOLEAN(TRUE) PARSED<br />';
+			$substr = 'true';
+			$type = self::T_BOOL;
+		} elseif ($substr === 'null' || $substr === 'NULL') {
+			if ($this->debug) echo 'NULL PARSED<br />';
+			$substr = 'null';
+			$type = self::T_NULL;
+		} elseif (is_numeric($substr)) {
+			$substr = (float) $substr;
+			if ((int) $substr == $substr) {
+				$substr = (int) $substr;
+			}
+			$type = self::T_NUMERIC;
+			if ($this->debug) echo 'NUMBER ('.$substr.') PARSED<br />';
+		} elseif (preg_match('{^-?(\d+|\d*(\.\d+))\s*([/*%+-]\s*-?(\d+|\d*(\.\d+)))+$}', $substr)) {
+			if ($this->debug) echo 'SIMPLE MATH PARSED<br />';
+			$type = self::T_MATH;
+			$substr = '('.$substr.')';
+		} elseif ($curBlock === 'condition' && array_search($substr, $breakChars, true) !== false) {
+			if ($this->debug) echo 'BREAKCHAR ('.$substr.') PARSED<br />';
+			$type = self::T_BREAKCHAR;
+			//$substr = '"'.$substr.'"';
+		} else {
+			$substr = $this->replaceStringVars('\''.str_replace('\'', '\\\'', $substr).'\'', '\'', $curBlock);
+			$type = self::T_UNQUOTED_STRING;
+			if ($this->debug) echo 'BLABBER ('.$substr.') CASTED AS STRING<br />';
+		}
+
+		if (is_array($parsingParams)) {
+			$parsingParams[] = array($substr, $src, $type);
+			return $parsingParams;
+		} elseif ($curBlock === 'namedparam') {
+			return array($substr, $src, $type);
+		} elseif ($curBlock === 'expression') {
+			return $substr;
+		} else {
+			throw new Exception('Something went wrong');
+		}
+	}
+
+	/**
+	 * replaces variables within a parsed string
+	 *
+	 * @param string $string the parsed string
+	 * @param string $first the first character parsed in the string, which is the string delimiter (' or ")
+	 * @param string $curBlock the current parser-block being processed
+	 * @return string the original string with variables replaced
+	 */
+	protected function replaceStringVars($string, $first, $curBlock='')
+	{
+		$pos = 0;
+		if ($this->debug) echo 'STRING VAR REPLACEMENT : '.$string.'<br>';
+		// replace vars
+		while (($pos = strpos($string, '$', $pos)) !== false) {
+			$prev = substr($string, $pos-1, 1);
+			if ($prev === '\\') {
+				$pos++;
+				continue;
+			}
+
+			$var = $this->parse($string, $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string')));
+			$len = $var[0];
+			$var = $this->parse(str_replace('\\'.$first, $first, $string), $pos, null, false, ($curBlock === 'modifier' ? 'modifier' : ($prev === '`' ? 'delimited_string':'string')));
+
+			if ($prev === '`' && substr($string, $pos+$len, 1) === '`') {
+				$string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos-1, $len+2);
+			} else {
+				$string = substr_replace($string, $first.'.'.$var[1].'.'.$first, $pos, $len);
+			}
+			$pos += strlen($var[1]) + 2;
+			if ($this->debug) echo 'STRING VAR REPLACEMENT DONE : '.$string.'<br>';
+		}
+
+		// handle modifiers
+		// TODO Obsolete?
+		$string = preg_replace_callback('#("|\')\.(.+?)\.\1((?:\|(?:@?[a-z0-9_]+(?:(?::("|\').+?\4|:[^`]*))*))+)#i', array($this, 'replaceModifiers'), $string);
+
+		// replace escaped dollar operators by unescaped ones if required
+		if ($first==="'") {
+			$string = str_replace('\\$', '$', $string);
+		}
+
+		return $string;
+	}
+
+	/**
+	 * replaces the modifiers applied to a string or a variable
+	 *
+	 * @param array $m the regex matches that must be array(1=>"double or single quotes enclosing a string, when applicable", 2=>"the string or var", 3=>"the modifiers matched")
+	 * @param string $curBlock the current parser-block being processed
+	 * @return string the input enclosed with various function calls according to the modifiers found
+	 */
+	protected function replaceModifiers(array $m, $curBlock = null, &$pointer = null)
+	{
+		if ($this->debug) echo 'PARSING MODIFIERS : '.$m[3].'<br />';
+
+		if ($pointer !== null) {
+			$pointer += strlen($m[3]);
+		}
+		// remove first pipe
+		$cmdstrsrc = substr($m[3], 1);
+		// remove last quote if present
+		if (substr($cmdstrsrc, -1, 1) === $m[1]) {
+			$cmdstrsrc = substr($cmdstrsrc, 0, -1);
+			$add = $m[1];
+		}
+
+		$output = $m[2];
+
+		$continue = true;
+		while (strlen($cmdstrsrc) > 0 && $continue) {
+			if ($cmdstrsrc[0] === '|') {
+				$cmdstrsrc = substr($cmdstrsrc, 1);
+				continue;
+			}
+			if ($cmdstrsrc[0] === ' ' || $cmdstrsrc[0] === ';' || substr($cmdstrsrc, 0, strlen($this->rd)) === $this->rd) {
+				if ($this->debug) echo 'MODIFIER PARSING ENDED, RIGHT DELIMITER or ";" FOUND<br/>';
+				$continue = false;
+				if ($pointer !== null) {
+					$pointer -= strlen($cmdstrsrc);
+				}
+				break;
+			}
+			$cmdstr = $cmdstrsrc;
+			$paramsep = ':';
+			if (!preg_match('/^(@{0,2}[a-z_][a-z0-9_]*)(:)?/i', $cmdstr, $match)) {
+				throw new Dwoo_Compilation_Exception($this, 'Invalid modifier name, started with : '.substr($cmdstr, 0, 10));
+			}
+			$paramspos = !empty($match[2]) ? strlen($match[1]) : false;
+			$func = $match[1];
+
+			$state = 0;
+			if ($paramspos === false) {
+				$cmdstrsrc = substr($cmdstrsrc, strlen($func));
+				$params = array();
+				if ($this->debug) echo 'MODIFIER ('.$func.') CALLED WITH NO PARAMS<br/>';
+			} else {
+				$paramstr = substr($cmdstr, $paramspos+1);
+				if (substr($paramstr, -1, 1) === $paramsep) {
+					$paramstr = substr($paramstr, 0, -1);
+				}
+
+				$ptr = 0;
+				$params = array();
+				while ($ptr < strlen($paramstr)) {
+					if ($this->debug) echo 'MODIFIER ('.$func.') START PARAM PARSING WITH POINTER AT '.$ptr.'<br/>';
+					if ($this->debug) echo $paramstr.'--'.$ptr.'--'.strlen($paramstr).'--modifier<br/>';
+					$params = $this->parse($paramstr, $ptr, strlen($paramstr), $params, 'modifier', $ptr);
+					if ($this->debug) echo 'PARAM PARSED, POINTER AT '.$ptr.'<br/>';
+
+					if ($ptr >= strlen($paramstr)) {
+						if ($this->debug) echo 'PARAM PARSING ENDED, PARAM STRING CONSUMED<br/>';
+						break;
+					}
+
+					if ($paramstr[$ptr] === ' ' || $paramstr[$ptr] === '|' || $paramstr[$ptr] === ';' || substr($paramstr, $ptr, strlen($this->rd)) === $this->rd) {
+						if ($this->debug) echo 'PARAM PARSING ENDED, " ", "|", RIGHT DELIMITER or ";" FOUND, POINTER AT '.$ptr.'<br/>';
+						if ($paramstr[$ptr] !== '|') {
+							$continue = false;
+							if ($pointer !== null) {
+								$pointer -= strlen($paramstr) - $ptr;
+							}
+						}
+						$ptr++;
+						break;
+					}
+					if ($ptr < strlen($paramstr) && $paramstr[$ptr] === ':') {
+						$ptr++;
+					}
+				}
+				$cmdstrsrc = substr($cmdstrsrc, strlen($func)+1+$ptr);
+				$paramstr = substr($paramstr, 0, $ptr);
+				foreach ($params as $k=>$p) {
+					if (is_array($p) && is_array($p[1])) {
+						$state |= 2;
+					} else {
+						if (($state & 2) && preg_match('#^(["\'])(.+?)\1$#', $p[0], $m)) {
+							$params[$k] = array($m[2], array('true', 'true'));
+						} else {
+							if ($state & 2) {
+								throw new Dwoo_Compilation_Exception($this, 'You can not use an unnamed parameter after a named one');
+							}
+							$state |= 1;
+						}
+					}
+				}
+			}
+
+			// check if we must use array_map with this plugin or not
+			$mapped = false;
+			if (substr($func, 0, 1) === '@') {
+				$func = substr($func, 1);
+				$mapped = true;
+			}
+
+			$pluginType = $this->getPluginType($func);
+
+			if ($state & 2) {
+				array_unshift($params, array('value', array($output, $output)));
+			} else {
+				array_unshift($params, array($output, $output));
+			}
+
+			if ($pluginType & Dwoo_Core::NATIVE_PLUGIN) {
+				$params = $this->mapParams($params, null, $state);
+
+				$params = $params['*'][0];
+
+				$params = self::implode_r($params);
+
+				if ($mapped) {
+					$output = '$this->arrayMap(\''.$func.'\', array('.$params.'))';
+				} else {
+					$output = $func.'('.$params.')';
+				}
+			} elseif ($pluginType & Dwoo_Core::PROXY_PLUGIN) {
+				$params = $this->mapParams($params, $this->getDwoo()->getPluginProxy()->getCallback($func), $state);
+				foreach ($params as &$p)
+					$p = $p[0];
+				$output = call_user_func(array($this->dwoo->getPluginProxy(), 'getCode'), $func, $params);
+			} elseif ($pluginType & Dwoo_Core::SMARTY_MODIFIER) {
+				$params = $this->mapParams($params, null, $state);
+				$params = $params['*'][0];
+
+				$params = self::implode_r($params);
+
+				if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+					$callback = $this->customPlugins[$func]['callback'];
+					if (is_array($callback)) {
+						if (is_object($callback[0])) {
+							$output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))';
+						} else {
+							$output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))';
+						}
+					} elseif ($mapped) {
+						$output = '$this->arrayMap(\''.$callback.'\', array('.$params.'))';
+					} else {
+						$output = $callback.'('.$params.')';
+					}
+				} elseif ($mapped) {
+					$output = '$this->arrayMap(\'smarty_modifier_'.$func.'\', array('.$params.'))';
+				} else {
+					$output = 'smarty_modifier_'.$func.'('.$params.')';
+				}
+			} else {
+				if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+					$callback = $this->customPlugins[$func]['callback'];
+					$pluginName = $callback;
+				} else {
+					$pluginName = 'Dwoo_Plugin_'.$func;
+
+					if ($pluginType & Dwoo_Core::CLASS_PLUGIN) {
+						$callback = array($pluginName, ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? 'compile' : 'process');
+					} else {
+						$callback = $pluginName . (($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) ? '_compile' : '');
+					}
+				}
+
+				$params = $this->mapParams($params, $callback, $state);
+
+				foreach ($params as &$p)
+					$p = $p[0];
+
+				if ($pluginType & Dwoo_Core::FUNC_PLUGIN) {
+					if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) {
+						if ($mapped) {
+							throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.');
+						}
+						if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+							$funcCompiler = $this->customPlugins[$func]['callback'];
+						} else {
+							$funcCompiler = 'Dwoo_Plugin_'.$func.'_compile';
+						}
+						array_unshift($params, $this);
+						$output = call_user_func_array($funcCompiler, $params);
+					} else {
+						array_unshift($params, '$this');
+
+						$params = self::implode_r($params);
+						if ($mapped) {
+							$output = '$this->arrayMap(\''.$pluginName.'\', array('.$params.'))';
+						} else {
+							$output = $pluginName.'('.$params.')';
+						}
+					}
+				} else {
+					if ($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) {
+						if ($mapped) {
+							throw new Dwoo_Compilation_Exception($this, 'The @ operator can not be used on compiled plugins.');
+						}
+						if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+							$callback = $this->customPlugins[$func]['callback'];
+							if (!is_array($callback)) {
+								if (!method_exists($callback, 'compile')) {
+									throw new Dwoo_Exception('Custom plugin '.$func.' must implement the "compile" method to be compilable, or you should provide a full callback to the method to use');
+								}
+								if (($ref = new ReflectionMethod($callback, 'compile')) && $ref->isStatic()) {
+									$funcCompiler = array($callback, 'compile');
+								} else {
+									$funcCompiler = array(new $callback, 'compile');
+								}
+							} else {
+								$funcCompiler = $callback;
+							}
+						} else {
+							$funcCompiler = array('Dwoo_Plugin_'.$func, 'compile');
+							array_unshift($params, $this);
+						}
+						$output = call_user_func_array($funcCompiler, $params);
+					} else {
+						$params = self::implode_r($params);
+
+						if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+							if (is_object($callback[0])) {
+								$output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array($this->plugins[\''.$func.'\'][\'callback\'][0], \''.$callback[1].'\'), array('.$params.'))';
+							} else {
+								$output = ($mapped ? '$this->arrayMap' : 'call_user_func_array').'(array(\''.$callback[0].'\', \''.$callback[1].'\'), array('.$params.'))';
+							}
+						} elseif ($mapped) {
+							$output = '$this->arrayMap(array($this->getObjectPlugin(\'Dwoo_Plugin_'.$func.'\'), \'process\'), array('.$params.'))';
+						} else {
+							$output = '$this->classCall(\''.$func.'\', array('.$params.'))';
+						}
+					}
+				}
+			}
+		}
+
+		if ($curBlock === 'var' || $m[1] === null) {
+			return $output;
+		} elseif ($curBlock === 'string' || $curBlock === 'root') {
+			return $m[1].'.'.$output.'.'.$m[1].(isset($add)?$add:null);
+		}
+	}
+
+	/**
+	 * recursively implodes an array in a similar manner as var_export() does but with some tweaks
+	 * to handle pre-compiled values and the fact that we do not need to enclose everything with
+	 * "array" and do not require top-level keys to be displayed
+	 *
+	 * @param array $params the array to implode
+	 * @param bool $recursiveCall if set to true, the function outputs key names for the top level
+	 * @return string the imploded array
+	 */
+	public static function implode_r(array $params, $recursiveCall = false)
+	{
+		$out = '';
+		foreach ($params as $k=>$p) {
+			if (is_array($p)) {
+				$out2 = 'array(';
+				foreach ($p as $k2=>$v)
+					$out2 .= var_export($k2, true).' => '.(is_array($v) ? 'array('.self::implode_r($v, true).')' : $v).', ';
+				$p = rtrim($out2, ', ').')';
+			}
+			if ($recursiveCall) {
+				$out .= var_export($k, true).' => '.$p.', ';
+			} else {
+				$out .= $p.', ';
+			}
+		}
+		return rtrim($out, ', ');
+	}
+
+	/**
+	 * returns the plugin type of a plugin and adds it to the used plugins array if required
+	 *
+	 * @param string $name plugin name, as found in the template
+	 * @return int type as a multi bit flag composed of the Dwoo plugin types constants
+	 */
+	protected function getPluginType($name)
+	{
+		$pluginType = -1;
+
+		if (($this->securityPolicy === null && (function_exists($name) || strtolower($name) === 'isset' || strtolower($name) === 'empty')) ||
+			($this->securityPolicy !== null && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) !== false)) {
+			$phpFunc = true;
+		} elseif ($this->securityPolicy !== null && function_exists($name) && array_key_exists(strtolower($name), $this->securityPolicy->getAllowedPhpFunctions()) === false) {
+			throw new Dwoo_Security_Exception('Call to a disallowed php function : '.$name);
+		}
+
+		while ($pluginType <= 0) {
+			if (isset($this->templatePlugins[$name])) {
+				$pluginType = Dwoo_Core::TEMPLATE_PLUGIN | Dwoo_Core::COMPILABLE_PLUGIN;
+			} elseif (isset($this->customPlugins[$name])) {
+				$pluginType = $this->customPlugins[$name]['type'] | Dwoo_Core::CUSTOM_PLUGIN;
+			} elseif (class_exists('Dwoo_Plugin_'.$name, false) !== false) {
+				if (is_subclass_of('Dwoo_Plugin_'.$name, 'Dwoo_Block_Plugin')) {
+					$pluginType = Dwoo_Core::BLOCK_PLUGIN;
+				} else {
+					$pluginType = Dwoo_Core::CLASS_PLUGIN;
+				}
+				$interfaces = class_implements('Dwoo_Plugin_'.$name, false);
+				if (in_array('Dwoo_ICompilable', $interfaces) !== false || in_array('Dwoo_ICompilable_Block', $interfaces) !== false) {
+					$pluginType |= Dwoo_Core::COMPILABLE_PLUGIN;
+				}
+			} elseif (function_exists('Dwoo_Plugin_'.$name) !== false) {
+				$pluginType = Dwoo_Core::FUNC_PLUGIN;
+			} elseif (function_exists('Dwoo_Plugin_'.$name.'_compile')) {
+				$pluginType = Dwoo_Core::FUNC_PLUGIN | Dwoo_Core::COMPILABLE_PLUGIN;
+			} elseif (function_exists('smarty_modifier_'.$name) !== false) {
+				$pluginType = Dwoo_Core::SMARTY_MODIFIER;
+			} elseif (function_exists('smarty_function_'.$name) !== false) {
+				$pluginType = Dwoo_Core::SMARTY_FUNCTION;
+			} elseif (function_exists('smarty_block_'.$name) !== false) {
+				$pluginType = Dwoo_Core::SMARTY_BLOCK;
+			} else {
+				if ($pluginType===-1) {
+					try {
+						$this->dwoo->getLoader()->loadPlugin($name, isset($phpFunc)===false);
+					} catch (Exception $e) {
+						if (isset($phpFunc)) {
+							$pluginType = Dwoo_Core::NATIVE_PLUGIN;
+						} elseif (is_object($this->dwoo->getPluginProxy()) && $this->dwoo->getPluginProxy()->handles($name)) {
+							$pluginType = Dwoo_Core::PROXY_PLUGIN;
+							break;
+						} else {
+							throw $e;
+						}
+					}
+				} else {
+					throw new Dwoo_Exception('Plugin "'.$name.'" could not be found');
+				}
+				$pluginType++;
+			}
+		}
+
+		if (($pluginType & Dwoo_Core::COMPILABLE_PLUGIN) === 0 && ($pluginType & Dwoo_Core::NATIVE_PLUGIN) === 0 && ($pluginType & Dwoo_Core::PROXY_PLUGIN) === 0) {
+			$this->addUsedPlugin($name, $pluginType);
+		}
+
+		return $pluginType;
+	}
+
+	/**
+	 * allows a plugin to load another one at compile time, this will also mark
+	 * it as used by this template so it will be loaded at runtime (which can be
+	 * useful for compiled plugins that rely on another plugin when their compiled
+	 * code runs)
+	 *
+	 * @param string $name the plugin name
+	 */
+	public function loadPlugin($name) {
+		$this->getPluginType($name);
+	}
+
+	/**
+	 * runs htmlentities over the matched <?php ?> blocks when the security policy enforces that
+	 *
+	 * @param array $match matched php block
+	 * @return string the htmlentities-converted string
+	 */
+	protected function phpTagEncodingHelper($match)
+	{
+		return htmlspecialchars($match[0]);
+	}
+
+	/**
+	 * maps the parameters received from the template onto the parameters required by the given callback
+	 *
+	 * @param array $params the array of parameters
+	 * @param callback $callback the function or method to reflect on to find out the required parameters
+	 * @param int $callType the type of call in the template, 0 = no params, 1 = php-style call, 2 = named parameters call
+	 * @param array $map the parameter map to use, if not provided it will be built from the callback
+	 * @return array parameters sorted in the correct order with missing optional parameters filled
+	 */
+	protected function mapParams(array $params, $callback, $callType=2, $map = null)
+	{
+		if (!$map) {
+			$map = $this->getParamMap($callback);
+		}
+
+		$paramlist = array();
+
+		// transforms the parameter array from (x=>array('paramname'=>array(values))) to (paramname=>array(values))
+		$ps = array();
+		foreach ($params as $p) {
+			if (is_array($p[1])) {
+				$ps[$p[0]] = $p[1];
+			} else {
+				$ps[] = $p;
+			}
+		}
+
+		// loops over the param map and assigns values from the template or default value for unset optional params
+		while (list($k,$v) = each($map)) {
+			if ($v[0] === '*') {
+				// "rest" array parameter, fill every remaining params in it and then break
+				if (count($ps) === 0) {
+					if ($v[1]===false) {
+						throw new Dwoo_Compilation_Exception($this, 'Rest argument missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', (is_array($callback) ? $callback[0] : $callback)));
+					} else {
+						break;
+					}
+				}
+				$tmp = array();
+				$tmp2 = array();
+				$tmp3 = array();
+				foreach ($ps as $i=>$p) {
+					$tmp[$i] = $p[0];
+					$tmp2[$i] = $p[1];
+					$tmp3[$i] = isset($p[2]) ? $p[2] : 0;
+					unset($ps[$i]);
+				}
+				$paramlist[$v[0]] = array($tmp, $tmp2, $tmp3);
+				unset($tmp, $tmp2, $i, $p);
+				break;
+			} elseif (isset($ps[$v[0]])) {
+				// parameter is defined as named param
+				$paramlist[$v[0]] = $ps[$v[0]];
+				unset($ps[$v[0]]);
+			} elseif (isset($ps[$k])) {
+				// parameter is defined as ordered param
+				$paramlist[$v[0]] = $ps[$k];
+				unset($ps[$k]);
+			} elseif ($v[1]===false) {
+				// parameter is not defined and not optional, throw error
+				if (is_array($callback)) {
+					if (is_object($callback[0])) {
+						$name = get_class($callback[0]) . '::' . $callback[1];
+					} else {
+						$name = $callback[0];
+					}
+				} else {
+					$name = $callback;
+				}
+
+				throw new Dwoo_Compilation_Exception($this, 'Argument '.$k.'/'.$v[0].' missing for '.str_replace(array('Dwoo_Plugin_', '_compile'), '', $name));
+			} elseif ($v[2]===null) {
+				// enforce lowercased null if default value is null (php outputs NULL with var export)
+				$paramlist[$v[0]] = array('null', null, self::T_NULL);
+			} else {
+				// outputs default value with var_export
+				$paramlist[$v[0]] = array(var_export($v[2], true), $v[2]);
+			}
+		}
+
+		if (count($ps)) {
+			foreach ($ps as $i=>$p) {
+				array_push($paramlist, $p);
+			}
+		}
+
+		return $paramlist;
+	}
+
+	/**
+	 * returns the parameter map of the given callback, it filters out entries typed as Dwoo and Dwoo_Compiler and turns the rest parameter into a "*"
+	 *
+	 * @param callback $callback the function/method to reflect on
+	 * @return array processed parameter map
+	 */
+	protected function getParamMap($callback)
+	{
+		if (is_null($callback)) {
+			return array(array('*', true));
+		}
+		if (is_array($callback)) {
+			$ref = new ReflectionMethod($callback[0], $callback[1]);
+		} else {
+			$ref = new ReflectionFunction($callback);
+		}
+
+		$out = array();
+		foreach ($ref->getParameters() as $param) {
+			if (($class = $param->getClass()) !== null && ($class->name === 'Dwoo' || $class->name === 'Dwoo_Core')) {
+				continue;
+			}
+			if (($class = $param->getClass()) !== null && $class->name === 'Dwoo_Compiler') {
+				continue;
+			}
+			if ($param->getName() === 'rest' && $param->isArray() === true) {
+				$out[] = array('*', $param->isOptional(), null);
+				continue;
+			}
+			$out[] = array($param->getName(), $param->isOptional(), $param->isOptional() ? $param->getDefaultValue() : null);
+		}
+
+		return $out;
+	}
+
+	/**
+	 * returns a default instance of this compiler, used by default by all Dwoo templates that do not have a
+	 * specific compiler assigned and when you do not override the default compiler factory function
+	 *
+	 * @see Dwoo_Core::setDefaultCompilerFactory()
+	 * @return Dwoo_Compiler
+	 */
+	public static function compilerFactory()
+	{
+		if (self::$instance === null) {
+			new self;
+		}
+		return self::$instance;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Core.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Core.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Core.php	(revision 825)
@@ -0,0 +1,1607 @@
+<?php
+
+define('DWOO_DIRECTORY', dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR);
+
+/**
+ * main dwoo class, allows communication between the compiler, template and data classes
+ *
+ * <pre>
+ * requirements :
+ *  php 5.2.0 or above (might work below, it's a rough estimate)
+ *  SPL and PCRE extensions (for php versions prior to 5.3.0)
+ *  mbstring extension for some string manipulation plugins (especially if you intend to use UTF-8)
+ * recommended :
+ *  hash extension (for Dwoo_Template_String - minor performance boost)
+ *
+ * project created :
+ *  2008-01-05
+ * </pre>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Core
+{
+    /**
+     * current version number
+     *
+     * @var string
+     */
+    const VERSION = '1.1.1';
+
+    /**
+     * unique number of this dwoo release
+     *
+     * this can be used by templates classes to check whether the compiled template
+     * has been compiled before this release or not, so that old templates are
+     * recompiled automatically when Dwoo is updated
+     */
+    const RELEASE_TAG = 17;
+
+    /**#@+
+     * constants that represents all plugin types
+     *
+     * these are bitwise-operation-safe values to allow multiple types
+     * on a single plugin
+     *
+     * @var int
+     */
+    const CLASS_PLUGIN = 1;
+    const FUNC_PLUGIN = 2;
+    const NATIVE_PLUGIN = 4;
+    const BLOCK_PLUGIN = 8;
+    const COMPILABLE_PLUGIN = 16;
+    const CUSTOM_PLUGIN = 32;
+    const SMARTY_MODIFIER = 64;
+    const SMARTY_BLOCK = 128;
+    const SMARTY_FUNCTION = 256;
+    const PROXY_PLUGIN = 512;
+    const TEMPLATE_PLUGIN = 1024;
+    /**#@-*/
+
+    /**
+     * character set of the template, used by string manipulation plugins
+     *
+     * it must be lowercase, but setCharset() will take care of that
+     *
+     * @see setCharset
+     * @see getCharset
+     * @var string
+     */
+    protected $charset = 'utf-8';
+
+    /**
+     * global variables that are accessible through $dwoo.* in the templates
+     *
+     * default values include:
+     *
+     * $dwoo.version - current version number
+     * $dwoo.ad - a Powered by Dwoo link pointing to dwoo.org
+     * $dwoo.now - the current time
+     * $dwoo.template - the current template filename
+     * $dwoo.charset - the character set used by the template
+     *
+     * on top of that, foreach and other plugins can store special values in there,
+     * see their documentation for more details.
+     *
+     * @private
+     * @var array
+     */
+    public $globals;
+
+    /**
+     * directory where the compiled templates are stored
+     *
+     * defaults to DWOO_COMPILEDIR (= dwoo_dir/compiled by default)
+     *
+     * @var string
+     */
+    protected $compileDir;
+
+    /**
+     * directory where the cached templates are stored
+     *
+     * defaults to DWOO_CACHEDIR (= dwoo_dir/cache by default)
+     *
+     * @var string
+     */
+    protected $cacheDir;
+
+    /**
+     * defines how long (in seconds) the cached files must remain valid
+     *
+     * can be overriden on a per-template basis
+     *
+     * -1 = never delete
+     * 0 = disabled
+     * >0 = duration in seconds
+     *
+     * @var int
+     */
+    protected $cacheTime = 0;
+
+    /**
+     * security policy object
+     *
+     * @var Dwoo_Security_Policy
+     */
+    protected $securityPolicy = null;
+
+    /**
+     * stores the custom plugins callbacks
+     *
+     * @see addPlugin
+     * @see removePlugin
+     * @var array
+     */
+    protected $plugins = array();
+
+    /**
+     * stores the filter callbacks
+     *
+     * @see addFilter
+     * @see removeFilter
+     * @var array
+     */
+    protected $filters = array();
+
+    /**
+     * stores the resource types and associated
+     * classes / compiler classes
+     *
+     * @var array
+     */
+    protected $resources = array
+    (
+        'file'      =>  array
+        (
+            'class'     =>  'Dwoo_Template_File',
+            'compiler'  =>  null
+        ),
+        'string'    =>  array
+        (
+            'class'     =>  'Dwoo_Template_String',
+            'compiler'  =>  null
+        )
+    );
+
+    /**
+     * the dwoo loader object used to load plugins by this dwoo instance
+     *
+     * @var Dwoo_ILoader
+     */
+    protected $loader = null;
+
+    /**
+     * currently rendered template, set to null when not-rendering
+     *
+     * @var Dwoo_ITemplate
+     */
+    protected $template = null;
+
+    /**
+     * stores the instances of the class plugins during template runtime
+     *
+     * @var array
+     */
+    protected $runtimePlugins;
+
+    /**
+     * stores the returned values during template runtime
+     *
+     * @var array
+     */
+    protected $returnData;
+
+    /**
+     * stores the data during template runtime
+     *
+     * @var array
+     * @private
+     */
+    public $data;
+
+    /**
+     * stores the current scope during template runtime
+     *
+     * this should ideally not be accessed directly from outside template code
+     *
+     * @var mixed
+     * @private
+     */
+    public $scope;
+
+    /**
+     * stores the scope tree during template runtime
+     *
+     * @var array
+     */
+    protected $scopeTree;
+
+    /**
+     * stores the block plugins stack during template runtime
+     *
+     * @var array
+     */
+    protected $stack;
+
+    /**
+     * stores the current block plugin at the top of the stack during template runtime
+     *
+     * @var Dwoo_Block_Plugin
+     */
+    protected $curBlock;
+
+    /**
+     * stores the output buffer during template runtime
+     *
+     * @var string
+     */
+    protected $buffer;
+
+    /**
+     * stores plugin proxy
+     *
+     * @var Dwoo_IPluginProxy
+     */
+    protected $pluginProxy;
+
+    /**
+     * constructor, sets the cache and compile dir to the default values if not provided
+     *
+     * @param string $compileDir path to the compiled directory, defaults to lib/compiled
+     * @param string $cacheDir path to the cache directory, defaults to lib/cache
+     */
+    public function __construct($compileDir = null, $cacheDir = null)
+    {
+        if ($compileDir !== null) {
+            $this->setCompileDir($compileDir);
+        }
+        if ($cacheDir !== null) {
+            $this->setCacheDir($cacheDir);
+        }
+        $this->initGlobals();
+    }
+
+    /**
+     * resets some runtime variables to allow a cloned object to be used to render sub-templates
+     */
+    public function __clone()
+    {
+        $this->template = null;
+        unset($this->data);
+        unset($this->returnData);
+    }
+
+    /**
+     * outputs the template instead of returning it, this is basically a shortcut for get(*, *, *, true)
+     *
+     * @see get
+     * @param mixed $tpl template, can either be a Dwoo_ITemplate object (i.e. Dwoo_Template_File), a valid path to a template, or
+     *                   a template as a string it is recommended to provide a Dwoo_ITemplate as it will probably make things faster,
+     *                   especially if you render a template multiple times
+     * @param mixed $data the data to use, can either be a Dwoo_IDataProvider object (i.e. Dwoo_Data) or an associative array. if you're
+     *                    rendering the template from cache, it can be left null
+     * @param Dwoo_ICompiler $compiler the compiler that must be used to compile the template, if left empty a default
+     *                                Dwoo_Compiler will be used.
+     * @return string nothing or the template output if $output is true
+     */
+    public function output($tpl, $data = array(), Dwoo_ICompiler $compiler = null)
+    {
+        return $this->get($tpl, $data, $compiler, true);
+    }
+
+    /**
+     * returns the given template rendered using the provided data and optional compiler
+     *
+     * @param mixed $tpl template, can either be a Dwoo_ITemplate object (i.e. Dwoo_Template_File), a valid path to a template, or
+     *                   a template as a string it is recommended to provide a Dwoo_ITemplate as it will probably make things faster,
+     *                   especially if you render a template multiple times
+     * @param mixed $data the data to use, can either be a Dwoo_IDataProvider object (i.e. Dwoo_Data) or an associative array. if you're
+     *                    rendering the template from cache, it can be left null
+     * @param Dwoo_ICompiler $compiler the compiler that must be used to compile the template, if left empty a default
+     *                                Dwoo_Compiler will be used.
+     * @param bool $output flag that defines whether the function returns the output of the template (false, default) or echoes it directly (true)
+     * @return string nothing or the template output if $output is false
+     */
+    public function get($_tpl, $data = array(), $_compiler = null, $_output = false)
+    {
+        // a render call came from within a template, so we need a new dwoo instance in order to avoid breaking this one
+        if ($this->template instanceof Dwoo_ITemplate) {
+            $clone = clone $this;
+            return $clone->get($_tpl, $data, $_compiler, $_output);
+        }
+
+        // auto-create template if required
+        if ($_tpl instanceof Dwoo_ITemplate) {
+            // valid, skip
+        } elseif (is_string($_tpl) && file_exists($_tpl)) {
+            $_tpl = new Dwoo_Template_File($_tpl);
+        } else {
+            throw new Dwoo_Exception('Dwoo->get/Dwoo->output\'s first argument must be a Dwoo_ITemplate (i.e. Dwoo_Template_File) or a valid path to a template file', E_USER_NOTICE);
+        }
+
+        // save the current template, enters render mode at the same time
+        // if another rendering is requested it will be proxied to a new Dwoo_Core(instance
+        $this->template = $_tpl;
+
+        // load data
+        if ($data instanceof Dwoo_IDataProvider) {
+            $this->data = $data->getData();
+        } elseif (is_array($data)) {
+            $this->data = $data;
+        } elseif ($data instanceof ArrayAccess) {
+            $this->data = $data;
+        } else {
+            throw new Dwoo_Exception('Dwoo->get/Dwoo->output\'s data argument must be a Dwoo_IDataProvider object (i.e. Dwoo_Data) or an associative array', E_USER_NOTICE);
+        }
+
+        $this->globals['template'] = $_tpl->getName();
+        $this->initRuntimeVars($_tpl);
+
+        // try to get cached template
+        $file = $_tpl->getCachedTemplate($this);
+        $doCache = $file === true;
+        $cacheLoaded = is_string($file);
+
+        if ($cacheLoaded === true) {
+            // cache is present, run it
+            if ($_output === true) {
+                include $file;
+                $this->template = null;
+            } else {
+                ob_start();
+                include $file;
+                $this->template = null;
+                return ob_get_clean();
+            }
+        } else {
+            // no cache present
+            if ($doCache === true) {
+                $dynamicId = uniqid();
+            }
+
+            // render template
+            $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
+            $out = include $compiledTemplate;
+
+            // template returned false so it needs to be recompiled
+            if ($out === false) {
+                $_tpl->forceCompilation();
+                $compiledTemplate = $_tpl->getCompiledTemplate($this, $_compiler);
+                $out = include $compiledTemplate;
+            }
+
+            if ($doCache === true) {
+                $out = preg_replace('/(<%|%>|<\?php|<\?|\?>)/', '<?php /*'.$dynamicId.'*/ echo \'$1\'; ?>', $out);
+                if (!class_exists('Dwoo_plugin_dynamic', false)) {
+                    $this->getLoader()->loadPlugin('dynamic');
+                }
+                $out = Dwoo_Plugin_dynamic::unescape($out, $dynamicId, $compiledTemplate);
+            }
+
+            // process filters
+            foreach ($this->filters as $filter) {
+                if (is_array($filter) && $filter[0] instanceof Dwoo_Filter) {
+                    $out = call_user_func($filter, $out);
+                } else {
+                    $out = call_user_func($filter, $this, $out);
+                }
+            }
+
+            if ($doCache === true) {
+                // building cache
+                $file = $_tpl->cache($this, $out);
+
+                // run it from the cache to be sure dynamics are rendered
+                if ($_output === true) {
+                    include $file;
+                    // exit render mode
+                    $this->template = null;
+                } else {
+                    ob_start();
+                    include $file;
+                    // exit render mode
+                    $this->template = null;
+                    return ob_get_clean();
+                }
+            } else {
+                // no need to build cache
+                // exit render mode
+                $this->template = null;
+                // output
+                if ($_output === true) {
+                    echo $out;
+                }
+                return $out;
+            }
+        }
+    }
+
+    /**
+     * re-initializes the globals array before each template run
+     *
+     * this method is only callede once when the Dwoo object is created
+     */
+    protected function initGlobals()
+    {
+        $this->globals = array
+        (
+            'version'   =>  self::VERSION,
+            'ad'        =>  '<a href="http://dwoo.org/">Powered by Dwoo</a>',
+            'now'       =>  $_SERVER['REQUEST_TIME'],
+            'charset'   =>  $this->charset,
+        );
+    }
+
+    /**
+     * re-initializes the runtime variables before each template run
+     *
+     * override this method to inject data in the globals array if needed, this
+     * method is called before each template execution
+     *
+     * @param Dwoo_ITemplate $tpl the template that is going to be rendered
+     */
+    protected function initRuntimeVars(Dwoo_ITemplate $tpl)
+    {
+        $this->runtimePlugins = array();
+        $this->scope =& $this->data;
+        $this->scopeTree = array();
+        $this->stack = array();
+        $this->curBlock = null;
+        $this->buffer = '';
+        $this->returnData = array();
+    }
+
+    /*
+     * --------- settings functions ---------
+     */
+
+    /**
+     * adds a custom plugin that is not in one of the plugin directories
+     *
+     * @param string $name the plugin name to be used in the templates
+     * @param callback $callback the plugin callback, either a function name,
+     *                           a class name or an array containing an object
+     *                           or class name and a method name
+     * @param bool $compilable if set to true, the plugin is assumed to be compilable
+     */
+    public function addPlugin($name, $callback, $compilable = false)
+    {
+        $compilable = $compilable ? self::COMPILABLE_PLUGIN : 0;
+        if (is_array($callback)) {
+            if (is_subclass_of(is_object($callback[0]) ? get_class($callback[0]) : $callback[0], 'Dwoo_Block_Plugin')) {
+                $this->plugins[$name] = array('type'=>self::BLOCK_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>(is_object($callback[0]) ? get_class($callback[0]) : $callback[0]));
+            } else {
+                $this->plugins[$name] = array('type'=>self::CLASS_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>(is_object($callback[0]) ? get_class($callback[0]) : $callback[0]), 'function'=>$callback[1]);
+            }
+        } elseif (class_exists($callback, false)) {
+            if (is_subclass_of($callback, 'Dwoo_Block_Plugin')) {
+                $this->plugins[$name] = array('type'=>self::BLOCK_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>$callback);
+            } else {
+                $this->plugins[$name] = array('type'=>self::CLASS_PLUGIN | $compilable, 'callback'=>$callback, 'class'=>$callback, 'function'=>($compilable ? 'compile' : 'process'));
+            }
+        } elseif (function_exists($callback)) {
+            $this->plugins[$name] = array('type'=>self::FUNC_PLUGIN | $compilable, 'callback'=>$callback);
+        } else {
+            throw new Dwoo_Exception('Callback could not be processed correctly, please check that the function/class you used exists');
+        }
+    }
+
+    /**
+     * removes a custom plugin
+     *
+     * @param string $name the plugin name
+     */
+    public function removePlugin($name)
+    {
+        if (isset($this->plugins[$name])) {
+            unset($this->plugins[$name]);
+        }
+    }
+
+    /**
+     * adds a filter to this Dwoo instance, it will be used to filter the output of all the templates rendered by this instance
+     *
+     * @param mixed $callback a callback or a filter name if it is autoloaded from a plugin directory
+     * @param bool $autoload if true, the first parameter must be a filter name from one of the plugin directories
+     */
+    public function addFilter($callback, $autoload = false)
+    {
+        if ($autoload) {
+            $class = 'Dwoo_Filter_'.$callback;
+
+            if (!class_exists($class, false) && !function_exists($class)) {
+                try {
+                    $this->getLoader()->loadPlugin($callback);
+                } catch (Dwoo_Exception $e) {
+                    if (strstr($callback, 'Dwoo_Filter_')) {
+                        throw new Dwoo_Exception('Wrong filter name : '.$callback.', the "Dwoo_Filter_" prefix should not be used, please only use "'.str_replace('Dwoo_Filter_', '', $callback).'"');
+                    } else {
+                        throw new Dwoo_Exception('Wrong filter name : '.$callback.', when using autoload the filter must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Filter_name"');
+                    }
+                }
+            }
+
+            if (class_exists($class, false)) {
+                $callback = array(new $class($this), 'process');
+            } elseif (function_exists($class)) {
+                $callback = $class;
+            } else {
+                throw new Dwoo_Exception('Wrong filter name : '.$callback.', when using autoload the filter must be in one of your plugin dir as "name.php" containg a class or function named "Dwoo_Filter_name"');
+            }
+
+            $this->filters[] = $callback;
+        } else {
+            $this->filters[] = $callback;
+        }
+    }
+
+    /**
+     * removes a filter
+     *
+     * @param mixed $callback callback or filter name if it was autoloaded
+     */
+    public function removeFilter($callback)
+    {
+        if (($index = array_search('Dwoo_Filter_'.$callback, $this->filters, true)) !== false) {
+            unset($this->filters[$index]);
+        } elseif (($index = array_search($callback, $this->filters, true)) !== false) {
+            unset($this->filters[$index]);
+        } else  {
+            $class = 'Dwoo_Filter_' . $callback;
+            foreach ($this->filters as $index=>$filter) {
+                if (is_array($filter) && $filter[0] instanceof $class) {
+                    unset($this->filters[$index]);
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * adds a resource or overrides a default one
+     *
+     * @param string $name the resource name
+     * @param string $class the resource class (which must implement Dwoo_ITemplate)
+     * @param callback $compilerFactory the compiler factory callback, a function that must return a compiler instance used to compile this resource, if none is provided. by default it will produce a Dwoo_Compiler object
+     */
+    public function addResource($name, $class, $compilerFactory = null)
+    {
+        if (strlen($name) < 2) {
+            throw new Dwoo_Exception('Resource names must be at least two-character long to avoid conflicts with Windows paths');
+        }
+
+        if (!class_exists($class)) {
+            throw new Dwoo_Exception('Resource class does not exist');
+        }
+
+        $interfaces = class_implements($class);
+        if (in_array('Dwoo_ITemplate', $interfaces) === false) {
+            throw new Dwoo_Exception('Resource class must implement Dwoo_ITemplate');
+        }
+
+        $this->resources[$name] = array('class'=>$class, 'compiler'=>$compilerFactory);
+    }
+
+    /**
+     * removes a custom resource
+     *
+     * @param string $name the resource name
+     */
+    public function removeResource($name)
+    {
+        unset($this->resources[$name]);
+        if ($name==='file') {
+            $this->resources['file'] = array('class'=>'Dwoo_Template_File', 'compiler'=>null);
+        }
+    }
+
+    /*
+     * --------- getters and setters ---------
+     */
+
+    /**
+     * sets the loader object to use to load plugins
+     *
+     * @param Dwoo_ILoader $loader loader object
+     */
+    public function setLoader(Dwoo_ILoader $loader)
+    {
+        $this->loader = $loader;
+    }
+
+    /**
+     * returns the current loader object or a default one if none is currently found
+     *
+     * @param Dwoo_ILoader
+     */
+    public function getLoader()
+    {
+        if ($this->loader === null) {
+            $this->loader = new Dwoo_Loader($this->getCompileDir());
+        }
+
+        return $this->loader;
+    }
+
+    /**
+     * returns the custom plugins loaded
+     *
+     * used by the Dwoo_ITemplate classes to pass the custom plugins to their Dwoo_ICompiler instance
+     *
+     * @return array
+     */
+    public function getCustomPlugins()
+    {
+        return $this->plugins;
+    }
+
+    /**
+     * returns the cache directory with a trailing DIRECTORY_SEPARATOR
+     *
+     * @return string
+     */
+    public function getCacheDir()
+    {
+        if ($this->cacheDir === null) {
+            $this->setCacheDir(DWOO_DIRECTORY.'cache'.DIRECTORY_SEPARATOR);
+        }
+
+        return $this->cacheDir;
+    }
+
+    /**
+     * sets the cache directory and automatically appends a DIRECTORY_SEPARATOR
+     *
+     * @param string $dir the cache directory
+     */
+    public function setCacheDir($dir)
+    {
+        $this->cacheDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR;
+        if (is_writable($this->cacheDir) === false) {
+            throw new Dwoo_Exception('The cache directory must be writable, chmod "'.$this->cacheDir.'" to make it writable');
+        }
+    }
+
+    /**
+     * returns the compile directory with a trailing DIRECTORY_SEPARATOR
+     *
+     * @return string
+     */
+    public function getCompileDir()
+    {
+        if ($this->compileDir === null) {
+            $this->setCompileDir(DWOO_DIRECTORY.'compiled'.DIRECTORY_SEPARATOR);
+        }
+
+        return $this->compileDir;
+    }
+
+    /**
+     * sets the compile directory and automatically appends a DIRECTORY_SEPARATOR
+     *
+     * @param string $dir the compile directory
+     */
+    public function setCompileDir($dir)
+    {
+        $this->compileDir = rtrim($dir, '/\\').DIRECTORY_SEPARATOR;
+        if (is_writable($this->compileDir) === false) {
+            throw new Dwoo_Exception('The compile directory must be writable, chmod "'.$this->compileDir.'" to make it writable');
+        }
+    }
+
+    /**
+     * returns the default cache time that is used with templates that do not have a cache time set
+     *
+     * @return int the duration in seconds
+     */
+    public function getCacheTime()
+    {
+        return $this->cacheTime;
+    }
+
+    /**
+     * sets the default cache time to use with templates that do not have a cache time set
+     *
+     * @param int $seconds the duration in seconds
+     */
+    public function setCacheTime($seconds)
+    {
+        $this->cacheTime = (int) $seconds;
+    }
+
+    /**
+     * returns the character set used by the string manipulation plugins
+     *
+     * the charset is automatically lowercased
+     *
+     * @return string
+     */
+    public function getCharset()
+    {
+        return $this->charset;
+    }
+
+    /**
+     * sets the character set used by the string manipulation plugins
+     *
+     * the charset will be automatically lowercased
+     *
+     * @param string $charset the character set
+     */
+    public function setCharset($charset)
+    {
+        $this->charset = strtolower((string) $charset);
+    }
+
+    /**
+     * returns the current template being rendered, when applicable, or null
+     *
+     * @return Dwoo_ITemplate|null
+     */
+    public function getTemplate()
+    {
+        return $this->template;
+    }
+
+    /**
+     * sets the current template being rendered
+     *
+     * @param Dwoo_ITemplate $tpl template object
+     */
+    public function setTemplate(Dwoo_ITemplate $tpl)
+    {
+        $this->template = $tpl;
+    }
+
+    /**
+     * sets the default compiler factory function for the given resource name
+     *
+     * a compiler factory must return a Dwoo_ICompiler object pre-configured to fit your needs
+     *
+     * @param string $resourceName the resource name (i.e. file, string)
+     * @param callback $compilerFactory the compiler factory callback
+     */
+    public function setDefaultCompilerFactory($resourceName, $compilerFactory)
+    {
+        $this->resources[$resourceName]['compiler'] = $compilerFactory;
+    }
+
+    /**
+     * returns the default compiler factory function for the given resource name
+     *
+     * @param string $resourceName the resource name
+     * @return callback the compiler factory callback
+     */
+    public function getDefaultCompilerFactory($resourceName)
+    {
+        return $this->resources[$resourceName]['compiler'];
+    }
+
+    /**
+     * sets the security policy object to enforce some php security settings
+     *
+     * use this if untrusted persons can modify templates
+     *
+     * @param Dwoo_Security_Policy $policy the security policy object
+     */
+    public function setSecurityPolicy(Dwoo_Security_Policy $policy = null)
+    {
+        $this->securityPolicy = $policy;
+    }
+
+    /**
+     * returns the current security policy object or null by default
+     *
+     * @return Dwoo_Security_Policy|null the security policy object if any
+     */
+    public function getSecurityPolicy()
+    {
+        return $this->securityPolicy;
+    }
+
+    /**
+     * sets the object that must be used as a plugin proxy when plugin can't be found
+     * by dwoo's loader
+     *
+     * @param Dwoo_IPluginProxy $pluginProxy the proxy object
+     */
+    public function setPluginProxy(Dwoo_IPluginProxy $pluginProxy) {
+        $this->pluginProxy = $pluginProxy;
+    }
+
+    /**
+     * returns the current plugin proxy object or null by default
+     *
+     * @param Dwoo_IPluginProxy|null the proxy object if any
+     */
+    public function getPluginProxy() {
+        return $this->pluginProxy;
+    }
+
+    /*
+     * --------- util functions ---------
+     */
+
+    /**
+     * [util function] checks whether the given template is cached or not
+     *
+     * @param Dwoo_ITemplate $tpl the template object
+     * @return bool
+     */
+    public function isCached(Dwoo_ITemplate $tpl)
+    {
+        return is_string($tpl->getCachedTemplate($this));
+    }
+
+    /**
+     * [util function] clears the cached templates if they are older than the given time
+     *
+     * @param int $olderThan minimum time (in seconds) required for a cached template to be cleared
+     * @return int the amount of templates cleared
+     */
+    public function clearCache($olderThan=-1)
+    {
+        $cacheDirs = new RecursiveDirectoryIterator($this->getCacheDir());
+        $cache = new RecursiveIteratorIterator($cacheDirs);
+        $expired = time() - $olderThan;
+        $count = 0;
+        foreach ($cache as $file) {
+            if ($cache->isDot() || $cache->isDir() || substr($file, -5) !== '.html') {
+                continue;
+            }
+            if ($cache->getCTime() < $expired) {
+                $count += unlink((string) $file) ? 1 : 0;
+            }
+        }
+        return $count;
+    }
+
+    /**
+     * [util function] fetches a template object of the given resource
+     *
+     * @param string $resourceName the resource name (i.e. file, string)
+     * @param string $resourceId the resource identifier (i.e. file path)
+     * @param int $cacheTime the cache time setting for this resource
+     * @param string $cacheId the unique cache identifier
+     * @param string $compileId the unique compiler identifier
+     * @return Dwoo_ITemplate
+     */
+    public function templateFactory($resourceName, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null)
+    {
+        if (isset($this->resources[$resourceName])) {
+            // TODO could be changed to $this->resources[$resourceName]['class']::templateFactory(..) in 5.3 maybe
+            return call_user_func(array($this->resources[$resourceName]['class'], 'templateFactory'), $this, $resourceId, $cacheTime, $cacheId, $compileId, $parentTemplate);
+        } else {
+            throw new Dwoo_Exception('Unknown resource type : '.$resourceName);
+        }
+    }
+
+    /**
+     * [util function] checks if the input is an array or arrayaccess object, optionally it can also check if it's empty
+     *
+     * @param mixed $value the variable to check
+     * @param bool $checkIsEmpty if true, the function will also check if the array|arrayaccess is empty,
+     *                              and return true only if it's not empty
+     * @return int|bool true if it's an array|arrayaccess (or the item count if $checkIsEmpty is true) or false if it's not an array|arrayaccess (or 0 if $checkIsEmpty is true)
+     */
+    public function isArray($value, $checkIsEmpty=false)
+    {
+        if (is_array($value) === true || $value instanceof ArrayAccess) {
+            if ($checkIsEmpty === false) {
+                return true;
+            } else {
+                return $this->count($value);
+            }
+        }
+    }
+
+    /**
+     * [util function] checks if the input is an array or a traversable object, optionally it can also check if it's empty
+     *
+     * @param mixed $value the variable to check
+     * @param bool $checkIsEmpty if true, the function will also check if the array|traversable is empty,
+     *                              and return true only if it's not empty
+     * @return int|bool true if it's an array|traversable (or the item count if $checkIsEmpty is true) or false if it's not an array|traversable (or 0 if $checkIsEmpty is true)
+     */
+    public function isTraversable($value, $checkIsEmpty=false)
+    {
+        if (is_array($value) === true) {
+            if ($checkIsEmpty === false) {
+                return true;
+            } else {
+                return count($value) > 0;
+            }
+        } elseif ($value instanceof Traversable) {
+            if ($checkIsEmpty === false) {
+                return true;
+            } else {
+                return $this->count($value);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * [util function] counts an array or arrayaccess/traversable object
+     * @param mixed $value
+     * @return int|bool the count for arrays and objects that implement countable, true for other objects that don't, and 0 for empty elements
+     */
+    public function count($value)
+    {
+        if (is_array($value) === true || $value instanceof Countable) {
+            return count($value);
+        } elseif ($value instanceof ArrayAccess) {
+            if ($value->offsetExists(0)) {
+                return true;
+            }
+        } elseif ($value instanceof Iterator) {
+            $value->rewind();
+            if ($value->valid()) {
+                return true;
+            }
+        } elseif ($value instanceof Traversable) {
+            foreach ($value as $dummy) {
+                return true;
+            }
+        }
+        return 0;
+    }
+
+    /**
+     * [util function] triggers a dwoo error
+     *
+     * @param string $message the error message
+     * @param int $level the error level, one of the PHP's E_* constants
+     */
+    public function triggerError($message, $level=E_USER_NOTICE)
+    {
+        if (!($tplIdentifier = $this->template->getResourceIdentifier())) {
+            $tplIdentifier = $this->template->getResourceName();
+        }
+        trigger_error('Dwoo error (in '.$tplIdentifier.') : '.$message, $level);
+    }
+
+    /*
+     * --------- runtime functions ---------
+     */
+
+    /**
+     * [runtime function] adds a block to the block stack
+     *
+     * @param string $blockName the block name (without Dwoo_Plugin_ prefix)
+     * @param array $args the arguments to be passed to the block's init() function
+     * @return Dwoo_Block_Plugin the newly created block
+     */
+    public function addStack($blockName, array $args=array())
+    {
+        if (isset($this->plugins[$blockName])) {
+            $class = $this->plugins[$blockName]['class'];
+        } else {
+            $class = 'Dwoo_Plugin_'.$blockName;
+        }
+
+        if ($this->curBlock !== null) {
+            $this->curBlock->buffer(ob_get_contents());
+            ob_clean();
+        } else {
+            $this->buffer .= ob_get_contents();
+            ob_clean();
+        }
+
+        $block = new $class($this);
+
+        $cnt = count($args);
+        if ($cnt===0) {
+            $block->init();
+        } elseif ($cnt===1) {
+            $block->init($args[0]);
+        } elseif ($cnt===2) {
+            $block->init($args[0], $args[1]);
+        } elseif ($cnt===3) {
+            $block->init($args[0], $args[1], $args[2]);
+        } elseif ($cnt===4) {
+            $block->init($args[0], $args[1], $args[2], $args[3]);
+        } else {
+            call_user_func_array(array($block,'init'), $args);
+        }
+
+        $this->stack[] = $this->curBlock = $block;
+        return $block;
+    }
+
+    /**
+     * [runtime function] removes the plugin at the top of the block stack
+     *
+     * calls the block buffer() function, followed by a call to end()
+     * and finally a call to process()
+     */
+    public function delStack()
+    {
+        $args = func_get_args();
+
+        $this->curBlock->buffer(ob_get_contents());
+        ob_clean();
+
+        $cnt = count($args);
+        if ($cnt===0) {
+            $this->curBlock->end();
+        } elseif ($cnt===1) {
+            $this->curBlock->end($args[0]);
+        } elseif ($cnt===2) {
+            $this->curBlock->end($args[0], $args[1]);
+        } elseif ($cnt===3) {
+            $this->curBlock->end($args[0], $args[1], $args[2]);
+        } elseif ($cnt===4) {
+            $this->curBlock->end($args[0], $args[1], $args[2], $args[3]);
+        } else {
+            call_user_func_array(array($this->curBlock, 'end'), $args);
+        }
+
+        $tmp = array_pop($this->stack);
+
+        if (count($this->stack) > 0) {
+            $this->curBlock = end($this->stack);
+            $this->curBlock->buffer($tmp->process());
+        } else {
+            $this->curBlock = null;
+            echo $tmp->process();
+        }
+
+        unset($tmp);
+    }
+
+    /**
+     * [runtime function] returns the parent block of the given block
+     *
+     * @param Dwoo_Block_Plugin $block
+     * @return Dwoo_Block_Plugin or false if the given block isn't in the stack
+     */
+    public function getParentBlock(Dwoo_Block_Plugin $block)
+    {
+        $index = array_search($block, $this->stack, true);
+        if ($index !== false && $index > 0) {
+            return $this->stack[$index-1];
+        }
+        return false;
+    }
+
+    /**
+     * [runtime function] finds the closest block of the given type, starting at the top of the stack
+     *
+     * @param string $type the type of plugin you want to find
+     * @return Dwoo_Block_Plugin or false if no plugin of such type is in the stack
+     */
+    public function findBlock($type)
+    {
+        if (isset($this->plugins[$type])) {
+            $type = $this->plugins[$type]['class'];
+        } else {
+            $type = 'Dwoo_Plugin_'.str_replace('Dwoo_Plugin_', '', $type);
+        }
+
+        $keys = array_keys($this->stack);
+        while (($key = array_pop($keys)) !== false) {
+            if ($this->stack[$key] instanceof $type) {
+                return $this->stack[$key];
+            }
+        }
+        return false;
+    }
+
+    /**
+     * [runtime function] returns a Dwoo_Plugin of the given class
+     *
+     * this is so a single instance of every class plugin is created at each template run,
+     * allowing class plugins to have "per-template-run" static variables
+     *
+     * @private
+     * @param string $class the class name
+     * @return mixed an object of the given class
+     */
+    public function getObjectPlugin($class)
+    {
+        if (isset($this->runtimePlugins[$class])) {
+            return $this->runtimePlugins[$class];
+        }
+        return $this->runtimePlugins[$class] = new $class($this);
+    }
+
+    /**
+     * [runtime function] calls the process() method of the given class-plugin name
+     *
+     * @param string $plugName the class plugin name (without Dwoo_Plugin_ prefix)
+     * @param array $params an array of parameters to send to the process() method
+     * @return string the process() return value
+     */
+    public function classCall($plugName, array $params = array())
+    {
+        $class = 'Dwoo_Plugin_'.$plugName;
+
+        $plugin = $this->getObjectPlugin($class);
+
+        $cnt = count($params);
+        if ($cnt===0) {
+            return $plugin->process();
+        } elseif ($cnt===1) {
+            return $plugin->process($params[0]);
+        } elseif ($cnt===2) {
+            return $plugin->process($params[0], $params[1]);
+        } elseif ($cnt===3) {
+            return $plugin->process($params[0], $params[1], $params[2]);
+        } elseif ($cnt===4) {
+            return $plugin->process($params[0], $params[1], $params[2], $params[3]);
+        } else {
+            return call_user_func_array(array($plugin, 'process'), $params);
+        }
+    }
+
+    /**
+     * [runtime function] calls a php function
+     *
+     * @param string $callback the function to call
+     * @param array $params an array of parameters to send to the function
+     * @return mixed the return value of the called function
+     */
+    public function arrayMap($callback, array $params)
+    {
+        if ($params[0] === $this) {
+            $addThis = true;
+            array_shift($params);
+        }
+        if ((is_array($params[0]) || ($params[0] instanceof Iterator && $params[0] instanceof ArrayAccess))) {
+            if (empty($params[0])) {
+                return $params[0];
+            }
+
+            // array map
+            $out = array();
+            $cnt = count($params);
+
+            if (isset($addThis)) {
+                array_unshift($params, $this);
+                $items = $params[1];
+                $keys = array_keys($items);
+
+                if (is_string($callback) === false) {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = call_user_func_array($callback, array(1=>$items[$i]) + $params);
+                    }
+                } elseif ($cnt===1) {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = $callback($this, $items[$i]);
+                    }
+                } elseif ($cnt===2) {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = $callback($this, $items[$i], $params[2]);
+                    }
+                } elseif ($cnt===3) {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = $callback($this, $items[$i], $params[2], $params[3]);
+                    }
+                } else {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = call_user_func_array($callback, array(1=>$items[$i]) + $params);
+                    }
+                }
+            } else {
+                $items = $params[0];
+                $keys = array_keys($items);
+
+                if (is_string($callback) === false) {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
+                    }
+                } elseif ($cnt===1) {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = $callback($items[$i]);
+                    }
+                } elseif ($cnt===2) {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = $callback($items[$i], $params[1]);
+                    }
+                } elseif ($cnt===3) {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = $callback($items[$i], $params[1], $params[2]);
+                    }
+                } elseif ($cnt===4) {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = $callback($items[$i], $params[1], $params[2], $params[3]);
+                    }
+                } else {
+                    while (($i = array_shift($keys)) !== null) {
+                        $out[] = call_user_func_array($callback, array($items[$i]) + $params);
+                    }
+                }
+            }
+            return $out;
+        } else {
+            return $params[0];
+        }
+    }
+
+    /**
+     * [runtime function] reads a variable into the given data array
+     *
+     * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
+     * @param mixed $data the data array or object to read from
+     * @param bool $safeRead if true, the function will check whether the index exists to prevent any notices from being output
+     * @return mixed
+     */
+    public function readVarInto($varstr, $data, $safeRead = false)
+    {
+        if ($data === null) {
+            return null;
+        }
+
+        if (is_array($varstr) === false) {
+            preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
+        } else {
+            $m = $varstr;
+        }
+        unset($varstr);
+
+        while (list($k, $sep) = each($m[1])) {
+            if ($sep === '.' || $sep === '[' || $sep === '') {
+                // strip enclosing quotes if present
+                $m[2][$k] = preg_replace('#^(["\']?)(.*?)\1$#', '$2', $m[2][$k]);
+
+                if ((is_array($data) || $data instanceof ArrayAccess) && ($safeRead === false || isset($data[$m[2][$k]]))) {
+                    $data = $data[$m[2][$k]];
+                } else {
+                    return null;
+                }
+            } else {
+                if (is_object($data) && ($safeRead === false || isset($data->$m[2][$k]))) {
+                    $data = $data->$m[2][$k];
+                } else {
+                    return null;
+                }
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * [runtime function] reads a variable into the parent scope
+     *
+     * @param int $parentLevels the amount of parent levels to go from the current scope
+     * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
+     * @return mixed
+     */
+    public function readParentVar($parentLevels, $varstr = null)
+    {
+        $tree = $this->scopeTree;
+        $cur = $this->data;
+
+        while ($parentLevels--!==0) {
+            array_pop($tree);
+        }
+
+        while (($i = array_shift($tree)) !== null) {
+            if (is_object($cur)) {
+                $cur = $cur->$i;
+            } else {
+                $cur = $cur[$i];
+            }
+        }
+
+        if ($varstr!==null) {
+            return $this->readVarInto($varstr, $cur);
+        } else {
+            return $cur;
+        }
+    }
+
+    /**
+     * [runtime function] reads a variable into the current scope
+     *
+     * @param string $varstr the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
+     * @return mixed
+     */
+    public function readVar($varstr)
+    {
+        if (is_array($varstr)===true) {
+            $m = $varstr;
+            unset($varstr);
+        } else {
+            if (strstr($varstr, '.') === false && strstr($varstr, '[') === false && strstr($varstr, '->') === false) {
+                if ($varstr === 'dwoo') {
+                    return $this->globals;
+                } elseif ($varstr === '__' || $varstr === '_root' ) {
+                    return $this->data;
+                    $varstr = substr($varstr, 6);
+                } elseif ($varstr === '_' || $varstr === '_parent') {
+                    $varstr = '.'.$varstr;
+                    $tree = $this->scopeTree;
+                    $cur = $this->data;
+                    array_pop($tree);
+
+                    while (($i = array_shift($tree)) !== null) {
+                        if (is_object($cur)) {
+                            $cur = $cur->$i;
+                        } else {
+                            $cur = $cur[$i];
+                        }
+                    }
+
+                    return $cur;
+                }
+
+                $cur = $this->scope;
+
+                if (isset($cur[$varstr])) {
+                    return $cur[$varstr];
+                } else {
+                    return null;
+                }
+            }
+
+            if (substr($varstr, 0, 1) === '.') {
+                $varstr = 'dwoo'.$varstr;
+            }
+
+            preg_match_all('#(\[|->|\.)?((?:[^.[\]-]|-(?!>))+)\]?#i', $varstr, $m);
+        }
+
+        $i = $m[2][0];
+        if ($i === 'dwoo') {
+            $cur = $this->globals;
+            array_shift($m[2]);
+            array_shift($m[1]);
+            switch ($m[2][0]) {
+
+            case 'get':
+                $cur = $_GET;
+                break;
+            case 'post':
+                $cur = $_POST;
+                break;
+            case 'session':
+                $cur = $_SESSION;
+                break;
+            case 'cookies':
+            case 'cookie':
+                $cur = $_COOKIE;
+                break;
+            case 'server':
+                $cur = $_SERVER;
+                break;
+            case 'env':
+                $cur = $_ENV;
+                break;
+            case 'request':
+                $cur = $_REQUEST;
+                break;
+            case 'const':
+                array_shift($m[2]);
+                if (defined($m[2][0])) {
+                    return constant($m[2][0]);
+                } else {
+                    return null;
+                }
+
+            }
+            if ($cur !== $this->globals) {
+                array_shift($m[2]);
+                array_shift($m[1]);
+            }
+        } elseif ($i === '__' || $i === '_root') {
+            $cur = $this->data;
+            array_shift($m[2]);
+            array_shift($m[1]);
+        } elseif ($i === '_' || $i === '_parent') {
+            $tree = $this->scopeTree;
+            $cur = $this->data;
+
+            while (true) {
+                array_pop($tree);
+                array_shift($m[2]);
+                array_shift($m[1]);
+                if (current($m[2]) === '_' || current($m[2]) === '_parent') {
+                    continue;
+                }
+
+                while (($i = array_shift($tree)) !== null) {
+                    if (is_object($cur)) {
+                        $cur = $cur->$i;
+                    } else {
+                        $cur = $cur[$i];
+                    }
+                }
+                break;
+            }
+        } else {
+            $cur = $this->scope;
+        }
+
+        while (list($k, $sep) = each($m[1])) {
+            if ($sep === '.' || $sep === '[' || $sep === '') {
+                if ((is_array($cur) || $cur instanceof ArrayAccess) && isset($cur[$m[2][$k]])) {
+                    $cur = $cur[$m[2][$k]];
+                } else {
+                    return null;
+                }
+            } elseif ($sep === '->') {
+                if (is_object($cur)) {
+                    $cur = $cur->$m[2][$k];
+                } else {
+                    return null;
+                }
+            } else {
+                return null;
+            }
+        }
+
+        return $cur;
+    }
+
+    /**
+     * [runtime function] assign the value to the given variable
+     *
+     * @param mixed $value the value to assign
+     * @param string $scope the variable string, using dwoo variable syntax (i.e. "var.subvar[subsubvar]->property")
+     * @return bool true if assigned correctly or false if a problem occured while parsing the var string
+     */
+    public function assignInScope($value, $scope)
+    {
+        $tree =& $this->scopeTree;
+        $data =& $this->data;
+
+        if (!is_string($scope)) {
+            return $this->triggerError('Assignments must be done into strings, ('.gettype($scope).') '.var_export($scope, true).' given', E_USER_ERROR);
+        }
+        if (strstr($scope, '.') === false && strstr($scope, '->') === false) {
+            $this->scope[$scope] = $value;
+        } else {
+            // TODO handle _root/_parent scopes ?
+            preg_match_all('#(\[|->|\.)?([^.[\]-]+)\]?#i', $scope, $m);
+
+            $cur =& $this->scope;
+            $last = array(array_pop($m[1]), array_pop($m[2]));
+
+            while (list($k, $sep) = each($m[1])) {
+                if ($sep === '.' || $sep === '[' || $sep === '') {
+                    if (is_array($cur) === false) {
+                        $cur = array();
+                    }
+                    $cur =& $cur[$m[2][$k]];
+                } elseif ($sep === '->') {
+                    if (is_object($cur) === false) {
+                        $cur = new stdClass;
+                    }
+                    $cur =& $cur->$m[2][$k];
+                } else {
+                    return false;
+                }
+            }
+
+            if ($last[0] === '.' || $last[0] === '[' || $last[0] === '') {
+                if (is_array($cur) === false) {
+                    $cur = array();
+                }
+                $cur[$last[1]] = $value;
+            } elseif ($last[0] === '->') {
+                if (is_object($cur) === false) {
+                    $cur = new stdClass;
+                }
+                $cur->$last[1] = $value;
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
+     * [runtime function] sets the scope to the given scope string or array
+     *
+     * @param mixed $scope a string i.e. "level1.level2" or an array i.e. array("level1", "level2")
+     * @param bool $absolute if true, the scope is set from the top level scope and not from the current scope
+     * @return array the current scope tree
+     */
+    public function setScope($scope, $absolute = false)
+    {
+        $old = $this->scopeTree;
+
+        if (is_string($scope)===true) {
+            $scope = explode('.', $scope);
+        }
+
+        if ($absolute===true) {
+            $this->scope =& $this->data;
+            $this->scopeTree = array();
+        }
+
+        while (($bit = array_shift($scope)) !== null) {
+            if ($bit === '_' || $bit === '_parent') {
+                array_pop($this->scopeTree);
+                $this->scope =& $this->data;
+                $cnt = count($this->scopeTree);
+                for ($i=0;$i<$cnt;$i++)
+                    $this->scope =& $this->scope[$this->scopeTree[$i]];
+            } elseif ($bit === '__' || $bit === '_root') {
+                $this->scope =& $this->data;
+                $this->scopeTree = array();
+            } elseif (isset($this->scope[$bit])) {
+                if($this->scope instanceof ArrayAccess) {
+                    $tmp = $this->scope[$bit];
+                    $this->scope =& $tmp;
+                } else {
+                    $this->scope =& $this->scope[$bit];
+                }
+                $this->scopeTree[] = $bit;
+            } else {
+                unset($this->scope);
+                $this->scope = null;
+            }
+        }
+
+        return $old;
+    }
+
+    /**
+     * [runtime function] returns the entire data array
+     *
+     * @return array
+     */
+    public function getData()
+    {
+        return $this->data;
+    }
+
+    /**
+     * [runtime function] sets a return value for the currently running template
+     *
+     * @param string $name var name
+     * @param mixed $value var value
+     */
+    public function setReturnValue($name, $value)
+    {
+        $this->returnData[$name] = $value;
+    }
+
+    /**
+     * [runtime function] retrieves the return values set by the template
+     *
+     * @return array
+     */
+    public function getReturnValues()
+    {
+        return $this->returnData;
+    }
+
+    /**
+     * [runtime function] returns a reference to the current scope
+     *
+     * @return &mixed
+     */
+    public function &getScope()
+    {
+        return $this->scope;
+    }
+
+    /**
+     * Redirects all calls to unexisting to plugin proxy.
+     *
+     * @param string Method name
+     * @param array  List of arguments
+     * @return mixed
+     */
+    public function __call($method, $args) {
+        $proxy = $this->getPluginProxy();
+        if (!$proxy) {
+            throw new Dwoo_Exception('Call to undefined method '.__CLASS__.'::'.$method.'()');
+        }
+        return call_user_func_array($proxy->getCallback($method), $args);
+    }
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Data.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Data.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Data.php	(revision 825)
@@ -0,0 +1,250 @@
+<?php
+
+/**
+ * dwoo data object, use it for complex data assignments or if you want to easily pass it
+ * around multiple functions to avoid passing an array by reference
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Data implements Dwoo_IDataProvider
+{
+	/**
+	 * data array
+	 *
+	 * @var array
+	 */
+	protected $data = array();
+
+	/**
+	 * returns the data array
+	 *
+	 * @return array
+	 */
+	public function getData()
+	{
+		return $this->data;
+	}
+
+	/**
+	 * clears a the entire data or only the given key
+	 *
+	 * @param array|string $name clears only one value if you give a name, multiple values if
+	 * 							  you give an array of names, or the entire data if left null
+	 */
+	public function clear($name = null)
+	{
+		if ($name === null) {
+			$this->data = array();
+		} elseif (is_array($name)) {
+			foreach ($name as $index)
+				unset($this->data[$index]);
+		} else {
+			unset($this->data[$name]);
+		}
+	}
+
+	/**
+	 * overwrites the entire data with the given array
+	 *
+	 * @param array $data the new data array to use
+	 */
+	public function setData(array $data)
+	{
+		$this->data = $data;
+	}
+
+	/**
+	 * merges the given array(s) with the current data with array_merge
+	 *
+	 * @param array $data the array to merge
+	 * @param array $data2 $data3 ... other arrays to merge, optional, etc.
+	 */
+	public function mergeData(array $data)
+	{
+		$args = func_get_args();
+		while (list(,$v) = each($args)) {
+			if (is_array($v)) {
+				$this->data = array_merge($this->data, $v);
+			}
+		}
+	}
+
+	/**
+	 * assigns a value or an array of values to the data object
+	 *
+	 * @param array|string $name an associative array of multiple (index=>value) or a string
+	 * 					   that is the index to use, i.e. a value assigned to "foo" will be
+	 * 					   accessible in the template through {$foo}
+	 * @param mixed $val the value to assign, or null if $name was an array
+	 */
+	public function assign($name, $val = null)
+	{
+		if (is_array($name)) {
+			reset($name);
+			while (list($k,$v) = each($name))
+				$this->data[$k] = $v;
+		} else {
+			$this->data[$name] = $val;
+		}
+	}
+   	
+   	/**
+   	 * allows to assign variables using the object syntax
+   	 * 
+   	 * @param string $name the variable name
+   	 * @param string $value the value to assign to it
+   	 */
+   	public function __set($name, $value)
+   	{
+   		$this->assign($name, $value);
+   	}
+
+	/**
+	 * assigns a value by reference to the data object
+	 *
+	 * @param string $name the index to use, i.e. a value assigned to "foo" will be
+	 * 					   accessible in the template through {$foo}
+	 * @param mixed $val the value to assign by reference
+	 */
+	public function assignByRef($name, &$val)
+	{
+		$this->data[$name] =& $val;
+	}
+
+	/**
+	 * appends values or an array of values to the data object
+	 *
+	 * @param array|string $name an associative array of multiple (index=>value) or a string
+	 * 					   that is the index to use, i.e. a value assigned to "foo" will be
+	 * 					   accessible in the template through {$foo}
+	 * @param mixed $val the value to assign, or null if $name was an array
+	 * @param bool $merge true to merge data or false to append, defaults to false
+	 */
+   	public function append($name, $val = null, $merge = false)
+   	{
+   		if (is_array($name)) {
+			foreach ($name as $key=>$val) {
+				if (isset($this->data[$key]) && !is_array($this->data[$key])) {
+					settype($this->data[$key], 'array');
+				}
+
+				if ($merge === true && is_array($val)) {
+					$this->data[$key] = $val + $this->data[$key];
+				} else {
+					$this->data[$key][] = $val;
+				}
+			}
+   		} elseif ($val !== null) {
+			if (isset($this->data[$name]) && !is_array($this->data[$name])) {
+				settype($this->data[$name], 'array');
+			} elseif (!isset($this->data[$name])) {
+				$this->data[$name] = array();
+			}
+
+			if ($merge === true && is_array($val)) {
+				$this->data[$name] = $val + $this->data[$name];
+			} else {
+				$this->data[$name][] = $val;
+			}
+   		}
+   	}
+
+	/**
+	 * appends a value by reference to the data object
+	 *
+	 * @param string $name the index to use, i.e. a value assigned to "foo" will be
+	 * 					   accessible in the template through {$foo}
+	 * @param mixed $val the value to append by reference
+	 * @param bool $merge true to merge data or false to append, defaults to false
+	 */
+   	public function appendByRef($name, &$val, $merge = false)
+   	{
+   		if (isset($this->data[$name]) && !is_array($this->data[$name])) {
+			settype($this->data[$name], 'array');
+		}
+
+   		if ($merge === true && is_array($val)) {
+   			foreach ($val as $key => &$val) {
+   				$this->data[$name][$key] =& $val;
+   			}
+   		} else {
+   			$this->data[$name][] =& $val;
+   		}
+   	}
+   	
+   	/**
+   	 * returns true if the variable has been assigned already, false otherwise
+   	 * 
+   	 * @param string $name the variable name
+   	 * @return bool 
+   	 */
+   	public function isAssigned($name)
+   	{
+   		return isset($this->data[$name]);
+   	}
+   	
+   	/**
+   	 * supports calls to isset($dwooData->var)
+   	 * 
+   	 * @param string $name the variable name
+   	 */
+   	public function __isset($name)
+   	{
+   		return isset($this->data[$name]);
+   	}
+   	
+   	/**
+   	 * unassigns/removes a variable
+   	 * 
+   	 * @param string $name the variable name
+   	 */
+   	public function unassign($name)
+   	{
+   		unset($this->data[$name]);
+   	}
+   	
+   	/**
+   	 * supports unsetting variables using the object syntax
+   	 * 
+   	 * @param string $name the variable name
+   	 */
+   	public function __unset($name)
+   	{
+   		unset($this->data[$name]);
+   	}
+   	
+   	/**
+   	 * returns a variable if it was assigned
+   	 * 
+   	 * @param string $name the variable name
+   	 * @return mixed
+   	 */
+   	public function get($name)
+   	{
+   		return $this->__get($name);
+   	}
+
+   	/**
+   	 * allows to read variables using the object syntax
+   	 * 
+   	 * @param string $name the variable name
+   	 * @return mixed
+   	 */
+   	public function __get($name)
+   	{
+   		if (isset($this->data[$name])) {
+   			return $this->data[$name];
+   		} else {
+   			throw new Dwoo_Exception('Tried to read a value that was not assigned yet : "'.$name.'"');
+   		}
+   	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Exception.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Exception.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Exception.php	(revision 825)
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * main dwoo exception class
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Exception extends Exception
+{
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Filter.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Filter.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Filter.php	(revision 825)
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * base class for filters
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+abstract class Dwoo_Filter
+{
+	/**
+	 * the dwoo instance that runs this filter
+	 *
+	 * @var Dwoo
+	 */
+	protected $dwoo;
+
+	/**
+	 * constructor, if you override it, call parent::__construct($dwoo); or assign
+	 * the dwoo instance yourself if you need it
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that runs this plugin
+	 */
+	public function __construct(Dwoo_Core $dwoo)
+	{
+		$this->dwoo = $dwoo;
+	}
+
+	/**
+	 * processes the input and returns it filtered
+	 *
+	 * @param string $input the template to process
+	 * @return string
+	 */
+	abstract public function process($input);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ICompilable.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ICompilable.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ICompilable.php	(revision 825)
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * interface that represents a compilable plugin
+ *
+ * implement this to notify the compiler that this plugin does not need to be loaded at runtime.
+ *
+ * to implement it right, you must implement <em>public static function compile(Dwoo_Compiler $compiler, $arg, $arg, ...)</em>,
+ * which replaces the <em>process()</em> method (that means <em>compile()</em> should have all arguments it requires).
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+interface Dwoo_ICompilable
+{
+	// this replaces the process function
+	//public static function compile(Dwoo_Compiler $compiler, $arg, $arg, ...);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ICompilable/Block.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ICompilable/Block.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ICompilable/Block.php	(revision 825)
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * interface that represents a compilable block plugin
+ *
+ * implement this to notify the compiler that this plugin does not need to be loaded at runtime.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+interface Dwoo_ICompilable_Block
+{
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ICompiler.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ICompiler.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ICompiler.php	(revision 825)
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * interface that represents a dwoo compiler
+ *
+ * while implementing this is enough to interact with Dwoo/Dwoo_Templates, it is not
+ * sufficient to interact with Dwoo_Plugins, however the main purpose of creating a
+ * new compiler would be to interact with other/different plugins, that is why this
+ * interface has been left with the minimum requirements.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+interface Dwoo_ICompiler
+{
+	/**
+	 * compiles the provided string down to php code
+	 *
+	 * @param string $templateStr the template to compile
+	 * @return string a compiled php code string
+	 */
+	public function compile(Dwoo_Core $dwoo, Dwoo_ITemplate $template);
+
+	/**
+	 * adds the custom plugins loaded into Dwoo to the compiler so it can load them
+	 *
+	 * @see Dwoo_Core::addPlugin
+	 * @param array $customPlugins an array of custom plugins
+	 */
+	public function setCustomPlugins(array $customPlugins);
+
+	/**
+	 * sets the security policy object to enforce some php security settings
+	 *
+	 * use this if untrusted persons can modify templates,
+	 * set it on the Dwoo object as it will be passed onto the compiler automatically
+	 *
+	 * @param Dwoo_Security_Policy $policy the security policy object
+	 */
+	public function setSecurityPolicy(Dwoo_Security_Policy $policy = null);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/IDataProvider.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/IDataProvider.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/IDataProvider.php	(revision 825)
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * interface that represents a dwoo data object
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+interface Dwoo_IDataProvider
+{
+	/**
+	 * returns the data as an associative array that will be used in the template
+	 *
+	 * @return array
+	 */
+	public function getData();
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/IElseable.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/IElseable.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/IElseable.php	(revision 825)
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * interface that represents a block plugin that supports the else functionality
+ *
+ * the else block will enter an "hasElse" parameter inside the parameters array
+ * of the closest parent implementing this interface, the hasElse parameter contains
+ * the else output that should be appended to the block's content (see foreach or other
+ * block for examples)
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+interface Dwoo_IElseable
+{
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ILoader.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ILoader.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ILoader.php	(revision 825)
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * interface for dwoo plugin loaders
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+interface Dwoo_ILoader
+{
+	/**
+	 * loads a plugin file
+	 *
+	 * the second parameter is used to avoid permanent rehashing when using php functions,
+	 * however this means that if you have add a plugin that overrides a php function you have
+	 * to delete the classpath.cache file(s) by hand to force a rehash of the plugins
+	 *
+	 * @param string $class the plugin name, without the Dwoo_Plugin_ prefix
+	 * @param bool $forceRehash if true, the class path caches will be rebuilt if the plugin is not found, in case it has just been added, defaults to true
+	 */
+	public function loadPlugin($class, $forceRehash = true);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/IPluginProxy.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/IPluginProxy.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/IPluginProxy.php	(revision 825)
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * interface that represents a dwoo plugin proxy
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Denis Arh <denis@arh.cc>
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Denis Arh, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+interface Dwoo_IPluginProxy
+{
+	/**
+	 * returns true or false to say whether the given plugin is handled by this proxy or not
+	 *
+	 * @param string $name the plugin name
+	 * @return bool true if the plugin is known and usable, otherwise false
+	 */
+	public function handles($name);
+
+	/**
+	 * returns the code (as a string) to call the plugin
+	 * (this will be executed at runtime inside the Dwoo class)
+	 *
+	 * @param string $name the plugin name
+	 * @param array $params a parameter array, array key "*" is the rest array
+	 * @return string
+	 */
+	public function getCode($name, $params);
+
+	/**
+	 * returns a callback to the plugin, this is used with the reflection API to
+	 * find out about the plugin's parameter names etc.
+	 *
+	 * should you need a rest array without the possibility to edit the
+	 * plugin's code, you can provide a callback to some
+	 * other function with the correct parameter signature, i.e. :
+	 * <code>
+	 * return array($this, "callbackHelper");
+	 * // and callbackHelper would be as such:
+	 * public function callbackHelper(array $rest=array()){}
+	 * </code>
+	 *
+	 * @param string $name the plugin name
+	 * @return callback
+	 */
+	public function getCallback($name);
+
+	/**
+	 * returns some code that will check if the plugin is loaded and if not load it
+	 * this is optional, if your plugins are autoloaded or whatever, just return an
+	 * empty string
+	 *
+	 * @param string $name the plugin name
+	 * @return string
+	 */
+	public function getLoader($name);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ITemplate.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ITemplate.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/ITemplate.php	(revision 825)
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * interface that represents a dwoo template
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+interface Dwoo_ITemplate
+{
+	/**
+	 * returns the cache duration for this template
+	 *
+	 * defaults to null if it was not provided
+	 *
+	 * @return int|null
+	 */
+	public function getCacheTime();
+
+	/**
+	 * sets the cache duration for this template
+	 *
+	 * can be used to set it after the object is created if you did not provide
+	 * it in the constructor
+	 *
+	 * @param int $seconds duration of the cache validity for this template, if
+	 * null it defaults to the Dwoo instance's cache time. 0 = disable and
+	 * -1 = infinite cache
+	 */
+	public function setCacheTime($seconds = null);
+
+	/**
+	 * returns the cached template output file name, true if it's cache-able but not cached
+	 * or false if it's not cached
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that requests it
+	 * @return string|bool
+	 */
+	public function getCachedTemplate(Dwoo_Core $dwoo);
+
+	/**
+	 * caches the provided output into the cache file
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that requests it
+	 * @param string $output the template output
+	 * @return mixed full path of the cached file or false upon failure
+	 */
+	public function cache(Dwoo_Core $dwoo, $output);
+
+	/**
+	 * clears the cached template if it's older than the given time
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that was used to cache that template
+	 * @param int $olderThan minimum time (in seconds) required for the cache to be cleared
+	 * @return bool true if the cache was not present or if it was deleted, false if it remains there
+	 */
+	public function clearCache(Dwoo_Core $dwoo, $olderThan = -1);
+
+	/**
+	 * returns the compiled template file name
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that requests it
+	 * @param Dwoo_ICompiler $compiler the compiler that must be used
+	 * @return string
+	 */
+	public function getCompiledTemplate(Dwoo_Core $dwoo, Dwoo_ICompiler $compiler = null);
+
+	/**
+	 * returns the template name
+	 *
+	 * @return string
+	 */
+	public function getName();
+
+	/**
+	 * returns the resource name for this template class
+	 *
+	 * @return string
+	 */
+	public function getResourceName();
+
+	/**
+	 * returns the resource identifier for this template or false if it has no identifier
+	 *
+	 * @return string|false
+	 */
+	public function getResourceIdentifier();
+
+	/**
+	 * returns the template source of this template
+	 *
+	 * @return string
+	 */
+	public function getSource();
+
+	/**
+	 * returns an unique string identifying the current version of this template,
+	 * for example a timestamp of the last modified date or a hash of the template source
+	 *
+	 * @return string
+	 */
+	public function getUid();
+
+	/**
+	 * returns the compiler used by this template, if it was just compiled, or null
+	 *
+	 * @return Dwoo_ICompiler
+	 */
+	public function getCompiler();
+
+	/**
+	 * returns some php code that will check if this template has been modified or not
+	 * 
+	 * if the function returns null, the template will be instanciated and then the Uid checked
+	 * 
+	 * @return string
+	 */
+	public function getIsModifiedCode();
+
+	/**
+	 * returns a new template object from the given resource identifier, null if no include is
+	 * possible (resource not found), or false if include is not permitted by this resource type
+	 *
+	 * this method should also check if $dwoo->getSecurityPolicy() is null or not and do the
+	 * necessary permission checks if required, if the security policy prevents the template
+	 * generation it should throw a new Dwoo_Security_Exception with a relevant message
+	 *
+	 * @param mixed $resourceId the resource identifier
+	 * @param int $cacheTime duration of the cache validity for this template,
+	 * 						 if null it defaults to the Dwoo instance that will
+	 * 						 render this template
+	 * @param string $cacheId the unique cache identifier of this page or anything else that
+	 * 						  makes this template's content unique, if null it defaults
+	 * 						  to the current url
+	 * @param string $compileId the unique compiled identifier, which is used to distinguish this
+	 * 							template from others, if null it defaults to the filename+bits of the path
+	 * @param Dwoo_ITemplate $parentTemplate the template that is requesting a new template object (through
+	 * 											an include, extends or any other plugin)
+	 * @return Dwoo_ITemplate|null|false
+	 */
+	public static function templateFactory(Dwoo_Core $dwoo, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Loader.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Loader.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Loader.php	(revision 825)
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * handles plugin loading and caching of plugins names/paths relationships
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Loader implements Dwoo_ILoader
+{
+	/**
+	 * stores the plugin directories
+	 *
+	 * @see addDirectory
+	 * @var array
+	 */
+	protected $paths = array();
+
+	/**
+	 * stores the plugins names/paths relationships
+	 * don't edit this on your own, use addDirectory
+	 *
+	 * @see addDirectory
+	 * @var array
+	 */
+	protected $classPath = array();
+
+	/**
+	 * path where class paths cache files are written
+	 *
+	 * @var string
+	 */
+	protected $cacheDir;
+
+	protected $corePluginDir;
+
+	public function __construct($cacheDir)
+	{
+		$this->corePluginDir = DWOO_DIRECTORY . 'plugins';
+		$this->cacheDir = rtrim($cacheDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
+
+		// include class paths or rebuild paths if the cache file isn't there
+		$cacheFile = $this->cacheDir.'classpath.cache.d'.Dwoo_Core::RELEASE_TAG.'.php';
+		if (file_exists($cacheFile)) {
+			$classpath = file_get_contents($cacheFile);
+			$this->classPath = unserialize($classpath) + $this->classPath;
+		} else {
+			$this->rebuildClassPathCache($this->corePluginDir, $cacheFile);
+		}
+	}
+
+	/**
+	 * rebuilds class paths, scans the given directory recursively and saves all paths in the given file
+	 *
+	 * @param string $path the plugin path to scan
+	 * @param string $cacheFile the file where to store the plugin paths cache, it will be overwritten
+	 */
+	protected function rebuildClassPathCache($path, $cacheFile)
+	{
+		if ($cacheFile!==false) {
+			$tmp = $this->classPath;
+			$this->classPath = array();
+		}
+
+		// iterates over all files/folders
+		$list = glob(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . '*');
+		if (is_array($list)) {
+			foreach ($list as $f) {
+				if (is_dir($f)) {
+					$this->rebuildClassPathCache($f, false);
+				} else {
+					$this->classPath[str_replace(array('function.','block.','modifier.','outputfilter.','filter.','prefilter.','postfilter.','pre.','post.','output.','shared.','helper.'), '', basename($f, '.php'))] = $f;
+				}
+			}
+		}
+
+		// save in file if it's the first call (not recursed)
+		if ($cacheFile!==false) {
+			if (!file_put_contents($cacheFile, serialize($this->classPath))) {
+				throw new Dwoo_Exception('Could not write into '.$cacheFile.', either because the folder is not there (create it) or because of the chmod configuration (please ensure this directory is writable by php), alternatively you can change the directory used with $dwoo->setCompileDir() or provide a custom loader object with $dwoo->setLoader()');
+			}
+			$this->classPath += $tmp;
+		}
+	}
+
+	/**
+	 * loads a plugin file
+	 *
+	 * @param string $class the plugin name, without the Dwoo_Plugin_ prefix
+	 * @param bool $forceRehash if true, the class path caches will be rebuilt if the plugin is not found, in case it has just been added, defaults to true
+	 */
+	public function loadPlugin($class, $forceRehash = true)
+	{
+		// a new class was added or the include failed so we rebuild the cache
+		if (!isset($this->classPath[$class]) || !(include $this->classPath[$class])) {
+			if ($forceRehash) {
+				$this->rebuildClassPathCache($this->corePluginDir, $this->cacheDir . 'classpath.cache.d'.Dwoo_Core::RELEASE_TAG.'.php');
+				foreach ($this->paths as $path=>$file) {
+					$this->rebuildClassPathCache($path, $file);
+				}
+				if (isset($this->classPath[$class])) {
+					include $this->classPath[$class];
+				} else {
+					throw new Dwoo_Exception('Plugin <em>'.$class.'</em> can not be found, maybe you forgot to bind it if it\'s a custom plugin ?', E_USER_NOTICE);
+				}
+			} else {
+				throw new Dwoo_Exception('Plugin <em>'.$class.'</em> can not be found, maybe you forgot to bind it if it\'s a custom plugin ?', E_USER_NOTICE);
+			}
+		}
+	}
+
+	/**
+	 * adds a plugin directory, the plugins found in the new plugin directory
+	 * will take precedence over the other directories (including the default
+	 * dwoo plugin directory), you can use this for example to override plugins
+	 * in a specific directory for a specific application while keeping all your
+	 * usual plugins in the same place for all applications.
+	 *
+	 * TOCOM don't forget that php functions overrides are not rehashed so you
+	 * need to clear the classpath caches by hand when adding those
+	 *
+	 * @param string $pluginDirectory the plugin path to scan
+	 */
+	public function addDirectory($pluginDirectory)
+	{
+		$pluginDir = realpath($pluginDirectory);
+		if (!$pluginDir) {
+			throw new Dwoo_Exception('Plugin directory does not exist or can not be read : '.$pluginDirectory);
+		}
+		$cacheFile = $this->cacheDir . 'classpath-'.substr(strtr($pluginDir, '/\\:'.PATH_SEPARATOR, '----'), strlen($pluginDir) > 80 ? -80 : 0).'.d'.Dwoo_Core::RELEASE_TAG.'.php';
+		$this->paths[$pluginDir] = $cacheFile;
+		if (file_exists($cacheFile)) {
+			$classpath = file_get_contents($cacheFile);
+			$this->classPath = unserialize($classpath) + $this->classPath;
+		} else {
+			$this->rebuildClassPathCache($pluginDir, $cacheFile);
+		}
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Plugin.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Plugin.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Plugin.php	(revision 825)
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * base plugin class
+ *
+ * you have to implement the <em>process()</em> method, it will receive the parameters that
+ * are in the template code
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+abstract class Dwoo_Plugin
+{
+	/**
+	 * the dwoo instance that runs this plugin
+	 *
+	 * @var Dwoo
+	 */
+	protected $dwoo;
+
+	/**
+	 * constructor, if you override it, call parent::__construct($dwoo); or assign
+	 * the dwoo instance yourself if you need it
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that runs this plugin
+	 */
+	public function __construct(Dwoo_Core $dwoo)
+	{
+		$this->dwoo = $dwoo;
+	}
+
+	// plugins should always implement :
+	// public function process($arg, $arg, ...)
+	// or for block plugins :
+	// public function init($arg, $arg, ...)
+
+	// this could be enforced with :
+	// abstract public function process(...);
+	// if my feature request gets enough interest one day
+	// see => http://bugs.php.net/bug.php?id=44043
+
+	/**
+	 * utility function that converts an array of compiled parameters (or rest array) to a string of xml/html tag attributes
+	 *
+	 * this is to be used in preProcessing or postProcessing functions, example :
+	 *  $p = $compiler->getCompiledParams($params);
+	 *  // get only the rest array as attributes
+	 *  $attributes = Dwoo_Plugin::paramsToAttributes($p['*']);
+	 *  // get all the parameters as attributes (if there is a rest array, it will be included)
+	 *  $attributes = Dwoo_Plugin::paramsToAttributes($p);
+	 *
+	 * @param array $params an array of attributeName=>value items that will be compiled to be ready for inclusion in a php string
+	 * @param string $delim the string delimiter you want to use (defaults to ')
+	 * @return string
+	 */
+	public static function paramsToAttributes(array $params, $delim = '\'')
+	{
+		if (isset($params['*'])) {
+			$params = array_merge($params, $params['*']);
+			unset($params['*']);
+		}
+
+		$out = '';
+		foreach ($params as $attr=>$val) {
+			$out .= ' '.$attr.'=';
+			if (trim($val, '"\'')=='' || $val=='null') {
+				$out .= str_replace($delim, '\\'.$delim, '""');
+			} elseif (substr($val, 0, 1) === $delim && substr($val, -1) === $delim) {
+				$out .= str_replace($delim, '\\'.$delim, '"'.substr($val, 1, -1).'"');
+			} else {
+				$out .= str_replace($delim, '\\'.$delim, '"') . $delim . '.'.$val.'.' . $delim . str_replace($delim, '\\'.$delim, '"');
+			}
+		}
+
+		return ltrim($out);
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Processor.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Processor.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Processor.php	(revision 825)
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * base class for processors
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+abstract class Dwoo_Processor
+{
+	/**
+	 * the compiler instance that runs this processor
+	 *
+	 * @var Dwoo
+	 */
+	protected $compiler;
+
+	/**
+	 * constructor, if you override it, call parent::__construct($dwoo); or assign
+	 * the dwoo instance yourself if you need it
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that runs this plugin
+	 */
+	public function __construct(Dwoo_Compiler $compiler)
+	{
+		$this->compiler = $compiler;
+	}
+
+	/**
+	 * processes the input and returns it filtered
+	 *
+	 * @param string $input the template to process
+	 * @return string
+	 */
+	abstract public function process($input);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Security/Exception.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Security/Exception.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Security/Exception.php	(revision 825)
@@ -0,0 +1,19 @@
+<?php
+
+/**
+ * dwoo security exception class
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Security_Exception extends Dwoo_Exception
+{
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Security/Policy.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Security/Policy.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Security/Policy.php	(revision 825)
@@ -0,0 +1,304 @@
+<?php
+
+/**
+ * represents the security settings of a dwoo instance, it can be passed around to different dwoo instances
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Security_Policy
+{
+	/**#@+
+	 * php handling constants, defaults to PHP_REMOVE
+	 *
+	 * PHP_REMOVE : remove all <?php ?> (+ short tags if your short tags option is on) from the input template
+	 * PHP_ALLOW : leave them as they are
+	 * PHP_ENCODE : run htmlentities over them
+	 *
+	 * @var int
+	 */
+	const PHP_ENCODE = 1;
+	const PHP_REMOVE = 2;
+	const PHP_ALLOW = 3;
+	/**#@-*/
+
+	/**#@+
+	 * constant handling constants, defaults to CONST_DISALLOW
+	 *
+	 * CONST_DISALLOW : throw an error if {$dwoo.const.*} is used in the template
+	 * CONST_ALLOW : allow {$dwoo.const.*} calls
+	 */
+	const CONST_DISALLOW = false;
+	const CONST_ALLOW = true;
+	/**#@-*/
+
+	/**
+	 * php functions that are allowed to be used within the template
+	 *
+	 * @var array
+	 */
+	protected $allowedPhpFunctions = array
+	(
+		'str_repeat' => true,
+		'number_format' => true,
+		'htmlentities' => true,
+		'htmlspecialchars' => true,
+		'long2ip' => true,
+		'strlen' => true,
+		'list' => true,
+		'empty' => true,
+		'count' => true,
+		'sizeof' => true,
+		'in_array' => true,
+		'is_array' => true,
+	);
+
+	/**
+	 * methods that are allowed to be used within the template
+	 *
+	 * @var array
+	 */
+	protected $allowedMethods = array();
+
+	/**
+	 * paths that are safe to use with include or other file-access plugins
+	 *
+	 * @var array
+	 */
+	protected $allowedDirectories = array();
+
+	/**
+	 * stores the php handling level
+	 *
+	 * defaults to Dwoo_Security_Policy::PHP_REMOVE
+	 *
+	 * @var int
+	 */
+	protected $phpHandling = self::PHP_REMOVE;
+
+	/**
+	 * stores the constant handling level
+	 *
+	 * defaults to Dwoo_Security_Policy::CONST_DISALLOW
+	 *
+	 * @var bool
+	 */
+	protected $constHandling = self::CONST_DISALLOW;
+
+	/**
+	 * adds a php function to the allowed list
+	 *
+	 * @param mixed $func function name or array of function names
+	 */
+	public function allowPhpFunction($func)
+	{
+		if (is_array($func))
+			foreach ($func as $fname)
+				$this->allowedPhpFunctions[strtolower($fname)] = true;
+		else
+			$this->allowedPhpFunctions[strtolower($func)] = true;
+	}
+
+	/**
+	 * removes a php function from the allowed list
+	 *
+	 * @param mixed $func function name or array of function names
+	 */
+	public function disallowPhpFunction($func)
+	{
+		if (is_array($func))
+			foreach ($func as $fname)
+				unset($this->allowedPhpFunctions[strtolower($fname)]);
+		else
+			unset($this->allowedPhpFunctions[strtolower($func)]);
+	}
+
+	/**
+	 * returns the list of php functions allowed to run, note that the function names
+	 * are stored in the array keys and not values
+	 *
+	 * @return array
+	 */
+	public function getAllowedPhpFunctions()
+	{
+		return $this->allowedPhpFunctions;
+	}
+
+	/**
+	 * adds a class method to the allowed list, this must be used for
+	 * both static and non static method by providing the class name
+	 * and method name to use
+	 *
+	 * @param mixed $class class name or array of array('class', 'method') couples
+	 * @param string $method method name
+	 */
+	public function allowMethod($class, $method = null)
+	{
+		if (is_array($class))
+			foreach ($class as $elem)
+				$this->allowedMethods[strtolower($elem[0])][strtolower($elem[1])] = true;
+		else
+			$this->allowedMethods[strtolower($class)][strtolower($method)] = true;
+	}
+
+	/**
+	 * removes a class method from the allowed list
+	 *
+	 * @param mixed $class class name or array of array('class', 'method') couples
+	 * @param string $method method name
+	 */
+	public function disallowMethod($class, $method = null)
+	{
+		if (is_array($class))
+			foreach ($class as $elem)
+				unset($this->allowedMethods[strtolower($elem[0])][strtolower($elem[1])]);
+		else
+			unset($this->allowedMethods[strtolower($class)][strtolower($method)]);
+	}
+
+	/**
+	 * returns the list of class methods allowed to run, note that the class names
+	 * and method names are stored in the array keys and not values
+	 *
+	 * @return array
+	 */
+	public function getAllowedMethods()
+	{
+		return $this->allowedMethods;
+	}
+
+	/**
+	 * adds a directory to the safelist for includes and other file-access plugins
+	 *
+	 * note that all the includePath directories you provide to the Dwoo_Template_File class
+	 * are automatically marked as safe
+	 *
+	 * @param mixed $path a path name or an array of paths
+	 */
+	public function allowDirectory($path)
+	{
+		if (is_array($path))
+			foreach ($path as $dir)
+				$this->allowedDirectories[realpath($dir)] = true;
+		else
+			$this->allowedDirectories[realpath($path)] = true;
+	}
+
+	/**
+	 * removes a directory from the safelist
+	 *
+	 * @param mixed $path a path name or an array of paths
+	 */
+	public function disallowDirectory($path)
+	{
+		if (is_array($path))
+			foreach ($path as $dir)
+				unset($this->allowedDirectories[realpath($dir)]);
+		else
+			unset($this->allowedDirectories[realpath($path)]);
+	}
+
+	/**
+	 * returns the list of safe paths, note that the paths are stored in the array
+	 * keys and not values
+	 *
+	 * @return array
+	 */
+	public function getAllowedDirectories()
+	{
+		return $this->allowedDirectories;
+	}
+
+	/**
+	 * sets the php handling level, defaults to REMOVE
+	 *
+	 * @param int $level one of the Dwoo_Security_Policy::PHP_* constants
+	 */
+	public function setPhpHandling($level = self::PHP_REMOVE)
+	{
+		$this->phpHandling = $level;
+	}
+
+	/**
+	 * returns the php handling level
+	 *
+	 * @return int the current level, one of the Dwoo_Security_Policy::PHP_* constants
+	 */
+	public function getPhpHandling()
+	{
+		return $this->phpHandling;
+	}
+
+	/**
+	 * sets the constant handling level, defaults to CONST_DISALLOW
+	 *
+	 * @param bool $level one of the Dwoo_Security_Policy::CONST_* constants
+	 */
+	public function setConstantHandling($level = self::CONST_DISALLOW)
+	{
+		$this->constHandling = $level;
+	}
+
+	/**
+	 * returns the constant handling level
+	 *
+	 * @return bool the current level, one of the Dwoo_Security_Policy::CONST_* constants
+	 */
+	public function getConstantHandling()
+	{
+		return $this->constHandling;
+	}
+
+	/**
+	 * this is used at run time to check whether method calls are allowed or not
+	 *
+	 * @param Dwoo_Core $dwoo dwoo instance that calls this
+	 * @param object $obj any object on which the method must be called
+	 * @param string $method lowercased method name
+	 * @param array $args arguments array
+	 * @return mixed result of method call or unll + E_USER_NOTICE if not allowed
+	 */
+	public function callMethod(Dwoo_Core $dwoo, $obj, $method, $args)
+	{
+		foreach ($this->allowedMethods as $class => $methods) {
+			if (!isset($methods[$method])) {
+				continue;
+			}
+			if ($obj instanceof $class) {
+				return call_user_func_array(array($obj, $method), $args);
+			}
+		}
+		$dwoo->triggerError('The current security policy prevents you from calling '.get_class($obj).'::'.$method.'()');
+		return null;
+	}
+
+	/**
+	 * this is used at compile time to check whether static method calls are allowed or not
+	 *
+	 * @param mixed $class lowercased class name or array('class', 'method') couple
+	 * @param string $method lowercased method name
+	 * @return bool
+	 */
+	public function isMethodAllowed($class, $method = null) {
+		if (is_array($class)) {
+			list($class, $method) = $class;
+		}
+		foreach ($this->allowedMethods as $allowedClass => $methods) {
+			if (!isset($methods[$method])) {
+				continue;
+			}
+			if ($class === $allowedClass || is_subclass_of($class, $allowedClass)) {
+				return true;
+			}
+		}
+		return false;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Smarty/Adapter.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Smarty/Adapter.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Smarty/Adapter.php	(revision 825)
@@ -0,0 +1,518 @@
+<?php
+
+if (!defined('DIR_SEP')) {
+	define('DIR_SEP', DIRECTORY_SEPARATOR);
+}
+
+if (!defined('SMARTY_PHP_PASSTHRU')) {
+	define('SMARTY_PHP_PASSTHRU',   0);
+	define('SMARTY_PHP_QUOTE',      1);
+	define('SMARTY_PHP_REMOVE',     2);
+	define('SMARTY_PHP_ALLOW',      3);
+}
+
+if (class_exists('Dwoo_Compiler', false) === false) {
+	require dirname(dirname(__FILE__)) . '/Compiler.php';
+}
+
+/**
+ * a Smarty compatibility layer for Dwoo
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Smarty__Adapter extends Dwoo_Core
+{
+	// magic get/set/call functions that handle unsupported features
+	public function __set($p, $v)
+	{
+		if ($p==='scope') {
+			$this->scope = $v;
+			return;
+		}
+		if ($p==='data') {
+			$this->data = $v;
+			return;
+		}
+		if (array_key_exists($p, $this->compat['properties']) !== false) {
+			if ($this->show_compat_errors) {
+				$this->triggerError('Property '.$p.' is not available in the Dwoo_Smarty_Adapter, however it might be implemented in the future, check out http://wiki.dwoo.org/index.php/SmartySupport for more details.', E_USER_NOTICE);
+			}
+			$this->compat['properties'][$p] = $v;
+		} else {
+			if ($this->show_compat_errors) {
+				$this->triggerError('Property '.$p.' is not available in the Dwoo_Smarty_Adapter, but it is not listed as such, so you might want to tell me about it at j.boggiano@seld.be', E_USER_NOTICE);
+			}
+		}
+	}
+
+	public function __get($p)
+	{
+		if (array_key_exists($p, $this->compat['properties']) !== false) {
+			if ($this->show_compat_errors) {
+				$this->triggerError('Property '.$p.' is not available in the Dwoo_Smarty_Adapter, however it might be implemented in the future, check out http://wiki.dwoo.org/index.php/SmartySupport for more details.', E_USER_NOTICE);
+			}
+			return $this->compat['properties'][$p];
+		} else {
+			if ($this->show_compat_errors) {
+				$this->triggerError('Property '.$p.' is not available in the Dwoo_Smarty_Adapter, but it is not listed as such, so you might want to tell me about it at j.boggiano@seld.be', E_USER_NOTICE);
+			}
+		}
+	}
+
+	public function __call($m, $a)
+	{
+		if (method_exists($this->dataProvider, $m)) {
+			call_user_func_array(array($this->dataProvider, $m), $a);
+		} elseif ($this->show_compat_errors) {
+			if (array_search($m, $this->compat['methods']) !== false) {
+				$this->triggerError('Method '.$m.' is not available in the Dwoo_Smarty_Adapter, however it might be implemented in the future, check out http://wiki.dwoo.org/index.php/SmartySupport for more details.', E_USER_NOTICE);
+			} else {
+				$this->triggerError('Method '.$m.' is not available in the Dwoo_Smarty_Adapter, but it is not listed as such, so you might want to tell me about it at j.boggiano@seld.be', E_USER_NOTICE);
+			}
+		}
+	}
+
+	// list of unsupported properties and methods
+	protected $compat = array
+	(
+		'methods' => array
+		(
+			'register_resource', 'unregister_resource', 'load_filter', 'clear_compiled_tpl',
+			'clear_config', 'get_config_vars', 'config_load'
+		),
+		'properties' => array
+		(
+			'cache_handler_func' => null,
+			'debugging' => false,
+			'error_reporting' => null,
+			'debugging_ctrl' => 'NONE',
+			'request_vars_order' => 'EGPCS',
+			'request_use_auto_globals' => true,
+			'use_sub_dirs' => false,
+			'autoload_filters' => array(),
+			'default_template_handler_func' => '',
+			'debug_tpl' => '',
+			'cache_modified_check' => false,
+			'default_modifiers' => array(),
+			'default_resource_type' => 'file',
+			'config_overwrite' => true,
+			'config_booleanize' => true,
+			'config_read_hidden' => false,
+			'config_fix_newlines' => true,
+			'config_class' => 'Config_File',
+		),
+	);
+
+	// security vars
+	public $security = false;
+	public $trusted_dir = array();
+	public $secure_dir = array();
+	public $php_handling = SMARTY_PHP_PASSTHRU;
+	public $security_settings = array
+	(
+		'PHP_HANDLING'    => false,
+		'IF_FUNCS'        => array
+		(
+			'list', 'empty', 'count', 'sizeof',
+			'in_array', 'is_array',
+		),
+		'INCLUDE_ANY'     => false,
+		'PHP_TAGS'        => false,
+		'MODIFIER_FUNCS'  => array(),
+		'ALLOW_CONSTANTS'  => false
+	);
+
+	// paths
+	public $template_dir = 'templates';
+	public $compile_dir = 'templates_c';
+	public $config_dir = 'configs';
+	public $cache_dir = 'cache';
+	public $plugins_dir = array();
+
+	// misc options
+	public $left_delimiter = '{';
+	public $right_delimiter = '}';
+	public $compile_check = true;
+	public $force_compile = false;
+	public $caching = 0;
+	public $cache_lifetime = 3600;
+	public $compile_id = null;
+	public $compiler_file = null;
+	public $compiler_class = null;
+
+	// dwoo/smarty compat layer
+	public $show_compat_errors = false;
+	protected $dataProvider;
+	protected $_filters = array('pre'=>array(), 'post'=>array(), 'output'=>array());
+	protected static $tplCache = array();
+	protected $compiler = null;
+
+	public function __construct()
+	{
+		parent::__construct();
+		$this->charset = 'iso-8859-1';
+		$this->dataProvider = new Dwoo_Data();
+		$this->compiler = new Dwoo_Compiler();
+	}
+
+	public function display($filename, $cacheId=null, $compileId=null)
+	{
+		$this->fetch($filename, $cacheId, $compileId, true);
+	}
+
+	public function fetch($filename, $cacheId=null, $compileId=null, $display=false)
+	{
+		$this->setCacheDir($this->cache_dir);
+		$this->setCompileDir($this->compile_dir);
+
+		if ($this->security) {
+			$policy = new Dwoo_Security_Policy();
+			$policy->addPhpFunction(array_merge($this->security_settings['IF_FUNCS'], $this->security_settings['MODIFIER_FUNCS']));
+
+			$phpTags = $this->security_settings['PHP_HANDLING'] ? SMARTY_PHP_ALLOW : $this->php_handling;
+			if ($this->security_settings['PHP_TAGS']) {
+				$phpTags = SMARTY_PHP_ALLOW;
+			}
+			switch($phpTags) {
+				case SMARTY_PHP_ALLOW:
+				case SMARTY_PHP_PASSTHRU:
+					$phpTags = Dwoo_Security_Policy::PHP_ALLOW;
+					break;
+				case SMARTY_PHP_QUOTE:
+					$phpTags = Dwoo_Security_Policy::PHP_ENCODE;
+					break;
+				case SMARTY_PHP_REMOVE:
+				default:
+					$phpTags = Dwoo_Security_Policy::PHP_REMOVE;
+					break;
+			}
+			$policy->setPhpHandling($phpTags);
+
+			$policy->setConstantHandling($this->security_settings['ALLOW_CONSTANTS']);
+
+			if ($this->security_settings['INCLUDE_ANY']) {
+				$policy->allowDirectory(preg_replace('{^((?:[a-z]:)?[\\\\/]).*}i', '$1', __FILE__));
+			} else {
+				$policy->allowDirectory($this->secure_dir);
+			}
+
+			$this->setSecurityPolicy($policy);
+		}
+
+		if (!empty($this->plugins_dir)) {
+			foreach ($this->plugins_dir as $dir) {
+				$this->getLoader()->addDirectory(rtrim($dir, '\\/'));
+			}
+		}
+
+		$tpl = $this->makeTemplate($filename, $cacheId, $compileId);
+		if ($this->force_compile) {
+			$tpl->forceCompilation();
+		}
+
+		if ($this->caching > 0) {
+			$this->cacheTime = $this->cache_lifetime;
+		} else {
+			$this->cacheTime = 0;
+		}
+
+		if ($this->compiler_class !== null) {
+			if ($this->compiler_file !== null && !class_exists($this->compiler_class, false)) {
+				include $this->compiler_file;
+			}
+			$this->compiler = new $this->compiler_class;
+		} else {
+			$this->compiler->addPreProcessor('smarty_compat', true);
+			$this->compiler->setLooseOpeningHandling(true);
+		}
+
+		$this->compiler->setDelimiters($this->left_delimiter, $this->right_delimiter);
+
+		return $this->get($tpl, $this->dataProvider, $this->compiler, $display===true);
+	}
+
+	public function get($_tpl, $data = array(), $_compiler = null, $_output = false)
+	{
+		if ($_compiler === null) {
+			$_compiler = $this->compiler;
+		}
+		return parent::get($_tpl, $data, $_compiler, $_output);
+	}
+
+	public function register_function($name, $callback, $cacheable=true, $cache_attrs=null)
+	{
+		if (isset($this->plugins[$name]) && $this->plugins[$name][0] !== self::SMARTY_FUNCTION) {
+			throw new Dwoo_Exception('Multiple plugins of different types can not share the same name');
+		}
+		$this->plugins[$name] = array('type'=>self::SMARTY_FUNCTION, 'callback'=>$callback);
+	}
+
+	public function unregister_function($name)
+	{
+		unset($this->plugins[$name]);
+	}
+
+	public function register_block($name, $callback, $cacheable=true, $cache_attrs=null)
+	{
+		if (isset($this->plugins[$name]) && $this->plugins[$name][0] !== self::SMARTY_BLOCK) {
+			throw new Dwoo_Exception('Multiple plugins of different types can not share the same name');
+		}
+		$this->plugins[$name] = array('type'=>self::SMARTY_BLOCK, 'callback'=>$callback);
+	}
+
+	public function unregister_block($name)
+	{
+		unset($this->plugins[$name]);
+	}
+
+	public function register_modifier($name, $callback)
+	{
+		if (isset($this->plugins[$name]) && $this->plugins[$name][0] !== self::SMARTY_MODIFIER) {
+			throw new Dwoo_Exception('Multiple plugins of different types can not share the same name');
+		}
+		$this->plugins[$name] = array('type'=>self::SMARTY_MODIFIER, 'callback'=>$callback);
+	}
+
+	public function unregister_modifier($name)
+	{
+		unset($this->plugins[$name]);
+	}
+
+	public function register_prefilter($callback)
+	{
+		$processor = new Dwoo_SmartyProcessorAdapter($this->compiler);
+		$processor->registerCallback($callback);
+		$this->_filters['pre'][] = $processor;
+		$this->compiler->addPreProcessor($processor);
+	}
+
+	public function unregister_prefilter($callback)
+	{
+		foreach ($this->_filters['pre'] as $index => $processor)
+			if ($processor->callback === $callback) {
+				$this->compiler->removePostProcessor($processor);
+				unset($this->_filters['pre'][$index]);
+			}
+	}
+
+	public function register_postfilter($callback)
+	{
+		$processor = new Dwoo_SmartyProcessorAdapter($this->compiler);
+		$processor->registerCallback($callback);
+		$this->_filters['post'][] = $processor;
+		$this->compiler->addPostProcessor($processor);
+	}
+
+	public function unregister_postfilter($callback)
+	{
+		foreach ($this->_filters['post'] as $index => $processor)
+			if ($processor->callback === $callback) {
+				$this->compiler->removePostProcessor($processor);
+				unset($this->_filters['post'][$index]);
+			}
+	}
+
+	public function register_outputfilter($callback)
+	{
+		$filter = new Dwoo_SmartyFilterAdapter($this);
+		$filter->registerCallback($callback);
+		$this->_filters['output'][] = $filter;
+		$this->addFilter($filter);
+	}
+
+	public function unregister_outputfilter($callback)
+	{
+		foreach ($this->_filters['output'] as $index => $filter)
+			if ($filter->callback === $callback) {
+				$this->removeOutputFilter($filter);
+				unset($this->_filters['output'][$index]);
+			}
+	}
+
+	function register_object($object, $object_impl, $allowed = array(), $smarty_args = false, $block_methods = array())
+	{
+		settype($allowed, 'array');
+		settype($block_methods, 'array');
+		settype($smarty_args, 'boolean');
+
+		if (!empty($allowed) && $this->show_compat_errors) {
+			$this->triggerError('You can register objects but can not restrict the method/properties used, this is PHP5, you have proper OOP access restrictions so use them.', E_USER_NOTICE);
+		}
+
+		if ($smarty_args) {
+			$this->triggerError('You can register objects but methods will be called using method($arg1, $arg2, $arg3), not as an argument array like smarty did.', E_USER_NOTICE);
+		}
+
+		if (!empty($block_methods)) {
+			$this->triggerError('You can register objects but can not use methods as being block methods, you have to build a plugin for that.', E_USER_NOTICE);
+		}
+
+		$this->dataProvider->assign($object, $object_impl);
+	}
+
+	function unregister_object($object)
+	{
+		$this->dataProvider->clear($object);
+	}
+
+	function get_registered_object($name) {
+		$data = $this->dataProvider->getData();
+		if (isset($data[$name]) && is_object($data[$name])) {
+			return $data[$name];
+		} else {
+			trigger_error('Dwoo_Compiler: object "'.$name.'" was not registered or is not an object', E_USER_ERROR);
+		}
+	}
+
+	public function template_exists($filename)
+	{
+		if (!is_array($this->template_dir)) {
+			return file_exists($this->template_dir.DIRECTORY_SEPARATOR.$filename);
+		} else {
+			foreach ($this->template_dir as $tpl_dir) {
+				if (file_exists($tpl_dir.DIRECTORY_SEPARATOR.$filename)) {
+					return true;
+				}
+			}
+			return false;
+		}
+	}
+
+   	public function is_cached($tpl, $cacheId = null, $compileId = null)
+   	{
+   		return $this->isCached($this->makeTemplate($tpl, $cacheId, $compileId));
+   	}
+
+   	public function append_by_ref($var, &$value, $merge=false)
+   	{
+   		$this->dataProvider->appendByRef($var, $value, $merge);
+   	}
+
+	public function assign_by_ref($name, &$val)
+	{
+		$this->dataProvider->assignByRef($name, $val);
+	}
+
+   	public function clear_assign($var)
+   	{
+   		$this->dataProvider->clear($var);
+   	}
+
+   	public function clear_all_assign()
+   	{
+   		$this->dataProvider->clear();
+   	}
+
+	public function get_template_vars($name=null)
+	{
+		if ($this->show_compat_errors) {
+			trigger_error('get_template_vars does not return values by reference, if you try to modify the data that way you should modify your code.', E_USER_NOTICE);
+		}
+
+		$data = $this->dataProvider->getData();
+   		if ($name === null)
+   			return $data;
+   		elseif (isset($data[$name]))
+   			return $data[$name];
+   		return null;
+   	}
+
+   	public function clear_all_cache($olderThan = 0)
+   	{
+   		$this->clearCache($olderThan);
+   	}
+
+   	public function clear_cache($template, $cacheId = null, $compileId = null, $olderThan = 0)
+   	{
+   		$this->makeTemplate($template, $cacheId, $compileId)->clearCache($olderThan);
+   	}
+
+	public function trigger_error($error_msg, $error_type = E_USER_WARNING)
+	{
+		$this->triggerError($error_msg, $error_type);
+	}
+
+	protected function initGlobals()
+	{
+		parent::initGlobals();
+		$this->globals['ldelim'] = '{';
+		$this->globals['rdelim'] = '}';
+	}
+
+	protected function makeTemplate($file, $cacheId, $compileId)
+	{
+   		if ($compileId === null)
+   			$compileId = $this->compile_id;
+
+		$hash = bin2hex(md5($file.$cacheId.$compileId, true));
+		if (!isset(self::$tplCache[$hash])) {
+			// abs path
+			if (substr($file, 0, 1) === '/' || substr($file, 1, 1) === ':') {
+				self::$tplCache[$hash] = new Dwoo_Template_File($file, null, $cacheId, $compileId);
+			} elseif (is_string($this->template_dir) || is_array($this->template_dir)) {
+				self::$tplCache[$hash] = new Dwoo_Template_File($file, null, $cacheId, $compileId, $this->template_dir);
+			} else {
+				throw new Exception('Unable to load "'.$file.'", check the template_dir');
+			}
+		}
+		return self::$tplCache[$hash];
+	}
+
+	public function triggerError($message, $level=E_USER_NOTICE)
+	{
+		if (is_object($this->template)) {
+			return parent::triggerError($message, $level);
+		}
+		trigger_error('Dwoo error : '.$message, $level);
+	}
+}
+
+class Dwoo_Smarty_Filter_Adapter extends Dwoo_Filter
+{
+	public $callback;
+
+	public function process($input)
+	{
+		return call_user_func($this->callback, $input);
+	}
+
+	public function registerCallback($callback)
+	{
+		$this->callback = $callback;
+	}
+}
+
+class Dwoo_Smarty_Processor_Adapter extends Dwoo_Processor
+{
+	public $callback;
+
+	public function process($input)
+	{
+		return call_user_func($this->callback, $input);
+	}
+
+	public function registerCallback($callback)
+	{
+		$this->callback = $callback;
+	}
+}
+
+// cloaks the adapter if possible with the smarty name to fool type-hinted plugins
+if (class_exists('Smarty', false) === false)
+{
+	interface Smarty {}
+	class Dwoo_Smarty_Adapter extends Dwoo_Smarty__Adapter implements Smarty {}
+}
+else
+{
+	class Dwoo_Smarty_Adapter extends Dwoo_Smarty__Adapter {}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Template/File.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Template/File.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Template/File.php	(revision 825)
@@ -0,0 +1,270 @@
+<?php
+
+/**
+ * represents a Dwoo template contained in a file
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Template_File extends Dwoo_Template_String
+{
+	/**
+	 * template filename
+	 *
+	 * @var string
+	 */
+	protected $file;
+
+	/**
+	 * include path(s) to look into to find this template
+	 *
+	 * @var array
+	 */
+	protected $includePath = null;
+
+	/**
+	 * resolved path cache when looking for a file in multiple include paths
+	 *
+	 * this is reset when the include path is changed
+	 *
+	 * @var string
+	 */
+	protected $resolvedPath = null;
+
+	/**
+	 * creates a template from a file
+	 *
+	 * @param string $file the path to the template file, make sure it exists
+	 * @param int $cacheTime duration of the cache validity for this template,
+	 * 						 if null it defaults to the Dwoo instance that will
+	 * 						 render this template
+	 * @param string $cacheId the unique cache identifier of this page or anything else that
+	 * 						  makes this template's content unique, if null it defaults
+	 * 						  to the current url
+	 * @param string $compileId the unique compiled identifier, which is used to distinguish this
+	 * 							template from others, if null it defaults to the filename+bits of the path
+	 * @param mixed $includePath a string for a single path to look into for the given file, or an array of paths
+	 */
+	public function __construct($file, $cacheTime = null, $cacheId = null, $compileId = null, $includePath = null)
+	{
+		$this->file = $file;
+		$this->name = basename($file);
+		$this->cacheTime = $cacheTime;
+
+		if ($compileId !== null) {
+			$this->compileId = str_replace('../', '__', strtr($compileId, '\\%?=!:;'.PATH_SEPARATOR, '/-------'));
+		}
+
+		if ($cacheId !== null) {
+			$this->cacheId = str_replace('../', '__', strtr($cacheId, '\\%?=!:;'.PATH_SEPARATOR, '/-------'));
+		}
+
+		if (is_string($includePath)) {
+			$this->includePath = array($includePath);
+		} elseif (is_array($includePath)) {
+			$this->includePath = $includePath;
+		}
+	}
+
+	/**
+	 * sets the include path(s) to where the given template filename must be looked up
+	 *
+	 * @param mixed $paths the path to look into, can be string for a single path or an array of paths
+	 */
+	public function setIncludePath($paths)
+	{
+		if (is_array($paths) === false) {
+			$paths = array($paths);
+		}
+
+		$this->includePath = $paths;
+		$this->resolvedPath = null;
+	}
+
+	/**
+	 * return the current include path(s)
+	 *
+	 * @return array
+	 */
+	public function getIncludePath()
+	{
+		return $this->includePath;
+	}
+
+	/**
+	 * Checks if compiled file is valid (exists and it's the modification is greater or
+	 * equal to the modification time of the template file)
+	 *
+	 * @param string file
+	 * @return boolean True cache file existance and it's modification time
+	 */
+	protected function isValidCompiledFile($file) {
+		return parent::isValidCompiledFile($file) && (int)$this->getUid() <= filemtime($file);
+	}
+
+	/**
+	 * returns the template source of this template
+	 *
+	 * @return string
+	 */
+	public function getSource()
+	{
+		return file_get_contents($this->getResourceIdentifier());
+	}
+
+	/**
+	 * returns the resource name for this template class
+	 *
+	 * @return string
+	 */
+	public function getResourceName()
+	{
+		return 'file';
+	}
+
+	/**
+	 * returns this template's source filename
+	 *
+	 * @return string
+	 */
+	public function getResourceIdentifier()
+	{
+		if ($this->resolvedPath !== null) {
+			return $this->resolvedPath;
+		} elseif ($this->includePath === null) {
+			return $this->file;
+		} else {
+			foreach ($this->includePath as $path) {
+				$path = rtrim($path, DIRECTORY_SEPARATOR);
+				if (file_exists($path.DIRECTORY_SEPARATOR.$this->file) === true) {
+					$this->resolvedPath = $path . DIRECTORY_SEPARATOR . $this->file;
+					return $this->resolvedPath;
+				}
+			}
+
+			throw new Dwoo_Exception('Template "'.$this->file.'" could not be found in any of your include path(s)');
+		}
+	}
+
+	/**
+	 * returns an unique value identifying the current version of this template,
+	 * in this case it's the unix timestamp of the last modification
+	 *
+	 * @return string
+	 */
+	public function getUid()
+	{
+		return (string) filemtime($this->getResourceIdentifier());
+	}
+
+	/**
+	 * returns a new template object from the given include name, null if no include is
+	 * possible (resource not found), or false if include is not permitted by this resource type
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance requiring it
+	 * @param mixed $resourceId the filename (relative to this template's dir) of the template to include
+	 * @param int $cacheTime duration of the cache validity for this template,
+	 * 						 if null it defaults to the Dwoo instance that will
+	 * 						 render this template
+	 * @param string $cacheId the unique cache identifier of this page or anything else that
+	 * 						  makes this template's content unique, if null it defaults
+	 * 						  to the current url
+	 * @param string $compileId the unique compiled identifier, which is used to distinguish this
+	 * 							template from others, if null it defaults to the filename+bits of the path
+	 * @param Dwoo_ITemplate $parentTemplate the template that is requesting a new template object (through
+	 * 											an include, extends or any other plugin)
+	 * @return Dwoo_Template_File|null
+	 */
+	public static function templateFactory(Dwoo_Core $dwoo, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null)
+	{
+		if (DIRECTORY_SEPARATOR === '\\') {
+			$resourceId = str_replace(array("\t", "\n", "\r", "\f", "\v"), array('\\t', '\\n', '\\r', '\\f', '\\v'), $resourceId);
+		}
+		$resourceId = strtr($resourceId, '\\', '/');
+
+		$includePath = null;
+
+		if (file_exists($resourceId) === false) {
+			if ($parentTemplate === null) {
+				$parentTemplate = $dwoo->getTemplate();
+			}
+			if ($parentTemplate instanceof Dwoo_Template_File) {
+				if ($includePath = $parentTemplate->getIncludePath()) {
+					if (strstr($resourceId, '../')) {
+						throw new Dwoo_Exception('When using an include path you can not reference a template into a parent directory (using ../)');
+					}
+				} else {
+					$resourceId = dirname($parentTemplate->getResourceIdentifier()).DIRECTORY_SEPARATOR.$resourceId;
+					if (file_exists($resourceId) === false) {
+						return null;
+					}
+				}
+			} else {
+				return null;
+			}
+		}
+
+		if ($policy = $dwoo->getSecurityPolicy()) {
+			while (true) {
+				if (preg_match('{^([a-z]+?)://}i', $resourceId)) {
+					throw new Dwoo_Security_Exception('The security policy prevents you to read files from external sources : <em>'.$resourceId.'</em>.');
+				}
+
+				if ($includePath) {
+					break;
+				}
+
+				$resourceId = realpath($resourceId);
+				$dirs = $policy->getAllowedDirectories();
+				foreach ($dirs as $dir=>$dummy) {
+					if (strpos($resourceId, $dir) === 0) {
+						break 2;
+					}
+				}
+				throw new Dwoo_Security_Exception('The security policy prevents you to read <em>'.$resourceId.'</em>');
+			}
+		}
+
+		$class = 'Dwoo_Template_File';
+		if ($parentTemplate) {
+			$class = get_class($parentTemplate);
+		}
+		return new $class($resourceId, $cacheTime, $cacheId, $compileId, $includePath);
+	}
+
+	/**
+	 * returns the full compiled file name and assigns a default value to it if
+	 * required
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that requests the file name
+	 * @return string the full path to the compiled file
+	 */
+	protected function getCompiledFilename(Dwoo_Core $dwoo)
+	{
+		// no compile id was provided, set default
+		if ($this->compileId===null) {
+			$this->compileId = str_replace('../', '__', strtr($this->getResourceIdentifier(), '\\:', '/-'));
+		}
+		return $dwoo->getCompileDir() . $this->compileId.'.d'.Dwoo_Core::RELEASE_TAG.'.php';
+	}
+
+	/**
+	 * returns some php code that will check if this template has been modified or not
+	 *
+	 * if the function returns null, the template will be instanciated and then the Uid checked
+	 *
+	 * @return string
+	 */
+	public function getIsModifiedCode()
+	{
+		return '"'.$this->getUid().'" == filemtime('.var_export($this->getResourceIdentifier(), true).')';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Template/String.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Template/String.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/Dwoo/Template/String.php	(revision 825)
@@ -0,0 +1,497 @@
+<?php
+
+/**
+ * represents a Dwoo template contained in a string
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Template_String implements Dwoo_ITemplate
+{
+	/**
+	 * template name
+	 *
+	 * @var string
+	 */
+	protected $name;
+
+	/**
+	 * template compilation id
+	 *
+	 * @var string
+	 */
+	protected $compileId;
+
+	/**
+	 * template cache id, if not provided in the constructor, it is set to
+	 * the md4 hash of the request_uri. it is however highly recommended to
+	 * provide one that will fit your needs.
+	 *
+	 * in all cases, the compilation id is prepended to the cache id to separate
+	 * templates with similar cache ids from one another
+	 *
+	 * @var string
+	 */
+	protected $cacheId;
+
+	/**
+	 * validity duration of the generated cache file (in seconds)
+	 *
+	 * set to -1 for infinite cache, 0 to disable and null to inherit the Dwoo instance's cache time
+	 *
+	 * @var int
+	 */
+	protected $cacheTime;
+
+	/**
+	 * boolean flag that defines whether the compilation should be enforced (once) or
+	 * not use this if you have issues with the compiled templates not being updated
+	 * but if you do need this it's most likely that you should file a bug report
+	 *
+	 * @var bool
+	 */
+	protected $compilationEnforced;
+
+	/**
+	 * caches the results of the file checks to save some time when the same
+	 * templates is rendered several times
+	 *
+	 * @var array
+	 */
+	protected static $cache = array('cached'=>array(), 'compiled'=>array());
+
+	/**
+	 * holds the compiler that built this template
+	 *
+	 * @var Dwoo_ICompiler
+	 */
+	protected $compiler;
+
+	/**
+	 * chmod value for all files written (cached or compiled ones)
+	 *
+	 * set to null if you don't want any chmod operation to happen
+	 *
+	 * @var int
+	 */
+	protected $chmod = 0777;
+
+	/**
+	 * creates a template from a string
+	 *
+	 * @param string $templateString the template to use
+	 * @param int $cacheTime duration of the cache validity for this template,
+	 * 						 if null it defaults to the Dwoo instance that will
+	 * 						 render this template, set to -1 for infinite cache or 0 to disable
+	 * @param string $cacheId the unique cache identifier of this page or anything else that
+	 * 						  makes this template's content unique, if null it defaults
+	 * 						  to the current url
+	 * @param string $compileId the unique compiled identifier, which is used to distinguish this
+	 * 							template from others, if null it defaults to the md4 hash of the template
+	 */
+	public function __construct($templateString, $cacheTime = null, $cacheId = null, $compileId = null)
+	{
+		$this->template = $templateString;
+		if (function_exists('hash')) {
+			$this->name = hash('md4', $templateString);
+		} else {
+			$this->name = md5($templateString);
+		}
+		$this->cacheTime = $cacheTime;
+
+		if ($compileId !== null) {
+			$this->compileId = str_replace('../', '__', strtr($compileId, '\\%?=!:;'.PATH_SEPARATOR, '/-------'));
+		}
+
+		if ($cacheId !== null) {
+			$this->cacheId = str_replace('../', '__', strtr($cacheId, '\\%?=!:;'.PATH_SEPARATOR, '/-------'));
+		}
+	}
+
+	/**
+	 * returns the cache duration for this template
+	 *
+	 * defaults to null if it was not provided
+	 *
+	 * @return int|null
+	 */
+	public function getCacheTime()
+	{
+		return $this->cacheTime;
+	}
+
+	/**
+	 * sets the cache duration for this template
+	 *
+	 * can be used to set it after the object is created if you did not provide
+	 * it in the constructor
+	 *
+	 * @param int $seconds duration of the cache validity for this template, if
+	 * null it defaults to the Dwoo instance's cache time. 0 = disable and
+	 * -1 = infinite cache
+	 */
+	public function setCacheTime($seconds = null)
+	{
+		$this->cacheTime = $seconds;
+	}
+
+	/**
+	 * returns the chmod value for all files written (cached or compiled ones)
+	 *
+	 * defaults to 0777
+	 *
+	 * @return int|null
+	 */
+	public function getChmod()
+	{
+		return $this->chmod;
+	}
+
+	/**
+	 * set the chmod value for all files written (cached or compiled ones)
+	 *
+	 * set to null if you don't want to do any chmod() operation
+	 *
+	 * @param int $mask new bitmask to use for all files
+	 */
+	public function setChmod($mask = null)
+	{
+		$this->chmod = $mask;
+	}
+
+	/**
+	 * returns the template name
+	 *
+	 * @return string
+	 */
+	public function getName()
+	{
+		return $this->name;
+	}
+
+	/**
+	 * returns the resource name for this template class
+	 *
+	 * @return string
+	 */
+	public function getResourceName()
+	{
+		return 'string';
+	}
+
+	/**
+	 * returns the resource identifier for this template, false here as strings don't have identifiers
+	 *
+	 * @return false
+	 */
+	public function getResourceIdentifier()
+	{
+		return false;
+	}
+
+	/**
+	 * returns the template source of this template
+	 *
+	 * @return string
+	 */
+	public function getSource()
+	{
+		return $this->template;
+	}
+
+	/**
+	 * returns an unique value identifying the current version of this template,
+	 * in this case it's the md4 hash of the content
+	 *
+	 * @return string
+	 */
+	public function getUid()
+	{
+		return $this->name;
+	}
+
+	/**
+	 * returns the compiler used by this template, if it was just compiled, or null
+	 *
+	 * @return Dwoo_ICompiler
+	 */
+	public function getCompiler()
+	{
+		return $this->compiler;
+	}
+
+	/**
+	 * marks this template as compile-forced, which means it will be recompiled even if it
+	 * was already saved and wasn't modified since the last compilation. do not use this in production,
+	 * it's only meant to be used in development (and the development of dwoo particularly)
+	 */
+	public function forceCompilation()
+	{
+		$this->compilationEnforced = true;
+	}
+
+	/**
+	 * returns the cached template output file name, true if it's cache-able but not cached
+	 * or false if it's not cached
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that requests it
+	 * @return string|bool
+	 */
+	public function getCachedTemplate(Dwoo_Core $dwoo)
+	{
+		if ($this->cacheTime !== null) {
+			$cacheLength = $this->cacheTime;
+		} else {
+			$cacheLength = $dwoo->getCacheTime();
+		}
+
+		// file is not cacheable
+		if ($cacheLength == 0) {
+			return false;
+		}
+
+		$cachedFile = $this->getCacheFilename($dwoo);
+
+		if (isset(self::$cache['cached'][$this->cacheId]) === true && file_exists($cachedFile)) {
+			// already checked, return cache file
+			return $cachedFile;
+		} elseif ($this->compilationEnforced !== true && file_exists($cachedFile) && ($cacheLength === -1 || filemtime($cachedFile) > ($_SERVER['REQUEST_TIME'] - $cacheLength)) && $this->isValidCompiledFile($this->getCompiledFilename($dwoo))) {
+			// cache is still valid and can be loaded
+			self::$cache['cached'][$this->cacheId] = true;
+			return $cachedFile;
+		} else {
+			// file is cacheable
+			return true;
+		}
+	}
+
+	/**
+	 * caches the provided output into the cache file
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that requests it
+	 * @param string $output the template output
+	 * @return mixed full path of the cached file or false upon failure
+	 */
+	public function cache(Dwoo_Core $dwoo, $output)
+	{
+		$cacheDir = $dwoo->getCacheDir();
+		$cachedFile = $this->getCacheFilename($dwoo);
+
+		// the code below is courtesy of Rasmus Schultz,
+		// thanks for his help on avoiding concurency issues
+		$temp = tempnam($cacheDir, 'temp');
+		if (!($file = @fopen($temp, 'wb'))) {
+			$temp = $cacheDir . uniqid('temp');
+			if (!($file = @fopen($temp, 'wb'))) {
+				trigger_error('Error writing temporary file \''.$temp.'\'', E_USER_WARNING);
+				return false;
+			}
+		}
+
+		fwrite($file, $output);
+		fclose($file);
+
+		$this->makeDirectory(dirname($cachedFile), $cacheDir);
+		if (!@rename($temp, $cachedFile)) {
+			@unlink($cachedFile);
+			@rename($temp, $cachedFile);
+		}
+
+		if ($this->chmod !== null) {
+			chmod($cachedFile, $this->chmod);
+		}
+
+		self::$cache['cached'][$this->cacheId] = true;
+
+		return $cachedFile;
+	}
+
+	/**
+	 * clears the cached template if it's older than the given time
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that was used to cache that template
+	 * @param int $olderThan minimum time (in seconds) required for the cache to be cleared
+	 * @return bool true if the cache was not present or if it was deleted, false if it remains there
+	 */
+	public function clearCache(Dwoo_Core $dwoo, $olderThan = -1)
+	{
+		$cachedFile = $this->getCacheFilename($dwoo);
+
+		return !file_exists($cachedFile) || (filectime($cachedFile) < (time() - $olderThan) && unlink($cachedFile));
+	}
+
+	/**
+	 * returns the compiled template file name
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that requests it
+	 * @param Dwoo_ICompiler $compiler the compiler that must be used
+	 * @return string
+	 */
+	public function getCompiledTemplate(Dwoo_Core $dwoo, Dwoo_ICompiler $compiler = null)
+	{
+		$compiledFile = $this->getCompiledFilename($dwoo);
+
+		if ($this->compilationEnforced !== true && isset(self::$cache['compiled'][$this->compileId]) === true) {
+			// already checked, return compiled file
+		} elseif ($this->compilationEnforced !== true && $this->isValidCompiledFile($compiledFile)) {
+			// template is compiled
+			self::$cache['compiled'][$this->compileId] = true;
+		} else {
+			// compiles the template
+			$this->compilationEnforced = false;
+
+			if ($compiler === null) {
+				$compiler = $dwoo->getDefaultCompilerFactory($this->getResourceName());
+
+				if ($compiler === null || $compiler === array('Dwoo_Compiler', 'compilerFactory')) {
+					if (class_exists('Dwoo_Compiler', false) === false) {
+						include DWOO_DIRECTORY . 'Dwoo/Compiler.php';
+					}
+					$compiler = Dwoo_Compiler::compilerFactory();
+				} else {
+					$compiler = call_user_func($compiler);
+				}
+			}
+
+			$this->compiler = $compiler;
+
+			$compiler->setCustomPlugins($dwoo->getCustomPlugins());
+			$compiler->setSecurityPolicy($dwoo->getSecurityPolicy());
+			$this->makeDirectory(dirname($compiledFile), $dwoo->getCompileDir());
+			file_put_contents($compiledFile, $compiler->compile($dwoo, $this));
+			if ($this->chmod !== null) {
+				chmod($compiledFile, $this->chmod);
+			}
+
+			self::$cache['compiled'][$this->compileId] = true;
+		}
+
+		return $compiledFile;
+	}
+
+	/**
+	 * Checks if compiled file is valid (it exists)
+	 *
+	 * @param string file
+	 * @return boolean True cache file existance
+	 */
+	protected function isValidCompiledFile($file) {
+		return file_exists($file);
+	}
+
+	/**
+	 * returns a new template string object with the resource id being the template source code
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance requiring it
+	 * @param mixed $resourceId the filename (relative to this template's dir) of the template to include
+	 * @param int $cacheTime duration of the cache validity for this template,
+	 * 						 if null it defaults to the Dwoo instance that will
+	 * 						 render this template
+	 * @param string $cacheId the unique cache identifier of this page or anything else that
+	 * 						  makes this template's content unique, if null it defaults
+	 * 						  to the current url
+	 * @param string $compileId the unique compiled identifier, which is used to distinguish this
+	 * 							template from others, if null it defaults to the filename+bits of the path
+	 * @param Dwoo_ITemplate $parentTemplate the template that is requesting a new template object (through
+	 * 											an include, extends or any other plugin)
+	 * @return Dwoo_Template_String
+	 */
+	public static function templateFactory(Dwoo_Core $dwoo, $resourceId, $cacheTime = null, $cacheId = null, $compileId = null, Dwoo_ITemplate $parentTemplate = null)
+	{
+		return new self($resourceId, $cacheTime, $cacheId, $compileId);
+	}
+
+	/**
+	 * returns the full compiled file name and assigns a default value to it if
+	 * required
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that requests the file name
+	 * @return string the full path to the compiled file
+	 */
+	protected function getCompiledFilename(Dwoo_Core $dwoo)
+	{
+		// no compile id was provided, set default
+		if ($this->compileId===null) {
+			$this->compileId = $this->name;
+		}
+		return $dwoo->getCompileDir() . $this->compileId.'.d'.Dwoo_Core::RELEASE_TAG.'.php';
+	}
+
+	/**
+	 * returns the full cached file name and assigns a default value to it if
+	 * required
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance that requests the file name
+	 * @return string the full path to the cached file
+	 */
+	protected function getCacheFilename(Dwoo_Core $dwoo)
+	{
+		// no cache id provided, use request_uri as default
+		if ($this->cacheId === null) {
+			if (isset($_SERVER['REQUEST_URI']) === true) {
+				$cacheId = $_SERVER['REQUEST_URI'];
+			} elseif (isset($_SERVER['SCRIPT_FILENAME']) && isset($_SERVER['argv'])) {
+				$cacheId = $_SERVER['SCRIPT_FILENAME'].'-'.implode('-', $_SERVER['argv']);
+			} else {
+				$cacheId = '';
+			}
+			// force compiled id generation
+			$this->getCompiledFilename($dwoo);
+
+			$this->cacheId = str_replace('../', '__', $this->compileId . strtr($cacheId, '\\%?=!:;'.PATH_SEPARATOR, '/-------'));
+		}
+		return $dwoo->getCacheDir() . $this->cacheId.'.html';
+	}
+
+	/**
+	 * returns some php code that will check if this template has been modified or not
+	 *
+	 * if the function returns null, the template will be instanciated and then the Uid checked
+	 *
+	 * @return string
+	 */
+	public function getIsModifiedCode()
+	{
+		return null;
+	}
+
+	/**
+	 * ensures the given path exists
+	 *
+	 * @param string $path any path
+	 * @param string $baseDir the base directory where the directory is created
+	 *                        ($path must still contain the full path, $baseDir
+	 *                        is only used for unix permissions)
+	 */
+	protected function makeDirectory($path, $baseDir = null)
+	{
+		if (is_dir($path) === true) {
+			return;
+		}
+
+		if ($this->chmod === null) {
+			$chmod = 0777;
+		} else {
+			$chmod = $this->chmod;
+		}
+		mkdir($path, $chmod, true);
+
+		// enforce the correct mode for all directories created
+		if (strpos(PHP_OS, 'WIN') !== 0 && $baseDir !== null) {
+			$path = strtr(str_replace($baseDir, '', $path), '\\', '/');
+			$folders = explode('/', trim($path, '/'));
+			foreach ($folders as $folder) {
+				$baseDir .= $folder . DIRECTORY_SEPARATOR;
+				chmod($baseDir, $chmod);
+			}
+		}
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/dwooAutoload.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/dwooAutoload.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/dwooAutoload.php	(revision 825)
@@ -0,0 +1,12 @@
+<?php
+
+include dirname(__FILE__) . DIRECTORY_SEPARATOR . 'Dwoo'. DIRECTORY_SEPARATOR . 'Core.php';
+
+function dwooAutoload($class)
+{
+	if (substr($class, 0, 5) === 'Dwoo_' || $class === 'Dwoo') {
+		include DWOO_DIRECTORY . strtr($class, '_', DIRECTORY_SEPARATOR).'.php';
+	}
+}
+
+spl_autoload_register('dwooAutoload');
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/a.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/a.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/a.php	(revision 825)
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * Outputs a html &lt;a&gt; tag
+ * <pre>
+ *  * href : the target URI where the link must point
+ *  * rest : any other attributes you want to add to the tag can be added as named parameters
+ * </pre>
+ *
+ * Example :
+ *
+ * <code>
+ * {* Create a simple link out of an url variable and add a special class attribute: *}
+ *
+ * {a $url class="external" /}
+ *
+ * {* Mark a link as active depending on some other variable : *}
+ *
+ * {a $link.url class=tif($link.active "active"); $link.title /}
+ *
+ * {* This is similar to: <a href="{$link.url}" class="{if $link.active}active{/if}">{$link.title}</a> *}
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_a extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init($href, array $rest=array())
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		$p = $compiler->getCompiledParams($params);
+
+		$out = Dwoo_Compiler::PHP_OPEN . 'echo \'<a '.self::paramsToAttributes($p);
+
+		return $out.'>\';' . Dwoo_Compiler::PHP_CLOSE;
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$p = $compiler->getCompiledParams($params);
+
+		// no content was provided so use the url as display text
+		if ($content == "") {
+			// merge </a> into the href if href is a string
+			if (substr($p['href'], -1) === '"' || substr($p['href'], -1) === '\'') {
+				return Dwoo_Compiler::PHP_OPEN . 'echo '.substr($p['href'], 0, -1).'</a>'.substr($p['href'], -1).';'.Dwoo_Compiler::PHP_CLOSE;
+			}
+			// otherwise append
+			return Dwoo_Compiler::PHP_OPEN . 'echo '.$p['href'].'.\'</a>\';'.Dwoo_Compiler::PHP_CLOSE;
+		}
+
+		// return content
+		return $content . '</a>';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/auto_escape.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/auto_escape.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/auto_escape.php	(revision 825)
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * Overrides the compiler auto-escape setting within the block
+ * <pre>
+ *  * enabled : if set to "on", "enable", true or 1 then the compiler autoescaping is enabled inside this block. set to "off", "disable", false or 0 to disable it
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_auto_escape extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	protected static $stack = array();
+
+	public function init($enabled)
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		$params = $compiler->getCompiledParams($params);
+		switch(strtolower(trim((string) $params['enabled'], '"\''))) {
+
+		case 'on':
+		case 'true':
+		case 'enabled':
+		case 'enable':
+		case '1':
+			$enable = true;
+			break;
+		case 'off':
+		case 'false':
+		case 'disabled':
+		case 'disable':
+		case '0':
+			$enable = false;
+			break;
+		default:
+			throw new Dwoo_Compilation_Exception($compiler, 'Auto_Escape : Invalid parameter ('.$params['enabled'].'), valid parameters are "enable"/true or "disable"/false');
+
+		}
+
+		self::$stack[] = $compiler->getAutoEscape();
+		$compiler->setAutoEscape($enable);
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$compiler->setAutoEscape(array_pop(self::$stack));
+		return $content;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/block.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/block.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/block.php	(revision 825)
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * This is used only when rendering a template that has blocks but is not extending anything, 
+ * it doesn't do anything by itself and should not be used outside of template inheritance context,
+ * see {@link http://wiki.dwoo.org/index.php/TemplateInheritance} to read more about it.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_block extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init($name='')
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		return $content;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/capture.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/capture.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/capture.php	(revision 825)
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * Captures all the output within this block and saves it into {$.capture.default} by default,
+ * or {$.capture.name} if you provide another name.
+ * <pre>
+ *  * name : capture name, used to read the value afterwards
+ *  * assign : if set, the value is also saved in the given variable
+ *  * cat : if true, the value is appended to the previous one (if any) instead of overwriting it
+ * </pre>
+ * If the cat parameter is true, the content
+ * will be appended to the existing content
+ *
+ * Example :
+ *
+ * <code>
+ * {capture "foo"}
+ *   Anything in here won't show, it will be saved for later use..
+ * {/capture}
+ * Output was : {$.capture.foo}
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_capture extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init($name = 'default', $assign = null, $cat = false, $trim = false)
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		return Dwoo_Compiler::PHP_OPEN.$prepend.'ob_start();'.$append.Dwoo_Compiler::PHP_CLOSE;
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$params = $compiler->getCompiledParams($params);
+
+		$out = $content . Dwoo_Compiler::PHP_OPEN.$prepend."\n".'$tmp = ob_get_clean();';
+		if ($params['trim'] !== 'false' && $params['trim'] !== 0) {
+			$out .= "\n".'$tmp = trim($tmp);';
+		}
+		if ($params['cat'] === 'true' || $params['cat'] === 1) {
+			$out .= "\n".'$tmp = $this->readVar(\'dwoo.capture.\'.'.$params['name'].') . $tmp;';
+		}
+		if ($params['assign'] !== 'null') {
+			$out .= "\n".'$this->scope['.$params['assign'].'] = $tmp;';
+		}
+		return $out . "\n".'$this->globals[\'capture\']['.$params['name'].'] = $tmp;'.$append.Dwoo_Compiler::PHP_CLOSE;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/dynamic.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/dynamic.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/dynamic.php	(revision 825)
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * Marks the contents of the block as dynamic. Which means that it will not be cached.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_dynamic extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init()
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		try {
+			$compiler->findBlock('dynamic');
+			return $content;
+		} catch (Dwoo_Compilation_Exception $e) {
+		}
+		$output = Dwoo_Compiler::PHP_OPEN .
+			'if($doCache) {'."\n\t".
+				'echo \'<dwoo:dynamic_\'.$dynamicId.\'>'.
+				str_replace('\'', '\\\'', $content) .
+				'</dwoo:dynamic_\'.$dynamicId.\'>\';'.
+			"\n} else {\n\t";
+				if(substr($content, 0, strlen(Dwoo_Compiler::PHP_OPEN)) == Dwoo_Compiler::PHP_OPEN) {
+					$output .= substr($content, strlen(Dwoo_Compiler::PHP_OPEN));
+				} else {
+					$output .= Dwoo_Compiler::PHP_CLOSE . $content;
+				}
+				if(substr($output, -strlen(Dwoo_Compiler::PHP_CLOSE)) == Dwoo_Compiler::PHP_CLOSE) {
+					$output = substr($output, 0, -strlen(Dwoo_Compiler::PHP_CLOSE));
+				} else {
+					$output .= Dwoo_Compiler::PHP_OPEN;
+				}
+			$output .= "\n}". Dwoo_Compiler::PHP_CLOSE;
+
+		return $output;
+	}
+
+	public static function unescape($output, $dynamicId, $compiledFile)
+	{
+		$output = preg_replace_callback('/<dwoo:dynamic_('.$dynamicId.')>(.+?)<\/dwoo:dynamic_'.$dynamicId.'>/s', array('self', 'unescapePhp'), $output, -1, $count);
+		// re-add the includes on top of the file
+		if ($count && preg_match('#/\* template head \*/(.+?)/\* end template head \*/#s', file_get_contents($compiledFile), $m)) {
+			$output = '<?php '.$m[1].' ?>'.$output;
+		}
+		return $output;
+	}
+
+	public static function unescapePhp($match)
+	{
+		return preg_replace('{<\?php /\*'.$match[1].'\*/ echo \'(.+?)\'; \?>}s', '$1', $match[2]);
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/else.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/else.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/else.php	(revision 825)
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * Generic else block, it supports all builtin optional-display blocks which are if/for/foreach/loop/with
+ *
+ * If any of those block contains an else statement, the content between {else} and {/block} (you do not
+ * need to close the else block) will be shown if the block's condition has no been met
+ *
+ * Example :
+ *
+ * <code>
+ * {foreach $array val}
+ *   $array is not empty so we display it's values : {$val}
+ * {else}
+ *   if this shows, it means that $array is empty or doesn't exist.
+ * {/foreach}
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_else extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init()
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		$preContent = '';
+		while (true) {
+			$preContent .= $compiler->removeTopBlock();
+			$block =& $compiler->getCurrentBlock();
+			$interfaces = class_implements($block['class'], false);
+			if (in_array('Dwoo_IElseable', $interfaces) !== false) {
+				break;
+			}
+		}
+
+		$params['initialized'] = true;
+		$compiler->injectBlock($type, $params);
+		return $preContent;
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		if (!isset($params['initialized'])) {
+			return '';
+		}
+
+		$block =& $compiler->getCurrentBlock();
+		$block['params']['hasElse'] = Dwoo_Compiler::PHP_OPEN."else {\n".Dwoo_Compiler::PHP_CLOSE . $content . Dwoo_Compiler::PHP_OPEN."\n}".Dwoo_Compiler::PHP_CLOSE;
+		return '';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/elseif.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/elseif.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/elseif.php	(revision 825)
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * Acts as a php elseif block, allowing you to add one more condition
+ * if the previous one(s) didn't match. See the {if} plugin for syntax details
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_elseif extends Dwoo_Plugin_if implements Dwoo_ICompilable_Block, Dwoo_IElseable
+{
+	public function init(array $rest)
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		$preContent = '';
+		while (true) {
+			$preContent .= $compiler->removeTopBlock();
+			$block =& $compiler->getCurrentBlock();
+			$interfaces = class_implements($block['class'], false);
+			if (in_array('Dwoo_IElseable', $interfaces) !== false) {
+				break;
+			}
+		}
+
+		$params['initialized'] = true;
+		$compiler->injectBlock($type, $params);
+		return $preContent;
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		if (!isset($params['initialized'])) {
+			return '';
+		}
+
+		$tokens = $compiler->getParamTokens($params);
+		$params = $compiler->getCompiledParams($params);
+
+		$pre = Dwoo_Compiler::PHP_OPEN."elseif (".implode(' ', self::replaceKeywords($params['*'], $tokens['*'], $compiler)).") {\n" . Dwoo_Compiler::PHP_CLOSE;
+		$post = Dwoo_Compiler::PHP_OPEN."\n}".Dwoo_Compiler::PHP_CLOSE;
+
+		if (isset($params['hasElse'])) {
+			$post .= $params['hasElse'];
+		}
+
+		$block =& $compiler->getCurrentBlock();
+		$block['params']['hasElse'] = $pre . $content . $post;
+		return '';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/for.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/for.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/for.php	(revision 825)
@@ -0,0 +1,146 @@
+<?php
+
+/**
+ * Similar to the php for block
+ * <pre>
+ *  * name : for name to access it's iterator variables through {$.for.name.var} see {@link http://wiki.dwoo.org/index.php/IteratorVariables} for details
+ *  * from : array to iterate from (which equals 0) or a number as a start value
+ *  * to : value to stop iterating at (equals count($array) by default if you set an array in from)
+ *  * step : defines the incrementation of the pointer at each iteration
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_for extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block, Dwoo_IElseable
+{
+	public static $cnt=0;
+
+	public function init($name, $from, $to=null, $step=1, $skip=0)
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		// get block params and save the current template pointer to use it in the postProcessing method
+		$currentBlock =& $compiler->getCurrentBlock();
+		$currentBlock['params']['tplPointer'] = $compiler->getPointer();
+
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$params = $compiler->getCompiledParams($params);
+		$tpl = $compiler->getTemplateSource($params['tplPointer']);
+
+		// assigns params
+		$from = $params['from'];
+		$name = $params['name'];
+		$step = $params['step'];
+		$to = $params['to'];
+
+ 		// evaluates which global variables have to be computed
+		$varName = '$dwoo.for.'.trim($name, '"\'').'.';
+		$shortVarName = '$.for.'.trim($name, '"\'').'.';
+		$usesAny = strpos($tpl, $varName) !== false || strpos($tpl, $shortVarName) !== false;
+		$usesFirst = strpos($tpl, $varName.'first') !== false || strpos($tpl, $shortVarName.'first') !== false;
+		$usesLast = strpos($tpl, $varName.'last') !== false || strpos($tpl, $shortVarName.'last') !== false;
+		$usesIndex = strpos($tpl, $varName.'index') !== false || strpos($tpl, $shortVarName.'index') !== false;
+		$usesIteration = $usesFirst || $usesLast || strpos($tpl, $varName.'iteration') !== false || strpos($tpl, $shortVarName.'iteration') !== false;
+		$usesShow = strpos($tpl, $varName.'show') !== false || strpos($tpl, $shortVarName.'show') !== false;
+		$usesTotal = $usesLast || strpos($tpl, $varName.'total') !== false || strpos($tpl, $shortVarName.'total') !== false;
+
+		if (strpos($name, '$this->scope[') !== false) {
+			$usesAny = $usesFirst = $usesLast = $usesIndex = $usesIteration = $usesShow = $usesTotal = true;
+		}
+
+		// gets foreach id
+		$cnt = self::$cnt++;
+
+		// builds pre processing output for
+		$out = Dwoo_Compiler::PHP_OPEN . "\n".'$_for'.$cnt.'_from = '.$from.';'.
+										"\n".'$_for'.$cnt.'_to = '.$to.';'.
+										"\n".'$_for'.$cnt.'_step = abs('.$step.');'.
+										"\n".'if (is_numeric($_for'.$cnt.'_from) && !is_numeric($_for'.$cnt.'_to)) { $this->triggerError(\'For requires the <em>to</em> parameter when using a numerical <em>from</em>\'); }'.
+										"\n".'$tmp_shows = $this->isArray($_for'.$cnt.'_from, true) || (is_numeric($_for'.$cnt.'_from) && (abs(($_for'.$cnt.'_from - $_for'.$cnt.'_to)/$_for'.$cnt.'_step) !== 0 || $_for'.$cnt.'_from == $_for'.$cnt.'_to));';
+		// adds for properties
+		if ($usesAny) {
+			$out .= "\n".'$this->globals["for"]['.$name.'] = array'."\n(";
+			if ($usesIndex) $out .="\n\t".'"index"		=> 0,';
+			if ($usesIteration) $out .="\n\t".'"iteration"		=> 1,';
+			if ($usesFirst) $out .="\n\t".'"first"		=> null,';
+			if ($usesLast) $out .="\n\t".'"last"		=> null,';
+			if ($usesShow) $out .="\n\t".'"show"		=> $tmp_shows,';
+			if ($usesTotal) $out .="\n\t".'"total"		=> $this->isArray($_for'.$cnt.'_from) ? floor($this->count($_for'.$cnt.'_from) / $_for'.$cnt.'_step) : (is_numeric($_for'.$cnt.'_from) ? abs(($_for'.$cnt.'_to + 1 - $_for'.$cnt.'_from)/$_for'.$cnt.'_step) : 0),';
+			$out.="\n);\n".'$_for'.$cnt.'_glob =& $this->globals["for"]['.$name.'];';
+		}
+		// checks if for must be looped
+		$out .= "\n".'if ($tmp_shows)'."\n{";
+		// set from/to to correct values if an array was given
+		$out .= "\n\t".'if ($this->isArray($_for'.$cnt.'_from'.(isset($params['hasElse']) ? ', true' : '').') == true) {
+		$_for'.$cnt.'_to = is_numeric($_for'.$cnt.'_to) ? $_for'.$cnt.'_to - $_for'.$cnt.'_step : $this->count($_for'.$cnt.'_from) - 1;
+		$_for'.$cnt.'_from = 0;
+	}';
+
+		// if input are pure numbers it shouldn't reorder them, if it's variables it gets too messy though so in that case a counter should be used
+		$reverse = false;
+		$condition = '<=';
+		$incrementer = '+';
+
+		if (preg_match('{^(["\']?)([0-9]+)\1$}', $from, $mN1) && preg_match('{^(["\']?)([0-9]+)\1$}', $to, $mN2)) {
+			$from = (int) $mN1[2];
+			$to = (int) $mN2[2];
+			if ($from > $to) {
+				$reverse = true;
+				$condition = '>=';
+				$incrementer = '-';
+			}
+		}
+
+		// reverse from and to if needed
+		if (!$reverse) {
+			$out .= "\n\t".'if ($_for'.$cnt.'_from > $_for'.$cnt.'_to) {
+				$tmp = $_for'.$cnt.'_from;
+				$_for'.$cnt.'_from = $_for'.$cnt.'_to;
+				$_for'.$cnt.'_to = $tmp;
+			}';
+		}
+
+		$out .= "\n\t".'for ($this->scope['.$name.'] = $_for'.$cnt.'_from; $this->scope['.$name.'] '.$condition.' $_for'.$cnt.'_to; $this->scope['.$name.'] '.$incrementer.'= $_for'.$cnt.'_step)'."\n\t{";
+		// updates properties
+		if ($usesIndex) {
+			$out .="\n\t\t".'$_for'.$cnt.'_glob["index"] = $this->scope['.$name.'];';
+		}
+		if ($usesFirst) {
+			$out .= "\n\t\t".'$_for'.$cnt.'_glob["first"] = (string) ($_for'.$cnt.'_glob["iteration"] === 1);';
+		}
+		if ($usesLast) {
+			$out .= "\n\t\t".'$_for'.$cnt.'_glob["last"] = (string) ($_for'.$cnt.'_glob["iteration"] === $_for'.$cnt.'_glob["total"]);';
+		}
+		$out .= "\n/* -- for start output */\n".Dwoo_Compiler::PHP_CLOSE;
+
+
+		// build post processing output and cache it
+		$postOut = Dwoo_Compiler::PHP_OPEN . '/* -- for end output */';
+		// update properties
+		if ($usesIteration) {
+			$postOut .= "\n\t\t".'$_for'.$cnt.'_glob["iteration"]+=1;';
+		}
+		// end loop
+		$postOut .= "\n\t}\n}\n".Dwoo_Compiler::PHP_CLOSE;
+
+		if (isset($params['hasElse'])) {
+			$postOut .= $params['hasElse'];
+		}
+
+		return $out . $content . $postOut;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/foreach.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/foreach.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/foreach.php	(revision 825)
@@ -0,0 +1,152 @@
+<?php
+
+/**
+ * Similar to the php foreach block, loops over an array
+ *
+ * Note that if you don't provide the item parameter, the key will act as item
+ * <pre>
+ *  * from : the array that you want to iterate over
+ *  * key : variable name for the key (or for the item if item is not defined)
+ *  * item : variable name for each item
+ *  * name : foreach name to access it's iterator variables through {$.foreach.name.var} see {@link http://wiki.dwoo.org/index.php/IteratorVariables} for details
+ * </pre>
+ * Example :
+ *
+ * <code>
+ * {foreach $array val}
+ *   {$val.something}
+ * {/foreach}
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_foreach extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block, Dwoo_IElseable
+{
+	public static $cnt=0;
+
+	public function init($from, $key=null, $item=null, $name='default', $implode=null)
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		// get block params and save the current template pointer to use it in the postProcessing method
+		$currentBlock =& $compiler->getCurrentBlock();
+		$currentBlock['params']['tplPointer'] = $compiler->getPointer();
+
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$params = $compiler->getCompiledParams($params);
+		$tpl = $compiler->getTemplateSource($params['tplPointer']);
+
+		// assigns params
+		$src = $params['from'];
+
+		if ($params['item'] !== 'null') {
+			if ($params['key'] !== 'null') {
+				$key = $params['key'];
+			}
+			$val = $params['item'];
+		} elseif ($params['key'] !== 'null') {
+			$val = $params['key'];
+		} else {
+			throw new Dwoo_Compilation_Exception($compiler, 'Foreach <em>item</em> parameter missing');
+		}
+		$name = $params['name'];
+
+		if (substr($val, 0, 1) !== '"' && substr($val, 0, 1) !== '\'') {
+			throw new Dwoo_Compilation_Exception($compiler, 'Foreach <em>item</em> parameter must be of type string');
+		}
+		if (isset($key) && substr($val, 0, 1) !== '"' && substr($val, 0, 1) !== '\'') {
+			throw new Dwoo_Compilation_Exception($compiler, 'Foreach <em>key</em> parameter must be of type string');
+		}
+
+		// evaluates which global variables have to be computed
+		$varName = '$dwoo.foreach.'.trim($name, '"\'').'.';
+		$shortVarName = '$.foreach.'.trim($name, '"\'').'.';
+		$usesAny = strpos($tpl, $varName) !== false || strpos($tpl, $shortVarName) !== false;
+		$usesFirst = strpos($tpl, $varName.'first') !== false || strpos($tpl, $shortVarName.'first') !== false;
+		$usesLast = strpos($tpl, $varName.'last') !== false || strpos($tpl, $shortVarName.'last') !== false;
+		$usesIndex = $usesFirst || strpos($tpl, $varName.'index') !== false || strpos($tpl, $shortVarName.'index') !== false;
+		$usesIteration = $usesLast || strpos($tpl, $varName.'iteration') !== false || strpos($tpl, $shortVarName.'iteration') !== false;
+		$usesShow = strpos($tpl, $varName.'show') !== false || strpos($tpl, $shortVarName.'show') !== false;
+		$usesTotal = $usesLast || strpos($tpl, $varName.'total') !== false || strpos($tpl, $shortVarName.'total') !== false;
+
+		if (strpos($name, '$this->scope[') !== false) {
+			$usesAny = $usesFirst = $usesLast = $usesIndex = $usesIteration = $usesShow = $usesTotal = true;
+		}
+
+		// override globals vars if implode is used
+		if ($params['implode'] !== 'null') {
+			$implode = $params['implode'];
+			$usesAny = true;
+			$usesLast = true;
+			$usesIteration = true;
+			$usesTotal = true;
+		}
+
+		// gets foreach id
+		$cnt = self::$cnt++;
+
+		// build pre content output
+		$pre = Dwoo_Compiler::PHP_OPEN . "\n".'$_fh'.$cnt.'_data = '.$src.';';
+		// adds foreach properties
+		if ($usesAny) {
+			$pre .= "\n".'$this->globals["foreach"]['.$name.'] = array'."\n(";
+			if ($usesIndex) $pre .="\n\t".'"index"		=> 0,';
+			if ($usesIteration) $pre .="\n\t".'"iteration"		=> 1,';
+			if ($usesFirst) $pre .="\n\t".'"first"		=> null,';
+			if ($usesLast) $pre .="\n\t".'"last"		=> null,';
+			if ($usesShow) $pre .="\n\t".'"show"		=> $this->isArray($_fh'.$cnt.'_data, true),';
+			if ($usesTotal) $pre .="\n\t".'"total"		=> $this->count($_fh'.$cnt.'_data),';
+			$pre.="\n);\n".'$_fh'.$cnt.'_glob =& $this->globals["foreach"]['.$name.'];';
+		}
+		// checks if foreach must be looped
+		$pre .= "\n".'if ($this->isTraversable($_fh'.$cnt.'_data'.(isset($params['hasElse']) ? ', true' : '').') == true)'."\n{";
+		// iterates over keys
+		$pre .= "\n\t".'foreach ($_fh'.$cnt.'_data as '.(isset($key)?'$this->scope['.$key.']=>':'').'$this->scope['.$val.'])'."\n\t{";
+		// updates properties
+		if ($usesFirst) {
+			$pre .= "\n\t\t".'$_fh'.$cnt.'_glob["first"] = (string) ($_fh'.$cnt.'_glob["index"] === 0);';
+		}
+		if ($usesLast) {
+			$pre .= "\n\t\t".'$_fh'.$cnt.'_glob["last"] = (string) ($_fh'.$cnt.'_glob["iteration"] === $_fh'.$cnt.'_glob["total"]);';
+		}
+		$pre .= "\n/* -- foreach start output */\n".Dwoo_Compiler::PHP_CLOSE;
+
+		// build post content output
+		$post = Dwoo_Compiler::PHP_OPEN . "\n";
+
+		if (isset($implode)) {
+			$post .= '/* -- implode */'."\n".'if (!$_fh'.$cnt.'_glob["last"]) {'.
+				"\n\t".'echo '.$implode.";\n}\n";
+		}
+		$post .= '/* -- foreach end output */';
+		// update properties
+		if ($usesIndex) {
+			$post.="\n\t\t".'$_fh'.$cnt.'_glob["index"]+=1;';
+		}
+		if ($usesIteration) {
+			$post.="\n\t\t".'$_fh'.$cnt.'_glob["iteration"]+=1;';
+		}
+		// end loop
+		$post .= "\n\t}\n}".Dwoo_Compiler::PHP_CLOSE;
+		if (isset($params['hasElse'])) {
+			$post .= $params['hasElse'];
+		}
+
+		return $pre . $content . $post;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/foreachelse.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/foreachelse.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/foreachelse.php	(revision 825)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * This plugin serves as a {else} block specifically for the {foreach} plugin.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_foreachelse extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init()
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		$with =& $compiler->findBlock('foreach', true);
+
+		$params['initialized'] = true;
+		$compiler->injectBlock($type, $params);
+
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		if (!isset($params['initialized'])) {
+			return '';
+		}
+
+		$block =& $compiler->getCurrentBlock();
+		$block['params']['hasElse'] = Dwoo_Compiler::PHP_OPEN."else {\n".Dwoo_Compiler::PHP_CLOSE . $content . Dwoo_Compiler::PHP_OPEN."\n}".Dwoo_Compiler::PHP_CLOSE;
+		return '';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/forelse.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/forelse.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/forelse.php	(revision 825)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * This plugin serves as a {else} block specifically for the {for} plugin.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_forelse extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init()
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		$with =& $compiler->findBlock('for', true);
+
+		$params['initialized'] = true;
+		$compiler->injectBlock($type, $params);
+
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		if (!isset($params['initialized'])) {
+			return '';
+		}
+
+		$block =& $compiler->getCurrentBlock();
+		$block['params']['hasElse'] = Dwoo_Compiler::PHP_OPEN."else {\n".Dwoo_Compiler::PHP_CLOSE . $content . Dwoo_Compiler::PHP_OPEN."\n}".Dwoo_Compiler::PHP_CLOSE;
+		return '';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/if.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/if.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/if.php	(revision 825)
@@ -0,0 +1,227 @@
+<?php
+
+/**
+ * Conditional block, the syntax is very similar to the php one, allowing () || && and
+ * other php operators. Additional operators and their equivalent php syntax are as follow :
+ *
+ * eq -> ==
+ * neq or ne -> !=
+ * gte or ge -> >=
+ * lte or le -> <=
+ * gt -> >
+ * lt -> <
+ * mod -> %
+ * not -> !
+ * X is [not] div by Y -> (X % Y) == 0
+ * X is [not] even [by Y] -> (X % 2) == 0 or ((X/Y) % 2) == 0
+ * X is [not] odd [by Y] -> (X % 2) != 0 or ((X/Y) % 2) != 0
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_if extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block, Dwoo_IElseable
+{
+	public function init(array $rest)
+	{
+	}
+
+	public static function replaceKeywords(array $params, array $tokens, Dwoo_Compiler $compiler)
+	{
+		$p = array();
+
+		reset($params);
+		while (list($k,$v) = each($params)) {
+			$v = (string) $v;
+			if(substr($v, 0, 1) === '"' || substr($v, 0, 1) === '\'') {
+				$vmod = strtolower(substr($v, 1, -1));
+			} else {
+				$vmod = strtolower($v);
+			}
+			switch($vmod) {
+
+			case 'and':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '&&';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case 'or':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '||';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case 'eq':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '==';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case 'ne':
+			case 'neq':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '!=';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case 'gte':
+			case 'ge':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '>=';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case 'lte':
+			case 'le':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '<=';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case 'gt':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '>';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case 'lt':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '<';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case 'mod':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '%';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case 'not':
+				if ($tokens[$k] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = '!';
+				} else {
+					$p[] = $v;
+				}
+				break;
+			case '<>':
+				$p[] = '!=';
+				break;
+			case '==':
+			case '!=':
+			case '>=':
+			case '<=':
+			case '>':
+			case '<':
+			case '===':
+			case '!==':
+			case '%':
+			case '!':
+				$p[] = $vmod;
+				break;
+			case 'is':
+				if ($tokens[$k] !== Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$p[] = $v;
+					break;
+				}
+				if (isset($params[$k+1]) && strtolower(trim($params[$k+1], '"\'')) === 'not' && $tokens[$k+1] === Dwoo_Compiler::T_UNQUOTED_STRING) {
+					$negate = true;
+					next($params);
+				} else {
+					$negate = false;
+				}
+				$ptr = 1+(int)$negate;
+				if ($tokens[$k+$ptr] !== Dwoo_Compiler::T_UNQUOTED_STRING) {
+					break;
+				}
+				if (!isset($params[$k+$ptr])) {
+					$params[$k+$ptr] = '';
+				} else {
+					$params[$k+$ptr] = trim($params[$k+$ptr], '"\'');
+				}
+				switch($params[$k+$ptr]) {
+
+				case 'div':
+					if (isset($params[$k+$ptr+1]) && strtolower(trim($params[$k+$ptr+1], '"\'')) === 'by') {
+						$p[] = ' % '.$params[$k+$ptr+2].' '.($negate?'!':'=').'== 0';
+						next($params);
+						next($params);
+						next($params);
+					} else {
+						throw new Dwoo_Compilation_Exception($compiler, 'If : Syntax error : syntax should be "if $a is [not] div by $b", found '.$params[$k-1].' is '.($negate?'not ':'').'div '.$params[$k+$ptr+1].' '.$params[$k+$ptr+2]);
+					}
+					break;
+				case 'even':
+					$a = array_pop($p);
+					if (isset($params[$k+$ptr+1]) && strtolower(trim($params[$k+$ptr+1], '"\'')) === 'by') {
+						$b = $params[$k+$ptr+2];
+						$p[] = '('.$a .' / '.$b.') % 2 '.($negate?'!':'=').'== 0';
+						next($params);
+						next($params);
+					} else {
+						$p[] = $a.' % 2 '.($negate?'!':'=').'== 0';
+					}
+					next($params);
+					break;
+				case 'odd':
+					$a = array_pop($p);
+					if (isset($params[$k+$ptr+1]) && strtolower(trim($params[$k+$ptr+1], '"\'')) === 'by') {
+						$b = $params[$k+$ptr+2];
+						$p[] = '('.$a .' / '.$b.') % 2 '.($negate?'=':'!').'== 0';
+						next($params);
+						next($params);
+					} else {
+						$p[] = $a.' % 2 '.($negate?'=':'!').'== 0';
+					}
+					next($params);
+					break;
+				default:
+					throw new Dwoo_Compilation_Exception($compiler, 'If : Syntax error : syntax should be "if $a is [not] (div|even|odd) [by $b]", found '.$params[$k-1].' is '.$params[$k+$ptr+1]);
+
+				}
+				break;
+			default:
+				$p[] = $v;
+
+			}
+		}
+
+		return $p;
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$tokens = $compiler->getParamTokens($params);
+		$params = $compiler->getCompiledParams($params);
+		$pre = Dwoo_Compiler::PHP_OPEN.'if ('.implode(' ', self::replaceKeywords($params['*'], $tokens['*'], $compiler)).") {\n".Dwoo_Compiler::PHP_CLOSE;
+
+		$post = Dwoo_Compiler::PHP_OPEN."\n}".Dwoo_Compiler::PHP_CLOSE;
+
+		if (isset($params['hasElse'])) {
+			$post .= $params['hasElse'];
+		}
+
+		return $pre . $content . $post;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/loop.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/loop.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/loop.php	(revision 825)
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * Loops over an array and moves the scope into each value, allowing for shorter loop constructs
+ *
+ * Note that to access the array key within a loop block, you have to use the {$_key} variable,
+ * you can not specify it yourself.
+ * <pre>
+ *  * from : the array that you want to iterate over
+ *  * name : loop name to access it's iterator variables through {$.loop.name.var} see {@link http://wiki.dwoo.org/index.php/IteratorVariables} for details
+ * </pre>
+ * Example :
+ *
+ * instead of a foreach block such as :
+ *
+ * <code>
+ * {foreach $variable value}
+ *   {$value.foo} {$value.bar}
+ * {/foreach}
+ * </code>
+ *
+ * you can do :
+ *
+ * <code>
+ * {loop $variable}
+ *   {$foo} {$bar}
+ * {/loop}
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_loop extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block, Dwoo_IElseable
+{
+	public static $cnt=0;
+
+	public function init($from, $name='default')
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		// get block params and save the current template pointer to use it in the postProcessing method
+		$currentBlock =& $compiler->getCurrentBlock();
+		$currentBlock['params']['tplPointer'] = $compiler->getPointer();
+
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$params = $compiler->getCompiledParams($params);
+		$tpl = $compiler->getTemplateSource($params['tplPointer']);
+
+		// assigns params
+		$src = $params['from'];
+		$name = $params['name'];
+
+		// evaluates which global variables have to be computed
+		$varName = '$dwoo.loop.'.trim($name, '"\'').'.';
+		$shortVarName = '$.loop.'.trim($name, '"\'').'.';
+		$usesAny = strpos($tpl, $varName) !== false || strpos($tpl, $shortVarName) !== false;
+		$usesFirst = strpos($tpl, $varName.'first') !== false || strpos($tpl, $shortVarName.'first') !== false;
+		$usesLast = strpos($tpl, $varName.'last') !== false || strpos($tpl, $shortVarName.'last') !== false;
+		$usesIndex = $usesFirst || strpos($tpl, $varName.'index') !== false || strpos($tpl, $shortVarName.'index') !== false;
+		$usesIteration = $usesLast || strpos($tpl, $varName.'iteration') !== false || strpos($tpl, $shortVarName.'iteration') !== false;
+		$usesShow = strpos($tpl, $varName.'show') !== false || strpos($tpl, $shortVarName.'show') !== false;
+		$usesTotal = $usesLast || strpos($tpl, $varName.'total') !== false || strpos($tpl, $shortVarName.'total') !== false;
+
+		if (strpos($name, '$this->scope[') !== false) {
+			$usesAny = $usesFirst = $usesLast = $usesIndex = $usesIteration = $usesShow = $usesTotal = true;
+		}
+
+		// gets foreach id
+		$cnt = self::$cnt++;
+
+		// builds pre processing output
+		$pre = Dwoo_Compiler::PHP_OPEN . "\n".'$_loop'.$cnt.'_data = '.$src.';';
+		// adds foreach properties
+		if ($usesAny) {
+			$pre .= "\n".'$this->globals["loop"]['.$name.'] = array'."\n(";
+			if ($usesIndex) $pre .="\n\t".'"index"		=> 0,';
+			if ($usesIteration) $pre .="\n\t".'"iteration"		=> 1,';
+			if ($usesFirst) $pre .="\n\t".'"first"		=> null,';
+			if ($usesLast) $pre .="\n\t".'"last"		=> null,';
+			if ($usesShow) $pre .="\n\t".'"show"		=> $this->isTraversable($_loop'.$cnt.'_data, true),';
+			if ($usesTotal) $pre .="\n\t".'"total"		=> $this->count($_loop'.$cnt.'_data),';
+			$pre.="\n);\n".'$_loop'.$cnt.'_glob =& $this->globals["loop"]['.$name.'];';
+		}
+		// checks if the loop must be looped
+		$pre .= "\n".'if ($this->isTraversable($_loop'.$cnt.'_data'.(isset($params['hasElse']) ? ', true' : '').') == true)'."\n{";
+		// iterates over keys
+		$pre .= "\n\t".'foreach ($_loop'.$cnt.'_data as $tmp_key => $this->scope["-loop-"])'."\n\t{";
+		// updates properties
+		if ($usesFirst) {
+			$pre .= "\n\t\t".'$_loop'.$cnt.'_glob["first"] = (string) ($_loop'.$cnt.'_glob["index"] === 0);';
+		}
+		if ($usesLast) {
+			$pre .= "\n\t\t".'$_loop'.$cnt.'_glob["last"] = (string) ($_loop'.$cnt.'_glob["iteration"] === $_loop'.$cnt.'_glob["total"]);';
+		}
+		$pre .= "\n\t\t".'$_loop'.$cnt.'_scope = $this->setScope(array("-loop-"));' . "\n/* -- loop start output */\n".Dwoo_Compiler::PHP_CLOSE;
+
+		// build post processing output and cache it
+		$post = Dwoo_Compiler::PHP_OPEN . "\n".'/* -- loop end output */'."\n\t\t".'$this->setScope($_loop'.$cnt.'_scope, true);';
+		// update properties
+		if ($usesIndex) {
+			$post.="\n\t\t".'$_loop'.$cnt.'_glob["index"]+=1;';
+		}
+		if ($usesIteration) {
+			$post.="\n\t\t".'$_loop'.$cnt.'_glob["iteration"]+=1;';
+		}
+		// end loop
+		$post .= "\n\t}\n}\n" . Dwoo_Compiler::PHP_CLOSE;
+		if (isset($params['hasElse'])) {
+			$post .= $params['hasElse'];
+		}
+
+		return $pre . $content . $post;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/section.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/section.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/section.php	(revision 825)
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * Compatibility plugin for smarty templates, do not use otherwise, this is deprecated.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_section extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block, Dwoo_IElseable
+{
+	public static $cnt=0;
+
+	public function init($name, $loop, $start = null, $step = null, $max = null, $show = true)
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$output = Dwoo_Compiler::PHP_OPEN;
+		$params = $compiler->getCompiledParams($params);
+
+		// assigns params
+		$loop = $params['loop'];
+		$start = $params['start'];
+		$max = $params['max'];
+		$name = $params['name'];
+		$step = $params['step'];
+		$show = $params['show'];
+
+		// gets unique id
+		$cnt = self::$cnt++;
+
+		$output .= '$this->globals[\'section\']['.$name.'] = array();'."\n".
+		'$_section'.$cnt.' =& $this->globals[\'section\']['.$name.'];'."\n";
+
+		if ($loop !== 'null') {
+			$output .= '$_section'.$cnt.'[\'loop\'] = is_array($tmp = '.$loop.') ? count($tmp) : max(0, (int) $tmp);'."\n";
+		} else {
+			$output .= '$_section'.$cnt.'[\'loop\'] = 1;'."\n";
+		}
+
+		if ($show !== 'null') {
+			$output .= '$_section'.$cnt.'[\'show\'] = '.$show.";\n";
+		} else {
+			$output .= '$_section'.$cnt.'[\'show\'] = true;'."\n";
+		}
+
+		if ($name !== 'null') {
+			$output .= '$_section'.$cnt.'[\'name\'] = '.$name.";\n";
+		} else {
+			$output .= '$_section'.$cnt.'[\'name\'] = true;'."\n";
+		}
+
+		if ($max !== 'null') {
+			$output .= '$_section'.$cnt.'[\'max\'] = (int)'.$max.";\n".
+						'if($_section'.$cnt.'[\'max\'] < 0) { $_section'.$cnt.'[\'max\'] = $_section'.$cnt.'[\'loop\']; }'."\n";
+		} else {
+			$output .= '$_section'.$cnt.'[\'max\'] = $_section'.$cnt.'[\'loop\'];'."\n";
+		}
+
+		if ($step !== 'null') {
+			$output .= '$_section'.$cnt.'[\'step\'] = (int)'.$step.' == 0 ? 1 : (int) '.$step.";\n";
+		} else {
+			$output .= '$_section'.$cnt.'[\'step\'] = 1;'."\n";
+		}
+
+		if ($start !== 'null') {
+			$output .= '$_section'.$cnt.'[\'start\'] = (int)'.$start.";\n";
+		} else {
+			$output .= '$_section'.$cnt.'[\'start\'] = $_section'.$cnt.'[\'step\'] > 0 ? 0 : $_section'.$cnt.'[\'loop\'] - 1;'."\n".
+						'if ($_section'.$cnt.'[\'start\'] < 0) { $_section'.$cnt.'[\'start\'] = max($_section'.$cnt.'[\'step\'] > 0 ? 0 : -1, $_section'.$cnt.'[\'loop\'] + $_section'.$cnt.'[\'start\']); } '."\n".
+						'else { $_section'.$cnt.'[\'start\'] = min($_section'.$cnt.'[\'start\'], $_section'.$cnt.'[\'step\'] > 0 ? $_section'.$cnt.'[\'loop\'] : $_section'.$cnt.'[\'loop\'] -1); }'."\n";
+		}
+
+/*		if ($usesAny) {
+			$output .= "\n".'$this->globals["section"]['.$name.'] = array'."\n(";
+			if ($usesIndex) $output .="\n\t".'"index"		=> 0,';
+			if ($usesIteration) $output .="\n\t".'"iteration"		=> 1,';
+			if ($usesFirst) $output .="\n\t".'"first"		=> null,';
+			if ($usesLast) $output .="\n\t".'"last"		=> null,';
+			if ($usesShow) $output .="\n\t".'"show"		=> ($this->isArray($_for'.$cnt.'_from, true)) || (is_numeric($_for'.$cnt.'_from) && $_for'.$cnt.'_from != $_for'.$cnt.'_to),';
+			if ($usesTotal) $output .="\n\t".'"total"		=> $this->isArray($_for'.$cnt.'_from) ? $this->count($_for'.$cnt.'_from) - $_for'.$cnt.'_skip : (is_numeric($_for'.$cnt.'_from) ? abs(($_for'.$cnt.'_to + 1 - $_for'.$cnt.'_from)/$_for'.$cnt.'_step) : 0),';
+			$out.="\n);\n".'$_section'.$cnt.'[\'glob\'] =& $this->globals["section"]['.$name.'];'."\n\n";
+		}
+*/
+
+		$output .= 'if ($_section'.$cnt.'[\'show\']) {'."\n";
+		if ($start === 'null' && $step === 'null' && $max === 'null') {
+			$output .= '	$_section'.$cnt.'[\'total\'] = $_section'.$cnt.'[\'loop\'];'."\n";
+		} else {
+			$output .= '	$_section'.$cnt.'[\'total\'] = min(ceil(($_section'.$cnt.'[\'step\'] > 0 ? $_section'.$cnt.'[\'loop\'] - $_section'.$cnt.'[\'start\'] : $_section'.$cnt.'[\'start\'] + 1) / abs($_section'.$cnt.'[\'step\'])), $_section'.$cnt.'[\'max\']);'."\n";
+		}
+		$output .= '	if ($_section'.$cnt.'[\'total\'] == 0) {'."\n".
+				   '		$_section'.$cnt.'[\'show\'] = false;'."\n".
+				   '	}'."\n".
+				   '} else {'."\n".
+				   '	$_section'.$cnt.'[\'total\'] = 0;'."\n}\n";
+		$output .= 'if ($_section'.$cnt.'[\'show\']) {'."\n";
+		$output .= "\t".'for ($this->scope['.$name.'] = $_section'.$cnt.'[\'start\'], $_section'.$cnt.'[\'iteration\'] = 1; '.
+				 	'$_section'.$cnt.'[\'iteration\'] <= $_section'.$cnt.'[\'total\']; '.
+				 	'$this->scope['.$name.'] += $_section'.$cnt.'[\'step\'], $_section'.$cnt.'[\'iteration\']++) {'."\n";
+		$output .= "\t\t".'$_section'.$cnt.'[\'rownum\'] = $_section'.$cnt.'[\'iteration\'];'."\n";
+		$output .= "\t\t".'$_section'.$cnt.'[\'index_prev\'] = $this->scope['.$name.'] - $_section'.$cnt.'[\'step\'];'."\n";
+		$output .= "\t\t".'$_section'.$cnt.'[\'index_next\'] = $this->scope['.$name.'] + $_section'.$cnt.'[\'step\'];'."\n";
+		$output .= "\t\t".'$_section'.$cnt.'[\'first\']      = ($_section'.$cnt.'[\'iteration\'] == 1);'."\n";
+		$output .= "\t\t".'$_section'.$cnt.'[\'last\']       = ($_section'.$cnt.'[\'iteration\'] == $_section'.$cnt.'[\'total\']);'."\n";
+
+		$output .= Dwoo_Compiler::PHP_CLOSE . $content . Dwoo_Compiler::PHP_OPEN;
+
+		$output .= "\n\t}\n} " . Dwoo_Compiler::PHP_CLOSE;
+
+		if (isset($params['hasElse'])) {
+			$output .= $params['hasElse'];
+		}
+
+		return $output;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/smartyinterface.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/smartyinterface.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/smartyinterface.php	(revision 825)
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Smarty compatibility layer for block plugins, this is used internally and you should not call it
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_smartyinterface extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init($__funcname, $__functype, array $rest=array()) {}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		$params = $compiler->getCompiledParams($params);
+		$func = $params['__funcname'];
+		$pluginType = $params['__functype'];
+		$params = $params['*'];
+
+		if ($pluginType & Dwoo_Core::CUSTOM_PLUGIN) {
+			$customPlugins = $compiler->getDwoo()->getCustomPlugins();
+			$callback = $customPlugins[$func]['callback'];
+			if (is_array($callback)) {
+				if (is_object($callback[0])) {
+					$callback = '$this->customPlugins[\''.$func.'\'][0]->'.$callback[1].'(';
+				} else {
+					$callback = ''.$callback[0].'::'.$callback[1].'(';
+				}
+			} else {
+				$callback = $callback.'(';
+			}
+		} else {
+			$callback = 'smarty_block_'.$func.'(';
+		}
+
+		$paramsOut = '';
+		foreach ($params as $i=>$p) {
+			$paramsOut .= var_export($i, true).' => '.$p.',';
+		}
+
+		$curBlock =& $compiler->getCurrentBlock();
+		$curBlock['params']['postOut'] = Dwoo_Compiler::PHP_OPEN.' $_block_content = ob_get_clean(); $_block_repeat=false; echo '.$callback.'$_tag_stack[count($_tag_stack)-1], $_block_content, $this, $_block_repeat); } array_pop($_tag_stack);'.Dwoo_Compiler::PHP_CLOSE;
+
+		return Dwoo_Compiler::PHP_OPEN.$prepend.' if (!isset($_tag_stack)){ $_tag_stack = array(); } $_tag_stack[] = array('.$paramsOut.'); $_block_repeat=true; '.$callback.'$_tag_stack[count($_tag_stack)-1], null, $this, $_block_repeat); while ($_block_repeat) { ob_start();'.Dwoo_Compiler::PHP_CLOSE;
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		return $content . $params['postOut'];
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/strip.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/strip.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/strip.php	(revision 825)
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Strips the spaces at the beginning and end of each line and also the line breaks
+ * <pre>
+ *  * mode : sets the content being stripped, available mode are 'default' or 'js'
+ *    for javascript, which strips the comments to prevent syntax errors
+ * </pre>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_strip extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init($mode = "default")
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$params = $compiler->getCompiledParams($params);
+
+		$mode = trim($params['mode'], '"\'');
+		switch ($mode) {
+			case 'js':
+			case 'javascript':
+				$content = preg_replace('#(?<!:)//\s[^\r\n]*|/\*.*?\*/#s','', $content);
+
+			case 'default':
+			default:
+		}
+
+		$content = preg_replace(array("/\n/","/\r/",'/(<\?(?:php)?|<%)\s*/'), array('','','$1 '), preg_replace('#^\s*(.+?)\s*$#m', '$1', $content));
+
+		return $content;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/template.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/template.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/template.php	(revision 825)
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * Defines a sub-template that can then be called (even recursively) with the defined arguments
+ * <pre>
+ *  * name : template name
+ *  * rest : list of arguments and optional default values
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_template extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init($name, array $rest = array())
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		$params = $compiler->getCompiledParams($params);
+		$parsedParams = array();
+		if (!isset($params['*']))
+			$params['*'] = array();
+		foreach ($params['*'] as $param=>$defValue) {
+			if (is_numeric($param)) {
+				$param = $defValue;
+				$defValue = null;
+			}
+			$param = trim($param, '\'"');
+			if (!preg_match('#^[a-z0-9_]+$#i', $param)) {
+				throw new Dwoo_Compilation_Exception($compiler, 'Function : parameter names must contain only A-Z, 0-9 or _');
+			}
+			$parsedParams[$param] = $defValue;
+		}
+		$params['name'] = substr($params['name'], 1, -1);
+		$params['*'] = $parsedParams;
+		$params['uuid'] = uniqid();
+		$compiler->addTemplatePlugin($params['name'], $parsedParams, $params['uuid']);
+		$currentBlock =& $compiler->getCurrentBlock();
+		$currentBlock['params'] = $params;
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$paramstr = 'Dwoo_Core $dwoo';
+		$init = 'static $_callCnt = 0;'."\n".
+		'$dwoo->scope[\' '.$params['uuid'].'\'.$_callCnt] = array();'."\n".
+		'$_scope = $dwoo->setScope(array(\' '.$params['uuid'].'\'.($_callCnt++)));'."\n";
+		$cleanup = '/* -- template end output */ $dwoo->setScope($_scope, true);';
+		foreach ($params['*'] as $param=>$defValue) {
+			if ($defValue === null) {
+				$paramstr.=', $'.$param;
+			} else {
+				$paramstr.=', $'.$param.' = '.$defValue;
+			}
+			$init .= '$dwoo->scope[\''.$param.'\'] = $'.$param.";\n";
+		}
+		$init .= '/* -- template start output */';
+
+		$funcName = 'Dwoo_Plugin_'.$params['name'].'_'.$params['uuid'];
+
+		$search = array(
+			'$this->charset',
+			'$this->',
+			'$this,',
+		);
+		$replacement = array(
+			'$dwoo->getCharset()',
+			'$dwoo->',
+			'$dwoo,',
+		);
+		$content = str_replace($search, $replacement, $content);
+
+		$body = 'if (!function_exists(\''.$funcName."')) {\nfunction ".$funcName.'('.$paramstr.') {'."\n$init".Dwoo_Compiler::PHP_CLOSE.
+			$prepend.$content.$append.
+			Dwoo_Compiler::PHP_OPEN.$cleanup."\n}\n}";
+		$compiler->addTemplatePlugin($params['name'], $params['*'], $params['uuid'], $body);
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/textformat.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/textformat.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/textformat.php	(revision 825)
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * Formats a string to the given format, you can wrap lines at a certain
+ * length and indent them
+ * <pre>
+ *  * wrap : maximum line length
+ *  * wrap_char : the character(s) to use to break the line
+ *  * wrap_cut : if true, the words that are longer than $wrap are cut instead of overflowing
+ *  * indent : amount of $indent_char to insert before every line
+ *  * indent_char : character(s) to insert before every line
+ *  * indent_first : amount of additional $indent_char to insert before the first line of each paragraphs
+ *  * style : some predefined formatting styles that set up every required variables, can be "email" or "html"
+ *  * assign : if set, the formatted text is assigned to that variable instead of being output
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_textformat extends Dwoo_Block_Plugin
+{
+	protected $wrap;
+	protected $wrapChar;
+	protected $wrapCut;
+	protected $indent;
+	protected $indChar;
+	protected $indFirst;
+	protected $assign;
+
+	public function init($wrap=80, $wrap_char="\r\n", $wrap_cut=false, $indent=0, $indent_char=" ", $indent_first=0, $style="", $assign="")
+	{
+		if ($indent_char === 'tab') {
+			$indent_char = "\t";
+		}
+
+		switch($style) {
+
+		case 'email':
+			$wrap = 72;
+			$indent_first = 0;
+			break;
+		case 'html':
+			$wrap_char = '<br />';
+			$indent_char = $indent_char == "\t" ? '&nbsp;&nbsp;&nbsp;&nbsp;':'&nbsp;';
+			break;
+
+		}
+
+		$this->wrap = (int) $wrap;
+		$this->wrapChar = (string) $wrap_char;
+		$this->wrapCut = (bool) $wrap_cut;
+		$this->indent = (int) $indent;
+		$this->indChar = (string) $indent_char;
+		$this->indFirst = (int) $indent_first + $this->indent;
+		$this->assign = (string) $assign;
+	}
+
+	public function process()
+	{
+		// gets paragraphs
+		$pgs = explode("\n", str_replace(array("\r\n", "\r"), "\n", $this->buffer));
+
+		while (list($i,) = each($pgs)) {
+			if (empty($pgs[$i])) {
+				continue;
+			}
+
+			// removes line breaks and extensive white space
+			$pgs[$i] = preg_replace(array('#\s+#', '#^\s*(.+?)\s*$#m'), array(' ', '$1'), str_replace("\n", '', $pgs[$i]));
+
+			// wordwraps + indents lines
+			$pgs[$i] = str_repeat($this->indChar, $this->indFirst) .
+			   		wordwrap(
+							$pgs[$i],
+							max($this->wrap - $this->indent, 1),
+							$this->wrapChar . str_repeat($this->indChar, $this->indent),
+							$this->wrapCut
+					);
+		}
+
+		if ($this->assign !== '') {
+			$this->dwoo->assignInScope(implode($this->wrapChar . $this->wrapChar, $pgs), $this->assign);
+		} else {
+			return implode($this->wrapChar . $this->wrapChar, $pgs);
+		}
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/topLevelBlock.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/topLevelBlock.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/topLevelBlock.php	(revision 825)
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Internal plugin used to wrap the template output, do not use in your templates as it will break them
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+final class Dwoo_Plugin_topLevelBlock extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init()
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		return '/* end template head */ ob_start(); /* template body */ '.Dwoo_Compiler::PHP_CLOSE;
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		return $content . Dwoo_Compiler::PHP_OPEN.' /* end template body */'."\n".'return $this->buffer . ob_get_clean();';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/with.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/with.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/with.php	(revision 825)
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * Moves the scope down into the provided variable, allowing you to use shorter
+ * variable names if you repeatedly access values into a single array
+ *
+ * The with block won't display anything at all if the provided scope is empty,
+ * so in effect it acts as {if $var}*content*{/if}
+ * <pre>
+ *  * var : the variable name to move into
+ * </pre>
+ * Example :
+ *
+ * instead of the following :
+ *
+ * <code>
+ * {if $long.boring.prefix}
+ *   {$long.boring.prefix.val} - {$long.boring.prefix.secondVal} - {$long.boring.prefix.thirdVal}
+ * {/if}
+ * </code>
+ *
+ * you can use :
+ *
+ * <code>
+ * {with $long.boring.prefix}
+ *   {$val} - {$secondVal} - {$thirdVal}
+ * {/with}
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_with extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block, Dwoo_IElseable
+{
+	protected static $cnt=0;
+
+	public function init($var)
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		$rparams = $compiler->getRealParams($params);
+		$cparams = $compiler->getCompiledParams($params);
+
+		$compiler->setScope($rparams['var']);
+
+
+		$pre = Dwoo_Compiler::PHP_OPEN. 'if ('.$cparams['var'].')'."\n{\n".
+			'$_with'.(self::$cnt).' = $this->setScope("'.$rparams['var'].'");'.
+			"\n/* -- start with output */\n".Dwoo_Compiler::PHP_CLOSE;
+
+		$post = Dwoo_Compiler::PHP_OPEN. "\n/* -- end with output */\n".
+			'$this->setScope($_with'.(self::$cnt++).', true);'.
+			"\n}\n".Dwoo_Compiler::PHP_CLOSE;
+
+		if (isset($params['hasElse'])) {
+			$post .= $params['hasElse'];
+		}
+
+		return $pre . $content . $post;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/withelse.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/withelse.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/blocks/withelse.php	(revision 825)
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * This plugin serves as a {else} block specifically for the {with} plugin.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_withelse extends Dwoo_Block_Plugin implements Dwoo_ICompilable_Block
+{
+	public function init()
+	{
+	}
+
+	public static function preProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $type)
+	{
+		$with =& $compiler->findBlock('with', true);
+
+		$params['initialized'] = true;
+		$compiler->injectBlock($type, $params);
+
+		return '';
+	}
+
+	public static function postProcessing(Dwoo_Compiler $compiler, array $params, $prepend, $append, $content)
+	{
+		if (!isset($params['initialized'])) {
+			return '';
+		}
+
+		$block =& $compiler->getCurrentBlock();
+		$block['params']['hasElse'] = Dwoo_Compiler::PHP_OPEN."else {\n".Dwoo_Compiler::PHP_CLOSE . $content . Dwoo_Compiler::PHP_OPEN."\n}".Dwoo_Compiler::PHP_CLOSE;
+		return '';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/filters/html_format.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/filters/html_format.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/filters/html_format.php	(revision 825)
@@ -0,0 +1,175 @@
+<?php
+
+/**
+ * Formats any html output (must be valid xml where every tag opened is closed)
+ * using a single tab for indenting. 'pre' and other whitespace sensitive
+ * tags should not be affected.
+ *
+ * It is not recommended to use this on every template if you render multiple
+ * templates per page, you should only use it once on the main page template so that
+ * everything is formatted in one pass.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Filter_html_format extends Dwoo_Filter
+{
+	/**
+	 * tab count to auto-indent the source
+	 *
+	 * @var int
+	 */
+	protected static $tabCount = -1;
+
+	/**
+	 * stores the additional data (following a tag) of the last call to open/close/singleTag
+	 *
+	 * @var string
+	 */
+	protected static $lastCallAdd = '';
+
+	/**
+	 * formats the input using the singleTag/closeTag/openTag functions
+	 *
+	 * It is auto indenting the whole code, excluding <textarea>, <code> and <pre> tags that must be kept intact.
+	 * Those tags must however contain only htmlentities-escaped text for everything to work properly.
+	 * Inline tags are presented on a single line with their content
+	 *
+	 * @param Dwoo_Core $dwoo the dwoo instance rendering this
+	 * @param string $input the xhtml to format
+	 * @return string formatted xhtml
+	 */
+	public function process($input)
+	{
+		self::$tabCount = -1;
+
+		// auto indent all but textareas & pre (or we have weird tabs inside)
+		$input = preg_replace_callback("#(<[^>]+>)(\s*)([^<]*)#", array('self', 'tagDispatcher'), $input);
+
+		return $input;
+	}
+
+	/**
+	 * helper function for format()'s preg_replace call
+	 *
+	 * @param array	$input	array of matches (1=>tag, 2=>whitespace(optional), 3=>additional non-html content)
+	 * @return string the indented tag
+	 */
+	protected static function tagDispatcher($input)
+	{
+		// textarea, pre, code tags and comments are to be left alone to avoid any non-wanted whitespace inside them so it just outputs them as they were
+		if (substr($input[1],0,9) == "<textarea" || substr($input[1],0,4) == "<pre" || substr($input[1],0,5) == "<code" || substr($input[1],0,4) == "<!--" || substr($input[1],0,9) == "<![CDATA[") {
+			return $input[1] . $input[3];
+		}
+		// closing textarea, code and pre tags and self-closed tags (i.e. <br />) are printed as singleTags because we didn't use openTag for the formers and the latter is a single tag
+		if (substr($input[1],0,10) == "</textarea" || substr($input[1],0,5) == "</pre" || substr($input[1],0,6) == "</code" || substr($input[1],-2) == "/>") {
+			return self::singleTag($input[1],$input[3],$input[2]);
+		}
+		// it's the closing tag
+		if ($input[0][1]=="/"){
+			return self::closeTag($input[1],$input[3],$input[2]);
+		}
+		// opening tag
+		return self::openTag($input[1],$input[3],$input[2]);
+	}
+
+	/**
+	 * returns an open tag and adds a tab into the auto indenting
+	 *
+	 * @param string $tag content of the tag
+	 * @param string $add additional data (anything before the following tag)
+	 * @param string $whitespace white space between the tag and the additional data
+	 * @return string
+	 */
+	protected static function openTag($tag,$add,$whitespace)
+	{
+		$tabs = str_pad('',self::$tabCount++,"\t");
+
+		if (preg_match('#^<(a|label|option|textarea|h1|h2|h3|h4|h5|h6|strong|b|em|i|abbr|acronym|cite|span|sub|sup|u|s|title)(?: [^>]*|)>#', $tag)) {
+			// if it's one of those tag it's inline so it does not require a leading line break
+			$result = $tag . $whitespace . str_replace("\n","\n".$tabs,$add);
+		} elseif (substr($tag,0,9) == '<!DOCTYPE') {
+			// it's the doctype declaration so no line break here either
+			$result = $tabs . $tag;
+		} else {
+			// normal block tag
+			$result = "\n".$tabs . $tag;
+
+			if (!empty($add)) {
+				$result .= "\n".$tabs."\t".str_replace("\n","\n\t".$tabs,$add);
+			}
+		}
+
+		self::$lastCallAdd = $add;
+
+		return $result;
+	}
+
+	/**
+	 * returns a closing tag and removes a tab from the auto indenting
+	 *
+	 * @param string $tag content of the tag
+	 * @param string $add additional data (anything before the following tag)
+	 * @param string $whitespace white space between the tag and the additional data
+	 * @return string
+	 */
+	protected static function closeTag($tag,$add,$whitespace)
+	{
+		$tabs = str_pad('',--self::$tabCount,"\t");
+
+		// if it's one of those tag it's inline so it does not require a leading line break
+		if (preg_match('#^</(a|label|option|textarea|h1|h2|h3|h4|h5|h6|strong|b|em|i|abbr|acronym|cite|span|sub|sup|u|s|title)>#', $tag)) {
+			$result = $tag . $whitespace . str_replace("\n","\n".$tabs,$add);
+		} else {
+			$result = "\n".$tabs.$tag;
+
+			if (!empty($add)) {
+				$result .= "\n".$tabs."\t".str_replace("\n","\n\t".$tabs,$add);
+			}
+		}
+
+		self::$lastCallAdd = $add;
+
+		return $result;
+	}
+
+	/**
+	 * returns a single tag with auto indenting
+	 *
+	 * @param string $tag content of the tag
+	 * @param string $add additional data (anything before the following tag)
+	 * @return string
+	 */
+	protected static function singleTag($tag,$add,$whitespace)
+	{
+		$tabs = str_pad('',self::$tabCount,"\t");
+
+		// if it's img, br it's inline so it does not require a leading line break
+		// if it's a closing textarea, code or pre tag, it does not require a leading line break either or it creates whitespace at the end of those blocks
+		if (preg_match('#^<(img|br|/textarea|/pre|/code)(?: [^>]*|)>#', $tag)) {
+			$result = $tag.$whitespace;
+
+			if (!empty($add)) {
+				$result .= str_replace("\n","\n".$tabs,$add);
+			}
+		} else {
+			$result = "\n".$tabs.$tag;
+
+			if (!empty($add)) {
+				$result .= "\n".$tabs.str_replace("\n","\n".$tabs,$add);
+			}
+		}
+
+		self::$lastCallAdd = $add;
+
+		return $result;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/assign.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/assign.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/assign.php	(revision 825)
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Assigns a value to a variable
+ * <pre>
+ *  * value : the value that you want to save
+ *  * var : the variable name (without the leading $)
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_assign_compile(Dwoo_Compiler $compiler, $value, $var)
+{
+	return '$this->assignInScope('.$value.', '.$var.')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/capitalize.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/capitalize.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/capitalize.php	(revision 825)
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Capitalizes the first letter of each word
+ * <pre>
+ *  * value : the string to capitalize
+ *  * numwords : if true, the words containing numbers are capitalized as well
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_capitalize(Dwoo_Core $dwoo, $value, $numwords=false)
+{
+	if ($numwords || preg_match('#^[^0-9]+$#',$value))
+	{
+		return mb_convert_case((string) $value,MB_CASE_TITLE, $dwoo->getCharset());
+	} else {
+		$bits = explode(' ', (string) $value);
+		$out = '';
+		while (list(,$v) = each($bits)) {
+			if (preg_match('#^[^0-9]+$#', $v)) {
+				$out .=	' '.mb_convert_case($v, MB_CASE_TITLE, $dwoo->getCharset());
+			} else {
+				$out .=	' '.$v;
+			}
+		}
+
+		return substr($out, 1);
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/cat.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/cat.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/cat.php	(revision 825)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Concatenates any number of variables or strings fed into it
+ * <pre>
+ *  * rest : two or more strings that will be merged into one
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_cat_compile(Dwoo_Compiler $compiler, $value, array $rest)
+{
+	return '('.$value.').('.implode(').(', $rest).')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_characters.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_characters.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_characters.php	(revision 825)
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Counts the characters in a string
+ * <pre>
+ *  * value : the string to process
+ *  * count_spaces : if true, the white-space characters are counted as well
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_count_characters_compile(Dwoo_Compiler $compiler, $value, $count_spaces=false)
+{
+	if ($count_spaces==='false') {
+		return 'preg_match_all(\'#[^\s\pZ]#u\', '.$value.', $tmp)';
+	} else {
+		return 'mb_strlen('.$value.', $this->charset)';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_paragraphs.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_paragraphs.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_paragraphs.php	(revision 825)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Counts the paragraphs in a string
+ * <pre>
+ *  * value : the string to process
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_count_paragraphs_compile(Dwoo_Compiler $compiler, $value)
+{
+	return '(preg_match_all(\'#[\r\n]+#\', '.$value.', $tmp)+1)';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_sentences.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_sentences.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_sentences.php	(revision 825)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Counts the sentences in a string
+ * <pre>
+ *  * value : the string to process
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_count_sentences_compile(Dwoo_Compiler $compiler, $value)
+{
+	return "preg_match_all('#[\w\pL]\.(?![\w\pL])#u', $value, \$tmp)";
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_words.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_words.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/count_words.php	(revision 825)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Counts the words in a string
+ * <pre>
+ *  * value : the string to process
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_count_words_compile(Dwoo_Compiler $compiler, $value)
+{
+	return 'preg_match_all(strcasecmp($this->charset, \'utf-8\')===0 ? \'#[\w\pL]+#u\' : \'#\w+#\', '.$value.', $tmp)';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/counter.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/counter.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/counter.php	(revision 825)
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * Initiates a counter that is incremented every time you call it
+ * <pre>
+ *  * name : the counter name, define it if you want to have multiple concurrent counters
+ *  * start : the start value, if it's set, it will reset the counter to this value, defaults to 1
+ *  * skip : the value to add to the counter at each call, defaults to 1
+ *  * direction : "up" (default) or "down" to define whether the counter increments or decrements
+ *  * print : if false, the counter will not output the current count, defaults to true
+ *  * assign : if set, the counter is saved into the given variable and does not output anything, overriding the print parameter
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_counter extends Dwoo_Plugin
+{
+	protected $counters = array();
+
+	public function process($name = 'default', $start = null, $skip = null, $direction = null, $print = null, $assign = null)
+	{
+		// init counter
+		if (!isset($this->counters[$name])) {
+			$this->counters[$name] = array
+			(
+				'count'		=>	$start===null ? 1 : (int) $start,
+				'skip'		=>	$skip===null ? 1 : (int) $skip,
+				'print'		=>	$print===null ? true : (bool) $print,
+				'assign'	=>	$assign===null ? null : (string) $assign,
+				'direction'	=>	strtolower($direction)==='down' ? -1 : 1,
+			);
+		}
+		// increment
+		else
+		{
+			// override setting if present
+			if ($skip !== null) {
+				$this->counters[$name]['skip'] = (int) $skip;
+			}
+
+			if ($direction !== null) {
+				$this->counters[$name]['direction'] = strtolower($direction)==='down' ? -1 : 1;
+			}
+
+			if ($print !== null) {
+				$this->counters[$name]['print'] = (bool) $print;
+			}
+
+			if ($assign !== null) {
+				$this->counters[$name]['assign'] = (string) $assign;
+			}
+
+			if ($start !== null) {
+				$this->counters[$name]['count'] = (int) $start;
+			} else {
+				$this->counters[$name]['count'] += ($this->counters[$name]['skip'] * $this->counters[$name]['direction']);
+			}
+		}
+
+		$out = $this->counters[$name]['count'];
+
+		if ($this->counters[$name]['assign'] !== null) {
+			$this->dwoo->assignInScope($out, $this->counters[$name]['assign']);
+		} elseif ($this->counters[$name]['print'] === true) {
+			return $out;
+		}
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/cycle.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/cycle.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/cycle.php	(revision 825)
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * Cycles between several values and returns one of them on each call
+ * <pre>
+ *  * name : the cycler name, specify if you need to have multiple concurrent cycles running
+ *  * values : an array of values or a string of values delimited by $delimiter
+ *  * print : if false, the pointer will go to the next one but not print anything
+ *  * advance : if false, the pointer will not advance to the next value
+ *  * delimiter : the delimiter used to split values if they are provided as a string
+ *  * assign : if set, the value is saved in that variable instead of being output
+ *  * reset : if true, the pointer is reset to the first value
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_cycle extends Dwoo_Plugin
+{
+	protected $cycles = array();
+
+	public function process($name = 'default', $values = null, $print = true, $advance = true, $delimiter = ',', $assign = null, $reset = false)
+	{
+		if ($values !== null) {
+			if (is_string($values)) {
+				$values = explode($delimiter, $values);
+			}
+
+			if (!isset($this->cycles[$name]) || $this->cycles[$name]['values'] !== $values) {
+				$this->cycles[$name]['index'] = 0;
+			}
+
+			$this->cycles[$name]['values'] = array_values($values);
+		} elseif (isset($this->cycles[$name])) {
+			$values = $this->cycles[$name]['values'];
+		}
+
+		if ($reset) {
+			$this->cycles[$name]['index'] = 0;
+		}
+
+		if ($print) {
+			$out = $values[$this->cycles[$name]['index']];
+		} else {
+			$out = null;
+		}
+
+		if ($advance) {
+			if ($this->cycles[$name]['index'] >= count($values)-1) {
+				$this->cycles[$name]['index'] = 0;
+			} else {
+				$this->cycles[$name]['index']++;
+			}
+		}
+
+		if ($assign === null) {
+			return $out;
+		}
+		$this->dwoo->assignInScope($out, $assign);
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/date_format.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/date_format.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/date_format.php	(revision 825)
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Formats a date
+ * <pre>
+ *  * value : the date, as a unix timestamp, mysql datetime or whatever strtotime() can parse
+ *  * format : output format, see {@link http://php.net/strftime} for details
+ *  * default : a default timestamp value, if the first one is empty
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.1
+ * @date       2008-12-24
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_date_format(Dwoo_Core $dwoo, $value, $format='%b %e, %Y', $default=null)
+{
+	if (!empty($value)) {
+		// convert if it's not a valid unix timestamp
+		if (preg_match('#^-?\d{1,10}$#', $value)===0) {
+			$value = strtotime($value);
+		}
+	} elseif (!empty($default)) {
+		// convert if it's not a valid unix timestamp
+		if (preg_match('#^-?\d{1,10}$#', $default)===0) {
+			$value = strtotime($default);
+		} else {
+			$value = $default;
+		}
+	} else {
+		return '';
+	}
+
+	// Credits for that windows compat block to Monte Ohrt who made smarty's date_format plugin
+	if (DIRECTORY_SEPARATOR == '\\') {
+		$_win_from = array('%D',       '%h', '%n', '%r',          '%R',    '%t', '%T');
+		$_win_to   = array('%m/%d/%y', '%b', "\n", '%I:%M:%S %p', '%H:%M', "\t", '%H:%M:%S');
+		if (strpos($format, '%e') !== false) {
+			$_win_from[] = '%e';
+			$_win_to[]   = sprintf('%\' 2d', date('j', $value));
+		}
+		if (strpos($format, '%l') !== false) {
+			$_win_from[] = '%l';
+			$_win_to[]   = sprintf('%\' 2d', date('h', $value));
+		}
+		$format = str_replace($_win_from, $_win_to, $format);
+	}
+	return strftime($format, $value);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/default.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/default.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/default.php	(revision 825)
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Returns a variable or a default value if it's empty
+ * <pre>
+ *  * value : the variable to check
+ *  * default : fallback value if the first one is empty
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_default_compile(Dwoo_Compiler $compiler, $value, $default='')
+{
+	return '(($tmp = '.$value.')===null||$tmp===\'\' ? '.$default.' : $tmp)';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/dump.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/dump.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/dump.php	(revision 825)
@@ -0,0 +1,173 @@
+<?php
+
+/**
+ * Dumps values of the given variable, or the entire data if nothing provided
+ * <pre>
+ *  * var : the variable to display
+ *  * show_methods : if set to true, the public methods of any object encountered are also displayed
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_dump extends Dwoo_Plugin
+{
+	protected $outputObjects;
+	protected $outputMethods;
+
+	public function process($var = '$', $show_methods = false)
+	{
+		$this->outputMethods = $show_methods;
+		if ($var === '$') {
+			$var = $this->dwoo->getData();
+			$out = '<div style="background:#aaa; padding:5px; margin:5px; color:#000;">data';
+		} else {
+			$out = '<div style="background:#aaa; padding:5px; margin:5px; color:#000;">dump';
+		}
+
+		$this->outputObjects = array();
+
+		if (!is_array($var)) {
+			if (is_object($var)) {
+				return $this->exportObj('', $var);
+			} else {
+				return $this->exportVar('', $var);
+			}
+		}
+
+		$scope = $this->dwoo->getScope();
+
+		if ($var === $scope) {
+			$out .= ' (current scope): <div style="background:#ccc;">';
+		} else {
+			$out .= ':<div style="padding-left:20px;">';
+		}
+
+		$out .= $this->export($var, $scope);
+
+		return $out .'</div></div>';
+	}
+
+	protected function export($var, $scope)
+	{
+		$out = '';
+		foreach ($var as $i=>$v) {
+			if (is_array($v) || (is_object($v) && $v instanceof Iterator)) {
+				$out .= $i.' ('.(is_array($v) ? 'array':'object: '.get_class($v)).')';
+				if ($v===$scope) {
+					$out .= ' (current scope):<div style="background:#ccc;padding-left:20px;">'.$this->export($v, $scope).'</div>';
+				} else {
+					$out .= ':<div style="padding-left:20px;">'.$this->export($v, $scope).'</div>';
+				}
+			} elseif (is_object($v)) {
+				$out .= $this->exportObj($i.' (object: '.get_class($v).'):', $v);
+			} else {
+				$out .= $this->exportVar($i.' = ', $v);
+			}
+		}
+		return $out;
+	}
+
+	protected function exportVar($i, $v)
+	{
+		if (is_string($v) || is_bool($v) || is_numeric($v)) {
+			return $i.htmlentities(var_export($v, true)).'<br />';
+		} elseif (is_null($v)) {
+			return $i.'null<br />';
+		} elseif (is_resource($v)) {
+			return $i.'resource('.get_resource_type($v).')<br />';
+		} else {
+			return $i.htmlentities(var_export($v, true)).'<br />';
+		}
+	}
+
+	protected function exportObj($i, $obj)
+	{
+		if (array_search($obj, $this->outputObjects, true) !== false) {
+			return $i . ' [recursion, skipped]<br />';
+		}
+
+		$this->outputObjects[] = $obj;
+
+		$list = (array) $obj;
+
+		$protectedLength = strlen(get_class($obj)) + 2;
+
+		$out = array();
+
+		if ($this->outputMethods) {
+			$ref = new ReflectionObject($obj);
+
+			foreach ($ref->getMethods() as $method) {
+				if (!$method->isPublic()) {
+					continue;
+				}
+
+				if (empty($out['method'])) {
+					$out['method'] = '';
+				}
+
+				$params = array();
+				foreach ($method->getParameters() as $param) {
+					$params[] = ($param->isPassedByReference() ? '&':'') . '$'.$param->getName() . ($param->isOptional() ? ' = '.var_export($param->getDefaultValue(), true) : '');
+				}
+
+				$out['method'] .= '(method) ' . $method->getName() .'('.implode(', ', $params).')<br />';
+			}
+		}
+
+		foreach ($list as $attributeName => $attributeValue) {
+			if(property_exists($obj, $attributeName)) {
+				$key = 'public';
+			} elseif(substr($attributeName, 0, 3) === "\0*\0") {
+				$key = 'protected';
+				$attributeName = substr($attributeName, 3);
+			} else {
+				$key = 'private';
+				$attributeName = substr($attributeName, $protectedLength);
+			}
+
+			if (empty($out[$key])) {
+				$out[$key] = '';
+			}
+
+			$out[$key] .= '('.$key.') ';
+
+			if (is_array($attributeValue)) {
+				$out[$key] .= $attributeName.' (array):<br />
+							<div style="padding-left:20px;">'.$this->export($attributeValue, false).'</div>';
+			} elseif (is_object($attributeValue)) {
+				$out[$key] .= $this->exportObj($attributeName.' (object: '.get_class($attributeValue).'):', $attributeValue);
+			} else {
+				$out[$key] .= $this->exportVar($attributeName.' = ', $attributeValue);
+			}
+		}
+
+		$return = $i . '<br /><div style="padding-left:20px;">';
+
+		if (!empty($out['method'])) {
+			$return .= $out['method'];
+		}
+
+		if (!empty($out['public'])) {
+			$return .= $out['public'];
+		}
+
+		if (!empty($out['protected'])) {
+			$return .= $out['protected'];
+		}
+
+		if (!empty($out['private'])) {
+			$return .= $out['private'];
+		}
+
+		return $return . '</div>';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/eol.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/eol.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/eol.php	(revision 825)
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Returns the correct end of line character(s) for the current operating system
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_eol_compile(Dwoo_Compiler $compiler)
+{
+	return 'PHP_EOL';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/escape.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/escape.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/escape.php	(revision 825)
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * Applies various escaping schemes on the given string
+ * <pre>
+ *  * value : the string to process
+ *  * format : escaping format to use, valid formats are : html, htmlall, url, urlpathinfo, quotes, hex, hexentity, javascript and mail
+ *  * charset : character set to use for the conversion (applies to some formats only), defaults to the current Dwoo charset
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_escape(Dwoo_Core $dwoo, $value='', $format='html', $charset=null)
+{
+	if ($charset === null) {
+		$charset = $dwoo->getCharset();
+	}
+
+	switch($format)
+	{
+
+	case 'html':
+		return htmlspecialchars((string) $value, ENT_QUOTES, $charset);
+	case 'htmlall':
+		return htmlentities((string) $value, ENT_QUOTES, $charset);
+	case 'url':
+		return rawurlencode((string) $value);
+	case 'urlpathinfo':
+		return str_replace('%2F', '/', rawurlencode((string) $value));
+	case 'quotes':
+		return preg_replace("#(?<!\\\\)'#", "\\'", (string) $value);
+	case 'hex':
+		$out = '';
+		$cnt = strlen((string) $value);
+		for ($i=0; $i < $cnt; $i++) {
+			$out .= '%' . bin2hex((string) $value[$i]);
+		}
+		return $out;
+	case 'hexentity':
+		$out = '';
+		$cnt = strlen((string) $value);
+		for ($i=0; $i < $cnt; $i++)
+			$out .= '&#x' . bin2hex((string) $value[$i]) . ';';
+		return $out;
+	case 'javascript':
+		return strtr((string) $value, array('\\'=>'\\\\',"'"=>"\\'",'"'=>'\\"',"\r"=>'\\r',"\n"=>'\\n','</'=>'<\/'));
+	case 'mail':
+		return str_replace(array('@', '.'), array('&nbsp;(AT)&nbsp;', '&nbsp;(DOT)&nbsp;'), (string) $value);
+	default:
+		return $dwoo->triggerError('Escape\'s format argument must be one of : html, htmlall, url, urlpathinfo, hex, hexentity, javascript or mail, "'.$format.'" given.', E_USER_WARNING);
+
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/eval.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/eval.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/eval.php	(revision 825)
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * Evaluates the given string as if it was a template
+ *
+ * Although this plugin is kind of optimized and will
+ * not recompile your string each time, it is still not
+ * a good practice to use it. If you want to have templates
+ * stored in a database or something you should probably use
+ * the Dwoo_Template_String class or make another class that
+ * extends it
+ * <pre>
+ *  * var : the string to use as a template
+ *  * assign : if set, the output of the template will be saved in this variable instead of being output
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_eval(Dwoo_Core $dwoo, $var, $assign = null)
+{
+	if ($var == '') {
+		return;
+	}
+
+	$tpl = new Dwoo_Template_String($var);
+	$clone = clone $dwoo;
+	$out = $clone->get($tpl, $dwoo->readVar('_parent'));
+
+	if ($assign !== null) {
+		$dwoo->assignInScope($out, $assign);
+	} else {
+		return $out;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/extends.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/extends.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/extends.php	(revision 825)
@@ -0,0 +1,161 @@
+<?php
+
+/**
+ * Extends another template, read more about template inheritance at {@link http://wiki.dwoo.org/index.php/TemplateInheritance}
+ * <pre>
+ *  * file : the template to extend
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+class Dwoo_Plugin_extends extends Dwoo_Plugin implements Dwoo_ICompilable
+{
+	protected static $childSource;
+	protected static $regex;
+	protected static $l;
+	protected static $r;
+	protected static $lastReplacement;
+
+	public static function compile(Dwoo_Compiler $compiler, $file)
+	{
+		list($l, $r) = $compiler->getDelimiters();
+		self::$l = preg_quote($l,'/');
+		self::$r = preg_quote($r,'/');
+		self::$regex = '/
+			'.self::$l.'block\s(["\']?)(.+?)\1'.self::$r.'(?:\r?\n?)
+			((?:
+				(?R)
+				|
+				[^'.self::$l.']*
+				(?:
+					(?! '.self::$l.'\/?block\b )
+					'.self::$l.'
+					[^'.self::$l.']*+
+				)*
+			)*)
+			'.self::$l.'\/block'.self::$r.'
+			/six';
+
+		if ($compiler->getLooseOpeningHandling()) {
+			self::$l .= '\s*';
+			self::$r = '\s*'.self::$r;
+		}
+		$inheritanceTree = array(array('source'=>$compiler->getTemplateSource()));
+		$curPath = dirname($compiler->getDwoo()->getTemplate()->getResourceIdentifier()) . DIRECTORY_SEPARATOR;
+		$curTpl = $compiler->getDwoo()->getTemplate();
+
+		while (!empty($file)) {
+			if ($file === '""' || $file === "''" || (substr($file, 0, 1) !== '"' && substr($file, 0, 1) !== '\'')) {
+				throw new Dwoo_Compilation_Exception($compiler, 'Extends : The file name must be a non-empty string');
+				return;
+			}
+
+			if (preg_match('#^["\']([a-z]{2,}):(.*?)["\']$#i', $file, $m)) {
+				// resource:identifier given, extract them
+				$resource = $m[1];
+				$identifier = $m[2];
+			} else {
+				// get the current template's resource
+				$resource = $curTpl->getResourceName();
+				$identifier = substr($file, 1, -1);
+			}
+
+			try {
+				$parent = $compiler->getDwoo()->templateFactory($resource, $identifier, null, null, null, $curTpl);
+			} catch (Dwoo_Security_Exception $e) {
+				throw new Dwoo_Compilation_Exception($compiler, 'Extends : Security restriction : '.$e->getMessage());
+			} catch (Dwoo_Exception $e) {
+				throw new Dwoo_Compilation_Exception($compiler, 'Extends : '.$e->getMessage());
+			}
+
+			if ($parent === null) {
+				throw new Dwoo_Compilation_Exception($compiler, 'Extends : Resource "'.$resource.':'.$identifier.'" not found.');
+			} elseif ($parent === false) {
+				throw new Dwoo_Compilation_Exception($compiler, 'Extends : Resource "'.$resource.'" does not support extends.');
+			}
+
+			$curTpl = $parent;
+			$newParent = array('source'=>$parent->getSource(), 'resource'=>$resource, 'identifier'=>$parent->getResourceIdentifier(), 'uid'=>$parent->getUid());
+			if (array_search($newParent, $inheritanceTree, true) !== false) {
+				throw new Dwoo_Compilation_Exception($compiler, 'Extends : Recursive template inheritance detected');
+			}
+			$inheritanceTree[] = $newParent;
+
+			if (preg_match('/^'.self::$l.'extends(?:\(?\s*|\s+)(?:file=)?\s*((["\']).+?\2|\S+?)\s*\)?\s*?'.self::$r.'/i', $parent->getSource(), $match)) {
+				$curPath = dirname($identifier) . DIRECTORY_SEPARATOR;
+				if (isset($match[2]) && $match[2] == '"') {
+					$file = '"'.str_replace('"', '\\"', substr($match[1], 1, -1)).'"';
+				} elseif (isset($match[2]) && $match[2] == "'") {
+					$file = '"'.substr($match[1], 1, -1).'"';
+				} else {
+					$file = '"'.$match[1].'"';
+				}
+			} else {
+				$file = false;
+			}
+		}
+
+		while (true) {
+			$parent = array_pop($inheritanceTree);
+			$child = end($inheritanceTree);
+			self::$childSource = $child['source'];
+			self::$lastReplacement = count($inheritanceTree) === 1;
+			if (!isset($newSource)) {
+				$newSource = $parent['source'];
+			}
+			$newSource = preg_replace_callback(self::$regex, array('Dwoo_Plugin_extends', 'replaceBlock'), $newSource);
+			$newSource = $l.'do extendsCheck('.var_export($parent['resource'].':'.$parent['identifier'], true).')'.$r.$newSource;
+
+			if (self::$lastReplacement) {
+				break;
+			}
+		}
+		$compiler->setTemplateSource($newSource);
+		$compiler->recompile();
+	}
+
+	protected static function replaceBlock(array $matches)
+	{
+		$matches[3] = self::removeTrailingNewline($matches[3]);
+
+		if (preg_match_all(self::$regex, self::$childSource, $override) && in_array($matches[2], $override[2])) {
+			$key = array_search($matches[2], $override[2]);
+			$override = self::removeTrailingNewline($override[3][$key]);
+
+			$l = stripslashes(self::$l);
+			$r = stripslashes(self::$r);
+
+			if (self::$lastReplacement) {
+				return preg_replace('/'.self::$l.'\$dwoo\.parent'.self::$r.'/is', $matches[3], $override);
+			}
+			return $l.'block '.$matches[1].$matches[2].$matches[1].$r.preg_replace('/'.self::$l.'\$dwoo\.parent'.self::$r.'/is', $matches[3], $override).$l.'/block'.$r;
+		}
+
+		if (preg_match(self::$regex, $matches[3])) {
+			return preg_replace_callback(self::$regex, array('Dwoo_Plugin_extends', 'replaceBlock'), $matches[3] );
+		}
+
+		if (self::$lastReplacement) {
+			return $matches[3];
+		}
+
+		return  $matches[0];
+	}
+
+	protected static function removeTrailingNewline($text)
+	{
+		return substr($text, -1) === "\n"
+				? substr($text, -2, 1) === "\r"
+					? substr($text, 0, -2)
+					: substr($text, 0, -1)
+				: $text;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/extendsCheck.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/extendsCheck.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/extendsCheck.php	(revision 825)
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * Checks whether an extended file has been modified, and if so recompiles the current template. This is for internal use only, do not use.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_extendsCheck_compile(Dwoo_Compiler $compiler, $file)
+{
+	preg_match('#^["\']([a-z]{2,}):(.*?)["\']$#i', $file, $m);
+	$resource = $m[1];
+	$identifier = $m[2];
+
+	$tpl = $compiler->getDwoo()->templateFactory($resource, $identifier);
+
+	if ($tpl === null) {
+		throw new Dwoo_Compilation_Exception($compiler, 'Load Templates : Resource "'.$resource.':'.$identifier.'" not found.');
+	} elseif ($tpl === false) {
+		throw new Dwoo_Compilation_Exception($compiler, 'Load Templates : Resource "'.$resource.'" does not support includes.');
+	}
+
+
+	$out = '\'\';// checking for modification in '.$resource.':'.$identifier."\r\n";
+	
+	$modCheck = $tpl->getIsModifiedCode();
+	
+	if ($modCheck) {
+		$out .= 'if (!('.$modCheck.')) { ob_end_clean(); return false; }';
+	} else {
+		$out .= 'try {
+	$tpl = $this->templateFactory("'.$resource.'", "'.$identifier.'");
+} catch (Dwoo_Exception $e) {
+	$this->triggerError(\'Load Templates : Resource <em>'.$resource.'</em> was not added to Dwoo, can not extend <em>'.$identifier.'</em>\', E_USER_WARNING);
+}
+if ($tpl === null)
+	$this->triggerError(\'Load Templates : Resource "'.$resource.':'.$identifier.'" was not found.\', E_USER_WARNING);
+elseif ($tpl === false)
+	$this->triggerError(\'Load Templates : Resource "'.$resource.'" does not support extends.\', E_USER_WARNING);
+if ($tpl->getUid() != "'.$tpl->getUid().'") { ob_end_clean(); return false; }';
+	}
+	
+	return $out;
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/fetch.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/fetch.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/fetch.php	(revision 825)
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * Reads a file
+ * <pre>
+ *  * file : path or URI of the file to read (however reading from another website is not recommended for performance reasons)
+ *  * assign : if set, the file will be saved in this variable instead of being output
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_fetch(Dwoo_Core $dwoo, $file, $assign = null)
+{
+	if ($file === '') {
+		return;
+	}
+
+	if ($policy = $dwoo->getSecurityPolicy()) {
+		while (true) {
+			if (preg_match('{^([a-z]+?)://}i', $file)) {
+				return $dwoo->triggerError('The security policy prevents you to read files from external sources.', E_USER_WARNING);
+			}
+
+			$file = realpath($file);
+			$dirs = $policy->getAllowedDirectories();
+			foreach ($dirs as $dir=>$dummy) {
+				if (strpos($file, $dir) === 0) {
+					break 2;
+				}
+			}
+			return $dwoo->triggerError('The security policy prevents you to read <em>'.$file.'</em>', E_USER_WARNING);
+		}
+	}
+	$file = str_replace(array("\t", "\n", "\r"), array('\\t', '\\n', '\\r'), $file);
+
+	$out = file_get_contents($file);
+
+	if ($assign === null) {
+		return $out;
+	}
+	$dwoo->assignInScope($out, $assign);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/include.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/include.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/include.php	(revision 825)
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * Inserts another template into the current one
+ * <pre>
+ *  * file : the resource name of the template
+ *  * cache_time : cache length in seconds
+ *  * cache_id : cache identifier for the included template
+ *  * compile_id : compilation identifier for the included template
+ *  * data : data to feed into the included template, it can be any array and will default to $_root (the current data)
+ *  * assign : if set, the output of the included template will be saved in this variable instead of being output
+ *  * rest : any additional parameter/value provided will be added to the data array
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_include(Dwoo_Core $dwoo, $file, $cache_time = null, $cache_id = null, $compile_id = null, $data = '_root', $assign = null, array $rest = array())
+{
+	if ($file === '') {
+		return;
+	}
+
+	if (preg_match('#^([a-z]{2,}):(.*)$#i', $file, $m)) {
+		// resource:identifier given, extract them
+		$resource = $m[1];
+		$identifier = $m[2];
+	} else {
+		// get the current template's resource
+		$resource = $dwoo->getTemplate()->getResourceName();
+		$identifier = $file;
+	}
+
+	try {
+		$include = $dwoo->templateFactory($resource, $identifier, $cache_time, $cache_id, $compile_id);
+	} catch (Dwoo_Security_Exception $e) {
+		return $dwoo->triggerError('Include : Security restriction : '.$e->getMessage(), E_USER_WARNING);
+	} catch (Dwoo_Exception $e) {
+		return $dwoo->triggerError('Include : '.$e->getMessage(), E_USER_WARNING);
+	}
+
+	if ($include === null) {
+		return $dwoo->triggerError('Include : Resource "'.$resource.':'.$identifier.'" not found.', E_USER_WARNING);
+	} elseif ($include === false) {
+		return $dwoo->triggerError('Include : Resource "'.$resource.'" does not support includes.', E_USER_WARNING);
+	}
+
+	if (is_string($data)) {
+		$vars = $dwoo->readVar($data);
+	} else {
+		$vars = $data;
+	}
+
+	if (count($rest)) {
+		$vars = $rest + $vars;
+	}
+
+	$clone = clone $dwoo;
+	$out = $clone->get($include, $vars);
+
+	if ($assign !== null) {
+		$dwoo->assignInScope($out, $assign);
+	}
+
+	foreach ($clone->getReturnValues() as $name => $value) {
+		$dwoo->assignInScope($value, $name);
+	}
+
+	if ($assign === null) {
+		return $out;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/indent.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/indent.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/indent.php	(revision 825)
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Indents every line of a text by the given amount of characters
+ * <pre>
+ *  * value : the string to indent
+ *  * by : how many characters should be inserted before each line
+ *  * char : the character(s) to insert
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_indent_compile(Dwoo_Compiler $compiler, $value, $by=4, $char=' ')
+{
+	return "preg_replace('#^#m', '".str_repeat(substr($char, 1, -1), trim($by, '"\''))."', $value)";
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/isset.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/isset.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/isset.php	(revision 825)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Checks whether a variable is not null
+ * <pre>
+ *  * var : variable to check
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_isset_compile(Dwoo_Compiler $compiler, $var)
+{
+	return '('.$var.' !== null)';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/load_templates.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/load_templates.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/load_templates.php	(revision 825)
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Loads sub-templates contained in an external file
+ * <pre>
+ *  * file : the resource name of the file to load
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_load_templates_compile(Dwoo_Compiler $compiler, $file)
+{
+	$file = substr($file, 1, -1);
+
+	if ($file === '') {
+		return;
+	}
+
+	if (preg_match('#^([a-z]{2,}):(.*)$#i', $file, $m)) {
+		// resource:identifier given, extract them
+		$resource = $m[1];
+		$identifier = $m[2];
+	} else {
+		// get the current template's resource
+		$resource = $compiler->getDwoo()->getTemplate()->getResourceName();
+		$identifier = $file;
+	}
+
+	$tpl = $compiler->getDwoo()->templateFactory($resource, $identifier);
+
+	if ($tpl === null) {
+		throw new Dwoo_Compilation_Exception($compiler, 'Load Templates : Resource "'.$resource.':'.$identifier.'" not found.');
+	} elseif ($tpl === false) {
+		throw new Dwoo_Compilation_Exception($compiler, 'Load Templates : Resource "'.$resource.'" does not support includes.');
+	}
+
+	$cmp = clone $compiler;
+	$cmp->compile($compiler->getDwoo(), $tpl);
+	foreach ($cmp->getTemplatePlugins() as $template=>$args) {
+		$compiler->addTemplatePlugin($template, $args['params'], $args['uuid'], $args['body']);
+	}
+	foreach ($cmp->getUsedPlugins() as $plugin=>$type) {
+		$compiler->addUsedPlugin($plugin, $type);
+	}
+
+	$out = '\'\';// checking for modification in '.$resource.':'.$identifier."\r\n";
+	
+	$modCheck = $tpl->getIsModifiedCode();
+	
+	if ($modCheck) {
+		$out .= 'if (!('.$modCheck.')) { ob_end_clean(); return false; }';
+	} else {
+		$out .= 'try {
+	$tpl = $this->templateFactory("'.$resource.'", "'.$identifier.'");
+} catch (Dwoo_Exception $e) {
+	$this->triggerError(\'Load Templates : Resource <em>'.$resource.'</em> was not added to Dwoo, can not extend <em>'.$identifier.'</em>\', E_USER_WARNING);
+}
+if ($tpl === null)
+	$this->triggerError(\'Load Templates : Resource "'.$resource.':'.$identifier.'" was not found.\', E_USER_WARNING);
+elseif ($tpl === false)
+	$this->triggerError(\'Load Templates : Resource "'.$resource.'" does not support extends.\', E_USER_WARNING);
+if ($tpl->getUid() != "'.$tpl->getUid().'") { ob_end_clean(); return false; }';
+	}
+	
+	return $out;
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/lower.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/lower.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/lower.php	(revision 825)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Makes the input string lower cased
+ * <pre>
+ *  * value : the string to process
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_lower_compile(Dwoo_Compiler $compiler, $value)
+{
+	return 'mb_strtolower((string) '.$value.', $this->charset)';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/mailto.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/mailto.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/mailto.php	(revision 825)
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * Outputs a mailto link with optional spam-proof (okay probably not) encoding
+ * <pre>
+ *  * address : target email address
+ *  * text : display text to show for the link, defaults to the address if not provided
+ *  * subject : the email subject
+ *  * encode : one of the available encoding (none, js, jscharcode or hex)
+ *  * cc : address(es) to carbon copy, comma separated
+ *  * bcc : address(es) to blind carbon copy, comma separated
+ *  * newsgroups : newsgroup(s) to post to, comma separated
+ *  * followupto : address(es) to follow up, comma separated
+ *  * extra : additional attributes to add to the &lt;a&gt; tag
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_mailto(Dwoo_Core $dwoo, $address, $text=null, $subject=null, $encode=null, $cc=null, $bcc=null, $newsgroups=null, $followupto=null, $extra=null)
+{
+	if (empty($address)) {
+		return '';
+	}
+	if (empty($text)) {
+		$text = $address;
+	}
+
+	// build address string
+	$address .= '?';
+
+	if (!empty($subject)) {
+		$address .= 'subject='.rawurlencode($subject).'&';
+	}
+	if (!empty($cc)) {
+		$address .= 'cc='.rawurlencode($cc).'&';
+	}
+	if (!empty($bcc)) {
+		$address .= 'bcc='.rawurlencode($bcc).'&';
+	}
+	if (!empty($newsgroups)) {
+		$address .= 'newsgroups='.rawurlencode($newsgroups).'&';
+	}
+	if (!empty($followupto)) {
+		$address .= 'followupto='.rawurlencode($followupto).'&';
+	}
+
+	$address = rtrim($address, '?&');
+
+	// output
+	switch($encode)
+	{
+
+	case 'none':
+	case null:
+		return '<a href="mailto:'.$address.'" '.$extra.'>'.$text.'</a>';
+
+	case 'js':
+	case 'javascript':
+		$str = 'document.write(\'<a href="mailto:'.$address.'" '.$extra.'>'.$text.'</a>\');';
+		$len = strlen($str);
+
+		$out = '';
+		for ($i=0; $i<$len; $i++) {
+			$out .= '%'.bin2hex($str[$i]);
+		}
+		return '<script type="text/javascript">eval(unescape(\''.$out.'\'));</script>';
+
+		break;
+	case 'javascript_charcode':
+	case 'js_charcode':
+	case 'jscharcode':
+	case 'jschar':
+		$str = '<a href="mailto:'.$address.'" '.$extra.'>'.$text.'</a>';
+		$len = strlen($str);
+
+		$out = '<script type="text/javascript">'."\n<!--\ndocument.write(String.fromCharCode(";
+		for ($i=0; $i<$len; $i++) {
+			$out .= ord($str[$i]).',';
+		}
+		return rtrim($out, ',') . "));\n-->\n</script>\n";
+
+		break;
+
+	case 'hex':
+		if (strpos($address, '?') !== false) {
+			return $dwoo->triggerError('Mailto: Hex encoding is not possible with extra attributes, use one of : <em>js, jscharcode or none</em>.', E_USER_WARNING);
+		}
+
+		$out = '<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;';
+		$len = strlen($address);
+		for ($i=0; $i<$len; $i++) {
+			if (preg_match('#\w#', $address[$i])) {
+				$out .= '%'.bin2hex($address[$i]);
+			} else {
+				$out .= $address[$i];
+			}
+		}
+		$out .= '" '.$extra.'>';
+		$len = strlen($text);
+		for ($i=0; $i<$len; $i++) {
+			$out .= '&#x'.bin2hex($text[$i]);
+		}
+		return $out.'</a>';
+
+	default:
+		return $dwoo->triggerError('Mailto: <em>encode</em> argument is invalid, it must be one of : <em>none (= no value), js, js_charcode or hex</em>', E_USER_WARNING);
+
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/math.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/math.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/math.php	(revision 825)
@@ -0,0 +1,130 @@
+<?php
+
+/**
+ * Computes a mathematical equation
+ * <pre>
+ *  * equation : the equation to compute, it can include normal variables with $foo or special math variables without the dollar sign
+ *  * format : output format, see {@link http://php.net/sprintf} for details
+ *  * assign : if set, the output is assigned into the given variable name instead of being output
+ *  * rest : all math specific variables that you use must be defined, see the example
+ * </pre>
+ * Example :
+ *
+ * <code>
+ * {$c=2}
+ * {math "(a+b)*$c/4" a=3 b=5}
+ *
+ * output is : 4 ( = (3+5)*2/4)
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_math_compile(Dwoo_Compiler $compiler, $equation, $format='', $assign='', array $rest=array())
+{
+	/**
+	 * Holds the allowed function, characters, operators and constants
+	 */
+	$allowed = array
+	(
+		'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+		'+', '-', '/', '*', '.', ' ', '<<', '>>', '%', '&', '^', '|', '~',
+		'abs(', 'ceil(', 'floor(', 'exp(', 'log10(',
+		'cos(', 'sin(', 'sqrt(', 'tan(',
+		'M_PI', 'INF', 'M_E',
+	);
+
+	/**
+	 * Holds the functions that can accept multiple arguments
+	 */
+	$funcs = array
+	(
+ 		'round(', 'log(', 'pow(',
+		'max(', 'min(', 'rand(',
+ 	);
+
+	$equation = $equationSrc = str_ireplace(array('pi', 'M_PI()', 'inf', ' e '), array('M_PI', 'M_PI', 'INF', ' M_E '), $equation);
+
+	$delim = $equation[0];
+	$open = $delim.'.';
+	$close = '.'.$delim;
+	$equation = substr($equation, 1, -1);
+	$out = '';
+	$ptr = 1;
+	$allowcomma = 0;
+	while (strlen($equation) > 0) {
+		$substr = substr($equation, 0, $ptr);
+		if (array_search($substr, $allowed) !== false) {
+			// allowed string
+			$out.=$substr;
+			$equation = substr($equation, $ptr);
+			$ptr = 0;
+		} elseif (array_search($substr, $funcs) !== false) {
+			// allowed func
+			$out.=$substr;
+			$equation = substr($equation, $ptr);
+			$ptr = 0;
+			$allowcomma++;
+			if ($allowcomma === 1) {
+				$allowed[] = ',';
+			}
+		} elseif (isset($rest[$substr])) {
+			// variable
+			$out.=$rest[$substr];
+			$equation = substr($equation, $ptr);
+			$ptr = 0;
+		} elseif ($substr === $open) {
+			// pre-replaced variable
+			preg_match('#.*\((?:[^()]*?|(?R))\)'.str_replace('.', '\\.', $close).'#', substr($equation, 2), $m);
+			if (empty($m)) {
+				preg_match('#.*?'.str_replace('.', '\\.', $close).'#', substr($equation, 2), $m);
+			}
+			$out.=substr($m[0], 0, -2);
+			$equation = substr($equation, strlen($m[0])+2);
+			$ptr = 0;
+		} elseif ($substr==='(') {
+			// opening parenthesis
+			if ($allowcomma>0) {
+				$allowcomma++;
+			}
+
+			$out.=$substr;
+			$equation = substr($equation, $ptr);
+			$ptr = 0;
+		} elseif ($substr===')') {
+			// closing parenthesis
+			if ($allowcomma>0) {
+				$allowcomma--;
+				if ($allowcomma===0) {
+					array_pop($allowed);
+				}
+			}
+
+			$out.=$substr;
+			$equation = substr($equation, $ptr);
+			$ptr = 0;
+		} elseif ($ptr >= strlen($equation)) {
+			// parse error if we've consumed the entire equation without finding anything valid
+			throw new Dwoo_Compilation_Exception($compiler, 'Math : Syntax error or variable undefined in equation '.$equationSrc.' at '.$substr);
+			return;
+		} else {
+			// nothing special, advance
+			$ptr++;
+		}
+	}
+	if ($format !== '\'\'') {
+		$out = 'sprintf('.$format.', '.$out.')';
+	}
+	if ($assign !== '\'\'') {
+		return '($this->assignInScope('.$out.', '.$assign.'))';
+	}
+	return '('.$out.')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/nl2br.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/nl2br.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/nl2br.php	(revision 825)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Converts line breaks into <br /> tags
+ * <pre>
+ *  * value : the string to process
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_nl2br_compile(Dwoo_Compiler $compiler, $value)
+{
+	return 'nl2br((string) '.$value.')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/optional.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/optional.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/optional.php	(revision 825)
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Prints out a variable without any notice if it doesn't exist
+ *
+ * <pre>
+ *  * value : the variable to print
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.1
+ * @date       2009-10-18
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_optional_compile(Dwoo_Compiler $compiler, $value)
+{
+	return $value;
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/regex_replace.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/regex_replace.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/regex_replace.php	(revision 825)
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Replaces the search string by the replace string using regular expressions
+ * <pre>
+ *  * value : the string to search into
+ *  * search : the string to search for, must be a complete regular expression including delimiters
+ *  * replace : the string to use as a replacement, must be a complete regular expression including delimiters
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_regex_replace(Dwoo_Core $dwoo, $value, $search, $replace)
+{
+	$search = (array) $search;
+	$cnt = count($search);
+
+	for ($i = 0; $i < $cnt; $i++) {
+		// Credits for this to Monte Ohrt who made smarty's regex_replace modifier
+		if (($pos = strpos($search[$i], "\0")) !== false) {
+			$search[$i] = substr($search[$i], 0, $pos);
+		}
+
+		if (preg_match('#[a-z\s]+$#is', $search[$i], $m) && (strpos($m[0], 'e') !== false)) {
+			$search[$i] = substr($search[$i], 0, -strlen($m[0])) . str_replace(array('e', ' '), '', $m[0]);
+		}
+	}
+
+	return preg_replace($search, $replace, $value);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/replace.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/replace.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/replace.php	(revision 825)
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * Replaces the search string by the replace string
+ * <pre>
+ *  * value : the string to search into
+ *  * search : the string to search for
+ *  * replace : the string to use as a replacement
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_replace_compile(Dwoo_Compiler $compiler, $value, $search, $replace, $case_sensitive = true)
+{
+	if ($case_sensitive == 'false' || (bool)$case_sensitive === false) {
+		return 'str_ireplace('.$search.', '.$replace.', '.$value.')';
+	} else {
+		return 'str_replace('.$search.', '.$replace.', '.$value.')';
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/return.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/return.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/return.php	(revision 825)
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * Inserts another template into the current one
+ * <pre>
+ *  * file : the resource name of the template
+ *  * cache_time : cache length in seconds
+ *  * cache_id : cache identifier for the included template
+ *  * compile_id : compilation identifier for the included template
+ *  * data : data to feed into the included template, it can be any array and will default to $_root (the current data)
+ *  * assign : if set, the output of the included template will be saved in this variable instead of being output
+ *  * rest : any additional parameter/value provided will be added to the data array
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_return_compile(Dwoo_Compiler $compiler, array $rest = array())
+{
+    $out = array();
+    foreach ($rest as $var => $val) {
+        $out[] = '$this->setReturnValue('.var_export($var, true).', '.$val.')';
+    }
+    return '('.implode('.', $out).')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/reverse.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/reverse.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/reverse.php	(revision 825)
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * Reverses a string or an array
+ * <pre>
+ *  * value : the string or array to reverse
+ *  * preserve_keys : if value is an array and this is true, then the array keys are left intact
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_reverse(Dwoo_Core $dwoo, $value, $preserve_keys=false)
+{
+	if (is_array($value)) {
+		return array_reverse($value, $preserve_keys);
+	} elseif(($charset=$dwoo->getCharset()) === 'iso-8859-1') {
+		return strrev((string) $value);
+	} else {
+	    $strlen = mb_strlen($value);
+	    $out = '';
+	    while ($strlen--) {
+	        $out .= mb_substr($value, $strlen, 1, $charset);
+	    }
+		return $out;
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/safe.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/safe.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/safe.php	(revision 825)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Marks the variable as safe and removes the auto-escape function, only useful if you turned auto-escaping on
+ * <pre>
+ *  * var : the variable to pass through untouched
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_safe_compile(Dwoo_Compiler $compiler, $var)
+{
+	return preg_replace('#\(is_string\(\$tmp=(.+)\) \? htmlspecialchars\(\$tmp, ENT_QUOTES, \$this->charset\) : \$tmp\)#', '$1', $var);
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/spacify.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/spacify.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/spacify.php	(revision 825)
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Adds spaces (or the given character(s)) between every character of a string
+ * <pre>
+ *  * value : the string to process
+ *  * space_char : the character(s) to insert between each character
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_spacify_compile(Dwoo_Compiler $compiler, $value, $space_char=' ')
+{
+	return 'implode('.$space_char.', str_split('.$value.', 1))';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/string_format.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/string_format.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/string_format.php	(revision 825)
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * Formats a string using the sprintf function
+ * <pre>
+ *  * value : the string to format
+ *  * format : the format to use, see {@link http://php.net/sprintf} for details
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_string_format_compile(Dwoo_Compiler $compiler, $value, $format)
+{
+	return 'sprintf('.$format.','.$value.')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/strip_tags.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/strip_tags.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/strip_tags.php	(revision 825)
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Removes all html tags
+ * <pre>
+ *  * value : the string to process
+ *  * addspace : if true, a space is added in place of every removed tag
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_strip_tags_compile(Dwoo_Compiler $compiler, $value, $addspace=true)
+{
+	if ($addspace==='true') {
+		return "preg_replace('#<[^>]*>#', ' ', $value)";
+	} else {
+		return "strip_tags($value)";
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/tif.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/tif.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/tif.php	(revision 825)
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * Ternary if operation
+ *
+ * It evaluates the first argument and returns the second if it's true, or the third if it's false
+ * <pre>
+ *  * rest : you can not use named parameters to call this, use it either with three arguments in the correct order (expression, true result, false result) or write it as in php (expression ? true result : false result)
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_tif_compile(Dwoo_Compiler $compiler, array $rest, array $tokens)
+{
+	// load if plugin
+	if (!class_exists('Dwoo_Plugin_if', false)) {
+		try {
+			$compiler->getDwoo()->getLoader()->loadPlugin('if');
+		} catch (Exception $e) {
+			throw new Dwoo_Compilation_Exception($compiler, 'Tif: the if plugin is required to use Tif');
+		}
+	}
+
+	if (count($rest) == 1) {
+		return $rest[0];
+	}
+
+	// fetch false result and remove the ":" if it was present
+	$falseResult = array_pop($rest);
+
+	if (trim(end($rest), '"\'') === ':') {
+		// remove the ':' if present
+		array_pop($rest);
+	} elseif (trim(end($rest), '"\'') === '?' || count($rest) === 1) {
+		if ($falseResult === '?' || $falseResult === ':') {
+			throw new Dwoo_Compilation_Exception($compiler, 'Tif: incomplete tif statement, value missing after '.$falseResult);
+		}
+		// there was in fact no false result provided, so we move it to be the true result instead
+		$trueResult = $falseResult;
+		$falseResult = "''";
+	}
+
+	// fetch true result if needed
+	if (!isset($trueResult)) {
+		$trueResult = array_pop($rest);
+		// no true result provided so we use the expression arg
+		if ($trueResult === '?') {
+			$trueResult = true;
+		}
+	}
+
+	// remove the '?' if present
+	if (trim(end($rest), '"\'') === '?') {
+		array_pop($rest);
+	}
+
+	// check params were correctly provided
+	if (empty($rest) || $trueResult === null || $falseResult === null) {
+		throw new Dwoo_Compilation_Exception($compiler, 'Tif: you must provide three parameters serving as <expression> ? <true value> : <false value>');
+	}
+
+	// parse condition
+	$condition = Dwoo_Plugin_if::replaceKeywords($rest, $tokens, $compiler);
+
+	return '(('.implode(' ', $condition).') ? '.($trueResult===true ? implode(' ', $condition) : $trueResult).' : '.$falseResult.')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/truncate.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/truncate.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/truncate.php	(revision 825)
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * Truncates a string at the given length
+ * <pre>
+ *  * value : text to truncate
+ *  * length : the maximum length for the string
+ *  * etc : the characters that are added to show that the string was cut off
+ *  * break : if true, the string will be cut off at the exact length, instead of cutting at the nearest space
+ *  * middle : if true, the string will contain the beginning and the end, and the extra characters will be removed from the middle
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.1.0
+ * @date       2009-07-18
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_truncate(Dwoo_Core $dwoo, $value, $length=80, $etc='...', $break=false, $middle=false)
+{
+	if ($length == 0) {
+		return '';
+	}
+
+	$value = (string) $value;
+	$etc = (string) $etc;
+	$length = (int) $length;
+
+	if (strlen($value) < $length) {
+		return $value;
+	}
+
+	$length = max($length - strlen($etc), 0);
+	if ($break === false && $middle === false) {
+		$value = preg_replace('#\s+(\S*)?$#', '', substr($value, 0, $length+1));
+	}
+	if ($middle === false) {
+		return substr($value, 0, $length) . $etc;
+	}
+	return substr($value, 0, ceil($length/2)) . $etc . substr($value, -floor($length/2));
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/upper.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/upper.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/upper.php	(revision 825)
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * Makes a string uppercased
+ * <pre>
+ *  * value : the text to uppercase
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_upper_compile(Dwoo_Compiler $compiler, $value)
+{
+	return 'mb_strtoupper((string) '.$value.', $this->charset)';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/whitespace.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/whitespace.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/whitespace.php	(revision 825)
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * Replaces all white-space characters with the given string
+ * <pre>
+ *  * value : the text to process
+ *  * with : the replacement string, note that any number of consecutive white-space characters will be replaced by a single replacement string
+ * </pre>
+ * Example :
+ *
+ * <code>
+ * {"a    b  c		d
+ *
+ * e"|whitespace}
+ *
+ * results in : a b c d e
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_whitespace_compile(Dwoo_Compiler $compiler, $value, $with=' ')
+{
+	return "preg_replace('#\s+#'.(strcasecmp(\$this->charset, 'utf-8')===0?'u':''), $with, $value)";
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/wordwrap.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/wordwrap.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/functions/wordwrap.php	(revision 825)
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * Wraps a text at the given line length
+ * <pre>
+ *  * value : the text to wrap
+ *  * length : maximum line length
+ *  * break : the character(s) to use to break the line
+ *  * cut : if true, the line is cut at the exact length instead of breaking at the nearest space
+ * </pre>
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_wordwrap_compile(Dwoo_Compiler $compiler, $value, $length=80, $break="\n", $cut=false)
+{
+	return 'wordwrap('.$value.','.$length.','.$break.','.$cut.')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/helper.array.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/helper.array.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/helper.array.php	(revision 825)
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Builds an array with all the provided variables, use named parameters to make an associative array
+ * <pre>
+ *  * rest : any number of variables, strings or anything that you want to store in the array
+ * </pre>
+ * Example :
+ *
+ * <code>
+ * {array(a, b, c)} results in array(0=>'a', 1=>'b', 2=>'c')
+ * {array(a=foo, b=5, c=array(4,5))} results in array('a'=>'foo', 'b'=>5, 'c'=>array(0=>4, 1=>5))
+ * </code>
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+function Dwoo_Plugin_array_compile(Dwoo_Compiler $compiler, array $rest=array())
+{
+	$out = array();
+	foreach ($rest as $key => $value) {
+		if (!is_numeric($key) && !strstr($key, '$this->scope')) {
+			$key = "'".$key."'";
+		}
+		$out[] = $key.'=>'.$value;
+	}
+	return 'array('.implode(', ', $out).')';
+}
Index: /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/processors/pre.smarty_compat.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/processors/pre.smarty_compat.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/dwoo/plugins/builtin/processors/pre.smarty_compat.php	(revision 825)
@@ -0,0 +1,73 @@
+<?php
+
+/**
+ * Performs some template conversions to allow smarty templates to be used by
+ * the Dwoo compiler.
+ *
+ * This software is provided 'as-is', without any express or implied warranty.
+ * In no event will the authors be held liable for any damages arising from the use of this software.
+ *
+ * @author     Jordi Boggiano <j.boggiano@seld.be>
+ * @copyright  Copyright (c) 2008, Jordi Boggiano
+ * @license    http://dwoo.org/LICENSE   Modified BSD License
+ * @link       http://dwoo.org/
+ * @version    1.0.0
+ * @date       2008-10-23
+ * @package    Dwoo
+ */
+class Dwoo_Processor_smarty_compat extends Dwoo_Processor
+{
+	public function process($input)
+	{
+		list($l, $r) = $this->compiler->getDelimiters();
+
+		$rl = preg_quote($l,'/');
+		$rr = preg_quote($r,'/');
+		$sectionParam = '(?:(name|loop|start|step|max|show)\s*=\s*(\S+))?\s*';
+		$input = preg_replace_callback('/'.$rl.'\s*section '.str_repeat($sectionParam, 6).'\s*'.$rr.'(.+?)(?:'.$rl.'\s*sectionelse\s*'.$rr.'(.+?))?'.$rl.'\s*\/section\s*'.$rr.'/is', array($this, 'convertSection'), $input);
+		$input = str_replace('$smarty.section.', '$smarty.for.', $input);
+
+		$smarty = array
+		(
+			'/'.$rl.'\s*ldelim\s*'.$rr.'/',
+			'/'.$rl.'\s*rdelim\s*'.$rr.'/',
+			'/'.$rl.'\s*\$smarty\.ldelim\s*'.$rr.'/',
+			'/'.$rl.'\s*\$smarty\.rdelim\s*'.$rr.'/',
+			'/\$smarty\./',
+			'/'.$rl.'\s*php\s*'.$rr.'/',
+			'/'.$rl.'\s*\/php\s*'.$rr.'/',
+			'/\|(@?)strip(\||'.$rr.')/',
+			'/'.$rl.'\s*sectionelse\s*'.$rr.'/',
+		);
+
+		$dwoo = array
+		(
+			'\\'.$l,
+			$r,
+			'\\'.$l,
+			$r,
+			'$dwoo.',
+			'<?php ',
+			' ?>',
+			'|$1whitespace$2',
+			$l.'else'.$r,
+		);
+
+		if (preg_match('{\|@([a-z][a-z0-9_]*)}i', $input, $matches)) {
+			trigger_error('The Smarty Compatibility Module has detected that you use |@'.$matches[1].' in your template, this might lead to problems as Dwoo interprets the @ operator differently than Smarty, see http://wiki.dwoo.org/index.php/Syntax#The_.40_Operator', E_USER_NOTICE);
+		}
+
+		return preg_replace($smarty, $dwoo, $input);
+	}
+
+	protected function convertSection(array $matches)
+	{
+		$params = array();
+		$index = 1;
+		while (!empty($matches[$index]) && $index < 13) {
+			$params[$matches[$index]] = $matches[$index+1];
+			$index += 2;
+		}
+		return str_replace('['.trim($params['name'], '"\'').']', '[$'.trim($params['name'], '"\'').']', $matches[0]);
+	}
+}
Index: /branches/1.0/web/addons/job_monarch/footer.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/footer.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/footer.php	(revision 825)
@@ -0,0 +1,44 @@
+<?php
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * SVN $Id$
+ */
+
+$tpl = new TemplatePower( "templates/footer.tpl" );
+$tpl->prepare();
+$tpl->assign("webfrontend-version",$version["webfrontend"]);
+//$tpl->assign("togaweb-version", "0.1.1");
+//$tpl->assign("togaarch-version", "0.1.1");
+//$tpl->assign("togaplug-version", "0.1.1");
+
+if ($version["gmetad"]) {
+   $tpl->assign("webbackend-component", "gmetad");
+   $tpl->assign("webbackend-version",$version["gmetad"]);
+}
+elseif ($version["gmond"]) {
+   $tpl->assign("webbackend-component", "gmond");
+   $tpl->assign("webbackend-version", $version["gmond"]);
+}
+
+$tpl->assign("parsetime", sprintf("%.4f", $parsetime) . "s");
+
+$tpl->printToScreen();
+?>
Index: /branches/1.0/web/addons/job_monarch/graph.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/graph.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/graph.php	(revision 825)
@@ -0,0 +1,993 @@
+<?php
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * SVN $Id$
+ */
+
+global $rrds, $start, $r, $conf, $m;
+
+include "./libtoga.php";
+
+# Graph specific variables
+$size = escapeshellcmd( rawurldecode( $_GET["z"] ));
+$graph = escapeshellcmd( rawurldecode( $_GET["g"] ));
+$grid = escapeshellcmd( rawurldecode( $_GET["G"] ));
+$self = escapeshellcmd( rawurldecode( $_GET["me"] ));
+$max = escapeshellcmd( rawurldecode( $_GET["x"] ));
+$min = escapeshellcmd( rawurldecode( $_GET["n"] ));
+$value = escapeshellcmd( rawurldecode( $_GET["v"] ));
+$load_color = escapeshellcmd( rawurldecode( $_GET["l"] ));
+$vlabel = escapeshellcmd( rawurldecode( $_GET["vl"] ));
+$j_title = escapeshellcmd( rawurldecode( $_GET["t"] ));
+$hostname = escapeshellcmd( rawurldecode( $_GET["h"] ));
+$range = escapeshellcmd( rawurldecode( $_GET["r"] ));
+
+if( strpos( $size, 'overview' ) )
+{
+    $my_dir = getcwd();
+
+    global $context;
+
+    $context = 'host';
+
+    chdir( $GANGLIA_PATH );
+
+    include "./ganglia.php";
+    include "./get_ganglia.php";
+
+    chdir( $my_dir );
+}
+
+if ( !empty( $_GET ) ) 
+{
+        extract( $_GET );
+}
+
+$sourcetime = $st;
+
+
+$cluster = $c;
+$metricname = ($g) ? $g : $m;
+
+# Assumes we have a $start variable (set in get_context.php).
+if ($size == "small") 
+{
+    $height = 40;
+    $width = 130;
+} 
+else if ($size == "medium") 
+{
+    $height = 75;
+    $width = 300;
+} 
+else if ($size == "overview-medium") 
+{
+    $height = 75;
+    $width = 300;
+} 
+else 
+{
+    $height = 100;
+    $width = 400;
+}
+
+// RB: Perform some formatting/spacing magic.. tinkered to fit
+//
+if ($size == 'small') {
+   $eol1 = '\\l';
+   $space1 = ' ';
+   $space2 = '         ';
+} else if ($size == 'medium') {
+   $eol1 = '';
+   $space1 = '';
+   $space2 = '';
+   $extras = ' --font LEGEND:7 ';
+} else if ($size == 'overview-medium') {
+   $eol1   = '';
+   $space1 = '';
+   $space2 = '';
+   $extras = ' --font LEGEND:7 ';
+} else if ($size == 'large') {
+   $eol1 = '';
+   $space1 = '       ';
+   $space2 = '       ';
+} else if ($size == 'xlarge') {
+   $eol1 = '';
+   $space1 = '             ';
+   $space2 = '             ';
+} else if ($size == 'mobile') {
+   $eol1 = '';
+   $space1 = ' ';
+   $space2 = '';
+}
+
+$jobstart_color = "3AE302";
+$jobstop_color = "F5164A";
+
+if($command) 
+{
+      $command = '';
+}
+
+$graph = $metricname;
+
+$rrd_dirs = Array();
+
+if (isset($graph)) 
+{
+    $series = '';
+    if( $size == 'overview-medium' )
+    {
+        $rrd_dirs[] = $conf['rrds'] . '/' . $cluster .'/'. $hostname;
+    }
+    else
+    {
+        $trd = new TarchRrdGraph( $cluster, $hostname );
+        $rrd_dirs = $trd->getRrdDirs( $period_start, $period_stop );
+
+    }
+
+    if($graph == "cpu_report") 
+    {
+
+        $title = "CPU: $hostname";
+
+        $upper_limit = "--upper-limit 100 --rigid";
+        $lower_limit = "--lower-limit 0";
+
+        $vertical_label = "--vertical-label Percent ";
+
+        $def_nr = 0;
+
+        foreach( $rrd_dirs as $rrd_dir ) 
+        {
+
+            $series .= "DEF:'cpu_user${def_nr}'='${rrd_dir}/cpu_user.rrd':'sum':AVERAGE "
+                ."DEF:'cpu_nice${def_nr}'='${rrd_dir}/cpu_nice.rrd':'sum':AVERAGE "
+                ."DEF:'cpu_system${def_nr}'='${rrd_dir}/cpu_system.rrd':'sum':AVERAGE "
+                ."DEF:'cpu_idle${def_nr}'='${rrd_dir}/cpu_idle.rrd':'sum':AVERAGE "
+                ."DEF:'cpu_wio${def_nr}'='${rrd_dir}/cpu_wio.rrd':'sum':AVERAGE ";
+
+            $report_names = array( "user", "nice", "system", "wio", "idle" );
+
+            if( $conf['graphreport_stats'] )
+            {
+                foreach( $report_names as $r )
+                {
+                    $series .= "CDEF:cpu_${r}${def_nr}_nonans=cpu_${r}${def_nr},UN,0,cpu_${r}${def_nr},IF ";
+                }
+            }
+
+            $def_nr++;
+        }
+
+        if( $conf['graphreport_stats'] )
+        {
+            $s_last     = $def_nr - 1;
+            $user_sum   = "CDEF:cpu_user=cpu_user0_nonans";
+            $nice_sum   = "CDEF:cpu_nice=cpu_nice0_nonans";
+            $system_sum = "CDEF:cpu_system=cpu_system0_nonans";
+            $wio_sum    = "CDEF:cpu_wio=cpu_wio0_nonans";
+            $idle_sum   = "CDEF:cpu_idle=cpu_idle0_nonans";
+
+            if( $s_last > 1 )
+            {
+                foreach (range(1, ($s_last)) as $print_nr ) 
+                {
+                    $user_sum   .= ",cpu_user{$print_nr}_nonans,+";
+                    $nice_sum   .= ",cpu_nice{$print_nr}_nonans,+";
+                    $system_sum .= ",cpu_system{$print_nr}_nonans,+";
+                    $wio_sum    .= ",cpu_wio{$print_nr}_nonans,+";
+                    $idle_sum   .= ",cpu_idle{$print_nr}_nonans,+";
+                }
+            }
+
+            $user_sum .= " ";
+            $nice_sum .= " ";
+            $system_sum .= " ";
+            $wio_sum .= " ";
+            $idle_sum .= " ";
+
+            $series .= $user_sum . $nice_sum . $system_sum . $wio_sum . $idle_sum;
+
+
+            $r_count = 0;
+
+            foreach( $report_names as $r )
+            {
+                $legend_str = ucfirst( $r );
+
+                if( $r_count == 0 )
+                {
+                    $graph_str = "AREA";
+                }
+                else
+                {
+                    $graph_str = "STACK";
+                }
+                foreach (range(0, ($s_last)) as $print_nr ) 
+                {
+
+                    $series .= "${graph_str}:'cpu_${r}${print_nr}'#".$conf['cpu_'.${r}.'_color'].":'${legend_str}\g' ";
+                    $legend_str = '';
+                }
+
+                $series .= "VDEF:'${r}_last'=cpu_${r},LAST ";
+                $series .= "VDEF:'${r}_min'=cpu_${r},MINIMUM ";
+                $series .= "VDEF:'${r}_avg'=cpu_${r},AVERAGE ";
+                $series .= "VDEF:'${r}_max'=cpu_${r},MAXIMUM ";
+
+                $spacefill = '';
+
+                $spacesize = 6-strlen($r);
+                foreach ( range( 0, $spacesize ) as $whatever )
+                {
+                    $spacefill .= ' ';
+                }
+
+                $series .= "GPRINT:'${r}_last':'${spacefill}Last\:%6.1lf%%' "
+                        . "GPRINT:'${r}_min':'${space1}Min\:%6.1lf%%${eol1}' "
+                        . "GPRINT:'${r}_avg':'${space2}Avg\:%6.1lf%%' "
+                        . "GPRINT:'${r}_max':'${space1}Max\:%6.1lf%%\\l' ";
+                        
+                $r_count = $r_count + 1;
+            }
+        }
+        else
+        {
+            $series .= "AREA:'cpu_user${def_nr}'#".$conf['cpu_user_color']."${user_str} "
+                    ."STACK:'cpu_nice${def_nr}'#".$conf['cpu_nice_color']."${nice_str} "
+                    ."STACK:'cpu_system${def_nr}'#".$conf['cpu_system_color']."${system_str} "
+                    ."STACK:'cpu_wio${def_nr}'#".$conf['cpu_wio_color']."${wio_str} "
+                    ."STACK:'cpu_idle${def_nr}'#".$conf['cpu_idle_color']."${idle_str} ";
+        }
+
+    } 
+    else if ($graph == "job_report") 
+    {
+        $title = "Jobs";
+
+        $lower_limit = "--lower-limit 0 --rigid";
+        $vertical_label = "--vertical-label Jobs";
+
+        $def_nr = 0;
+
+        $rrd_dir = $conf['rrds'] . "/$clustername/$hostname/";
+
+        $rj_rrd    = $rrd_dir . "zplugin_monarch_rj.rrd";
+        $qj_rrd    = $rrd_dir . "zplugin_monarch_qj.rrd";
+
+        $sorted_hosts    = array();
+        $sorted_hosts[]  = $rjqj_host;
+
+        $rj_str = ":'Running Jobs'";
+        $qj_str = ":'Queued Jobs'";
+
+        $series .= "DEF:'running_jobs'='${rj_rrd}':'sum':AVERAGE "
+            ."DEF:'queued_jobs'='${qj_rrd}':'sum':AVERAGE ";
+
+        
+        $series .= "LINE3:'running_jobs'#ff0000${rj_str} ";
+
+        if ( $conf['graphreport_stats'] ) 
+        {
+            $series .= "CDEF:running_pos=running_jobs,0,INF,LIMIT "
+                    . "VDEF:running_last=running_pos,LAST "
+                    . "VDEF:running_min=running_pos,MINIMUM "
+                    . "VDEF:running_avg=running_pos,AVERAGE "
+                    . "VDEF:running_max=running_pos,MAXIMUM "
+                    . "GPRINT:'running_last':' ${space1}Last\:%5.0lf' "
+                    . "GPRINT:'running_min':'${space1}Min\:%5.0lf${eol1}' "
+                    . "GPRINT:'running_avg':'${space2}Avg\:%5.0lf' "
+                    . "GPRINT:'running_max':'${space1}Max\:%5.0lf\\l' ";
+        }
+
+        $series .= "LINE3:'queued_jobs'#999999${qj_str} ";
+
+        if ( $conf['graphreport_stats'] ) 
+        {
+            $series .= "CDEF:queued_pos=queued_jobs,0,INF,LIMIT "
+                    . "VDEF:queued_last=queued_pos,LAST "
+                    . "VDEF:queued_min=queued_pos,MINIMUM "
+                    . "VDEF:queued_avg=queued_pos,AVERAGE "
+                    . "VDEF:queued_max=queued_pos,MAXIMUM "
+                    . "GPRINT:'queued_last':'  ${space1}Last\:%5.0lf' "
+                    . "GPRINT:'queued_min':'${space1}Min\:%5.0lf${eol1}' "
+                    . "GPRINT:'queued_avg':'${space2}Avg\:%5.0lf' "
+                    . "GPRINT:'queued_max':'${space1}Max\:%5.0lf\\l' ";
+        }
+    } 
+    else if ($graph == "mem_report") 
+    {
+        $title = "Memory: $hostname";
+
+        $lower_limit = "--lower-limit 0 --rigid";
+        $extras .= "--base 1024";
+        $vertical_label = "--vertical-label Bytes";
+
+        $def_nr = 0;
+
+        foreach( $rrd_dirs as $rrd_dir ) 
+        {
+            $series .= "DEF:'mem_total${def_nr}'='${rrd_dir}/mem_total.rrd':'sum':AVERAGE "
+                ."CDEF:'bmem_total${def_nr}'=mem_total${def_nr},1024,* "
+                ."DEF:'mem_shared${def_nr}'='${rrd_dir}/mem_shared.rrd':'sum':AVERAGE "
+                ."CDEF:'bmem_shared${def_nr}'=mem_shared${def_nr},1024,* "
+                ."DEF:'mem_free${def_nr}'='${rrd_dir}/mem_free.rrd':'sum':AVERAGE "
+                ."CDEF:'bmem_free${def_nr}'=mem_free${def_nr},1024,* "
+                ."DEF:'mem_cached${def_nr}'='${rrd_dir}/mem_cached.rrd':'sum':AVERAGE "
+                ."CDEF:'bmem_cached${def_nr}'=mem_cached${def_nr},1024,* "
+                ."DEF:'mem_buffer${def_nr}'='${rrd_dir}/mem_buffers.rrd':'sum':AVERAGE "
+                ."CDEF:'bmem_buffer${def_nr}'=mem_buffer${def_nr},1024,* "
+                ."CDEF:'bmem_used${def_nr}'='bmem_total${def_nr}','bmem_shared${def_nr}',-,'bmem_free${def_nr}',-,'bmem_cached${def_nr}',-,'bmem_buffer${def_nr}',- "
+                ."DEF:'swap_total${def_nr}'='${rrd_dir}/swap_total.rrd':'sum':AVERAGE "
+                ."DEF:'swap_free${def_nr}'='${rrd_dir}/swap_free.rrd':'sum':AVERAGE "
+                ."CDEF:'bmem_swap${def_nr}'='swap_total${def_nr}','swap_free${def_nr}',-,1024,* ";
+
+            $report_names = array( "used", "shared", "cached", "buffer", "swap", "total" );
+
+            if( $conf['graphreport_stats'] )
+            {
+                foreach( $report_names as $r )
+                {
+                    $series .= "CDEF:bmem_${r}${def_nr}_nonans=bmem_${r}${def_nr},UN,0,bmem_${r}${def_nr},IF ";
+                }
+            }
+
+            $def_nr++;
+        }
+
+        if( $conf['graphreport_stats'] )
+        {
+            $s_last     = $def_nr - 1;
+
+            foreach( $report_names as $r )
+            {
+                $cdef_sum   = "CDEF:bmem_${r}=bmem_${r}0_nonans";
+
+                if( $s_last > 1 )
+                {
+                    foreach (range(1, ($s_last)) as $print_nr ) 
+                    {
+                        $user_sum   .= ",bmem_${r}{$print_nr}_nonans,+";
+                    }
+                }
+                $cdef_sum .= " ";
+
+                $series   .= $cdef_sum;
+            }
+
+            $r_count = 0;
+
+            $conf['mem_buffer_color'] = $conf['mem_buffered_color'];
+            $conf['mem_swap_color']   = $conf['mem_swapped_color'];
+            $conf['mem_total_color']  = $conf['cpu_num_color'];
+
+            foreach( $report_names as $r )
+            {
+                $legend_str = ucfirst( $r );
+
+                if( $r == "total" )
+                {
+                    $graph_str  = "LINE2";
+                }
+                else if( $r_count == 0 )
+                {
+                    $graph_str  = "AREA";
+                }
+                else
+                {
+                    $graph_str  = "STACK";
+                }
+                foreach (range(0, ($s_last)) as $print_nr ) 
+                {
+                    $series .= "${graph_str}:'bmem_${r}${print_nr}'#".$conf['mem_'.${r}.'_color'].":'${legend_str}\g' ";
+                    $legend_str = '';
+                }
+
+                $series .= "VDEF:'${r}_last'=bmem_${r},LAST ";
+                $series .= "VDEF:'${r}_min'=bmem_${r},MINIMUM ";
+                $series .= "VDEF:'${r}_avg'=bmem_${r},AVERAGE ";
+                $series .= "VDEF:'${r}_max'=bmem_${r},MAXIMUM ";
+
+                $spacefill = '';
+
+                $spacesize = 6-strlen($r); // max length 'swapped' = 7
+                foreach ( range( 0, $spacesize ) as $whatever )
+                {
+                    $spacefill .= ' ';
+                }
+                $series .= "GPRINT:'${r}_last':'${spacefill}Last\:%6.1lf%s' "
+                        . "GPRINT:'${r}_min':'${space1}Min\:%6.1lf%s${eol1}' "
+                        . "GPRINT:'${r}_avg':'${space2}Avg\:%6.1lf%s' "
+                        . "GPRINT:'${r}_max':'${space1}Max\:%6.1lf%s\\l' ";
+                $r_count = $r_count + 1;
+            }
+        }
+        else
+        {
+            $series .= "AREA:'bmem_used${def_nr}'#".$conf['mem_used_color']."${memuse_str} " 
+                    ."STACK:'bmem_shared${def_nr}'#".$conf['mem_shared_color']."${memshared_str} " 
+                    ."STACK:'bmem_cached${def_nr}'#".$conf['mem_cached_color']."${memcached_str} " 
+                    ."STACK:'bmem_buffer${def_nr}'#".$conf['mem_buffered_color']."${membuff_str} "
+                    ."STACK:'bmem_swap${def_nr}'#".$conf['mem_swapped_color']."${memswap_str} "; 
+        }
+
+    } 
+    else if ($graph == "load_report") 
+    {
+        $title = "Load: $hostname";
+
+        $lower_limit = "--lower-limit 0 --rigid";
+        $vertical_label = "--vertical-label 'Load/Procs'";
+
+        $def_nr = 0;
+
+        foreach( $rrd_dirs as $rrd_dir ) 
+        {
+
+            if( $def_nr == 0 ) 
+            {
+
+                $load_str = ":'1-min Load'";
+                $cpu_str = ":'CPUs'";
+                $run_str = ":'Running Processes'";
+            } 
+            else 
+            {
+                $load_str = "";
+                $cpu_str = "";
+                $run_str = "";
+            }
+
+            $series .= "DEF:'load_load${def_nr}'='${rrd_dir}/load_one.rrd':'sum':AVERAGE "
+                ."DEF:'load_procs${def_nr}'='${rrd_dir}/proc_run.rrd':'sum':AVERAGE "
+                ."DEF:'load_cpus${def_nr}'='${rrd_dir}/cpu_num.rrd':'sum':AVERAGE ";
+
+            $report_names = array( "load", "procs", "cpus" );
+
+            if( $conf['graphreport_stats'] )
+            {
+                foreach( $report_names as $r )
+                {
+                    $series .= "CDEF:load_${r}${def_nr}_nonans=load_${r}${def_nr},UN,0,load_${r}${def_nr},IF ";
+                }
+            }
+
+            $def_nr++;
+        }
+
+        if( $conf['graphreport_stats'] )
+        {
+            $s_last     = $def_nr - 1;
+
+            foreach( $report_names as $r )
+            {
+                $cdef_sum   = "CDEF:load_${r}=load_${r}0_nonans";
+
+                if( $s_last > 1 )
+                {
+                    foreach (range(1, ($s_last)) as $print_nr ) 
+                    {
+                        $user_sum   .= ",load_${r}{$print_nr}_nonans,+";
+                    }
+                }
+                $cdef_sum .= " ";
+
+                $series   .= $cdef_sum;
+            }
+
+            $conf['load_load_color']  = $conf['load_one_color'];
+            $conf['load_procs_color'] = $conf['proc_run_color'];
+            $conf['load_cpus_color']  = $conf['cpu_num_color'];
+
+            foreach( $report_names as $r )
+            {
+                $legend_str = ucfirst( $r );
+
+                if( $r == 'load' )
+                {
+                    $graph_str  = "AREA";
+                }
+                else
+                {
+                    $graph_str  = "LINE2";
+                }
+                foreach (range(0, ($s_last)) as $print_nr ) 
+                {
+                    $series .= "${graph_str}:'load_${r}${print_nr}'#".$conf['load_'.${r}.'_color'].":'${legend_str}\g' ";
+                    $legend_str = '';
+                }
+
+                $series .= "VDEF:'${r}_last'=load_${r},LAST ";
+                $series .= "VDEF:'${r}_min'=load_${r},MINIMUM ";
+                $series .= "VDEF:'${r}_avg'=load_${r},AVERAGE ";
+                $series .= "VDEF:'${r}_max'=load_${r},MAXIMUM ";
+
+                $spacefill = '';
+
+                $spacesize = 6-strlen($r); // max length 'swapped' = 7
+                foreach ( range( 0, $spacesize ) as $whatever )
+                {
+                    $spacefill .= ' ';
+                }
+                $series .= "GPRINT:'${r}_last':'${spacefill}Last\:%6.1lf%s' "
+                        . "GPRINT:'${r}_min':'${space1}Min\:%6.1lf%s${eol1}' "
+                        . "GPRINT:'${r}_avg':'${space2}Avg\:%6.1lf%s' "
+                        . "GPRINT:'${r}_max':'${space1}Max\:%6.1lf%s\\l' ";
+            }
+        }
+        else
+        {
+            $series .="AREA:'load_one${def_nr}'#".$conf['load_one_color']."${load_str} ";
+            $series .="LINE2:'cpu_num${def_nr}'#".$conf['cpu_num_color']."${cpu_str} ";
+            $series .="LINE2:'proc_run${def_nr}'#".$conf['proc_run_color']."${run_str} ";
+        }
+    } 
+    else if ($graph == "network_report") 
+    {
+        $title = "Network: $hostname";
+
+        $lower_limit = "--lower-limit 0 --rigid";
+        $extras .= "--base 1024";
+        $vertical_label = "--vertical-label 'Bytes/sec'";
+
+        $def_nr = 0;
+
+        foreach( $rrd_dirs as $rrd_dir ) 
+        {
+            $series .= "DEF:'bytes_in${def_nr}'='${rrd_dir}/bytes_in.rrd':'sum':AVERAGE "
+                    ."DEF:'bytes_out${def_nr}'='${rrd_dir}/bytes_out.rrd':'sum':AVERAGE ";
+
+            $report_names = array( "in", "out" );
+
+            if( $conf['graphreport_stats'] )
+            {
+                foreach( $report_names as $r )
+                {
+                    $series .= "CDEF:bytes_${r}${def_nr}_nonans=bytes_${r}${def_nr},UN,0,bytes_${r}${def_nr},IF ";
+                }
+            }
+
+            $def_nr++;
+        }
+
+        if( $conf['graphreport_stats'] )
+        {
+            $s_last     = $def_nr - 1;
+
+            foreach( $report_names as $r )
+            {
+                $cdef_sum   = "CDEF:bytes_${r}=bytes_${r}0_nonans";
+
+                if( $s_last > 1 )
+                {
+                    foreach (range(1, ($s_last)) as $print_nr ) 
+                    {
+                        $user_sum   .= ",bytes_${r}{$print_nr}_nonans,+";
+                    }
+                }
+                $cdef_sum .= " ";
+
+                $series   .= $cdef_sum;
+            }
+
+            $r_count = 0;
+
+            $conf['bytes_out_color'] = $conf['mem_used_color'];
+            $conf['bytes_in_color']  = $conf['mem_cached_color'];
+
+            foreach( $report_names as $r )
+            {
+                $legend_str = ucfirst( $r );
+
+                $graph_str  = "LINE2";
+
+                foreach (range(0, ($s_last)) as $print_nr ) 
+                {
+                    $series .= "${graph_str}:'bytes_${r}${print_nr}'#".$conf['bytes_'.${r}.'_color'].":'${legend_str}\g' ";
+                    $legend_str = '';
+                }
+
+                $series .= "VDEF:'${r}_last'=bytes_${r},LAST ";
+                $series .= "VDEF:'${r}_min'=bytes_${r},MINIMUM ";
+                $series .= "VDEF:'${r}_avg'=bytes_${r},AVERAGE ";
+                $series .= "VDEF:'${r}_max'=bytes_${r},MAXIMUM ";
+
+                $spacefill = '';
+
+                $spacesize = 6-strlen($r); // max length 'swapped' = 7
+                foreach ( range( 0, $spacesize ) as $whatever )
+                {
+                    $spacefill .= ' ';
+                }
+                $series .= "GPRINT:'${r}_last':'${spacefill}Last\:%6.1lf%s' "
+                        . "GPRINT:'${r}_min':'${space1}Min\:%6.1lf%s${eol1}' "
+                        . "GPRINT:'${r}_avg':'${space2}Avg\:%6.1lf%s' "
+                        . "GPRINT:'${r}_max':'${space1}Max\:%6.1lf%s\\l' ";
+
+            }
+        }
+        else
+        {
+                $series .= "LINE2:'bytes_in${def_nr}'#".$conf['mem_cached_color']."'Bytes In' "
+                        ."LINE2:'bytes_out${def_nr}'#".$conf['mem_used_color']."'Bytes Out' ";
+        }
+
+    } 
+    else if ($graph == "packet_report") 
+    {
+        $title = "Packets: $hostname";
+
+        $lower_limit = "--lower-limit 0 --rigid";
+        $extras .= "--base 1024";
+        $vertical_label = "--vertical-label 'Packets/sec'";
+
+        $def_nr = 0;
+
+        foreach( $rrd_dirs as $rrd_dir ) 
+        {
+
+            $series .= "DEF:'pkts_in${def_nr}'='${rrd_dir}/pkts_in.rrd':'sum':AVERAGE "
+                    ."DEF:'pkts_out${def_nr}'='${rrd_dir}/pkts_out.rrd':'sum':AVERAGE ";
+
+            $report_names = array( "in", "out" );
+
+            if( $conf['graphreport_stats'] )
+            {
+                foreach( $report_names as $r )
+                {
+                    $series .= "CDEF:pkts_${r}${def_nr}_nonans=pkts_${r}${def_nr},UN,0,pkts_${r}${def_nr},IF ";
+                }
+            }
+
+            $def_nr++;
+        }
+
+        if( $conf['graphreport_stats'] )
+        {
+            $s_last     = $def_nr - 1;
+
+            foreach( $report_names as $r )
+            {
+                $cdef_sum   = "CDEF:pkts_${r}=pkts_${r}0_nonans";
+
+                if( $s_last > 1 )
+                {
+                    foreach (range(1, ($s_last)) as $print_nr ) 
+                    {
+                        $user_sum   .= ",pkts_${r}{$print_nr}_nonans,+";
+                    }
+                }
+                $cdef_sum .= " ";
+
+                $series   .= $cdef_sum;
+            }
+
+            $r_count = 0;
+
+            $conf['pkts_out_color'] = $conf['mem_used_color'];
+            $conf['pkts_in_color']  = $conf['mem_cached_color'];
+
+            foreach( $report_names as $r )
+            {
+                $legend_str = ucfirst( $r );
+
+                $graph_str  = "LINE2";
+
+                foreach (range(0, ($s_last)) as $print_nr ) 
+                {
+                    $series .= "${graph_str}:'pkts_${r}${print_nr}'#".$conf['pkts_'.${r}.'_color'].":'${legend_str}\g' ";
+                    $legend_str = '';
+                }
+
+                $series .= "VDEF:'${r}_last'=pkts_${r},LAST ";
+                $series .= "VDEF:'${r}_min'=pkts_${r},MINIMUM ";
+                $series .= "VDEF:'${r}_avg'=pkts_${r},AVERAGE ";
+                $series .= "VDEF:'${r}_max'=pkts_${r},MAXIMUM ";
+
+                $spacefill = '';
+
+                $spacesize = 6-strlen($r); // max length 'swapped' = 7
+                foreach ( range( 0, $spacesize ) as $whatever )
+                {
+                    $spacefill .= ' ';
+                }
+                $series .= "GPRINT:'${r}_last':'${spacefill}Last\:%6.1lf%s' "
+                        . "GPRINT:'${r}_min':'${space1}Min\:%6.1lf%s${eol1}' "
+                        . "GPRINT:'${r}_avg':'${space2}Avg\:%6.1lf%s' "
+                        . "GPRINT:'${r}_max':'${space1}Max\:%6.1lf%s\\l' ";
+
+            }
+        }
+        else
+        {
+                $series .= "LINE2:'pkts_in${def_nr}'#".$conf['mem_cached_color']."'Packets In' "
+                        ."LINE2:'pkts_out${def_nr}'#".$conf['mem_used_color']."'Packets Out' ";
+        }
+
+    } 
+    else 
+    {
+        /* Custom graph */
+        $style = "";
+
+        $subtitle = $metricname;
+        if($context == "host")
+        {
+            if ($size == "small")
+                $prefix = $metricname;
+            else
+                $prefix = $hostname;
+
+            $value = $value>1000 ? number_format($value) : number_format($value, 2);
+        }
+
+        if (is_numeric($max))
+            $upper_limit = "--upper-limit '$max' ";
+        if (is_numeric($min))
+            $lower_limit ="--lower-limit '$min' ";
+
+        if ($vlabel)
+        {
+            $vertical_label = "--vertical-label '$vlabel'";
+        }
+        else 
+        {
+            if ($upper_limit or $lower_limit) 
+            {
+                $max = $max>1000 ? number_format($max) : number_format($max, 2);
+                $min = $min>0 ? number_format($min,2) : $min;
+
+                $vertical_label ="--vertical-label '$min - $max' ";
+            }
+        }
+
+        $def_nr = 0;
+
+
+        foreach( $rrd_dirs as $rrd_dir ) 
+        {
+
+            if( $def_nr == 0 ) 
+            {
+                $title_str = ":'${subtitle}'";
+            } 
+            else 
+            {
+                $title_str = "";
+            }
+
+            $rrd_file = "$rrd_dir/$metricname.rrd";
+            $series .= "DEF:'sum${def_nr}'='$rrd_file':'sum':AVERAGE "
+                ."AREA:'sum${def_nr}'#".$conf['default_metric_color']."${title_str} ";
+
+            if( $conf['graphreport_stats'] )
+            {
+                $series .= "CDEF:sum${def_nr}_nonans=sum${def_nr},UN,0,sum${def_nr},IF ";
+            }
+
+            $def_nr++;
+        }
+
+        if( $conf['graphreport_stats'] )
+        {
+            $s_last         = $def_nr - 1;
+            $series_sum     = "CDEF:sum=sum0_nonans";
+
+            if( $def_nr > 1 )
+            {
+                foreach (range(1, ($s_last)) as $print_nr ) 
+                {
+                    $series_sum     .= ",sum{$print_nr}_nonans,+";
+                }
+            }
+
+            $series_sum .= " ";
+
+            $series_last    = "VDEF:'sum_last'=sum,LAST ";
+            $series_minimum = "VDEF:'sum_min'=sum,MINIMUM ";
+            $series_average = "VDEF:'sum_avg'=sum,AVERAGE ";
+            $series_maximum = "VDEF:'sum_max'=sum,MAXIMUM ";
+
+            $series .= $series_sum . $series_last . $series_minimum . $series_average . $series_maximum;
+
+            $series .= "COMMENT:\"\\n\" ";
+            $series .= "GPRINT:'sum_last':'${space1}Last\:%6.1lf%s' "
+                    . "GPRINT:'sum_min':'${space1}Min\:%6.1lf%s${eol1}' "
+                    . "GPRINT:'sum_avg':'${space2}Avg\:%6.1lf%s' "
+                    . "GPRINT:'sum_max':'${space1}Max\:%6.1lf%s\\l' ";
+        }
+    }
+}
+if( $series != '' ) 
+{
+    if ($job_start)
+    {
+        $series .= "VRULE:${job_start}#${jobstart_color}:'job start':dashes=4,2 ";
+    }
+    if ($job_stop)
+    {
+        $series .= "VRULE:${job_stop}#${jobstop_color}:'job stop':dashes=4,2 ";
+    }
+}
+
+if($graph == "job_report")
+{
+    if($range == 'job' )
+    {
+        $title = "Last: $j_title";
+    }
+    else
+    {
+        $title = "Last: $range";
+    }
+}
+else if( !isset( $title ) )
+{
+    $title = "$hostname";
+}
+
+function determineXGrid( $p_start, $p_stop ) 
+{
+
+    $period = intval( $p_stop - $p_start );
+
+    // Syntax: <minor_grid_lines_time_declr>:<major_grid_lines_time_declr>:<labels_time_declr>:<offset>:<format>
+    //
+    // Where each <*time_declr*> = <time_type>:<time_interval>
+
+    //$my_lines1 = intval( $period / 3.0 );
+    //$my_lines2 = intval( $period / 6.0 );
+
+    //$my_grid = "SECOND:$my_lines2:SECOND:$my_lines1:SECOND:$my_lines1:0:%R";
+
+    //return "--x-grid $my_grid";
+
+    // Less than 1 minute
+    if( $period < 60 ) 
+    {
+
+        $tm_formt = "%X";
+        $my_grid = "SECOND:15:SECOND:30:SECOND:30:0:$tm_formt";
+
+    // Less than 10 minutes
+    } 
+    else if( $period < 600 ) 
+    {
+
+        $tm_formt = "%R";
+        $my_grid = "MINUTE:1:MINUTE:3:MINUTE:3:0:$tm_formt";
+
+    // Less than 1 hour
+    } 
+    else if( $period < 3600 ) 
+    {
+
+        $tm_formt = "%R";
+        $my_grid = "MINUTE:5:MINUTE:15:MINUTE:15:0:$tm_formt";
+
+    // Less than 15 hour
+    } 
+    else if( $period < 3600 ) 
+    {
+
+        $tm_formt = "%R";
+        $my_grid = "HOUR:1:HOUR:2:HOUR:2:0:$tm_formt";
+
+    // Less than 1 day
+    //
+    } 
+    else if( $period < 86400 ) 
+    {
+
+        $tm_formt = "%R";
+        $my_grid = "HOUR:2:HOUR:5:HOUR:5:0:$tm_formt";
+
+    // Less than 15 days
+    //
+    } 
+    else if( $period < 1296000 ) 
+    {
+
+        $tm_formt = "%e-%m";
+        $my_grid = "HOUR:1:DAY:3:DAY:3:0:'$tm_formt'";
+        
+    // Less than 30 days (a month)
+    //
+    } 
+    else if( $period < 2592000 ) 
+    {
+
+        $tm_formt = "%e-%m";
+        $my_grid = "DAY:5:DAY:10:DAY:10:0:'$tm_formt'";
+    }
+
+    if( isset( $my_grid ) ) 
+    {
+
+        $ret_str = "--x-grid $my_grid";
+        return array($ret_str,$tm_formt);
+
+    } 
+    else 
+    {
+        return array( "", "" );
+    }
+}
+
+$lower_limit = "--lower-limit 0";
+
+if( !isset( $load_color ) or ( $load_color == '') )
+{
+    $load_color = 'FFFFFF';
+}
+
+# Calculate time range.
+if ( isset($sourcetime) )
+{
+    $end = $sourcetime;
+    # Get_context makes start negative.
+    $start = $sourcetime + $start;
+
+    # Fix from Phil Radden, but step is not always 15 anymore.
+    if ($range=="month")
+    {
+        $end = floor($end / 672) * 672;
+    }
+        $command = $conf['rrdtool']. " graph - --start $start --end $end ".
+                "$upper_limit $lower_limit ".
+                "--width $width --height $height $lower_limit $vertical_label ".
+                "--title '$title' $extras $background ".
+                $series;
+}
+else
+{
+    $command = $conf['rrdtool'] . " graph - --start $period_start --end $period_stop ".
+                "$upper_limit $lower_limit ".
+               "--width $width --height $height $lower_limit --color BACK#$load_color $vertical_label ".
+               "--title '$title' $extras $background ".
+               $series;
+}
+
+$debug=0;
+
+
+# Did we generate a command?   Run it.
+if($command) 
+{
+    /*Make sure the image is not cached*/
+    header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT");   // Date in the past
+    header ("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
+    header ("Cache-Control: no-cache, must-revalidate");   // HTTP/1.1
+    header ("Pragma: no-cache");                     // HTTP/1.0
+    if ($debug) 
+    {
+        header ("Content-type: text/html");
+        print "$command\n\n\n\n\n";
+    } 
+    else 
+    {
+        header ("Content-type: image/gif");
+        passthru($command);
+    }
+}
+?>
Index: /branches/1.0/web/addons/job_monarch/host_view.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/host_view.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/host_view.php	(revision 825)
@@ -0,0 +1,282 @@
+<?php
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006-2013  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * SVN $Id$
+ */
+
+include_once "./libtoga.php";
+
+if( $view == "overview-host" )
+{
+    $my_dir = getcwd();
+
+    global $context;
+
+    $context = 'cluster';
+
+    chdir( $GANGLIA_PATH );
+
+    include "./ganglia.php";
+    include "./get_ganglia.php";
+
+    chdir( $my_dir );
+}
+
+function datetimeToEpoch( $datetime ) 
+{
+
+        $datetime_fields = explode( ' ', $datetime );
+
+        $date = $datetime_fields[0];
+        $time = $datetime_fields[1];
+
+        $date_fields = explode( '-', $date );
+
+        $days = $date_fields[0];
+        $months = $date_fields[1];
+        $years = $date_fields[2];
+
+        //printf( "days = %s months = %s years = %s\n", $days, $months, $years );
+
+        $time_fields = explode( ':', $time );
+
+        $hours = $time_fields[0];
+        $minutes = $time_fields[1];
+        $seconds = $time_fields[2];
+
+        $timestamp = mktime( $hours, $minutes, $seconds, $months, $days, $years );
+
+        return $timestamp;
+}
+
+function makeHostView() 
+{
+
+    global $dwoo, $metrics, $clustername, $hostname;
+    global $cluster_ul, $hosts_up, $get_metric_string;
+    global $cluster, $period_start, $period_stop;
+    global $job_start, $job_stop, $view, $conf, $range;
+
+    $tpl = new Dwoo_Template_File("templates/host_view.tpl");
+    $tpl_data = new Dwoo_Data();
+
+    $rrdirs = array();
+
+    if( $view == "overview-host" )
+    {
+        $rrdirs[] = $conf['rrds'] . '/' . $clustername .'/'. $hostname;
+    }
+    else
+    {
+        $trd    = new TarchRrdGraph( $clustername, $hostname );
+        $rrdirs = $trd->getRrdDirs( $period_start, $period_stop );
+    }
+
+    $longtitle    = "Batch Archive Node Report :: Powered by Job Monarch!";
+    $title        = "Batch Archive Node Report";
+
+    makeHeader( 'host_view', $title, $longtitle );
+
+    #print_r( $rrdirs);
+
+    $metrics = $metrics[$hostname];
+    $mymetrics = array();
+
+    foreach( $rrdirs as $rrdir ) 
+    {
+        #printf("rrd dir %s\n", $rrdir );
+        if( $view == "overview-host" )
+        {
+            $mymetrics = $metrics;
+            unset( $mymetrics['last_reported_timestamp'] ); // Ganglia bug?
+            #print_r( $mymetrics );
+        }
+        else
+        {
+            #printf("archive mode\n");
+            $ml    = $trd->dirList( $rrdir );
+
+            foreach( $ml as $lmetr )
+            {
+                $metrn_fields = explode( '.', $lmetr );
+
+                $metrn        = $metrn_fields[0];
+
+                if( !in_array( $metrn, $mymetrics ) )
+                {
+                    $mymetrics[$metrn]    = $metrics[$metrn];
+                }
+            }
+        }
+    }
+
+    $hosts_up = $hosts_up[$hostname];
+
+    $tpl_data->assign("cluster", $clustername);
+    $tpl_data->assign("host", $hostname);
+    $tpl_data->assign("node_image", "../../".node_image($metrics));
+    $tpl_data->assign("sort",$sort);
+    $tpl_data->assign("range",$range);
+
+    if( !is_numeric( $period_start ) ) 
+    {
+        $period_start = datetimeToEpoch( $period_start );
+    }
+    if( !is_numeric( $period_stop ) ) 
+    {
+        $period_stop = datetimeToEpoch( $period_stop );
+    }
+
+    if($hosts_up)
+          $tpl_data->assign("node_msg", "This host is up and running."); 
+    else
+          $tpl_data->assign("node_msg", "This host is down."); 
+
+    $cluster_url=rawurlencode($clustername);
+    $tpl_data->assign("cluster_url", $cluster_url);
+
+    $graphargs = "h=$hostname&r=$range&job_start=$job_start&job_stop=$job_stop";
+
+    if( $range == 'job' )
+    {
+        $graphargs .= "&period_start=$period_start&period_stop=$period_stop";
+    }
+    else
+    {
+        $tijd = time();
+        $graphargs .= "&st=$tijd";
+    }
+
+    $tpl_data->assign("graphargs", "$graphargs");
+
+    # For the node view link.
+    $tpl_data->assign("node_view","./?p=2&c=$cluster_url&h=$hostname");
+
+    $tpl_data->assign("ip", $hosts_up['IP']);
+
+    #print_r( $mymetrics );
+
+    foreach ($mymetrics as $name => $v)
+    {
+        if ($v['TYPE'] == "string" or $v['TYPE']=="timestamp" or $always_timestamp[$name] or $v['NAME']=='last_reported_timestamp')
+        {
+            # Long gmetric name/values will disrupt the display here.
+            if ($v['SOURCE'] == "gmond") $s_metrics[$name] = $v;
+        }
+        else if ($v['SLOPE'] == "zero" or $always_constant[$name])
+        {
+            $c_metrics[$name] = $v;
+        }
+        else if ($reports[$metric])
+        {
+            continue;
+        }
+        else
+        {
+            $graphargs = "c=$cluster_url&h=$hostname&m=$name&z=overview-medium&r=$range&job_start=$job_start&job_stop=$job_stop";
+
+            if( $range == 'job' )
+            {
+                $graphargs .= "&period_start=$period_start&period_stop=$period_stop";
+            }
+            else
+            {
+                $tijd = time();
+                $graphargs .= "&st=$tijd";
+            }
+            # Adding units to graph 2003 by Jason Smith <smithj4@bnl.gov>.
+            if ($v['UNITS']) 
+            {
+                $encodeUnits = rawurlencode($v['UNITS']);
+                $graphargs .= "&vl=$encodeUnits";
+            }
+            $g_metrics[$name]['graph'] = $graphargs;
+        }
+    }
+    # Add the uptime metric for this host. Cannot be done in ganglia.php,
+    # since it requires a fully-parsed XML tree. The classic contructor problem.
+    $s_metrics['uptime']['TYPE'] = "string";
+    $s_metrics['uptime']['VAL'] = uptime($cluster['LOCALTIME'] - $metrics['boottime']['VAL']);
+
+    # Add the gmond started timestamps & last reported time (in uptime format) from
+    # the HOST tag:
+    $s_metrics['gmond_started']['TYPE'] = "timestamp";
+    $s_metrics['gmond_started']['VAL'] = $hosts_up['GMOND_STARTED'];
+    $s_metrics['last_reported']['TYPE'] = "string";
+    $s_metrics['last_reported']['VAL'] = uptime($cluster['LOCALTIME'] - $hosts_up['REPORTED']);
+
+    # Show string metrics
+    if (is_array($s_metrics))
+    {
+        ksort($s_metrics);
+        $string_metric_info_loop = array();
+        foreach ($s_metrics as $name => $v )
+        {
+            $metric_info = array();
+            $metric_info["name"] = $name;
+            if( $v['TYPE']=="timestamp" or $always_timestamp[$name])
+            {
+                $metric_info["value"] = date("r", $v['VAL']);
+            }
+            else
+            {
+                $metric_info["value"] = $v['VAL']." ". $v['UNITS'];
+            }
+            $string_metric_info_loop[] = $metric_info;
+        }
+        $tpl_data->assign("string_metric_info", $string_metric_info_loop );
+    }
+
+    # Show constant metrics.
+    if (is_array($c_metrics))
+    {
+        ksort($c_metrics);
+        $const_metric_info_loop = array();
+        foreach ($c_metrics as $name => $v )
+        {
+            $const_info = array();
+            $const_infp["name"] = $name;
+            $const_info["value"] = $v[VAL]." ". $v[UNITS];
+            $const_metric_info_loop[] = $const_info;
+        }
+        $tpl_data->assign("const_metric_info", $const_metric_info_loop );
+    }
+
+    # Show graphs.
+    if (is_array($g_metrics))
+    {
+        ksort($g_metrics);
+        $vol_metric_info_loop = array();
+
+        $i = 0;
+        foreach ( $g_metrics as $name => $v )
+        {
+            $metric_info = array();
+            $metric_info["graphargs"] = $v['graph'];
+            $metric_info["alt"] = "$hostname $name";
+            $vol_metric_info_loop[] = $metric_info;
+        } 
+        $tpl_data->assign("vol_metric_info", $vol_metric_info_loop );
+    }
+    $dwoo->output($tpl, $tpl_data);
+}
+
+?>
Index: /branches/1.0/web/addons/job_monarch/image.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/image.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/image.php	(revision 825)
@@ -0,0 +1,162 @@
+<?php
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * SVN $Id$
+ */
+
+include_once "./libtoga.php";
+
+if ( !empty( $_GET ) ) {
+        extract( $_GET );
+}
+
+function checkSessionData() {
+
+    global $_SESSION;
+
+    session_start();
+
+    if( isset( $_SESSION["data"] ) ) 
+    {
+        $myxml_data    = &$_SESSION["data"];
+    } 
+    else 
+    {
+        $myxml_data    = null;
+    }
+
+    if( $myxml_data == null ) 
+    {
+        $ds             = new DataSource();
+        $myxml_data     = $ds->getData();
+    }
+    return $myxml_data;
+}
+
+
+$httpvars = new HTTPVariables( $HTTP_GET_VARS, $_GET );
+$view = $httpvars->getHttpVar( "j_view" );
+$clustername = $httpvars->getClusterName();
+
+if( isset($id) && ($id!='')) $filter['id']=$id;
+if( isset($state) && ($state!='')) $filter['state']=$state;
+if( isset($owner) && ($owner!='')) $filter['owner']=$owner;
+if( isset($queue) && ($queue!='')) $filter['queue']=$queue;
+
+function drawHostImage() {
+
+    global $clustername, $hostname, $data_gatherer;
+
+    $ds             = new DataSource();
+    $myxml_data     = $ds->getData();
+
+    $data_gatherer    = new DataGatherer( $clustername );
+
+    $data_gatherer->parseXML( $myxml_data );
+
+    if( $data_gatherer->isJobmonRunning() )
+        $ic = new HostImage( $data_gatherer, $clustername, $hostname );
+    else
+        $ic = new EmptyImage();
+
+    $ic->draw();
+}
+
+function drawSmallClusterImage() {
+
+    global $clustername, $data_gatherer;
+
+    $ds             = new DataSource();
+    $myxml_data     = $ds->getData();
+
+    $data_gatherer    = new DataGatherer( $clustername );
+
+    $data_gatherer->parseXML( $myxml_data );
+
+    if( $data_gatherer->isJobmonRunning() ) {
+        $ic = new ClusterImage( $myxml_data, $clustername );
+        $ic->setSmall();
+    } else {
+        $ic = new EmptyImage();
+    }
+
+    $ic->draw();
+}
+
+function drawBigClusterImage() {
+
+    global $filter, $clustername;
+
+    $myxml_data    = checkSessionData();
+
+    $ic = new ClusterImage( $myxml_data, $clustername );
+    $ic->setBig();
+
+    if( isset( $filter ) ) {
+        foreach( $filter as $filtername=>$filtervalue ) {
+            switch( $filtername ) {
+
+                case "id":
+                    $ic->setFilter( 'jobid', $filtervalue );
+                    break;
+                case "owner":
+                    $ic->setFilter( 'owner', $filtervalue);
+                    break;
+                case "queue":
+                    $ic->setFilter( 'queue', $filtervalue);
+                    break;
+                case "state":
+                    $ic->setFilter( 'status', $filtervalue);
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+    $ic->draw();
+}
+
+switch( $view ) {
+
+    case "small-clusterimage":
+
+        drawSmallClusterImage();
+        
+        break;
+
+    case "big-clusterimage":
+
+        drawBigClusterImage();
+    
+        break;
+
+    case "hostimage":
+
+        drawHostImage();
+    
+        break;
+
+    default:
+
+        break;
+}
+
+?>
Index: /branches/1.0/web/addons/job_monarch/index.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/index.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/index.php	(revision 825)
@@ -0,0 +1,510 @@
+<?php
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006-2013  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * SVN $Id$
+ */
+
+$my_dir = getcwd();
+
+include_once "./libtoga.php";
+include_once "./dwoo/dwooAutoload.php";
+
+$r = escapeshellcmd( rawurldecode( $_GET["r"] ));
+$range = $r;
+if( !isset($range) or $range == '') $range= "job";
+
+global $context;
+
+#$context = 'cluster';
+
+#chdir( $GANGLIA_PATH );
+
+#include "./ganglia.php";
+#include "./get_ganglia.php";
+
+#chdir( $my_dir );
+
+if ( !empty( $_GET ) ) 
+{
+    extract( $_GET );
+}
+
+global $GANGLIA_PATH;
+
+//include_once "./class.TemplatePower.inc.php";
+
+$httpvars = new HTTPVariables( $HTTP_GET_VARS, $_GET );
+$clustername = $httpvars->getClusterName();
+$view = $httpvars->getHttpVar( "j_view" );
+
+$filter = array();
+
+if( !isset($view) && $clustername!='') $view = "overview";
+if( !isset($sortorder) ) $sortorder = "asc";
+if( !isset($sortby) ) $sortby = "id";
+
+if( isset( $filterorder ) && ($filterorder!='') ) 
+{
+    $myfilter_fields = explode( ",", $filterorder );
+} 
+else 
+{
+    if( isset($queue) && ($queue!='')) $filter['queue']=$queue;
+    if( isset($state) && ($state!='')) $filter['state']=$state;
+    if( isset($owner) && ($owner!='')) $filter['owner']=$owner;
+    if( isset($id) && ($id!='')) $filter['id']=$id;
+}
+
+// Fill filter array in order they were picked by user
+if( isset($myfilter_fields) ) 
+{
+
+    foreach( $myfilter_fields as $myfilter ) 
+    {
+
+        switch( $myfilter ) 
+        {
+
+            case "queue":
+                $filter['queue']=$queue;
+                break;
+            case "state":
+                $filter['state']=$state;
+                break;
+            case "owner":
+                $filter['owner']=$owner;
+                break;
+            case "id":
+                $filter['id']=$id;
+                break;
+        }
+    }
+}
+
+function epochToDatetime( $epoch ) 
+{
+
+        return strftime( "%d-%m-%Y %H:%M:%S", $epoch );
+}
+
+function makeHeader( $page_call, $title, $longtitle ) 
+{
+    global $dwoo, $grid, $context, $initgrid;
+    global $jobrange, $jobstart;
+    global $page, $gridwalk, $clustername;
+    global $parentgrid, $physical, $hostname;
+    global $self, $filter, $cluster_url, $get_metric_string;
+    global $metrics, $reports, $m, $conf;
+    global $default_refresh, $filterorder, $view;
+    global $JOB_ARCHIVE, $period_start, $period_stop, $h, $id;
+    global $job_start, $job_stop, $range, $r, $metricname;
+    global $conf, $show_hosts;
+    try
+       {
+          //$dwoo = new Dwoo($conf['dwoo_compiled_dir'], $conf['dwoo_cache_dir']);
+          $dwoo = new Dwoo( 'dwoo/compiled', 'dwoo/cache' );
+       }
+    catch (Exception $e)
+       {
+       print "<H4>There was an error initializing the Dwoo PHP Templating Engine: ".
+          $e->getMessage() . "<br><br>The compile directory should be owned and writable by the apache user.</H4>";
+          exit;
+       }
+
+    
+    if( isset($conf['default_metric']) and !isset($m) )
+        $metricname = $conf['default_metric'];
+    else
+        if( isset( $m ) )
+            $metricname = $m;
+        else
+            $metricname = "load_one";
+
+    $header = "header";
+
+    # Maintain our path through the grid tree.
+    $me = $self . "@" . $grid[$self][AUTHORITY];
+
+    $gridstack = array();
+    $gridstack[] = $me;
+
+    if ($gridwalk=="fwd") 
+    {
+        # push our info on gridstack, format is "name@url>name2@url".
+        if (end($gridstack) != $me) 
+        {
+            $gridstack[] = $me;
+        }
+    } 
+    else if ($gridwalk=="back") 
+    {
+        # pop a single grid off stack.
+        if (end($gridstack) != $me) 
+        {
+            array_pop($gridstack);
+        }
+    }
+
+    $gridstack_str = join(">", $gridstack);
+    $gridstack_url = rawurlencode($gridstack_str);
+
+    if ($initgrid or $gridwalk) 
+    {
+        # Use cookie so we dont have to pass gridstack around within this site.
+        # Cookie values are automatically urlencoded. Expires in a day.
+        setcookie("gs", $gridstack_str, time() + 86400);
+    }
+
+    # Invariant: back pointer is second-to-last element of gridstack. Grid stack never
+    # has duplicate entries.
+    list($parentgrid, $parentlink) = explode("@", $gridstack[count($gridstack)-2]);
+
+    $tpl = new Dwoo_Template_File("templates/header.tpl");
+    $tpl_data = new Dwoo_Data();
+
+    $tpl_data->assign( "date", date("r") );
+    $tpl_data->assign( "longpage_title", $longtitle );
+    $tpl_data->assign( "page_title", $title );
+
+    # The page to go to when "Get Fresh Data" is pressed.
+    $tpl_data->assign("page","./");
+
+    # Templated Logo image
+    $tpl_data->assign("images","./templates/$template_name/images");
+
+    #
+    # Used when making graphs via graph.php. Included in most URLs
+    #
+    $sort_url=rawurlencode($sort);
+    $get_metric_string = "m=$metric&r=$range&s=$sort_url&hc=$hostcols";
+
+    if ($jobrange and $jobstart)
+        $get_metric_string .= "&jr=$jobrange&js=$jobstart";
+
+    # Set the Alternate view link.
+    $cluster_url=rawurlencode($clustername);
+    $node_url=rawurlencode($hostname);
+
+    # Make some information available to templates.
+    $tpl_data->assign("cluster_url", $cluster_url);
+    # Build the node_menu
+    $node_menu = "";
+
+    if ($parentgrid) 
+    {
+        $node_menu .= "<B>$parentgrid $meta_designator</B> ";
+        $node_menu .= "<B>&gt;</B>\n";
+    }
+
+    # Show grid.
+    $mygrid =  ($self == "unspecified") ? "" : $self;
+    $node_menu .= "<B><A HREF=\"../..\">$mygrid $meta_designator</A></B> ";
+    $node_menu .= "<B>&gt;</B>\n";
+
+    $tpl_data->assign("self", $mygrid);
+
+    if ($physical)
+    {
+        $node_menu .= hiddenvar("p", $physical);
+    }
+
+    if ( $clustername ) 
+    {
+        $url = rawurlencode($clustername);
+        $node_menu .= "<B><A HREF=\"../../?c=".rawurlencode($clustername)."\">$clustername</A></B> ";
+        $node_menu .= "<B>&gt;</B>\n";
+        $node_menu .= hiddenvar("c", $clustername);
+    }
+
+    if (!count($metrics) && $view!='' && $view!='search' && $view!='host') 
+    {
+        echo "<h4>Cannot find any metrics for selected cluster \"$clustername\", exiting.</h4>\n";
+        echo "Check ganglia XML tree (telnet $ganglia_ip $ganglia_port)\n";
+        exit;
+    }
+    reset($metrics);
+    $firsthost = key($metrics);
+
+    $mmfh    = array();
+
+    $mmfh    = $metrics[$firsthost];
+
+    $context_metrics    = array();
+
+    foreach( $mmfh as $mm => $bla )
+    {
+        $context_metrics[] = $mm;
+    }
+
+    foreach ($reports as $mr => $mfoo)
+        $context_metrics[] = $mr;
+
+    $node_menu .= "<B><A HREF=\"./?c=".rawurlencode($clustername)."\">Joblist</A></B> ";
+
+    if( isset( $hostname ) && ( $view != 'host' ) ) 
+    {
+
+        $node_menu .= "<B>&gt;</B>\n";
+        $href = "<A HREF=\"./?c=".rawurlencode($clustername)."&h=".$hostname."\">";
+        $node_menu .= "<B>$href";
+        $node_menu .= "host: $hostname</A></B> ";
+    }
+
+    if( count( $filter ) > 0 && $view != "search" ) 
+    {
+
+        $my_ct = 1;
+        $filter_nr = count( $filter );
+
+        foreach( $filter as $filtername=>$filterval ) 
+        {
+
+            $node_menu .= "<B>&gt;</B>\n";
+
+            $href = "<A HREF=\"./?c=".rawurlencode($clustername);
+            $temp_ct = 0;
+            $n_filter = $filter;
+            $my_filterorder = "";
+            $my_filters = array_keys( $filter );
+
+            foreach( $n_filter as $n_filtername=>$n_filterval ) 
+            {
+
+                if( $temp_ct < $my_ct ) 
+                {
+                    $href .= "&". $n_filtername . "=" . $n_filterval;
+
+                    if( $my_filterorder == "" )
+                        $my_filterorder = $my_filters[$temp_ct];
+                    else
+                        $my_filterorder .= "," . $my_filters[$temp_ct];
+                }
+
+                $temp_ct++;
+            }
+            $href .= "&filterorder=$my_filterorder\">";
+
+            if( $my_ct < $filter_nr )
+                $node_menu .= "<B>$href$filtername: $filterval</A></B> ";
+            else
+                $node_menu .= "<B>$filtername: $filterval</B> ";
+
+            $my_ct++;
+        }
+    }
+
+    $tpl_data->assign("view", $view);
+
+    if( array_key_exists( "id", $filter ) or isset($hostname) ) 
+    {
+
+        #$range = "job";
+
+        if( ( $page_call != "host_view" ) && ( $page_call != "search" ) )
+        {
+
+            if (is_array($context_metrics) ) 
+            {
+                $metric_menu = "<B>Metric</B>&nbsp;&nbsp;"
+                    ."<SELECT NAME=\"m\" OnChange=\"toga_form.submit();\">\n";
+
+                sort($context_metrics);
+                foreach( $context_metrics as $k ) 
+                {
+                    $url = rawurlencode($k);
+                    $metric_menu .= "<OPTION VALUE=\"$url\" ";
+                    if ($k == $metricname )
+                        $metric_menu .= "SELECTED";
+                    $metric_menu .= ">$k\n";
+                }
+                $metric_menu .= "</SELECT>\n";
+            }
+
+        }
+
+        $tpl_data->assign("metric_menu", $metric_menu );
+
+        if( $view == "host" or $view == "overview-host" ) 
+        {
+            $tpl_data->assign("timeperiod", "yes" );
+            if( is_numeric( $period_start ) ) 
+            {
+                $period_start = epochToDatetime( $period_start );
+            }
+            if( is_numeric( $period_stop ) ) 
+            {
+                $period_stop = epochToDatetime( $period_stop );
+            }
+            $tpl_data->assign("period_start", $period_start );
+            $tpl_data->assign("period_stop", $period_stop );
+            $tpl_data->assign("hostname", $hostname );
+
+            if( $view == "host" or $view == "overview-host" ) 
+            {
+                $tpl_data->assign("hostview", "yes");
+                $tpl_data->assign("job_start", $job_start );
+                $tpl_data->assign("job_stop", $job_stop );
+            }
+        } 
+
+    }
+
+    if( array_key_exists( "id", $filter ) or isset($hostname) ) 
+    {
+
+        #$range = "job";
+
+        if( ( $page_call != "host_view" ) && ( $view != "search" ) )
+        {
+            $context_ranges[]="hour";
+            $context_ranges[]="day";
+            $context_ranges[]="week";
+            $context_ranges[]="month";
+            $context_ranges[]="year";
+            $context_ranges[]="job";
+
+            $range_menu = "<B>Last</B>&nbsp;&nbsp;" ."<SELECT NAME=\"r\" OnChange=\"toga_form.submit();\">\n";
+            foreach ($context_ranges as $v) 
+            {
+                $url=rawurlencode($v);
+                $range_menu .= "<OPTION VALUE=\"$url\" ";
+                if ($v == $range)
+                {
+                    $range_menu .= "SELECTED";
+                }
+                $range_menu .= ">$v\n";
+            }
+            $range_menu .= "</SELECT>\n";
+
+            $tpl_data->assign("range_menu", $range_menu);
+        }
+
+    }
+
+    if( $view == "search" or $view == "host" ) 
+    {
+
+        $node_menu .= "<B>&gt;</B>\n";
+        $node_menu .= "<B>Jobarchive</B> ";
+        $form_name = "archive_search_form";
+        $tpl_data->assign("form_name", $form_name );
+
+    } 
+    else 
+    {
+        $form_name = "toga_form";
+        $tpl_data->assign("form_name", $form_name );
+    }
+
+    if( $JOB_ARCHIVE && $page_call == 'overview' ) 
+    {
+        $tpl_data->assign( "search", "yes" );
+        $tpl_data->assign( "cluster_url", rawurlencode($clustername) );
+        $tpl_data->assign( "cluster", $clustername );
+    }
+    $tpl_data->assign( "cluster", $clustername );
+    $tpl_data->assign("node_menu", $node_menu);
+
+    # Make sure that no data is cached..
+    header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT");    # Date in the past
+    header ("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); # always modified
+    header ("Cache-Control: no-cache, must-revalidate");  # HTTP/1.1
+    header ("Pragma: no-cache");                          # HTTP/1.0
+
+    $dwoo->output($tpl, $tpl_data);
+}
+
+function makeFooter() 
+{
+    global $dwoo, $version, $parsetime, $monarchversion;
+
+    $tpl = new Dwoo_Template_File("templates/footer.tpl");
+    $tpl_data = new Dwoo_Data();
+
+    $tpl_data->assign("webfrontendversion",$version["webfrontend"]);
+    $tpl_data->assign("monarchversion", $monarchversion);
+
+    if ($version["gmetad"]) 
+    {
+        $tpl_data->assign("webbackendcomponent", "gmetad");
+        $tpl_data->assign("webbackendversion",$version["gmetad"]);
+    } 
+    else if ($version["gmond"]) 
+    {
+        $tpl_data->assign("webbackendcomponent", "gmond");
+        $tpl_data->assign("webbackendversion", $version["gmond"]);
+    }
+
+    $tpl_data->assign("parsetime", sprintf("%.4f", $parsetime) . "s");
+
+    $dwoo->output($tpl, $tpl_data);
+}
+
+if( isset( $h ) and $h != '' ) 
+{
+    $hostname = $h;
+}
+
+//$tpl_data->assign( "footer", template( "templates/footer.tpl" ) );
+
+$longtitle = "Batch Report :: Powered by Job Monarch!";
+$title = "Batch Report";
+//$tpl_data->assign("cluster_url", rawurlencode($clustername) );
+//$tpl_data->assign("cluster", $clustername );
+
+#makeHeader();
+
+switch( $view ) 
+{
+
+    case "overview":
+
+        include "./overview.php";
+        makeOverview();
+        break;
+
+    case "search":
+
+        include "./search.php";
+        makeSearchPage();
+        break;
+
+    case "host":
+
+        include "./host_view.php";
+        makeHostView();
+        break;
+
+    case "overview-host":
+
+        include "./host_view.php";
+        makeHostView();
+        break;
+
+    default:
+
+        include "./overview.php";
+        makeOverview();
+        break;
+}
+
+makeFooter();
+?>
Index: /branches/1.0/web/addons/job_monarch/libtoga.js
===================================================================
--- /branches/1.0/web/addons/job_monarch/libtoga.js	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/libtoga.js	(revision 825)
@@ -0,0 +1,96 @@
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * SVN $Id$
+ */
+
+function setSort( sortbyval ) {
+
+	if( sortbyval != document.toga_form.sortby.value ) {
+
+		document.toga_form.sortby.value = sortbyval;
+		document.toga_form.sortorder.value = "asc";
+
+	} else {
+
+		if( document.toga_form.sortorder.value == "desc" )
+			document.toga_form.sortorder.value = "asc";
+		else if( document.toga_form.sortorder.value == "asc" )
+			document.toga_form.sortorder.value = "desc";
+	}
+
+	document.forms['toga_form'].submit();
+}
+
+function setFilter( filtername, filterval ) {
+
+	//document.toga_form.id.value = '';
+	//document.toga_form.queue.value = '';
+	//document.toga_form.state.value = '';
+	//document.toga_form.user.value = '';
+	var myfilterorder = document.toga_form.elements['filterorder'].value;
+
+	if( document.toga_form.elements[filtername] ) {
+		document.toga_form.elements[filtername].value = filterval;
+		if( myfilterorder != '')
+			myfilterorder = myfilterorder + "," + filtername;
+		else
+			myfilterorder = filtername;
+
+	}
+	document.toga_form.elements['filterorder'].value = myfilterorder;
+
+	//setTimeout( "document.forms['toga_form'].submit();", 1000 );
+
+	document.forms['toga_form'].submit();
+}
+
+//function removeFilters( filters ) {
+
+	//var myfilter_fields = filters.split( " " );
+	//for( var i=0; i<myfilter_fields.length; i++ ) {
+		//removeFilter( myfilter_fields[i] );
+		//setTimeout( "removeFilter( "+myfilter_fields[i]+" );", 50 );
+	//}
+	// delay 100 ms before submit or fields might not be set
+	//document.forms['toga_form'].submit();", 100 );
+
+	//setTimeout( "document.forms['toga_form'].submit();", 1000 );
+//}
+
+//function removeFilter( filtername ) {
+
+//      var filterorder_fields = document.toga_form.elements['filterorder'].value.split( "," );
+//      var myfilterorder = '';
+
+//      for( var i=0; i<filterorder_fields.length; i++ ) {
+//              if( filterorder_fields[i] != filtername ) {
+//                      if( myfilterorder != '') {
+//                              myfilterorder = myfilterorder + "," + filterorder_fields[i];
+//                              //alert('myfilterorder = '+myfilterorder);
+//                      } else {
+//                              myfilterorder = filterorder_fields[i];
+//                              //alert('emyfilterorder = '+myfilterorder);
+//                      }
+//              }
+//      }
+//      document.toga_form.elements[filtername].value = '';
+//      document.toga_form.elements['filterorder'].value = myfilterorder;
+//}
Index: /branches/1.0/web/addons/job_monarch/libtoga.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/libtoga.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/libtoga.php	(revision 825)
@@ -0,0 +1,2630 @@
+<?php
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006-2013  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * SVN $Id$
+ *
+ */
+
+
+class HTTPVariables
+{
+    var $clustername, $metricname;
+    var $restvars, $httpvars;
+
+    function HTTPVariables( $httpvars, $getvars )
+    {
+        $this->restvars        = array();
+
+        $this->clustername    = isset( $httpvars["c"] ) ? $httpvars["c"] : $getvars["c"];
+        $this->metricname    = isset( $httpvars["m"] ) ? $httpvars["m"] : $getvars["m"];
+
+        if( count( $httpvars ) > 0 )
+        {
+            foreach( $httpvars as $httpvar => $httpval )
+            {
+                if( $httpval )
+                {
+                    $this->restvars[$httpvar] = $httpval;
+                }
+            }
+        }
+
+        if( count( $getvars ) > 0 )
+        {
+            foreach( $getvars as $getvar => $getval )
+            {
+                if( $getval )
+                {
+                    $this->restvars[$getvar] = $getval;
+                }
+            }
+        }
+    }
+
+    function getClusterName()
+    {
+        return $this->clustername;
+    }
+
+    function getMetricName()
+    {
+        return $this->metricname;
+    }
+
+    function getHttpVar( $var )
+    {
+        if( isset( $this->restvars[$var] ) )
+        {
+            return $this->restvars[$var];
+        }
+        else
+        {
+            return null;
+        }
+    }
+}
+
+$CLUSTER_CONFS    = array();
+
+//ini_set("memory_limit","1G");
+set_time_limit(0);
+
+// Monarch's conf
+//
+include_once "./conf.php";
+include_once "./version.php";
+
+global $GANGLIA_PATH;
+global $RRDTOOL;
+global $JOB_ARCHIVE_DIR;
+global $JOB_ARCHIVE_DBASE;
+global $JOB_ARCHIVE_SQL_USER;
+global $JOB_ARCHIVE_SQL_PASSWORD;
+global $skan_str;
+global $x_first, $y_first;
+global $CLUSTER_CONFS;
+
+$my_dir = getcwd();
+
+// Load Ganglia's PHP
+chdir( $GANGLIA_PATH );
+
+include_once "./eval_conf.php";
+include_once "./functions.php";
+include_once "./get_context.php";
+
+global $GLOBALS, $conf, $metrics, $version, $rrdtool_version, $context;
+
+// ganglia start
+$metrics = array();
+$version = array();
+
+$version["webfrontend"] = $GLOBALS["ganglia_version"];
+
+# Get rrdtool version
+$rrdtool_version = array();
+exec($conf['rrdtool'], $rrdtool_version);
+$rrdtool_version = explode(" ", $rrdtool_version[0]);
+$rrdtool_version = $rrdtool_version[1];
+$version["rrdtool"] = "$rrdtool_version";
+
+// ganglia end
+
+$context = 'cluster';
+
+// Back to our PHP
+chdir( $my_dir );
+
+global $SMALL_CLUSTERIMAGE_MAXWIDTH, $SMALL_CLUSTERIMAGE_NODEWIDTH, $DATA_SOURCE, $HTTP_GET_VARS, $_GET;
+$httpvars = new HTTPVariables( $HTTP_GET_VARS, $_GET );
+
+// Set cluster context so that Ganglia will
+// provide us with the correct metrics array
+//
+global $context, $clustername, $reports;
+
+global $default_metric;
+
+// Ganglia's array of host metrics
+//
+global $hosts_up;
+global $start;
+
+global $DATETIME_FORMAT;
+
+function makeDate( $time )
+{
+    global $DATETIME_FORMAT;
+    return strftime( $DATETIME_FORMAT, $time );
+}
+
+
+class TarchDbase
+{
+    var $ip, $dbase, $conn;
+
+    function TarchDbase( $ip = null, $dbase = null, $user =null, $password=null )
+    {
+        global $CLUSTER_CONFS, $clustername;
+        global $JOB_ARCHIVE_DBASE;
+
+        // Import cluster specific settings
+        //
+        foreach( $CLUSTER_CONFS as $confcluster => $conffile )
+        {
+            if( strtolower( trim($this->clustername) ) == strtolower(trim($confcluster)) )
+            {
+                include_once $conffile;
+            }
+        }
+
+        $db_fields = explode( '/', $JOB_ARCHIVE_DBASE );
+
+        $this->ip    = $db_fields[0];
+        $this->dbase    = $db_fields[1];
+        $this->conn    = null;
+    }
+
+    function connect()
+    {
+        global $JOB_ARCHIVE_SQL_USER, $JOB_ARCHIVE_SQL_PASSWORD;
+
+        $connect_args ='';
+        if( $this->ip == null )
+        {
+            $connect_args .= "dbname=".$this->dbase;
+        }
+        else
+        {
+            $connect_args .= "host=".$this->ip." dbname=".$this->dbase;
+        }
+        if( isset($JOB_ARCHIVE_SQL_USER) )
+        {
+            $connect_args .= " user=".$JOB_ARCHIVE_SQL_USER;
+        }
+        if( isset($JOB_ARCHIVE_SQL_PASSWORD) )
+        {
+            $connect_args .= " password=".$JOB_ARCHIVE_SQL_PASSWORD;
+        }
+
+
+        $this->conn = pg_connect( $connect_args );
+
+    }
+
+    function searchDbase( $id = null, $queue = null, $owner = null, $name = null, $start_from_time = null, $start_to_time = null, $end_from_time = null, $end_to_time = null )
+    {
+        global $SEARCH_RESULT_LIMIT;
+
+        if( $id )
+        {
+            $select_query = "SELECT job_id FROM jobs WHERE job_id = '$id' AND job_status = 'F'";
+            $this->resultcount = 1;
+        }
+        else
+        {
+            $query_args = array();
+            
+            if( $queue )
+            {
+                $query_args[] = "job_queue ='$queue'";
+            }
+            if( $owner )
+            {
+                $query_args[] = "job_owner ='$owner'";
+            }
+            if( $name )
+            {
+                $query_args[] = "job_name = '$name'";
+            }
+            if( $start_from_time )
+            {
+                $query_args[] = "CAST(job_start_timestamp as INT) >= $start_from_time";
+            }
+            if( $start_to_time )
+            {
+                $query_args[] = "CAST(job_start_timestamp as INT) <= $start_to_time";
+            }
+            if( $end_from_time )
+            {
+                $query_args[] = "CAST(job_stop_timestamp as INT) >= $end_from_time";
+            }
+            if( $end_to_time )
+            {
+                $query_args[] = "CAST(job_stop_timestamp as INT) <= $end_to_time";
+            }
+
+            $query = "FROM jobs WHERE job_status = 'F' AND ";
+            $extra_query_args = '';
+
+            foreach( $query_args as $myquery )
+            {
+                if( $extra_query_args == '' )
+                {
+                    $extra_query_args = $myquery;
+                }
+                else
+                {
+                    $extra_query_args .= " AND ".$myquery;
+                }
+            }
+            $query .= $extra_query_args;
+
+            $count_result_idname = "COUNT(job_id)";
+            $select_result_idname = "job_id";
+
+            $count_query = "SELECT " . $count_result_idname . " " . $query;
+
+            $count_result = $this->queryDbase( $count_query );
+            $this->resultcount = (int) $count_result[0]['count'];
+
+            $select_query = "SELECT " . $select_result_idname . " " . $query . " ORDER BY job_id DESC LIMIT " . $SEARCH_RESULT_LIMIT;
+        }
+
+        $ids = $this->queryDbase( $select_query );
+
+        //print_r( $ids );
+
+        $ret = array();
+
+        foreach( $ids as $crow)
+        {
+            $ret[] = $crow['job_id'];
+        }
+
+        return $ret;
+    }
+
+    function getNodesForJob( $jobid )
+    {
+        $result = $this->queryDbase( "SELECT node_id FROM job_nodes WHERE job_id = '$jobid'" );
+
+        $nodes = array();
+
+        foreach( $result as $result_row ) 
+        {
+            $nodes[] = $this->getNodeArray( $result_row['node_id'] );
+        }
+
+        return $nodes;
+    }
+
+    function getJobsForNode( $nodeid )
+    {
+        $result = $this->queryDbase( "SELECT job_id FROM job_nodes WHERE node_id = '$nodeid'" );
+
+        $jobs = array();
+
+        foreach( $result as $result_row )
+        {
+            $jobs[] = $this->getJobArray( $result_row['job_id'] );
+        }
+        return $jobs;
+    }
+
+    function getJobArray( $id )
+    {
+        $result = $this->queryDbase( "SELECT * FROM jobs WHERE job_id = '$id'" );
+
+        return ( $this->makeArray( $result[0] ) );
+    }
+
+    function getNodeArray( $id )
+    {
+        $result = $this->queryDbase( "SELECT * FROM nodes WHERE node_id = '$id'" );
+
+        return ( $this->makeArray( $result[0] ) );
+    }
+
+    function makeArray( $result_row )
+    {
+        $myar = array();
+
+        foreach( $result_row as $mykey => $myval )
+        {
+            $map_key = explode( '_', $mykey );
+
+            $rmap_key = array_reverse( $map_key );
+            array_pop( $rmap_key );
+            $map_key = array_reverse( $rmap_key );
+            
+            $newkey = implode( '_', $map_key );
+            
+            $myar[$newkey] = $result_row[$mykey];
+        }
+
+        return $myar;
+    }
+
+    function queryDbase( $query )
+    {
+        $result_rows = array();
+   
+        if( $this->conn == null )
+        {
+            $this->connect();
+        }
+
+        if( $this->conn == null )
+        {
+            printf(" no connection!\n");
+        }
+        $result = pg_query( $this->conn, $query );
+
+        while ($row = pg_fetch_assoc($result))
+        {
+            //print_r( $row );
+            $result_rows[] = $row;
+        }
+
+        return $result_rows;
+    }
+}
+
+class TarchRrdGraph
+{
+    var $rrdbin, $rrdvalues, $clustername, $hostname, $tempdir, $tarchdir, $metrics;
+
+    function TarchRrdGraph( $clustername, $hostname )
+    {
+        global $conf;
+        global $JOB_ARCHIVE_DIR;
+
+        $this->rrdbin      = $conf['rrdtool'];
+        $this->rrdvalues   = array();
+        $this->tarchdir    = $JOB_ARCHIVE_DIR;
+        $this->clustername = $clustername;
+        $this->hostname    = $hostname;
+    }
+
+    function doCmd( $command )
+    {
+        $pipe = popen( $command . ' 2>&1', 'r' );
+
+        if (!$pipe)
+        {
+            print "pipe failed.";
+            return "";
+        }
+
+        $output = '';
+
+        while(!feof($pipe))
+        {
+            $output .= fread($pipe, 1024);
+        }
+
+        pclose($pipe);
+
+        $output = explode( "\n", $output );
+
+        return $output;
+    }
+
+    function dirList( $dir )
+    {
+        $dirlist = array();
+
+        if ($handle = opendir( $dir ))
+        {
+            while (false !== ($file = readdir($handle)))
+            {
+                if ($file != "." && $file != "..")
+                {
+                    $dirlist[] = $file;
+                }
+            }
+            closedir($handle);
+        }
+
+        return $dirlist;
+    }
+
+    function getTimePeriods( $start, $end )
+    {
+        $times = array();
+        $dirlist = $this->dirList( $this->tarchdir . '/' . $this->clustername . '/' . $this->hostname );
+
+        $first = 0;
+        $last = 9999999999999;
+
+        foreach( $dirlist as $dir )
+        {
+            if( $dir > $first and $dir <= $start )
+            {
+                $first = $dir;
+            }
+            if( $dir < $last and $dir >= $end )
+            {
+                $last = $dir;
+            }
+        }
+
+        foreach( $dirlist as $dir )
+        {
+            if( $dir >= $first and $dir <= $last and !array_key_exists( $dir, $times ) )
+            {
+                $times[] = $dir;
+            }
+        }
+
+        sort( $times );
+
+        return $times;
+    }
+
+    function getRrdDirs( $start, $stop )
+    {
+        $timess = $this->getTimePeriods( $start, $stop );
+
+        $rrd_files = array();
+
+        foreach( $timess as $time )
+        {
+            $rrd_files[] = $this->tarchdir . '/' . $this->clustername . '/' . $this->hostname. '/'.$time;
+        }
+
+        return $rrd_files;
+    }
+
+    function getRrdFiles( $metric, $start, $stop )
+    {
+        $times = $this->getTimePeriods( $start, $stop );
+
+        $rrd_files = array();
+
+        foreach( $times as $time )
+        {
+            $rrd_files[] = $this->tarchdir . '/' . $this->clustername . '/' . $this->hostname . '/' .$time. '/' . $metric. '.rrd';
+        }
+
+        return $rrd_files;
+    }
+}
+
+class DataSource
+{
+    var $data, $ip, $port;
+
+    function DataSource()
+    {
+        global $DATA_SOURCE, $clustername;
+
+        $ds_fields     = explode( ':', $DATA_SOURCE );
+
+        $ds_ip         = $ds_fields[0];
+        $ds_port       = $ds_fields[1];
+
+        $this->ip       = $ds_ip;
+        $this->port     = $ds_port;
+        $this->clustername = $clustername;
+    }
+
+    function getData()
+    {
+        $errstr        = '';
+        $errno         = 0;
+        $timeout       = 3;
+
+        $fp = fsockopen( $this->ip, $this->port, $errno, $errstr, $timeout );
+
+        if( !$fp )
+        {
+            echo 'Unable to connect to '.$this->ip.':'.$this->port;
+            return;
+        }
+
+        if( $this->port == '8652' )
+        {
+            $request = "/" . $this->clustername . "\n";
+            $rc = fputs($fp, $request);
+            if (!$rc)
+            {
+                $error = "Could not sent request to gmetad: $errstr";
+                if ($debug) print "<br/>DEBUG: $error\n";
+                   return FALSE;
+            }
+        }
+
+        stream_set_timeout( $fp, 30 );
+
+        while ( !feof( $fp ) )
+        {
+            $data .= fread( $fp, 16384 );
+        }
+
+        fclose( $fp );
+
+        return $data;
+    }
+}
+
+class DataGatherer
+{
+    var $xmlhandler, $data, $httpvars;
+
+    function DataGatherer( $cluster )
+    {
+        $this->cluster    = $cluster;
+        $this->httpvars = $httpvars;
+    }
+
+    function parseXML( $data )
+    {
+
+        $this->parser         = xml_parser_create();
+        $this->xmlhandler     = new TorqueXMLHandler( $this->cluster );
+
+        xml_parser_set_option( $this->parser, XML_OPTION_CASE_FOLDING, 0 );
+        xml_set_element_handler( $this->parser, array( &$this->xmlhandler, 'startElement' ), array( &$this->xmlhandler, 'stopElement' ) );
+
+        if ( !xml_parse( $this->parser, $data ) )
+        {
+            $error = sprintf( 'XML error: %s at %d', xml_error_string( xml_get_error_code( $this->parser ) ), xml_get_current_line_number( $this->parser ) );
+        }
+        $handler = &$this->xmlhandler;
+        $handler->finishUp();
+    }
+
+    function printInfo()
+    {
+        $handler = $this->xmlhandler;
+        $handler->printInfo();
+    }
+
+    function getUsingFQDN()
+    {
+        $handler = $this->xmlhandler;
+        return $handler->getUsingFQDN();
+    }
+
+    function getMetrics()
+    {
+        $handler = $this->xmlhandler;
+        return $handler->getMetrics();
+    }
+    function getNodes()
+    {
+        $handler = $this->xmlhandler;
+        return $handler->getNodes();
+    }
+
+    function getNode( $node )
+    {
+        $handler = $this->xmlhandler;
+        return $handler->getNode( $node );
+    }
+
+    function getCpus()
+    {
+        $handler = $this->xmlhandler;
+        return $handler->getCpus();
+    }
+
+    function getJobs()
+    {
+        $handler = $this->xmlhandler;
+        return $handler->getJobs();
+    }
+
+    function getJob( $job )
+    {
+        $handler = $this->xmlhandler;
+        return $handler->getJob( $job );
+    }
+
+    function getHeartbeat()
+    {
+        $handler = $this->xmlhandler;
+        return $handler->getHeartbeat();
+    }
+
+    function isJobmonRunning()
+    {
+        $handler = $this->xmlhandler;
+        return $handler->isJobmonRunning();
+    }
+}
+
+class TorqueXMLHandler
+{
+    var $clusters, $heartbeat, $nodes, $jobs, $clustername, $proc_cluster;
+
+    function TorqueXMLHandler( $clustername )
+    {
+        $this->jobs        = array();
+        $this->clusters     = array();
+        $this->nodes         = array();
+        $this->metrics      = array();
+        $this->heartbeat     = array();
+        $this->down_nodes    = array();
+        $this->offline_nodes    = array();
+        $this->clustername    = $clustername;
+        $this->proc_cluster = null;
+        $this->proc_hostname = null;
+        $this->fqdn        = 1;
+    }
+
+    function getUsingFQDN()
+    {
+        return $this->fqdn;
+    }
+
+    function getCpus()
+    {
+        $cpus = 0;
+
+        if( isset( $this->jobs ) && count( $this->jobs ) > 0 )
+        {
+            foreach( $this->jobs as $jobid=>$jobattrs )
+            {
+                $nodes    = count( $jobattrs['nodes'] );
+                $ppn    = (int) $jobattrs['ppn'] ? $jobattrs['ppn'] : 1;
+                $mycpus    = $nodes * $ppn;
+
+                $cpus    = $cpus + $mycpus;
+            }
+        }
+    }
+
+    function isJobmonRunning()
+    {
+        if (isset( $this->heartbeat['time'] ))
+        {
+            return 1;
+        }
+        else
+        {
+            return 0;
+        }
+    }
+
+    function makeHostname( $thostname, $tdomain=null )
+    {
+        // Should hostname be FQDN or short w/o domain
+        //
+        $nodes = &$this->nodes;
+
+        $fqdn = 1;
+
+        //$tdomain = explode( '.', $thostname );
+        //
+        // TODO?: extract domain from hostname or something?
+
+        if( $tdomain )
+        {
+            $domain_len    = 0 - strlen( $tdomain );
+
+            // Let's see if Ganglia use's FQDN or short hostnames
+            //
+            foreach( $nodes as $hostname => $nimage )
+            {
+                if( strpos( $hostname, $tomdain ) !== false )
+                {
+                    $fqdn    = 0;
+                }
+            }
+        }
+        else
+        {
+            $fqdn    = 0;
+        }
+    
+        if( $tdomain && $fqdn )
+        {
+            if( strpos( $thostname, $tdomain ) !== false )
+            {
+                $thostname = $thostname . '.'.$tdomain;
+            } 
+            else
+            {
+                $thostname = $thostname;
+            }
+        }
+
+        return $thostname;
+    }
+
+    function startElement( $parser, $name, $attrs )
+    {
+        global $cluster, $metrics, $self, $grid;
+
+        $jobid = null;
+
+        if( $name == 'GRID' )
+        {
+            $self = $attrs['NAME'];
+            $grid = $attrs;
+
+            return null;
+        }
+
+        if( $name == 'CLUSTER' )
+        {
+            // ganglia start
+            $cluster = $attrs;
+            // ganglia end
+
+            $this->proc_cluster = $attrs['NAME'];
+            //printf("set proc cluster to %s\n", $attrs['NAME'] );
+            return null;
+        }
+        if( $this->proc_cluster != $this->clustername )
+        {
+            //printf("cluster does not match: %s\n", $this->clustername );
+            return null;
+        }
+
+        if( $name == 'HOST' )
+        {
+            // ganglia start
+            $hostname = $attrs['NAME'];
+            $this->proc_hostname = $hostname;
+
+            # Pseudo metrics - add useful HOST attributes like gmond_started & last_reported to the metrics list:
+            $metrics[$hostname]['gmond_started']['NAME'] = "GMOND_STARTED";
+            $metrics[$hostname]['gmond_started']['VAL'] = $attrs['GMOND_STARTED'];
+            $metrics[$hostname]['gmond_started']['TYPE'] = "timestamp";
+            $metrics[$hostname]['last_reported']['NAME'] = "REPORTED";
+            $metrics[$hostname]['last_reported']['VAL'] = $attrs['REPORTED'];
+            $metrics[$hostname]['last_reported']['TYPE'] = "string";
+            $metrics[$hostname]['ip_address']['NAME'] = "IP";
+            $metrics[$hostname]['ip_address']['VAL'] = $attrs['IP'];
+            $metrics[$hostname]['ip_address']['TYPE'] = "string";
+            $metrics[$hostname]['location']['NAME'] = "LOCATION";
+            $metrics[$hostname]['location']['VAL'] = $attrs['LOCATION'];
+            $metrics[$hostname]['location']['TYPE'] = "string";
+            // ganglia end
+
+            //printf( "host %s\n", $hostname );
+            //$location = $attrs['LOCATION'];
+
+            if( !isset( $this->nodes[$hostname] ) )
+            {
+                $this->nodes[$hostname] = new NodeImage( $this->proc_cluster, $hostname );
+            }
+            return null;
+        }
+
+        if( $name == 'METRIC' )
+        {
+            $hostname = $this->proc_hostname;
+
+            // ganglia start
+            $metricname = rawurlencode($attrs['NAME']);
+            $metrics[$hostname][$metricname] = $attrs;
+            // ganglia end
+
+            if ( strpos( $attrs['NAME'], 'zplugin_monarch' ) === false )
+            {
+                return null;
+            }
+
+            if( strpos( $attrs['NAME'], 'zplugin_monarch_heartbeat' ) !== false )
+            {
+                $this->heartbeat['time'] = $attrs['VAL'];
+                return;
+            }
+            else if( strpos( $attrs['NAME'], 'zplugin_monarch_down' ) !== false )
+            {
+                $fields        = explode( ' ', $attrs['VAL'] );
+
+                $nodes_down    = array();
+                $down_domain    = null;
+
+                foreach( $fields as $f )
+                {
+                    $togavalues    = explode( '=', $f );
+
+                    $toganame    = $togavalues[0];
+                    $togavalue    = $togavalues[1];
+
+                    if( $toganame == 'nodes' )
+                    {
+                        $mynodes = explode( ';', $togavalue );
+
+                        foreach( $mynodes as $node )
+                        {
+                            $nodes_down[] = $node;
+                        }
+                    }
+                    else if( $toganame == 'domain' )
+                    {
+                        $down_domain = $togavalue;
+                    }
+                    else if( $toganame == 'reported' )
+                    {
+                        if( !isset( $this->down_nodes['heartbeat'] ) )
+                        {
+                            $this->down_nodes[$togavalue]    = array( $nodes_down, $down_domain );
+                        }
+                    }
+                }
+                return;
+            }
+            else if( strpos( $attrs['NAME'], 'zplugin_monarch_offline' ) !== false )
+            {
+                $fields        = explode( ' ', $attrs['VAL'] );
+
+                $nodes_offline    = array();
+                $offline_domain    = null;
+
+                foreach( $fields as $f )
+                {
+                    $togavalues    = explode( '=', $f );
+
+                    $toganame    = $togavalues[0];
+                    $togavalue    = $togavalues[1];
+
+                    if( $toganame == 'nodes' )
+                    {
+                        $mynodes = explode( ';', $togavalue );
+
+                        foreach( $mynodes as $node )
+                        {
+                            $nodes_offline[] = $node;
+                        }
+                    }
+                    else if( $toganame == 'domain' )
+                    {
+                        $offline_domain = $togavalue;
+                    }
+                    else if( $toganame == 'reported' )
+                    {
+                        if( !isset( $this->offline_nodes['heartbeat'] ) )
+                        {
+                            $this->offline_nodes[$togavalue] = array( $nodes_offline, $offline_domain );
+                        }
+                    }
+                }
+                return;
+            }
+            else if( strpos( $attrs['NAME'], 'zplugin_monarch_job' ) !== false )
+            {
+                sscanf( $attrs['NAME'], 'zplugin_monarch_job_%d_%s$', $monincr, $jobid );
+
+                if( !isset( $this->jobs[$jobid] ) )
+                {
+                    $this->jobs[$jobid] = array();
+                }
+
+                $fields = explode( ' ', $attrs['VAL'] );
+
+                foreach( $fields as $f )
+                {
+                    $togavalues = explode( '=', $f );
+
+                    $toganame = $togavalues[0];
+                    $togavalue = $togavalues[1];
+
+                    if( $toganame == 'nodes' )
+                    {
+                        if( $this->jobs[$jobid]['status'] == 'R' )
+                        {
+                            if( !isset( $this->jobs[$jobid][$toganame] ) )
+                            {
+                                $this->jobs[$jobid][$toganame] = array();
+                            }
+
+                            $mynodes = explode( ';', $togavalue );
+
+                            foreach( $mynodes as $node )
+                            {
+                                if( !in_array( $node, $this->jobs[$jobid][$toganame] ) )
+                                {
+                                    array_push( $this->jobs[$jobid][$toganame], $node );
+                                }
+                            }
+
+                        }
+                        else if( $this->jobs[$jobid]['status'] == 'Q' )
+                        {
+                            $this->jobs[$jobid][$toganame] = $togavalue;
+                        }
+                    }
+                    else
+                    {
+                        $this->jobs[$jobid][$toganame] = $togavalue;
+                    }
+                }
+
+                if( isset( $this->jobs[$jobid]['nodes'] ) )
+                {
+                    $nr_nodes = count( $this->jobs[$jobid]['nodes'] );
+        
+                    if( $this->jobs[$jobid]['status'] == 'R' )
+                    {
+
+                        if( isset( $this->jobs[$jobid]['domain'] ) )
+                        {
+                            $domain        = $this->jobs[$jobid]['domain'];
+                            $domain_len    = 0 - strlen( $domain );
+
+                            $first_host    = key( array_slice($this->nodes, 0, 1, true) );
+
+                            // Let's see if Ganglia use's FQDN or short hostnames
+                            //
+                            if( strpos( $first_host, $domain ) === false )
+                            {
+                                $this->fqdn    = 0;
+                            }
+                        }
+                        else
+                        {
+                            $this->fqdn    = 0;
+                        }
+
+                        foreach( $this->jobs[$jobid]['nodes'] as $node )
+                        {
+
+                            // Only add domain name to the hostname if Ganglia is doing that too
+                            //
+                            if( $this->fqdn && isset( $this->jobs[$jobid]['domain'] ) )
+                            {
+                                if( strpos( $node, $domain ) === false )
+                                {
+                                    $host = $node. '.'.$domain;
+                                } else
+                                {
+                                    $host = $node;
+                                }
+                            }
+                            else
+                            {
+                                $host    = $node;
+                            }
+
+                            if( !isset( $this->nodes[$host] ) )
+                            {
+                                $my_node = new NodeImage( $this->proc_cluster, $host );
+                            }
+                            else
+                            {
+                                $my_node = $this->nodes[$host];
+                            }
+
+                            if( !$my_node->hasJob( $jobid ) )
+                            {
+                                if( isset( $jobs[$jobid]['ppn'] ) )
+                                {
+                                    $my_node->addJob( $jobid, ((int) $jobs[$jobid]['ppn']) );
+                                }
+                                else
+                                {
+                                    $my_node->addJob( $jobid, 1 );
+                                }
+                            }
+
+                            $this->nodes[$host] = $my_node;
+                        }
+                    }
+                }
+            }
+            return;
+        }
+    }
+
+    function finishUp( )
+    {
+        $nodes    = $this->nodes;
+
+        if( sizeof( $this->down_nodes ) > 0 )
+        {
+            foreach( $this->down_nodes as $reported => $dnodes )
+            {
+                if( $reported == $this->heartbeat['time'] )
+                {
+                    $domain = $dnodes[1];
+
+                    foreach( $dnodes[0] as $downhost )
+                    {
+                        $downhost = $this->makeHostname( $downhost, $domain );
+
+                        if( isset( $nodes[$downhost] ) )
+                        {
+                            // OMG PHP4 is fking stupid!
+                            // $nodes[$downhost]->setDown( 1 ) won't work here..
+                            //
+                            $mynode = $nodes[$downhost];
+                            $mynode->setDown( 1 );
+                            $nodes[$downhost] = $mynode;
+                        }
+                    }
+                }
+            }
+        }
+
+        if( sizeof( $this->offline_nodes ) > 0 )
+        {
+            foreach( $this->offline_nodes as $reported => $onodes )
+            {
+                if( $reported == $this->heartbeat['time'] )
+                {
+                    $domain = $onodes[1];
+
+                    foreach( $onodes[0] as $offlinehost )
+                    {
+                        $offlinehost = $this->makeHostname( $offlinehost, $domain );
+
+                        if( isset( $nodes[$offlinehost] ) )
+                        {
+                            // OMG PHP4 is fking stupid!
+                            // $nodes[$offlinehost]->setDown( 1 ) won't work here..
+                            //
+                            $mynode = $nodes[$offlinehost];
+                            $mynode->setOffline( 1 );
+                            $nodes[$offlinehost] = $mynode;
+                        }
+                    }
+                }
+            }
+        }
+
+        $this->nodes = $nodes;
+    }
+
+    function stopElement( $parser, $name )
+    {
+    }
+
+    function printInfo()
+    {
+        $jobs = &$this->jobs;
+
+        printf( "---jobs---\n" );
+
+        foreach( $jobs as $jobid => $job )
+        {
+            printf( "job %s\n", $jobid );
+
+            if( isset( $job['nodes'] ) )
+            {
+                foreach( $job['nodes'] as $node )
+                {
+                    $mynode = $this->nodes[$node];
+                    $hostname = $mynode->getHostname();
+                    $location = $mynode->getLocation();
+
+                    printf( "\t- node %s\tlocation %s\n", $hostname, $location );
+                }
+            }
+        }
+
+        printf( "---nodes---\n" );
+
+        $nodes = &$this->nodes;
+
+        foreach( $nodes as $node )
+        {
+            $hostname = $node->getHostname();
+            $location = $node->getLocation();
+            $jobs = implode( ' ', $node->getJobs() );
+            printf( "* node %s\tlocation %s\tjobs %s\n", $hostname, $location, $jobs );
+        }
+    }
+
+    function getNodes()
+    {
+        return $this->nodes;
+    }
+
+    function getNode( $node )
+    {
+        $nodes = &$this->nodes;
+
+        if( isset( $nodes[$node] ) )
+        {
+            return $nodes[$node];
+        }
+        else
+        {
+            return NULL;
+        }
+    }
+
+    function getJobs()
+    {
+        return $this->jobs;
+    }
+
+    function getJob( $job )
+    {
+        $jobs = &$this->jobs;
+
+        if( isset( $jobs[$job] ) )
+        {
+            return $jobs[$job];
+        }
+        else
+        {
+            return NULL;
+        }
+    }
+
+    function getHeartbeat()
+    {
+        return $this->heartbeat['time'];
+    }
+}
+
+class NodeImage
+{
+    var $image, $x, $y, $hostname, $jobs, $tasks, $showinfo;
+
+    function NodeImage( $cluster, $hostname )
+    {
+        global $SMALL_CLUSTERIMAGE_NODEWIDTH;
+
+        $this->jobs        = array();
+        $this->tasks        = 0;
+        $this->hostname        = $hostname;
+        $this->clustername    = $cluster;
+        $this->showinfo        = 1;
+        $this->size        = $SMALL_CLUSTERIMAGE_NODEWIDTH;
+        $this->down        = 0;
+        $this->offline        = 0;
+    }
+
+    function addJob( $jobid, $cpus )
+    {
+        $jobs        = &$this->jobs;
+        $jobs[]        = $jobid;
+        $this->jobs    = $jobs;
+
+        $this->addTask( $cpus );
+    }
+
+    function hasJob( $jobid )
+    {
+        $jobfound = 0;
+
+        if( count( $this->jobs ) > 0 )
+        {
+            foreach( $this->jobs as $job )
+            {
+                if( $job == $jobid )
+                {
+                    $jobfound = 1;
+                }
+            }
+        }
+
+        return $jobfound;
+    }
+
+    function addTask( $cpus )
+    {
+        $this->tasks = $this->tasks + $cpus;
+    }
+    function setDown( $down )
+    {
+        $this->down = $down;
+    }
+    function isDown()
+    {
+        return $this->down;
+    }
+    function setOffline( $offline )
+    {
+        $this->offline = $offline;
+    }
+    function isOffline()
+    {
+        return $this->offline;
+    }
+    function setImage( $image )
+    {
+        $this->image = $image;
+    }
+    function setCoords( $x, $y )
+    {
+        $this->x = $x;
+        $this->y = $y;
+    }
+    function getX()
+    {
+        return $this->x;
+    }
+    function getY()
+    {
+        return $this->y;
+    }
+
+    function getImagemapArea()
+    {
+        $area_topleft        = $this->x . "," . $this->y;
+        $area_bottomright    = ($this->x + $this->size) . "," . ($this->y + $this->size);
+        $area_coords        = $area_topleft . "," . $area_bottomright;
+
+        $area_href        = "./?c=" . $this->clustername . "&h=" . $this->hostname;
+
+        $area_tooltip        = $this->hostname;
+
+        if( $this->down)
+        {
+            $area_tooltip        = $area_tooltip . ": DOWN";
+        }
+        else if( $this->offline )
+        {
+            $area_tooltip        = $area_tooltip . ": OFFLINE";
+        }
+
+        $area_tooltip        = $area_tooltip . ": " . implode( " ", $this->jobs );
+
+        $tag_href        = "HREF=\"" . $area_href . "\"";
+        $tag_coords        = "COORDS=\"" . $area_coords . "\"";
+        $tag_tooltip1        = "ALT=\"" . $area_tooltip . "\"";
+        $tag_tooltip2        = "TITLE=\"" . $area_tooltip . "\"";
+
+        return ("<AREA SHAPE=\"RECT\" " . $tag_coords . " " . $tag_href . " " . $tag_tooltip1 . " " . $tag_tooltip2 . ">");
+    }
+
+    function colorHex( $color )
+    {
+        $my_color = imageColorAllocate( $this->image, hexdec( substr( $color, 0, 2 )), hexdec( substr( $color, 2, 2 )), hexdec( substr( $color, 4, 2 )) );
+
+        return $my_color;
+    }
+
+    function setLoad( $load )
+    {
+        $this->load = $load;
+    }
+
+    function setHostname( $hostname )
+    {
+        $this->hostname = $hostname;
+    }
+
+    function getHostname()
+    {
+        return $this->hostname;
+    }
+
+    function getJobs()
+    {
+        return $this->jobs;
+    }
+
+    function setShowinfo( $showinfo )
+    {
+        $this->showinfo = $showinfo;
+    }
+
+    function drawSmall()
+    {
+        global $SMALL_CLUSTERIMAGE_NODEWIDTH;
+
+        $this->size    = $SMALL_CLUSTERIMAGE_NODEWIDTH;
+
+        $this->draw();
+    }
+
+    function drawBig()
+    {
+        global $BIG_CLUSTERIMAGE_NODEWIDTH;
+
+        $this->size    = $BIG_CLUSTERIMAGE_NODEWIDTH;
+
+        $this->drawShadow();
+        $this->draw();
+    }
+
+    function drawShadow()
+    {
+        // offset of drop shadow from top left
+        //
+        $ds_offset  = 5;
+
+        // number of steps from black to background color
+        //
+        $ds_steps   = 15;
+
+        // distance between steps
+        //
+        $ds_spread = 1;
+
+        // define the background color
+        //
+        $background = array("r" => 255, "g" => 255, "b" => 255);
+
+        // create a new canvas.  New canvas dimensions should be larger than the original's
+        //
+        $width  = $this->size + $ds_offset;
+        $height = $this->size + $ds_offset;
+
+        // determine the offset between colors
+        //
+        $step_offset = array("r" => ($background['r'] / $ds_steps), "g" => ($background['g'] / $ds_steps), "b" => ($background['b'] / $ds_steps));
+
+        // calculate and allocate the needed colors
+        //
+        $current_color = $background;
+
+        for ($i = 0; $i <= $ds_steps ; $i++)
+        {
+            $colors[$i] = imagecolorallocate($this->image, round($current_color['r']), round($current_color['g']), round($current_color['b']));
+
+            $current_color['r'] -= $step_offset['r'];
+            $current_color['g'] -= $step_offset['g'];
+            $current_color['b'] -= $step_offset['b'];
+        }
+
+        // draw overlapping rectangles to create a drop shadow effect
+        //
+        for ($i = 3; $i < count($colors); $i++)
+        {
+            imagefilledrectangle( $this->image, ($this->x + $ds_offset), ($this->y + $ds_offset), ($this->x + $width), ($this->y + $height), $colors[$i] );
+            $width -= $ds_spread;
+            $height -= $ds_spread;
+        }
+    }
+
+    function draw()
+    {
+        global $JOB_NODE_MARKING, $NODE_DOWN_MARKING, $NODE_OFFLINE_MARKING;
+
+        $black_color = imageColorAllocate( $this->image, 0, 0, 0 );
+        $size = $this->size;
+
+        imageFilledRectangle( $this->image, $this->x, $this->y, $this->x+($size), $this->y+($size), $black_color );
+
+        if( $this->showinfo)
+        {
+            $this->load = $this->determineLoad();
+
+            if( !isset( $this->image ) or !isset( $this->x ) or !isset( $this->y ) )
+            {
+                printf( "aborting\n" );
+                printf( "x %d y %d load %f\n", $this->x, $this->y, $load );
+                return;
+            }
+
+            // Convert Ganglias Hexadecimal load color to a Decimal one
+            //
+            $load = $this->determineLoad();    
+            $usecolor = $this->colorHex( load_color($load) );
+            imageFilledRectangle( $this->image, $this->x+1, $this->y+1, $this->x+($size-1), $this->y+($size-1), $usecolor );
+            if( $this->down )
+            {
+                imageString( $this->image, 1, $this->x+(($size/2)-1), $this->y+(($size/2)-4), $NODE_DOWN_MARKING, $black_color );
+            }
+            else if( $this->offline )
+            {
+                imageString( $this->image, 1, $this->x+(($size/2)-1), $this->y+(($size/2)-4), $NODE_OFFLINE_MARKING, $black_color );
+            }
+            else if( count( $this->jobs ) > 0 )
+            {
+                imageString( $this->image, 1, $this->x+(($size/2)-1), $this->y+(($size/2)-4), $JOB_NODE_MARKING, $black_color );
+            }
+        }
+        else
+        {
+            // White
+            $usecolor = imageColorAllocate( $this->image, 255, 255, 255 );
+            imageFilledRectangle( $this->image, $this->x+1, $this->y+1, $this->x+($size-1), $this->y+($size-1), $usecolor );
+        }
+    }
+
+    function determineLoad()
+    {
+        global $metrics;
+
+        $cpus = $metrics[$this->hostname]['cpu_num']['VAL'];
+        if (!$cpus)
+        {
+            $cpus=1;
+        }
+
+        $load_one    = $metrics[$this->hostname]['load_one']['VAL'];
+        $load        = ((float) $load_one)/$cpus;
+
+        return $load;
+    }
+}
+
+class ClusterImage
+{
+    var $dataget, $image, $clustername;
+    var $filtername, $filters;
+
+    function ClusterImage( $data, $clustername )
+    {
+        $this->dataget      = new DataGatherer( $clustername );
+        $this->data         = $data;
+        $this->clustername  = $clustername;
+        $this->filters      = array();
+        $this->size         = 's';
+        $this->width        = 0;
+        $this->height       = 0;
+        $this->output       = 1;
+        $this->jobs         = null;
+        $this->nodes        = null;
+        $this->parsed       = false;
+    }
+
+    function getWidth()
+    {
+        return $this->width;
+    }
+    function getHeight()
+    {
+        return $this->height;
+    }
+    function setSmall()
+    {
+        $this->size    = 's';
+    }
+    function setBig()
+    {
+        $this->size    = 'b';
+    }
+    function setNoimage()
+    {
+        $this->output    = 0;
+    }
+    function checkParse()
+    {
+        if( $this->parsed == false )
+        {
+            $this->dataget->parseXML( $this->data );
+            $this->parsed = true;
+        }
+    }
+    function getJobs()
+    {
+        if( $this->jobs == null )
+        {
+            $this->checkParse();
+            $mydatag = $this->dataget;
+            $this->jobs = $mydatag->getJobs();
+        }
+
+        return $this->jobs;
+    }
+    function setJobs($jobs)
+    {
+        $this->jobs   = &$jobs;
+    }
+    function getNodes()
+    {
+        if( $this->nodes== null )
+        {
+            $this->checkParse();
+            $mydatag = $this->dataget;
+            $this->nodes = $mydatag->getNodes();
+        }
+
+        return $this->nodes;
+    }
+    function setNodes($nodes)
+    {
+        $this->nodes    = &$nodes;
+    }
+    function isSmall()
+    {
+        return ($this->size == 's');
+    }
+    function isBig()
+    {
+        return ($this->size == 'b');
+    }
+    function setFilter( $filtername, $filtervalue )
+    {
+        $this->filters[$filtername] = $filtervalue;
+    }
+
+    function filterNodes( $jobs, $nodes )
+    {
+        $filtered_nodes = array();
+
+        //print_r( $nodes );
+
+        foreach( $nodes as $node )
+        {
+            $hostname = $node->getHostname();
+
+            $addhost = 1;
+
+            if( count( $this->filters ) > 0 )
+            {
+                $mynjobs = $node->getJobs();
+
+                if( count( $mynjobs ) > 0 )
+                {
+                    foreach( $mynjobs as $myjob )
+                    {
+                        foreach( $this->filters as $filtername => $filtervalue )
+                        {
+                            if( $filtername!=null && $filtername!='' )
+                            {
+                                if( $filtername == 'jobid' && !$node->hasJob( $filtervalue) )
+                                {
+                                    $addhost = 0;
+                                }
+                                else if( $filtername != 'jobid' )
+                                {
+                                    if( $jobs[$myjob][$filtername] != $filtervalue )
+                                    {
+                                        $addhost = 0;
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+                else
+                {
+                    $addhost = 0;
+                }
+            }
+
+            if( $addhost )
+            {
+                $filtered_nodes[] = $hostname;
+            }
+        }
+
+        return $filtered_nodes;
+    }
+
+    function draw()
+    {
+        global $SMALL_CLUSTERIMAGE_MAXWIDTH, $SMALL_CLUSTERIMAGE_NODEWIDTH;
+        global $BIG_CLUSTERIMAGE_MAXWIDTH, $BIG_CLUSTERIMAGE_NODEWIDTH;
+        global $CLUSTER_CONFS, $confcluster, $SHOW_EMPTY_COLUMN, $SHOW_EMPTY_ROW;
+
+        global $SORTBY_HOSTNAME, $SORT_ORDER, $skan_str;
+        global $x_first, $y_first;
+
+        foreach( $CLUSTER_CONFS as $confcluster => $conffile )
+        {
+            if( strtolower( trim($this->clustername) ) == strtolower(trim($confcluster)) )
+            {
+                include_once $conffile;
+            }
+        }
+
+        if( $this->isSmall() )
+        {
+            $max_width    = $SMALL_CLUSTERIMAGE_MAXWIDTH;
+            $node_width    = $SMALL_CLUSTERIMAGE_NODEWIDTH;
+        }
+        else if( $this->isBig() )
+        {
+            $max_width    = $BIG_CLUSTERIMAGE_MAXWIDTH;
+            $node_width    = $BIG_CLUSTERIMAGE_NODEWIDTH;
+        }
+
+        $nodes        = $this->getNodes();
+        $nodes_hosts    = array_keys( $nodes );
+
+        $nodes_nr    = count( $nodes );
+
+        $nodes_size    = $nodes_nr*$node_width;
+        $node_rows    = 0;
+
+        if( $nodes_size > $max_width )
+        {
+            $nodes_per_row = ( (int) ($max_width/$node_width) );
+        }
+        else
+        {
+            $nodes_per_row = $nodes_size;
+            $node_rows = 1;
+        }
+
+        if( $nodes_per_row < $nodes_nr )
+        {
+            $node_rows = ( (int) ($nodes_nr/$nodes_per_row) );
+            $node_rest = fmod( $nodes_nr, $nodes_per_row );
+
+            if( $node_rest > 0 )
+            {
+                $node_rows++;
+            }
+        }
+
+        $y_offset    = 0;
+        $font         = 2;
+        $fontwidth    = ImageFontWidth( $font );
+        $fontheight    = ImageFontHeight( $font );
+        $fontspaceing    = 2;
+        $y_offset    = $fontheight + (2 * $fontspaceing);
+
+        $this->width    = $max_width;
+        $this->height    = ($y_offset + (($node_rows*$node_width)+1) );
+
+        $jobs = $this->getJobs();
+        $filtered_nodes = $this->filterNodes( $jobs, $nodes );
+
+        if( $SORTBY_HOSTNAME != "" )
+        {
+                $sorted     = array();
+
+            $x_first    = 0;
+            $y_first    = 0;
+
+            $skan_str    = $SORTBY_HOSTNAME;
+
+            global $x_present, $y_present;
+            $x_present    = false;
+            $y_present    = false;
+
+            // Should we scan by X, Y or both
+            //
+            if(strpos( $SORTBY_HOSTNAME, "{x}" ) != false )
+            {
+                $x_str        = "{x}";
+                $x_present    = true;
+            }
+            else if(strpos( $SORTBY_HOSTNAME, "{X}" ) != false )
+            {
+                $x_str        = "{X}";
+                $x_present    = true;
+            }
+            if(strpos( $SORTBY_HOSTNAME, "{y}" ) != false )
+            {
+                $y_str        = "{y}";
+                $y_present    = true;
+            }
+            else if(strpos( $SORTBY_HOSTNAME, "{Y}" ) != false )
+            {
+                $y_str        = "{Y}";
+                $y_present    = true;
+            }
+
+            // If we should scan for both X and Y: see which one is first
+            //
+            if(( strpos( $SORTBY_HOSTNAME, $x_str ) < strpos( $SORTBY_HOSTNAME, $y_str ) ) && ( $x_present && $y_present ))
+            {
+                $x_first    = 1;
+            }
+            else if(( strpos( $SORTBY_HOSTNAME, $x_str ) > strpos( $SORTBY_HOSTNAME, $y_str ) ) && ( $x_present && $y_present ))
+            {
+                $y_first    = 1;
+        
+            }
+            else if( $x_present )
+            {
+                $x_first    = 1;
+            }
+            else if( $y_present )
+            {
+                $y_first    = 1;
+            }
+
+            // Now replace our {x} and {y} with %d for sscanf parsing 
+            //
+            if(( $x_first ) && ( $x_present && $y_present ) )
+            {
+                $skan_str    = str_replace( $x_str, "%d", $skan_str );
+                $skan_str    = str_replace( $y_str, "%d", $skan_str );
+            } 
+            else if( $x_present)
+            {
+                $skan_str    = str_replace( $x_str, "%d", $skan_str );
+            }
+            else if( $y_present)
+            {
+                $skan_str    = str_replace( $y_str, "%d", $skan_str );
+            }
+
+            $x_min        = null;
+            $x_max        = null;
+            $y_min        = null;
+            $y_max        = null;
+
+            $x_columns    = array();
+            $y_rows        = array();
+
+            // Now let's walk through all our nodes and see which one are valid for our scan pattern
+            //
+            foreach( $nodes as $hostname => $node )
+            {
+                $x    = null;
+                $y    = null;
+
+                if( $x_present && $y_present )
+                {
+                    if( $x_first )
+                    {
+                        $n = sscanf( $hostname, $skan_str, $x, $y );
+                    }
+                    else if( $y_first )
+                    {
+                        $n = sscanf( $hostname, $skan_str, $y, $x );
+                    }
+
+                    // Remove nodes that don't match
+                    //
+                    if( $n < 2 )
+                    {
+                        // This node hostname has no match for: {x} and {y}
+                        //
+                        unset( $nodes[$hostname] );
+                    }
+                }
+                else if( $x_present && !$y_present )
+                {
+                    $n = sscanf( $hostname, $skan_str, $x );
+
+                    // Remove nodes that don't match
+                    //
+                    if( $n < 1 )
+                    {
+                        // This node hostname has no match for: {x}
+                        //
+                        unset( $nodes[$hostname] );
+                    }
+                    $y    = 1;
+                }
+                else if( $y_present && !$x_present )
+                {
+                    $n = sscanf( $hostname, $skan_str, $y );
+
+                    // Remove nodes that don't match
+                    //
+                    if( $n < 1 )
+                    {
+                        // This node hostname has no match for: {y}
+                        //
+                        unset( $nodes[$hostname] );
+                    }
+                    $x    = 1;
+                }
+
+                // Determine the lowest value of {x} that exists in all node hostnames
+                //
+                if( !$x_min && $x != null )
+                {
+                    $x_min    = $x;
+                }
+                else if( $x < $x_min && $x != null )
+                {
+                    $x_min    = $x;
+                }
+
+                // Determine the highest value of {x} that exists in all node hostnames
+                //
+                if( !$x_max && $x != null )
+                {
+                    $x_max    = $x;
+                }
+                else if( $x > $x_max && $x != null )
+                {
+                    $x_max    = $x;
+                }
+
+                // Determine the lowest value of {y} that exists in all node hostnames
+                //
+                if( !$y_min && $y != null )
+                {
+                    $y_min    = $y;
+                }
+                else if( $y < $y_min && $y != null )
+                {
+                    $y_min    = $y;
+                }
+
+                // Determine the highest value of {y} that exists in all node hostnames
+                //
+                if( !$y_max && $y != null )
+                {
+                    $y_max    = $y;
+                }
+                else if( $y > $y_max && $y != null )
+                {
+                    $y_max    = $y;
+                }
+
+                // Store which non-empty columns and rows we found
+                //
+                if( !in_array( $x, $x_columns ) )
+                {
+                    $x_columns[] = $x;
+                }
+                if( !in_array( $y, $y_rows ) )
+                {
+                    $y_rows[] = $y;
+                }
+            }
+
+            // Sort all the nodes (alpha and numerically)
+            // 1: gb-r1n1, 2: gb-r1n2, 3: gb-r2n1, etc
+            //
+            $sorted_nodes    = usort( $nodes, "cmp" );
+
+            //print_r( $x_columns ) ;
+
+            $cur_node    = 0;
+
+            $x_offset    = 0;
+            $y_offset    = 0;
+            $font         = 2;
+            $fontwidth    = ImageFontWidth( $font );
+            $fontheight    = ImageFontHeight( $font );
+            $fontspaceing    = 2;
+
+            if( $this->isSmall() ) 
+            {
+                $y_offset    = $y_offset + (2 * $fontspaceing) + $fontheight;
+            }
+
+            if( $this->isBig() ) 
+            {
+                $y_offset    = ($fontheight * (1 + strlen( $x_max) ) ) + ((2 + strlen( $x_max)) * $fontspaceing);
+                $x_offset    = ($fontwidth * (1 + strlen( $y_max) ) ) + ((2 + strlen( $y_max)) * $fontspaceing);
+            }
+
+            $image_width    = $x_offset + ($node_width * ($x_max-$x_min+2));
+
+            if( $this->isSmall() ) 
+            {
+                $image_width    = $max_width;
+            }
+            else if( $this->isBig() ) 
+            {
+                $image_width    = ($image_width < $max_width) ? $image_width : $max_width;
+            }
+            $image_height    = $y_offset + ($node_width * ($y_max-$y_min+2));
+
+            $this->width    = $image_width;
+            $this->heigth    = $image_heigth;
+
+            $image        = imageCreateTrueColor( $image_width, $image_height );
+            $colorwhite    = imageColorAllocate( $image, 255, 255, 255 );
+
+            imageFill( $image, 0, 0, $colorwhite );
+
+            if( $this->isSmall() )
+            {
+                // Draw a fancy little header text to explain what it is
+                //
+                $colorblue    = imageColorAllocate( $image, 0, 0, 255 );
+
+                imageString( $image, $font, 2, 2, "Job Monarch: ".count($jobs)." jobs", $colorblue );
+            }
+
+            if( $this->isBig() && ( isset( $SORT_XLABEL ) || isset( $SORT_YLABEL ) ) )
+            {
+                $colorblue    = imageColorAllocate( $image, 0, 0, 255 );
+
+                if( isset( $SORT_XLABEL ) )
+                {
+                    // Print the {x} label: rack
+                    //
+                    imageString( $image, $font, $x_offset, $fontspaceing, $SORT_XLABEL, $colorblue );
+                }
+
+                if( isset( $SORT_YLABEL ) )
+                {
+                    // Stupid php without imageStringDown function... we'll make one ourself
+                    //
+
+                    // Print the {y} label: node
+                    //
+                    imageStringDown( $image, $font, $fontspaceing, $y_offset, $SORT_YLABEL, $colorblue );
+                }
+            }
+
+            $previous_n    = 0;
+            $previous_m    = 0;
+            $x_empty_count    = 0;
+            $y_empty_count    = 0;
+
+            // Let's start assigning x,y coordinates now
+            //
+            for( $n = $x_min; $n <= $x_max; $n++ )
+            {
+                for( $m = $y_min; $m <= $y_max; $m++ )
+                {
+                    if( $x_min > 0 )
+                    {
+                        $x    = $x_offset + ( ($n-$x_min) * $node_width ) - ($x_empty_count * $node_width);
+                    }
+                    if( $y_min > 0 )
+                    {
+                        $y    = $y_offset + ( ($m-$y_min) * $node_width ) - ($y_empty_count * $node_width);
+                    }
+
+                    // Don't show empty rows/columns if option enabled
+                    //
+                    if( !in_array( $n, $x_columns ) && !$SHOW_EMPTY_COLUMN )
+                    {
+                        // Skip to next iteration: we don't want a empty column
+                        //
+                        if( $n > $previous_n )
+                        {
+                            $previous_n = $n;
+                            $x_empty_count++;
+                        }
+                        continue;
+                    }
+                    if( !in_array( $m, $y_rows ) && !$SHOW_EMPTY_ROW )
+
+                    {
+                        // Skip to next iteration: we don't want a empty column
+                        //
+                        if( $m > $previous_m )
+                        {
+                            $previous_m = $m;
+                            $y_empty_count++;
+                        }
+                        continue;
+                    }
+
+                    if( $this->isBig() ) 
+                    {
+                        // Draw y(node) column number header
+                        //
+                        if(( $n == $x_min ) && ( isset($SORT_YLABEL) ) )
+                        {
+                            $mfontspacing    = 1;
+
+                            $ylabel_x    = $x - ( $fontwidth * strlen( $y_max ) ) - $mfontspacing;
+                            $ylabel_y    = $y;
+
+                            imageString( $image, $font, $ylabel_x, $ylabel_y, strval( $m ), $colorblue );
+
+                            $xmin_hit[$n]    = true;
+                        }
+
+                        // Draw x(rack) column number header
+                        //
+                        if(( $m == $y_min ) && ( isset($SORT_XLABEL) ) )
+                        {
+                            $mfontspacing    = 2;
+                            $xlabel_y    = $y - ( $fontheight * strlen( $x_max ) );
+                            $xlabel_x    = $x + $mfontspacing; 
+
+                            imageStringDown( $image, $font, $xlabel_x, $xlabel_y, strval( $n ), $colorblue );
+                        }
+                    }
+
+                    if( isset( $nodes[$cur_node] ) ) 
+                    {
+                        $host    = $nodes[$cur_node]->getHostname();
+
+                        if( $x_present && $y_present )
+                        {
+                            if( $x_first )
+                            {
+                                $nn = sscanf( $host, $skan_str, $rx, $ry );
+                            }
+                            else if( $y_first )
+                            {
+                                $nn = sscanf( $host, $skan_str, $ry, $rx );
+                            }
+                            if ( $nn < 2 )
+                            {
+                                //printf( "skipping node %s - y present & x present + <2 x,y matchs\n", $host);
+                                continue;
+                            }
+                            if( intval( $rx ) > $n )
+                            {
+                                // If x(rack) is higher than current x, skip to next x(rack)
+                                //
+                                $m        = $y_max + 1;
+
+                                continue;
+                            }
+                            if( intval( $ry ) > $m )
+                            {
+                                // If y(node) is higher than current y, skip to next y(node)
+                                //
+                                continue;
+                            }
+                        }
+                        else if( $x_present )
+                        {
+                            $nn = sscanf( $host, $skan_str, $rx );
+                        }
+                        else if( $y_present )
+                        {
+                            $nn = sscanf( $host, $skan_str, $ry );
+                        }
+
+                        if( !in_array( $host, $filtered_nodes ) )
+                        {
+                            // This node has been filtered out: we only want to see certain nodes
+                            //
+                            $nodes[$cur_node]->setShowinfo( 0 );
+                        }
+
+                        $nodes[$cur_node]->setCoords( $x, $y );
+                        $nodes[$cur_node]->setImage( $image );
+
+                        if( $this->isSmall() )
+                        {
+                            $nodes[$cur_node]->drawSmall();
+                        }
+                        else if( $this->isBig() )
+                        {
+                            $nodes[$cur_node]->drawBig();
+                        }
+
+                        $cur_node++;
+                    }
+                }
+            }
+
+        }
+        else
+        {
+            if( $this->isSmall() )
+            {
+                $image        = imageCreateTrueColor( $max_width, ($y_offset + (($node_rows*$node_width)+1) ) );
+            }
+            else if( $this->isBig() )
+            {
+                $image_width    = ($node_width * $nodes_nr) + 2;
+                $image_width    = ($image_width < $max_width) ? $image_width : $max_width;
+                $image        = imageCreateTrueColor( $image_width, ($y_offset + (($node_rows*$node_width)+1) ) );
+            }
+            $colorwhite    = imageColorAllocate( $image, 255, 255, 255 );
+
+            imageFill( $image, 0, 0, $colorwhite );
+
+            if( $this->isSmall() )
+            {
+                $colorblue    = imageColorAllocate( $image, 0, 0, 255 );
+
+                imageString( $image, $font, 2, 2, "Job Monarch: ".count( $jobs)." jobs", $colorblue );
+            }
+
+            for( $n = 0; $n < $node_rows; $n++ )
+            {
+                for( $m = 0; $m < $nodes_per_row; $m++ )
+                {
+                    $x = ($m * $node_width);
+                    $y = $y_offset + ($n * $node_width);
+
+                    $cur_node = ($n * $nodes_per_row) + ($m);
+                    $host = isset( $nodes_hosts[$cur_node] ) ? $nodes_hosts[$cur_node] : '';
+
+                    if( isset( $nodes[$host] ) )
+                    {
+                        $nodes[$host]->setCoords( $x, $y );
+                        $nodes[$host]->setImage( $image );
+
+                        if( !in_array( $host, $filtered_nodes ) )
+                        {
+                            $nodes[$host]->setShowinfo( 0 );
+                        }
+
+                        if( $this->isSmall() )
+                        {
+                            $nodes[$host]->drawSmall();
+                        }
+                        else if( $this->isBig() )
+                        {
+                            $nodes[$host]->drawBig();
+                        }
+                    }
+                }
+            }
+        }
+    
+        $this->nodes    = &$nodes;
+
+        if ($this->output)
+        {
+            header( 'Content-type: image/png' );
+            imagePNG( $image );
+            imageDestroy( $image );
+        }
+    }
+
+    function getImagemapArea()
+    {
+        $clusterimage_map    = "";
+
+        $nodes = &$this->getNodes();
+
+        foreach( $nodes as $node )
+        {
+            $node_map          = $node->getImagemapArea();
+            $clusterimage_map .= $node_map;
+        }
+
+        return $clusterimage_map;
+    }
+}
+
+class EmptyImage
+{
+    function draw()
+    {
+        $image        = imageCreateTrueColor( 1, 1 );
+        $colorwhite    = imageColorAllocate( $image, 255, 255, 255 );
+        imageFill( $image, 0, 0, $colorwhite );                         
+
+        header( 'Content-type: image/png' );
+        imagePNG( $image );
+        imageDestroy( $image );
+    }
+}
+
+class HostImage
+{
+    var $data_gather, $cluster, $host, $node, $image;
+    var $headerstrlen;
+
+    function HostImage( $data_gather, $cluster, $host )
+    {
+        $this->data_gather     = $data_gather;
+        $this->cluster        = $cluster;
+        $this->host        = $host;
+        $this->y_offset        = 0;
+        $this->font        = 2;
+        $this->fontspaceing    = 2;
+        $this->headerstrlen    = array();
+
+        $this->fontheight    = ImageFontHeight( $this->font );
+        $this->fontwidth    = ImageFontWidth( $this->font );
+
+        $dg            = &$this->data_gather;
+        $this->node        = &$dg->getNode( $this->host );
+        $n            = &$this->node;
+        $this->njobs        = $n->getJobs();
+    }
+
+    function drawJobs()
+    {
+        $dg                     = &$this->data_gather;
+        $colorblack        = imageColorAllocate( $this->image, 0, 0, 0 );
+
+        for( $n = 0; $n < count( $this->njobs ); $n++ )
+        {
+            $jobid            = $this->njobs[$n];
+            $jobinfo        = $dg->getJob( $jobid );
+
+            $xoffset        = 5;
+            imageString( $this->image, $this->font, $xoffset, $this->y_offset, strval( $jobid ), $colorblack );
+
+            foreach( $this->headerstrlen as $headername => $headerlen )
+            {
+                if( $headername == 'nodes' )
+                {
+                    $attrval    = strval( count( $jobinfo['nodes'] ) );
+                }
+                else if( $headername == 'cpus' )
+                {
+                    if( !isset( $jobinfo['ppn'] ) )
+                    {
+                        $jobinfo['ppn'] = 1;
+                    }
+
+                    $attrval    = strval( count( $jobinfo['nodes'] ) * intval( $jobinfo['ppn'] ) );
+                }
+                else if( $headername == 'runningtime' )
+                {
+                    $attrval    = makeTime( intval( $jobinfo['reported'] ) - intval( $jobinfo['start_timestamp'] ) );
+                }
+                else
+                {
+                    $attrval    = strval( $jobinfo[$headername] );
+                }
+
+                imageString( $this->image, $this->font, $xoffset, $this->y_offset, $attrval, $colorblack );
+        
+                $xoffset    = $xoffset + ($this->fontwidth * ( $headerlen + 1 ) );
+            }
+            
+            $this->newLineOffset();
+        }
+    }
+
+    function drawHeader()
+    {
+        $dg                     = &$this->data_gather;
+
+        for( $n = 0; $n < count( $this->njobs ); $n++ )
+        {
+            $jobid            = $this->njobs[$n];
+            $jobinfo        = $dg->getJob( $jobid );
+
+            if( !isset( $this->headerstrlen['id'] ) )
+            {
+                $this->headerstrlen['id']    = strlen( strval( $jobid ) );
+            }
+            else if( strlen( strval( $jobid ) ) > $this->headerstrlen['id'] )
+            {
+                $this->headerstrlen['id']    = strlen( strval( $jobid ) );
+            }
+
+            if( !isset( $this->headerstrlen['owner'] ) )
+            {
+                $this->headerstrlen['owner']    = strlen( strval( $jobinfo['owner'] ) );
+            }
+            else if( strlen( strval( $jobinfo['owner'] ) ) > $this->headerstrlen['owner'] )
+            {
+                $this->headerstrlen['owner']    = strlen( strval( $jobinfo['owner'] ) );
+            }
+
+            if( !isset( $this->headerstrlen['queue'] ) )
+            {
+                $this->headerstrlen['queue']    = strlen( strval( $jobinfo['queue'] ) );
+            }
+            else if( strlen( strval( $jobinfo['queue'] ) ) > $this->headerstrlen['queue'] )
+            {
+                $this->headerstrlen['queue']    = strlen( strval( $jobinfo['queue'] ) );
+            }
+
+            if( !isset( $jobinfo['ppn'] ) )
+            {
+                $jobinfo['ppn'] = 1;
+            }
+
+            $cpus            = count( $jobinfo['nodes'] ) * intval( $jobinfo['ppn'] );
+
+            if( !isset( $this->headerstrlen['cpus'] ) )
+            {
+                $this->headerstrlen['cpus']    = strlen( strval( $cpus ) );
+            }
+            else if( strlen( strval( $cpus ) ) > $this->headerstrlen['cpus'] )
+            {
+                $this->headerstrlen['cpus']    = strlen( strval( $cpus ) );
+            }
+
+            $nodes            = count( $jobinfo['nodes'] );
+
+            if( !isset( $this->headerstrlen['nodes'] ) )
+            {
+                $this->headerstrlen['nodes']    = strlen( strval( $nodes ) );
+            }
+            else if( strlen( strval( $nodes) ) > $this->headerstrlen['nodes'] )
+            {
+                $this->headerstrlen['nodes']    = strlen( strval( $nodes ) );
+            }
+
+            $runningtime        = makeTime( intval( $jobinfo[reported] ) - intval( $jobinfo['start_timestamp'] ) );
+
+            if( !isset( $this->headerstrlen['runningtime'] ) )
+            {
+                $this->headerstrlen['runningtime']    = strlen( strval( $runningtime) );
+            }
+            else if( strlen( strval( $runningtime) ) > $this->headerstrlen['runningtime'] )
+            {
+                $this->headerstrlen['runningtime']    = strlen( strval( $runningtime) );
+            }
+
+            if( !isset( $this->headerstrlen['name'] ) )
+            {
+                $this->headerstrlen['name']    = strlen( strval( $jobinfo['name'] ) );
+            }
+            else if( strlen( strval( $jobinfo['name'] ) ) > $this->headerstrlen['name'] )
+            {
+                $this->headerstrlen['name']    = strlen( strval( $jobinfo['name'] ) );
+            }
+        }
+
+        $xoffset    = 5;
+
+        foreach( $this->headerstrlen as $headername => $headerlen )
+        {
+            $colorgreen    = imageColorAllocate( $this->image, 0, 200, 0 );
+
+            if( $headerlen < strlen( $headername ) )
+            {
+                $this->headerstrlen[$headername]    = strlen( $headername );
+            }
+
+            imageString( $this->image, $this->font, $xoffset, $this->y_offset, ucfirst( $headername ), $colorgreen );
+
+            $xoffset    = $xoffset + ($this->fontwidth * ( $this->headerstrlen[$headername] + 1 ) );
+        }
+        $this->newLineOffset();
+    }
+
+    function newLineOffset()
+    {
+        $this->y_offset        = $this->y_offset + $this->fontheight + $this->fontspaceing;
+    }
+
+    function draw()
+    {
+        $xlen        = 450;
+        $ylen        = ( count( $this->njobs ) * ( $this->fontheight + $this->fontspaceing ) ) + (3 * $this->fontheight);
+
+        $this->image    = imageCreateTrueColor( $xlen, $ylen );
+        $colorwhite    = imageColorAllocate( $this->image, 255, 255, 255 );
+        imageFill( $this->image, 0, 0, $colorwhite );                         
+
+        $colorblue    = imageColorAllocate( $this->image, 0, 0, 255 );
+
+        imageString( $this->image, $this->font, 1, $this->y_offset, "Monarch Joblist - host: ".$this->host, $colorblue );
+        $this->newLineOffset();
+
+        $this->drawHeader();
+        $this->drawJobs();
+
+        header( 'Content-type: image/png' );
+        imagePNG( $this->image );
+        imageDestroy( $this->image );
+    }
+}
+
+function imageStringDown( &$image, $font, $x, $y, &$s, &$col )
+{
+    $fw    = imagefontwidth( $font);
+    $fh    = imagefontheight( $font);
+    
+    $fontspacing = 0;
+
+    $fx    = $x;
+    $fy    = $y;
+
+    for( $n=0; $n<strlen( $s ); $n++ )
+    {
+        $myc    = $s{$n};
+
+        imagestring( $image, $font, $fx, $fy, $myc, $col );
+
+        $fy    += ($fontspacing + $fh );
+    }
+}
+
+function array_rem( $val, &$arr )
+{
+    // Delete val from arr
+    //
+    $i    = array_search( $val, $arr );
+
+    if( $i == false ) return false;
+
+    $arr    = array_merge( array_slice( $arr, 0, $i ), array_slice( $arr, $i+1, count( $arr ) ) );
+
+    return true;
+}
+
+function cmp( $a, $b ) 
+{
+    global $SORT_ORDER;
+    global $skan_str;
+    global $x_first, $y_first;
+    global $x_present, $y_present;
+
+    $a_node        = $a;
+    $b_node        = $b;
+    $a        = $a_node->getHostname();
+    $b        = $b_node->getHostname();
+
+    if( $a == $b ) return 0;
+
+    $a_x        = 0;
+    $b_x        = 0;
+    $a_y        = 0;
+    $b_y        = 0;
+
+    if( $x_present && $y_present )
+    {
+        if( $x_first )
+        {
+            $n = sscanf( $a, $skan_str, $a_x, $a_y );
+            $n = sscanf( $b, $skan_str, $b_x, $b_y );
+        }
+        else if( $y_first )
+        {
+            $n = sscanf( $a, $skan_str, $a_y, $a_x );
+            $n = sscanf( $b, $skan_str, $b_y, $b_x );
+        }
+    } 
+    else if( $x_present && !$y_present )
+    {
+        $n = sscanf( $a, $skan_str, $a_x );
+        $n = sscanf( $b, $skan_str, $b_x );
+    }
+    else if( $y_present && !$x_present )
+    {
+        $n = sscanf( $a, $skan_str, $a_y );
+        $n = sscanf( $b, $skan_str, $b_y );
+    }
+
+    if ( $SORT_ORDER=="desc" )
+    {
+
+        if( $x_present && $y_present )
+        {
+            // 1  = a < b
+            // -1 = a > b
+            //
+            if ($a_x == $b_x)
+            {
+                if ($a_y < $b_y)
+                {
+                    return 1;
+                }
+                else if ($a_y > $b_y)
+                {
+                    return -1;
+                }
+            }
+            else if ($a_x < $b_x)
+            {
+                return 1;
+            }
+            else if ($a_x > $b_x)
+            {
+                return -1;
+            }
+        } 
+        else if( $x_present && !$y_present )
+        {
+            if ($a_x < $b_x)
+            {
+                return 1;
+            }
+            else if ($a_x > $b_x)
+            {
+                return -1;
+            }
+        }
+        else if( $y_present && !$x_present )
+        {
+            if ($a_y < $b_y)
+            {
+                return 1;
+            }
+            else if ($a_y > $b_y)
+            {
+                return -1;
+            }
+        }
+    }
+    else if ( $SORT_ORDER == "asc" )
+    {
+
+        if( $x_present && $y_present )
+        {
+            // 1  = a > b
+            // -1 = a < b
+            //
+            if ($a_x == $b_x)
+            {
+                if ($a_y > $b_y)
+                {
+                    return 1;
+                }
+                else if ($a_y < $b_y)
+                {
+                    return -1;
+                }
+            }
+            else if ($a_x > $b_x)
+            {
+                return 1;
+            }
+            else if ($a_x < $b_x)
+            {
+                return -1;
+            }
+        }
+        else if( $x_present && !$y_present )
+        {
+            if ($a_x > $b_x)
+            {
+                return 1;
+            }
+            else if ($a_x < $b_x)
+            {
+                return -1;
+            }
+        }
+        else if( $y_present && !$x_present )
+        {
+            if ($a_y > $b_y)
+            {
+                return 1;
+            }
+            else if ($a_y < $b_y)
+            {
+                return -1;
+            }
+        }
+    }
+}
+function makeTime( $time )
+{
+        $days = intval( $time / 86400 );
+        $time = ($days>0) ? $time % ($days * 86400) : $time;
+
+        $date_str = '';
+        $day_str = '';
+
+        if( $days > 0 )
+        {
+            if( $days > 1 )
+            {
+                $day_str .= $days . ' days';
+            }
+            else
+            {
+                $day_str .= $days . ' day';
+            }
+        }
+
+        $hours = intval( $time / 3600 );
+        $time = $hours ? $time % ($hours * 3600) : $time;
+
+        if( $hours > 0 )
+        {
+             $date_str .= $hours . ':';
+             $date_unit = 'hours'; 
+        }
+
+        $minutes = intval( $time / 60 );
+        $seconds = $minutes ? $time % ($minutes * 60) : $time;
+
+        if( $minutes > 0 )
+        {
+            if( $minutes >= 10 )
+            {
+                $date_str .= $minutes . ':';
+            }
+            else
+            {
+                $date_str .= '0' . $minutes . ':';
+            }
+                $date_unit = (!isset($date_unit)) ? 'minutes' : $date_unit;
+        }
+        else
+        {
+            if($hours > 0 )
+            {
+                $date_str .= '00:';
+                $date_unit = (!isset($date_unit)) ? 'minutes' : $date_unit;
+            }
+        }
+
+        $date_unit = (!isset($date_unit)) ? 'seconds' : $date_unit;
+
+        if( $seconds > 0 )
+        {
+            if( $seconds >= 10 )
+            {
+                $date_str .= $seconds . ' ' . $date_unit;
+            }
+            else
+            {
+                $date_str .= '0' . $seconds . ' ' . $date_unit;
+            }
+        }
+        else if ( $hours > 0 or $minutes > 0 )
+        {
+            $date_str .= '00 ' . $date_unit;
+        }
+
+        if( $days > 0)
+        {
+            if( $hours > 0 or $minutes > 0 or $seconds > 0 )
+            {
+                $date_str = $day_str . ' - ' . $date_str;
+            }
+            else
+            {
+                $date_str = $day_str;
+            }
+        }
+        return $date_str;
+}
+?>
Index: /branches/1.0/web/addons/job_monarch/overview.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/overview.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/overview.php	(revision 825)
@@ -0,0 +1,1160 @@
+<?php
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006-2013  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * SVN $Id$
+ */
+
+global $GANGLIA_PATH, $clustername, $tpl_data, $filter, $cluster, $get_metric_string, $cluster_url, $sh;
+global $hosts_up, $m, $start, $end, $filterorder, $COLUMN_REQUESTED_MEMORY, $COLUMN_QUEUED, $COLUMN_NODES, $hostname, $piefilter;
+global $longtitle, $title, $range;
+
+include_once "./dwoo/dwooAutoload.php";
+
+global $dwoo;
+
+$tpl = new Dwoo_Template_File("templates/overview.tpl");
+$tpl_data = array();
+
+$tpl_data[ "clustername"]= $clustername ;
+
+if( $JOB_ARCHIVE )
+{
+    $tpl_data[ "cluster_url"]= rawurlencode($clustername) ;
+}
+
+$rjqj_start = null;
+$ds         = new DataSource();
+$myxml_data = $ds->getData();
+
+//print_r( $myxml_data );
+
+$data_gatherer = new DataGatherer( $clustername );
+$data_gatherer->parseXML( $myxml_data );
+
+$heartbeat = $data_gatherer->getHeartbeat();
+$jobs      = $data_gatherer->getJobs();
+$gnodes    = $data_gatherer->getNodes();
+$cpus      = $data_gatherer->getCpus();
+$use_fqdn  = $data_gatherer->getUsingFQDN();
+
+//print_r( $gnodes );
+
+function setupFilterSettings() 
+{
+
+    global $tpl_data, $filter, $clustername, $piefilter, $data_gatherer, $myxml_data, $filterorder, $_SESSION, $data_gatherer;
+    global $jobs, $gnodes;
+
+    $filter_image_url = "";
+
+    foreach( $filter as $filtername => $filtervalue ) 
+    {
+        $tpl_data[ $filtername ] = $filtervalue;
+
+        $filter_image_url    .= "&$filtername=$filtervalue";
+    }
+
+    session_start();
+
+    unset( $_SESSION["data"] );
+    $_SESSION["data"] = &$myxml_data;
+
+    $ic = new ClusterImage( $myxml_data, $clustername );
+
+    $ic->setJobs( $jobs );
+    $ic->setNodes( $gnodes );
+    $ic->setBig();
+    $ic->setNoimage();
+    $ic->draw();
+
+    $tpl_data[ "clusterimage"]= "./image.php?". session_name() . "=" . session_id() ."&c=".rawurlencode($clustername)."&j_view=big-clusterimage".$filter_image_url;
+
+    $tpl_data[ "node_clustermap"]= "yes";
+    $tpl_data[ "node_area_map"]= $ic->getImagemapArea();
+
+    $tpl_data[ "order"]= $filterorder;
+
+    if( array_key_exists( "id", $filter ) ) 
+    {
+        $piefilter = 'id';
+    } 
+    else if( array_key_exists( "owner", $filter ) ) 
+    {
+        $piefilter = 'owner';
+    } 
+    else if( array_key_exists( "queue", $filter ) ) 
+    {
+        $piefilter = 'queue';
+    }
+
+    $pie    = drawPie();
+
+    $tpl_data["pie"]= $pie;
+}
+
+function timeToEpoch( $time ) 
+{
+    $time_fields = explode( ':', $time );
+
+    if( count( $time_fields ) == 3 ) 
+    {
+        $hours   = $time_fields[0];
+        $minutes = $time_fields[1];
+        $seconds = $time_fields[2];
+
+    } 
+    else if( count( $time_fields ) == 2 ) 
+    {
+        $hours   = 0;
+        $minutes = $time_fields[0];
+        $seconds = $time_fields[1];
+
+    } 
+    else if( count( $time_fields ) == 1 ) 
+    {
+        $hours   = 0;
+        $minutes = 0;
+        $seconds = $time_fields[0];
+    }
+
+    $myepoch = intval( $seconds + (intval( $minutes * 60 )) + (intval( $hours * 3600 )) );
+
+    return $myepoch;
+}
+
+function colorRed( $color ) 
+{
+    return substr( $color, 0, 2 );
+}
+
+function colorGreen( $color ) 
+{
+    return substr( $color, 2, 2 );
+}
+
+function colorBlue( $color ) 
+{
+    return substr( $color, 4, 2 );
+}
+
+function colorDiffer( $first, $second ) 
+{
+    // Make sure these two colors differ atleast 50 R/G/B
+    $min_diff = 50;
+
+    $c1r   = hexDec( colorRed( $first ) );
+    $c1g   = hexDec( colorGreen( $first ) );
+    $c1b   = hexDec( colorBlue( $first ) );
+
+    $c2r   = hexDec( colorRed( $second ) );
+    $c2g   = hexDec( colorGreen( $second ) );
+    $c2b   = hexDec( colorBlue( $second ) );
+
+    $rdiff = ($c1r >= $c2r) ? $c1r - $c2r : $c2r - $c1r;
+    $gdiff = ($c1g >= $c2g) ? $c1g - $c2g : $c2g - $c1g;
+    $bdiff = ($c1b >= $c2b) ? $c1b - $c2b : $c2b - $c1b;
+
+    if( $rdiff >= $min_diff or $gdiff >= $min_diff or $bdiff >= $min_diff ) 
+    {
+        return TRUE;
+
+    } 
+    else 
+    {
+        return FALSE;
+    }
+}
+
+function randomColor( $known_colors ) 
+{
+    // White (000000) would be invisible
+    $start       = "004E00";
+    
+    $start_red   = colorRed( $start );
+    $start_green = colorGreen( $start );
+    $start_blue  = colorBlue( $start );
+    
+    $end         = "FFFFFF";
+
+    $end_red     = colorRed( $end );
+    $end_green   = colorGreen( $end );
+    $end_blue    = colorBlue( $end );
+
+    $change_color= TRUE;
+
+    while( $change_color ) 
+    {
+        $change_color= FALSE;
+
+        $new_red     = rand( hexDec( $start_red ), hexDec( $end_red ) );
+        $new_green   = rand( hexDec( $start_green ), hexDec( $end_green ) );
+        $new_blue    = rand( hexDec( $start_blue ), hexDec( $end_blue ) );
+
+        $new         = decHex( $new_red ) . decHex( $new_green ) . decHex( $new_blue );
+
+        foreach( $known_colors as $old )
+        {
+            if( !colorDiffer( $new, $old ) )
+            {
+                 $change_color = TRUE;
+            }
+        }
+    }
+
+    // Whoa! Actually found a good color ;)
+    return $new;
+}
+
+// Code these some day
+function drawJobPie() { }
+
+function drawUserPie() { }
+
+function drawQueuePie() { }
+
+function drawPie() 
+{
+    global $jobs, $gnodes, $piefilter, $filter, $metrics;
+
+    $nodes = $gnodes;
+
+    if( isset($piefilter) )    
+    {
+        $pie_args = "title=" . rawurlencode("Cluster ".$piefilter." usage");
+    } 
+    else 
+    {
+        $pie_args = "title=" . rawurlencode("Cluster queue usage");
+    }
+
+    $pie_args .= "&size=250x150";
+
+    $queues    = array();
+    $nr_jobs   = count( $jobs );
+    $nr_nodes  = count( $nodes );
+
+    $nr_cpus   = cluster_sum("cpu_num", $metrics);
+
+    $empty_cpus= 0;
+    $used_cpus = 0;
+
+    $job_weight= array();
+
+    foreach( $nodes as $node ) 
+    {
+        $myjobs    = $node->getJobs();
+        $myhost    = $node->getHostname();
+        $node_cpus = $metrics[$myhost]["cpu_num"]['VAL'];
+        $job_cpu   = 0;
+
+        foreach( $myjobs as $myjob ) 
+        {
+            $job_cpu += isset( $jobs[$myjob]['ppn'] ) ? $jobs[$myjob]['ppn'] : 1;
+        }
+
+        $node_freecpu= $node_cpus - $job_cpu;
+
+        $empty_cpus += $node_freecpu;
+    }
+
+    $empty_cpus = ( $empty_cpus >= 0 ) ? $empty_cpus : 0;
+    $used_cpus  = $nr_cpus - $empty_cpus;
+
+    $empty_percentage= $empty_cpus;
+
+    $qcolors   = array();
+    $color     = randomColor( $qcolors );
+    $qcolors[] = $color;
+    $pie_args .= "&free=$empty_percentage,$color";
+
+    if( isset( $piefilter ) )
+    {
+        $filterpie = array();
+    }
+
+    foreach( $nodes as $node )
+    {
+        $node_jobs    = $node->getJobs();
+        $nr_node_jobs = count( $node_jobs );
+        $myhost       = $node->getHostname();
+        $node_cpus    = $metrics[$myhost]["cpu_num"]['VAL'];
+
+        foreach( $node_jobs as $myjob )
+        {
+            $job_cpu = isset( $jobs[$myjob]['ppn'] ) ? $jobs[$myjob]['ppn'] : 1;
+
+            // Determine the weight of this job
+            // - what percentage of the cpus is in use by this job
+            //
+            //$job_weight[$myjob]    = ( $job_cpu / $nr_cpus );
+            $job_weight[$myjob]    = $job_cpu;
+
+            if( isset( $piefilter ) ) {
+
+                $countjob = 1;
+
+                if( $piefilter == 'id' )
+                {
+                    if( $myjob != $filter[$piefilter] )
+                    {
+                        $countjob = 0;
+                    }
+                }
+                else if( $piefilter == 'owner' )
+                {
+                    if( $jobs[$myjob]['owner'] != $filter[$piefilter] )
+                    {
+                        $countjob = 0;
+                    }
+                }
+                else
+                {
+                    if( $jobs[$myjob][$piefilter] != $filter[$piefilter] )
+                    {
+                        $countjob = 0;
+                    }
+                }
+
+                if( $countjob )
+                {
+
+                    if( !isset( $filterpie[$filter[$piefilter]] ) )
+                    {
+                        $filterpie[$filter[$piefilter]] = $job_weight[$myjob];
+                    }
+                    else
+                    {
+
+                        $filterpie[$filter[$piefilter]] = $filterpie[$filter[$piefilter]] + $job_weight[$myjob];
+                    }
+                }
+                else
+                {
+                    if( !isset( $filterpie["other"] ) )
+                    {
+                        $filterpie["other"] = $job_weight[$myjob];
+                    }
+                    else
+                    {
+                        $filterpie["other"] = $filterpie["other"] + $job_weight[$myjob];
+                    }
+
+                }
+                
+            }
+            else
+            {
+
+                $qname = $jobs[$myjob]['queue'];
+
+                if( !isset( $queues[$qname] ) )
+                {
+                    $queues[$qname] = $job_weight[$myjob];
+                }
+                else
+                {
+                    $queues[$qname] = $queues[$qname] + $job_weight[$myjob];
+                }
+            }
+        }
+    }
+
+    if( isset( $piefilter ) )
+    {
+        $graphvals = $filterpie;
+    }
+    else
+    {
+        $graphvals = $queues;
+    }
+
+    foreach( $graphvals as $name => $totalweight) 
+    {
+        $percentage    = $totalweight;
+        
+        $color         = randomColor( $qcolors );
+        $qcolors[]     = $color;
+        $pie_args     .= "&$name=$percentage,$color";
+    }
+    $pie = "../../pie.php?$pie_args";
+
+    return $pie;
+}
+
+
+function sortJobs( $jobs, $sortby, $sortorder ) 
+{
+    $sorted    = array();
+
+    $cmp    = create_function( '$a, $b', 
+        "global \$sortby, \$sortorder;".
+
+        "if( \$a == \$b ) return 0;".
+
+        "if (\$sortorder==\"desc\")".
+            "return ( \$a > \$b ) ? 1 : -1;".
+        "else if (\$sortorder==\"asc\")".
+            "return ( \$a < \$b ) ? 1 : -1;" );
+
+    if( isset( $jobs ) && count( $jobs ) > 0 ) 
+    {
+        foreach( $jobs as $jobid => $jobattrs ) 
+        {
+                $state     = $jobattrs['status'];
+                $owner     = $jobattrs['owner'];
+                $queue     = $jobattrs['queue'];
+                $name      = $jobattrs['name'];
+                $req_cpu   = $jobattrs['requested_time'];
+                $req_memory= $jobattrs['requested_memory'];
+
+                if( $state == 'R' )
+                {
+                    $nodes = count( $jobattrs['nodes'] );
+                }
+                else
+                {
+                    $nodes = $jobattrs['nodes'];
+                }
+
+                $ppn         = (int) $jobattrs['ppn'] ? $jobattrs['ppn'] : 1;
+                $cpus        = $nodes * $ppn;
+                $queued_time = (int) $jobattrs['queued_timestamp'];
+                $start_time  = (int) $jobattrs['start_timestamp'];
+                $runningtime = $report_time - $start_time;
+
+                switch( $sortby ) 
+                {
+                    case "id":
+                        $sorted[$jobid] = $jobid;
+                        break;
+
+                    case "state":
+                        $sorted[$jobid] = $state;
+                        break;
+
+                    case "owner":
+                        $sorted[$jobid] = $owner;
+                        break;
+
+                    case "queue":
+                        $sorted[$jobid] = $queue;
+                        break;
+
+                    case "name":
+                        $sorted[$jobid] = $name;
+                        break;
+
+                    case "req_cpu":
+                        $sorted[$jobid] = timeToEpoch( $req_cpu );
+                        break;
+
+                    case "req_mem":
+                        $sorted[$jobid] = $req_memory;
+                        break;
+
+                    case "nodes":
+                        $sorted[$jobid] = $nodes;
+                        break;
+
+                    case "cpus":
+                        $sorted[$jobid] = $cpus;
+                        break;
+
+                    case "queued":
+                        $sorted[$jobid] = $queued_time;
+                        break;
+
+                    case "start":
+                        $sorted[$jobid] = $start_time;
+                        break;
+
+                    case "runningtime":
+                        $sorted[$jobid] = $runningtime;
+                        break;
+
+                    default:
+                        break;
+                }
+        }
+    }
+
+    if( $sortorder == "asc" )
+    {
+        asort( $sorted );
+    }
+    else if( $sortorder == "desc" )
+    {
+        arsort( $sorted );
+    }
+
+    return $sorted;
+}
+
+function makeOverview() 
+{
+    global $dwoo, $tpl, $tpl_data, $jobs, $nodes, $heartbeat, $clustername, $tpl_data;
+    global $sortorder, $sortby, $filter, $sh, $hc, $m, $range;
+    global $cluster_url, $get_metric_string, $host_url, $metrics;
+    global $start, $end, $reports, $gnodes, $default_showhosts;
+    global $COLUMN_QUEUED, $COLUMN_REQUESTED_MEMORY, $COLUMN_NODES, $hostname;
+    global $cluster, $use_fqdn, $myxml_data, $data_gatherer;
+
+    $metricname        = $m;
+    if( isset($conf['default_metric']) and ($metricname =='') )
+        $metricname = $conf['default_metric'];
+    else
+        if( isset( $m ) )
+            $metricname = $m;
+        else
+            $metricname = "load_one";
+
+    $tpl_data["sortorder"]= $sortorder;
+    $tpl_data["sortby"]= $sortby;
+
+    $sorted_jobs        = sortJobs( $jobs, $sortby, $sortorder );
+
+    $even               = 1;
+
+    $used_jobs          = 0;
+    $used_cpus          = 0;
+    $used_nodes         = 0;
+
+    $queued_jobs        = 0;
+    $queued_nodes       = 0;
+    $queued_cpus        = 0;
+
+    $total_nodes        = 0;
+    $total_cpus         = 0;
+    $total_jobs         = 0;
+
+    $all_used_nodes     = array();
+    $total_used_nodes   = array();
+
+    $running_name_nodes = array();
+
+    $running_nodes      = 0;
+    $running_jobs       = 0;
+    $running_cpus       = 0;
+
+    $avail_nodes        = count( $gnodes );
+    $avail_cpus         = cluster_sum("cpu_num", $metrics);
+
+    $view_cpus          = 0;
+    $view_jobs          = 0;
+    $view_nodes         = 0;
+
+    $all_nodes          = 0;
+    $all_jobs           = 0;
+    $all_cpus           = 0;
+
+    $view_name_nodes    = array();
+
+    // Is the "requested memory" column enabled in the config
+    //
+    if( $COLUMN_REQUESTED_MEMORY ) 
+    {
+        $tpl_data[ "column_header_req_mem"]= "yes";
+    }
+
+    // Is the "nodes hostnames" column enabled in the config
+    //
+    if( $COLUMN_NODES ) 
+    {
+        $tpl_data[ "column_header_nodes"]= "yes";
+    }
+
+    // Is the "queued time" column enabled in the config
+    //
+    if( $COLUMN_QUEUED ) 
+    {
+        $tpl_data[ "column_header_queued"]= "yes" ;
+    }
+
+    $last_displayed_job = null;
+
+    $rjqj_host = null;
+
+    $na_nodes  = 0;
+    $na_cpus   = 0;
+
+    foreach( $metrics as $bhost => $bmetric )
+    {
+        foreach( $bmetric as $mname => $mval )
+        {
+            if( ( $mname == 'zplugin_monarch_rj' ) || ($mname == 'zplugin_monarch_qj') )
+            {
+                $rjqj_host = $bhost;
+            }
+        }
+    }
+
+    foreach( $gnodes as $ghost => $gnode )
+    {
+        if( $gnode->isDown() || $gnode->isOffline() )
+        {
+            $na_nodes += 1;
+            $na_cpus  += $metrics[$ghost]['cpu_num']['VAL'];
+        }
+    }
+
+    $node_list = array();
+
+    foreach( $sorted_jobs as $jobid => $sortdec ) 
+    {
+        $report_time     = $jobs[$jobid]['reported'];
+
+        if( $jobs[$jobid]['status'] == 'R' )
+        {
+            $nodes = count( $jobs[$jobid]['nodes'] );
+        }
+        else if( $jobs[$jobid]['status'] == 'Q' )
+        {
+            $nodes = $jobs[$jobid]['nodes'];
+        }
+
+        $ppn  = isset( $jobs[$jobid]['ppn'] ) ? $jobs[$jobid]['ppn'] : 1;
+        $cpus = $nodes * $ppn;
+
+        if( $report_time == $heartbeat ) 
+        {
+            $display_job    = 1;
+
+            if( $jobs[$jobid]['status'] == 'R' ) 
+            {
+                foreach( $jobs[$jobid]['nodes'] as $tempnode ) 
+                {
+                    $all_used_nodes[] = $tempnode;
+                }
+            }
+
+            $used_cpus += $cpus;
+
+            if( $jobs[$jobid]['status'] == 'R' ) 
+            {
+                $running_cpus     += $cpus;
+
+                $running_jobs++;
+
+                $found_node_job    = 0;
+
+                foreach( $jobs[$jobid]['nodes'] as $tempnode ) 
+                {
+                    $running_name_nodes[] = $tempnode;
+
+                    if( isset( $hostname ) && $hostname != '' ) 
+                    {
+                        $domain_len     = 0 - strlen( $jobs[$jobid]['domain'] );
+                        $hostnode     = $tempnode;
+
+                        if( $use_fqdn == 1)
+                        {
+                            if( substr( $hostnode, $domain_len ) != $jobs[$jobid]['domain'] ) 
+                            {
+                                $hostnode = $hostnode. '.'. $jobs[$jobid]['domain'];
+                            }
+                        }
+
+                        if( $hostname == $hostnode ) 
+                        {
+                            $found_node_job = 1;
+                            $display_job = 1;
+                        } 
+                        else if( !$found_node_job ) 
+                        {
+                            $display_job = 0;
+                        }
+                    }
+                }
+            }
+
+            if( $jobs[$jobid]['status'] == 'Q' ) 
+            {
+                if( isset( $hostname ) && $hostname != '' )
+                {
+                    $display_job = 0;
+                }
+
+                $queued_cpus  += $cpus;
+                $queued_nodes += $nodes;
+
+                $queued_jobs++;
+            }
+
+            foreach( $filter as $filtername=>$filtervalue ) 
+            {
+                if( $filtername == 'id' && $jobid != $filtervalue )
+                {
+                    $display_job = 0;
+                }
+                else if( $filtername == 'state' && $jobs[$jobid]['status'] != $filtervalue )
+                {
+                    $display_job = 0;
+                }
+                else if( $filtername == 'queue' && $jobs[$jobid]['queue'] != $filtervalue )
+                {
+                    $display_job = 0;
+                }
+                else if( $filtername == 'owner' && $jobs[$jobid]['owner'] != $filtervalue )
+                {
+                    $display_job = 0;
+                }
+            }
+
+
+            if( $display_job ) 
+            {
+                unset( $job_loop );
+                $job_loop = array();
+                $job_loop["clustername"] = $clustername;
+
+                $job_loop["id"] = $jobid;
+
+                $last_displayed_job     = $jobid;
+
+                $job_loop["state"] = $jobs[$jobid]['status'];
+
+                $fullstate         = '';
+
+                if( $jobs[$jobid]['status'] == 'R' ) 
+                {
+                    $fullstate     = "Running";
+                } 
+                else if( $jobs[$jobid]['status'] == 'Q' ) 
+                {
+                    $fullstate     = "Queued";
+                }
+
+                $job_loop["fullstate"] = $fullstate;
+                
+                $job_loop["owner"] = $jobs[$jobid]['owner'];
+                $job_loop["queue"] = $jobs[$jobid]['queue'];
+
+                $fulljobname         = $jobs[$jobid]['name'];
+                $shortjobname        = '';
+
+                $job_loop["fulljobname"] = $fulljobname;
+
+                $fulljobname_fields    = explode( ' ', $fulljobname );
+
+                $capjobname        = 0;
+
+                if( strlen( $fulljobname_fields[0] ) > 10 )
+                {
+                    $capjobname    = 1;
+                }
+
+                if( $capjobname ) 
+                {
+                    $job_loop[ "jobname_hint_start" ] = "yes";
+
+                    $shortjobname     = substr( $fulljobname, 0, 10 ) . '..';
+                } 
+                else 
+                {
+                    $shortjobname     = $fulljobname;
+                }
+                
+                $job_loop["name"] = $shortjobname;
+
+                if( $capjobname ) 
+                {
+                    $job_loop[ "jobname_hint_end" ] = "yes";
+                }
+
+                $domain         = $jobs[$jobid]['domain'];
+
+                $job_loop["req_cpu"] = makeTime( timeToEpoch( $jobs[$jobid]['requested_time'] ) );
+
+                if( $COLUMN_REQUESTED_MEMORY ) 
+                {
+                    $job_loop[ "column_req_mem" ] = "yes";
+                    $job_loop["req_memory"] = $jobs[$jobid]['requested_memory'];
+                }
+
+
+                if( $COLUMN_QUEUED ) 
+                {
+                    $job_loop[ "column_queued" ] = "yes";
+                    $job_loop["queued"] = makeDate( $jobs[$jobid]['queued_timestamp'] );
+                }
+                if( $COLUMN_NODES ) 
+                {
+                    $job_loop[ "column_nodes" ] = "yes";
+                    //echo "colum nodes";
+                }
+
+                $ppn       = isset( $jobs[$jobid]['ppn'] ) ? $jobs[$jobid]['ppn'] : 1;
+                $cpus      = $nodes * $ppn;
+
+                $job_loop["nodes"] = $nodes;
+                $job_loop["cpus"] = $cpus;
+
+                $start_time= (int) $jobs[$jobid]['start_timestamp'];
+                $job_start = $start_time;
+
+
+                $view_cpus += $cpus;
+
+                $view_jobs++;
+
+                if( $jobs[$jobid]['status'] == 'R' ) 
+                {
+                    $job_runningtime    = $heartbeat - $start_time;
+                    if( $rjqj_start == null ) 
+                    {
+                        $rjqj_start = intval( $start_time - (intval( $job_runningtime * 0.10 ) ) );
+                    }
+                    else if( $start_time < $rjqj_start )
+                    {
+                        $rjqj_start = intval( $start_time - (intval( $job_runningtime * 0.10 ) ) );
+                    }
+
+                    foreach( $jobs[$jobid]['nodes'] as $tempnode )
+                    {
+                        $view_name_nodes[]     = $tempnode;
+                    }
+
+                    if( $COLUMN_NODES ) 
+                    {
+                        $job_loop[ "column_nodes" ] = "yes";
+
+                        $mynodehosts         = array();
+
+                        foreach( $jobs[$jobid]['nodes'] as $shortnode ) 
+                        {
+                            if( $use_fqdn == 1)
+                            {
+                                $mynode     = $shortnode.".".$jobs[$jobid]['domain'];
+                            }
+                            $myhost_href    = "./?c=".$clustername."&h=".$mynode;
+                            $mynodehosts[]  = "<A HREF=\"".$myhost_href."\">".$shortnode."</A>";
+                        }
+
+                        $nodes_hostnames    = implode( " ", $mynodehosts );
+
+                        $job_loop["nodes_hostnames"] = $nodes_hostnames;
+                    }
+                } 
+                else if( $jobs[$jobid]['status'] == 'Q' ) 
+                {
+                    $view_nodes     += (int) $jobs[$jobid]['nodes'];
+                }
+
+                if( $even ) 
+                {
+                    $job_loop["nodeclass"] = "even";
+
+                    $even         = 0;
+                } 
+                else 
+                {
+                    $job_loop["nodeclass"] = "odd";
+
+                    $even         = 1;
+                }
+
+                if( $start_time ) 
+                {
+                    $runningtime        = makeTime( $report_time - $start_time );
+                    $job_runningtime    = $heartbeat - $start_time;
+
+                    $job_loop["started"] = makeDate( $start_time );
+                    $job_loop["runningtime"] = $runningtime;
+                }
+                $node_list[] = &$job_loop;
+            }
+            $tpl_data["node_list"]= &$node_list ;
+        }
+    }
+    if( intval($view_jobs) == 1 and $start_time )
+    {
+        if( $last_displayed_job != null )
+        {
+            $filter['id'] = $last_displayed_job;
+        }
+    }
+    // Running / queued amount jobs graph
+    //
+    if( $rjqj_host != null )
+    {
+        $rjqj_graphargs = "?z=overview-medium&c=$clustername&h=$rjqj_host&g=job_report&r=$range";
+        if( $range == 'job' )
+        {
+            $rjqj_end = time();
+
+            if( $rjqj_start == null ) 
+            { // probably no running jobs in this view: only queued
+                $rjqj_start = $rjqj_end - (24 * 3600); // default to 1 day
+            }
+            $rjqj_title = rawurlencode( makeTime( ($rjqj_end - $rjqj_start) ) );
+            $rjqj_graphargs .= "&period_start=$rjqj_start&period_stop=$rjqj_end&t=$rjqj_title";
+        }
+        else
+        {
+            $rjqj_graphargs .= "&st=$cluster[LOCALTIME]";
+        }
+        if( intval($view_jobs) == 1 and $start_time )
+        {
+            $job_start     = $jobs[$last_displayed_job]['start_timestamp'];
+            $rjqj_graphargs .= "&job_start=$start_time";
+        }
+
+        $rjqj_str  = "<A HREF=\"./graph.php$rjqj_graphargs\">";
+        $rjqj_str .= "<IMG BORDER=0 SRC=\"./graph.php$rjqj_graphargs\">";
+        $rjqj_str .= "</A>";
+
+        $tpl_data[ "rjqj_graph"]= $rjqj_str ;
+    }
+
+    $all_used_nodes     = array_unique( $all_used_nodes );
+    $view_name_nodes    = array_unique( $view_name_nodes );
+    $running_name_nodes = array_unique( $running_name_nodes );
+
+    $used_nodes         = count( $all_used_nodes );
+    $view_nodes        += count( $view_name_nodes );
+    $running_nodes     += count( $running_name_nodes );
+
+    $total_nodes        = $queued_nodes + $running_nodes;
+    $total_cpus         = $queued_cpus + $running_cpus;
+    $total_jobs         = $queued_jobs + $running_jobs;
+
+    $free_nodes         = $avail_nodes - $running_nodes - $na_nodes;
+    $free_nodes         = ( $free_nodes >= 0 ) ? $free_nodes : 0;
+    $free_cpus          = $avail_cpus - $running_cpus - $na_cpus;
+    $free_cpus          = ( $free_cpus >= 0 ) ? $free_cpus : 0;
+
+    $tpl_data[ "avail_nodes"]= $avail_nodes ;
+    $tpl_data[ "avail_cpus"]= $avail_cpus ;
+
+    $tpl_data[ "queued_nodes"]= $queued_nodes ;
+    $tpl_data[ "queued_jobs"]= $queued_jobs ;
+    $tpl_data[ "queued_cpus"]= $queued_cpus ;
+
+    // Only display "Unavailable" in count overview there are any
+    //
+    if( $na_nodes > 0 )
+    {
+        $tpl_data[ "na_nodes"]= "yes";
+
+        $tpl_data[ "na_nodes"]= $na_nodes ;
+        $tpl_data[ "na_cpus"]= $na_cpus ;
+    }
+
+    $tpl_data[ "total_nodes"]= $total_nodes ;
+    $tpl_data[ "total_jobs"]= $total_jobs ;
+    $tpl_data[ "total_cpus"]= $total_cpus ;
+
+    $tpl_data[ "running_nodes"]= $running_nodes ;
+    $tpl_data[ "running_jobs"]= $running_jobs ;
+    $tpl_data[ "running_cpus"]= $running_cpus ;
+
+    $tpl_data[ "used_nodes"]= $used_nodes ;
+    $tpl_data[ "used_jobs"]= $used_jobs ;
+    $tpl_data[ "used_cpus"]= $used_cpus ;
+
+    $tpl_data[ "free_nodes"]= $free_nodes ;
+    $tpl_data[ "free_cpus"]= $free_cpus ;
+
+    $tpl_data[ "view_nodes"]= $view_nodes ;
+    $tpl_data[ "view_jobs"]= $view_jobs ;
+    $tpl_data[ "view_cpus"]= $view_cpus ;
+
+    $tpl_data[ "report_time"]= makeDate( $heartbeat);
+
+
+    global $longtitle, $title;
+
+    $longtitle = "Batch Report :: Powered by Job Monarch!";
+    $title = "Batch Report";
+
+    makeHeader( 'overview', $title, $longtitle );
+
+    setupFilterSettings();
+
+    if( intval($view_jobs) == 1 and $start_time ) 
+    {
+        $tpl_data[ "showhosts"]= "yes" ;
+
+        $showhosts     = isset($sh) ? $sh : $default_showhosts;
+
+        $tpl_data[ "checked$showhosts"]= "checked" ;
+
+        $sorted_list = array();
+
+        if( $showhosts ) 
+        {
+            if( !isset( $start ) ) 
+            {
+                $start    ="jobstart";
+            }
+            if( !isset( $stop ) ) 
+            {
+                $stop    ="now";
+            }
+
+            $sorted_hosts = array();
+            $hosts_up     = $jobs[$filter['id']]['nodes'];
+
+            $r            = intval($job_runningtime * 1.2);
+
+            $jobrange     = -$r ;
+            $jobstart     = $start_time;
+
+            if ( $reports[$metricname] )
+            {
+                $metricval     = "g";
+            }
+            else
+            {
+                $metricval    = "m";
+            }
+                
+            foreach ( $hosts_up as $host ) 
+            {
+                $domain_len         = 0 - strlen( $domain );
+
+                if( $use_fqdn )
+                {
+                    if( substr( $host, $domain_len ) != $domain ) 
+                    {
+                        $host         = $host . '.' . $domain;
+                    }
+                }
+                $cpus             = 0;
+
+                $cpus             = $metrics[$host]["cpu_num"]["VAL"];
+
+                if( $cpus == 0 )
+                {
+                    $cpus        = 1;
+                }
+
+                $load_one         = $metrics[$host]["load_one"]['VAL'];
+                $load             = ((float) $load_one) / $cpus;
+                $host_load[$host] = $load;
+
+                $percent_hosts[load_color($load)] ++;
+
+                if ($metricname=="load_one")
+                {
+                    $sorted_hosts[$host]     = $load;
+                }
+                else
+                {
+                    $sorted_hosts[$host]     = $metrics[$host][$metricname]['VAL'];
+                }
+            }
+
+            switch ( $sort ) 
+            {
+                case "descending":
+                    arsort( $sorted_hosts );
+                    break;
+
+                case "by hostname":
+                    ksort( $sorted_hosts );
+                    break;
+
+                case "ascending":
+                    asort( $sorted_hosts );
+                    break;
+
+                default:
+                    break;
+            }
+
+            // First pass to find the max value in all graphs for this
+            // metric. The $start,$end variables comes from get_context.php,
+            // included in index.php.
+            //
+            list($min, $max) = find_limits($sorted_hosts, $metricname);
+
+            // Second pass to output the graphs or metrics.
+            $i = 1;
+
+            $metric_loop = array();
+            foreach ( $sorted_hosts as $host=>$value  ) 
+            {
+                $host_url    = rawurlencode( $host );
+                $cluster_url = rawurlencode( $clustername );
+
+                $textval     = "";
+
+                $val         = $metrics[$host][$metricname];
+                $class       = "metric";
+
+                if ( $val["TYPE"] == "timestamp" || $always_timestamp[$metricname] ) 
+                {
+                    $textval     = date( "r", $val["VAL"] );
+                } 
+                elseif ( $val["TYPE"] == "string" || $val["SLOPE"] == "zero" || $always_constant[$metricname] || ($max_graphs > 0 and $i > $max_graphs ))
+                {
+                    $textval     = $val["VAL"] . " " . $val["UNITS"];
+                } 
+                else 
+                {
+                    $job_start     = $jobs[$last_displayed_job]['start_timestamp'];
+                    $period_end    = $cluster['LOCALTIME'];
+                    $runningtime   = intval( $period_end ) - intval( $job_start );
+                    $load_color    = load_color($host_load[$host]);
+                    $period_start  = intval( $job_start - (intval( $runningtime * 0.10 ) ) );
+                    //printf("last job %s job start %s runningtime %s period start %s", $last_displayed_job, $jobstart, $job_runningtime, $period_start);
+                    $graphargs     = ($reports[$metricname]) ? "g=$metricname&" : "m=$metricname&";
+                    $graphargs    .= "z=overview-medium&c=$cluster_url&r=$range&h=$host_url&l=$load_color&v=".$val['VAL']."&job_start=$job_start&vl=".$val['UNITS'];
+                    $host_link     = "?j_view=overview-host&c=$cluster_url&r=$range&h=$host_url&job_start=$jobstart";
+
+                    if( $range == 'job' )
+                    {
+                        $graphargs     .= "&period_start=$period_start&period_stop=$period_end";
+                        $host_link     .= "&period_start=$period_start&period_stop=$period_end";
+                    }
+                    else
+                    {
+                        $graphargs     .= "&st=$period_end";
+                        $host_link     .= "&st=$period_end";
+                    }
+                    if( $max > 0 ) 
+                    {
+                        $graphargs    .= "&x=$max&n=$min";
+                    }
+                }
+                if ($textval) 
+                {
+                    $cell    = "<td class=$class>".  "<b><a href=$host_link>$host</a></b><br>".  "<i>$metricname:</i> <b>$textval</b></td>";
+                } else {
+                    $cell    = "<A HREF=\"$host_link\">" . "<IMG SRC=\"./graph.php?$graphargs\" " . "ALT=\"$host\" BORDER=0></A>";
+                }
+
+                $metric_loop["metric_image"] = $cell;
+
+                //if(! ($i++ % $hostcols) )
+                //{
+                //     $metric_loop["br"] = "</tr><tr>";
+                //}
+                $sorted_list[] = $metric_loop;
+            }
+            $tpl_data["sorted_list"]= $sorted_list ;
+        }
+    }
+    $dwoo->output($tpl, $tpl_data);
+}
+
+?>
Index: /branches/1.0/web/addons/job_monarch/search.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/search.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/search.php	(revision 825)
@@ -0,0 +1,506 @@
+<?php
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006-2013  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * SVN $Id$
+ */
+
+include_once "./dwoo/dwooAutoload.php";
+
+global $dwoo;
+
+global $clustername, $m, $metric;
+
+function validateFormInput() {
+    global $clustername, $dwoo, $id, $owner, $name, $start_from_time, $start_to_time, $queue;
+    global $end_from_time, $end_to_time, $period_start, $period_stop, $tpl_data;
+
+    $error = false;
+    $error_msg = "<FONT COLOR=\"red\"><B>";
+    $show_msg = 0;
+
+    $none_set = 0;
+
+    if( $id == '' and $owner== '' and $name == '' and $start_from_time == '' and $start_to_time == '' and $queue == '' and $end_from_time == '' and $end_to_time == '') {
+        $error = true;
+        $show_msg = 1;
+        $error_msg .= "No search criteria set!";
+    }
+
+    if( !$error and $period_start != '' ) 
+    {
+        $pstart_epoch = datetimeToEpoch( $period_start );
+        if( $period_stop != '' ) 
+        {
+
+            $pstop_epoch = datetimeToEpoch( $period_stop );
+
+            if( $pstart_epoch > $pstop_epoch ) {
+
+                $show_msg = 1;
+                $error = true;
+                $error_msg .= "Graph timeperiod reset: start date/time can't be later than end";
+                $period_stop = '';
+                $period_start = '';
+            } else if( $pstop_epoch == $pstart_epoch ) {
+
+                $show_msg = 1;
+                $error = true;
+                $error_msg .= "Graph timeperiod reset: start and end date/time can't be the same";
+                $period_stop = '';
+                $period_start = '';
+            }
+        }
+    }
+
+    $error_msg .= "</B></FONT>";
+
+    return array( $error_msg, $error);
+}
+
+function datetimeToEpoch( $datetime ) {
+
+    $datetime_fields = explode( ' ', $datetime );
+
+    $date = $datetime_fields[0];
+    $time = $datetime_fields[1];
+
+    $date_fields = explode( '-', $date );
+
+    $days = $date_fields[0];
+    $months = $date_fields[1];
+    $years = $date_fields[2];
+
+    $time_fields = explode( ':', $time );
+
+    $hours = $time_fields[0];
+    $minutes = $time_fields[1];
+    $seconds = $time_fields[2];
+
+    $timestamp = mktime( $hours, $minutes, $seconds, $months, $days, $years );
+
+    return $timestamp;
+}
+
+function timeToEpoch( $time ) {
+
+        $time_fields = explode( ':', $time );
+
+        if( count($time_fields) == 3 ) {
+
+                $hours = $time_fields[0];
+                $minutes = $time_fields[1];
+                $seconds = $time_fields[2];
+
+        } else if( count($time_fields) == 2 ) {
+
+                $hours = 0;
+                $minutes = $time_fields[0];
+                $seconds = $time_fields[1];
+
+        } else if( count($time_fields) == 1 ) {
+
+                $hours = 0;
+                $minutes = 0;
+                $seconds = $time_fields[0];
+        }
+
+        $myepoch = intval( $seconds + (intval( $minutes * 60 )) + (intval( $hours * 3600 )) );
+
+        return $myepoch;
+}
+
+function sortJobs( $jobs, $nodes, $sortby, $sortorder ) {
+
+        $sorted = array();
+
+        $cmp = create_function( '$a, $b',
+                "global \$sortby, \$sortorder;".
+
+                "if( \$a == \$b ) return 0;".
+
+                "if (\$sortorder==\"desc\")".
+                        "return ( \$a < \$b ) ? 1 : -1;".
+                "else if (\$sortorder==\"asc\")".
+                        "return ( \$a > \$b ) ? 1 : -1;" );
+
+        foreach( $jobs as $jobid => $jobattrs ) {
+
+                        $state = $jobattrs['status'];
+                        $owner = $jobattrs['owner'];
+                        $queue = $jobattrs['queue'];
+                        $name = $jobattrs['name'];
+                        $req_cpu = $jobattrs['requested_time'];
+                        $req_memory = $jobattrs['requested_memory'];
+
+                        $mynodes = count( $nodes[$jobid] );
+
+                        $ppn = (int) $jobattrs['ppn'] ? $jobattrs['ppn'] : 1;
+                        $cpus = $mynodes * $ppn;
+                        $start_time = (int) $jobattrs['start_timestamp'];
+                        $stop_time = (int) $jobattrs['stop_timestamp'];
+                        $runningtime = $stop_time - $start_time;
+
+                        switch( $sortby ) {
+                                case "id":
+                                        $sorted[$jobid] = $jobid;
+                                        break;
+
+                                case "state":
+                                        $sorted[$jobid] = $state;
+                                        break;
+
+                                case "owner":
+                                        $sorted[$jobid] = $owner;
+                                        break;
+
+                                case "queue":
+                                        $sorted[$jobid] = $queue;
+                                        break;
+
+                                case "name":
+                                        $sorted[$jobid] = $name;
+                                        break;
+
+                                case "req_cpu":
+                                        $sorted[$jobid] = timeToEpoch( $req_cpu );
+                                        break;
+
+                                case "req_mem":
+                                        $sorted[$jobid] = $req_memory;
+                                        break;
+
+                                case "nodes":
+                                        $sorted[$jobid] = $mynodes;
+                                        break;
+
+                                case "cpus":
+                                        $sorted[$jobid] = $cpus;
+                                        break;
+
+                                case "start":
+                                        $sorted[$jobid] = $start_time;
+                                        break;
+
+                case "finished":
+                    $sorted[$jobid] = $stop_time;
+                    break;
+
+                                case "runningtime":
+                                        $sorted[$jobid] = $runningtime;
+                                        break;
+
+                                default:
+                                        break;
+
+                        }
+        }
+
+        if( $sortorder == "asc" )
+                arsort( $sorted );
+        else if( $sortorder == "desc" )
+                asort( $sorted );
+
+        return array_keys( $sorted );
+}
+
+function makeSearchPage() {
+    global $clustername, $dwoo, $id, $owner, $name, $start_from_time, $start_to_time, $queue;
+    global $end_from_time, $end_to_time, $filter, $default_showhosts, $m, $hosts_up, $hc;
+    global $period_start, $period_stop, $sortby, $sortorder, $COLUMN_REQUESTED_MEMORY;
+    global $SEARCH_RESULT_LIMIT, $COLUMN_NODES, $metricname, $self, $conf;
+
+    $longtitle = "Batch Archive Search :: Powered by Job Monarch!";
+    $title = "Batch Archive Search";
+
+    makeHeader( 'search', $title, $longtitle );
+
+    $tpl = new Dwoo_Template_File("templates/search.tpl");
+    $tpl_data = new Dwoo_Data();
+
+    $tpl_data->assign( "self", $self );
+    $tpl_data->assign( "cluster", $clustername );
+    $tpl_data->assign( "id_value", $id );
+    $tpl_data->assign( "owner_value", $owner);
+    $tpl_data->assign( "queue_value", $queue );
+    $tpl_data->assign( "name_value", $name );
+    $tpl_data->assign( "start_from_value", rawurldecode( $start_from_time ) );
+    $tpl_data->assign( "start_to_value", rawurldecode( $start_to_time ) );
+    $tpl_data->assign( "end_from_value", rawurldecode( $end_from_time ) );
+    $tpl_data->assign( "end_to_value", rawurldecode( $end_to_time ) );
+
+    list( $form_error_msg, $form_error ) = validateFormInput();
+
+    if( $form_error == true )
+    {
+        $tpl_data->assign( "form_error_msg", $form_error_msg );
+    } 
+    else if( $form_error == false ) 
+    {
+        $tpl_data->assign( "search_results", "yes" );
+        $tpl_data->assign( "sortby", $sortby);
+        $tpl_data->assign( "sortorder", $sortorder);
+        $tpl_data->assign( "form_name", "archive_search_form" );
+        $tdb = new TarchDbase( "127.0.0.1" );
+        if( $start_from_time ) $start_from_time = datetimeToEpoch( $start_from_time );
+        if( $start_to_time ) $start_to_time = datetimeToEpoch( $start_to_time );
+        if( $end_from_time ) $end_from_time = datetimeToEpoch( $end_from_time );
+        if( $end_to_time ) $end_to_time = datetimeToEpoch( $end_to_time );
+        $search_ids = $tdb->searchDbase( $id, $queue, $owner, $name, $start_from_time, $start_to_time, $end_from_time, $end_to_time );
+
+        //print_r( $search_ids );
+        if( ($tdb->resultcount) > (int) $SEARCH_RESULT_LIMIT ) {
+       
+            $tpl_data->assign( "form_error_msg", "Got " . $tdb->resultcount . " search results, output limited to last " . $SEARCH_RESULT_LIMIT . " jobs." );
+        }
+
+        $jobs = array();
+        $nodes = array();
+
+        $even = 1;
+
+        foreach( $search_ids as $myid ) {
+
+            $jobs[$myid] = $tdb->getJobArray( $myid );
+            $nodes[$myid] = $tdb->getNodesForJob( $myid );
+        }
+
+        if( $COLUMN_REQUESTED_MEMORY ) {
+            $tpl_data->assign( "column_header_req_mem", "yes" );
+        }
+        if( $COLUMN_NODES ) {
+            $tpl_data->assign( "column_header_nodes", "yes" );
+        }
+
+        $sorted_search = sortJobs( $jobs, $nodes, $sortby, $sortorder );
+
+        $node_loop = array();
+        foreach( $sorted_search as $sortid ) {
+
+            $job = $jobs[$sortid];
+            $foundid = $job['id'];
+
+            $node_list = array();
+            $node_list["id"]= $job['id'];
+            $node_list["state"]= $job['status'];
+            $node_list["owner"]= $job['owner'];
+            $node_list["queue"]= $job['queue'];
+            $node_list["name"]= $job['name'];
+            $node_list["req_cpu"]= makeTime( TimeToEpoch( $job['requested_time'] ) );
+
+            if( $COLUMN_REQUESTED_MEMORY ) 
+            {
+                $node_list["column_req_mem"] = "yes";
+                $node_list["req_memory"]= $job['requested_memory'];
+            }
+            if( $COLUMN_NODES) 
+            {
+
+                $job_nodes    = array();
+
+                foreach( $nodes[$foundid] as $mynode )
+                {
+                    if( strpos( $mynode['hostname'], "." ) !== false )
+                    {
+                        $shorthost = explode( '.', $mynode['hostname'] );
+                        $job_nodes[] = $shorthost[0];
+                    }
+                    else
+                    {
+                        $job_nodes[] = $mynode['hostname'];
+                    }
+                }
+
+                $node_list["column_nodes_hostnames"] = "yes";
+                $nodes_hostnames = implode( " ", $job_nodes );
+                $node_list["nodes_hostnames"]= $nodes_hostnames;
+            }
+
+            $nodes_nr = count( $nodes[$foundid] );
+
+            if( $even ) {
+
+                $node_list["nodeclass"]= "even";
+                $even = 0;
+            } else {
+
+                $node_list["nodeclass"]= "odd";
+                $even = 1;
+            }
+
+            $ppn = (int) $job['ppn'] ? $job['ppn'] : 1;
+            $cpus = $nodes_nr * $ppn;
+
+            $node_list["nodes"]= $nodes_nr;
+            $node_list["cpus"]= $cpus;
+
+            $job_start = $job['start_timestamp'];
+            $job_stop = $job['stop_timestamp'];
+            $runningtime = intval( $job_stop - $job_start );
+            $node_list["started"]= makeDate( $job_start );
+            $node_list["finished"]= makeDate( $job_stop );
+            $node_list["runningtime"]= makeTime( $runningtime );
+            
+            $node_loop[]=$node_list;
+        }
+        //print_r( $node_loop );
+        $tpl_data->assign("node_list", $node_loop );
+
+        if( count( $search_ids ) == 1 ) {
+
+            $tpl_data->assign( "showhosts", "yes" );
+
+            $showhosts = isset($sh) ? $sh : $default_showhosts;
+            $tpl_data->assign("checked$showhosts", "checked");
+
+            if( $showhosts ) {
+
+                if( !$period_start ) // Add an extra 10% to graphstart
+                    $period_start = intval( $job_start - (intval( $runningtime * 0.10 ) ) );
+                else
+                    $period_start = datetimeToEpoch( $period_start );
+
+                if( !$period_stop ) // Add an extra 10% to graphend
+                    $period_stop = intval( $job_stop + (intval( $runningtime * 0.10 ) ) );
+                else
+                    $period_stop = datetimeToEpoch( $period_stop );
+
+                $tpl_data->assign( "timeperiod", "yes" );
+
+                $tpl_data->assign("period_start", epochToDatetime( $period_start ) );
+                $tpl_data->assign("period_stop", epochToDatetime( $period_stop ) );
+
+                $hosts_up = array();
+
+                foreach( $nodes[$id] as $mynode )
+                    $hosts_up[] = $mynode['hostname'];
+
+                $sorted_hosts = array();
+
+                foreach ($hosts_up as $host ) {
+                    $cpus = $metrics[$host]["cpu_num"]['VAL'];
+                    if (!$cpus) $cpus=1;
+                    $load_one  = $metrics[$host]["load_one"]['VAL'];
+                    $load = ((float) $load_one)/$cpus;
+                    $host_load[$host] = $load;
+                    $percent_hosts['load_color'.($load)] += 1;
+                    if ($metricname=="load_one")
+                        $sorted_hosts[$host] = $load;
+                    else
+                        $sorted_hosts[$host] = $metrics[$host][$metricname]['VAL'];
+                }
+                switch ($sort) {
+                    case "descending":
+                        arsort($sorted_hosts);
+                        break;
+                    case "by hostname":
+                        ksort($sorted_hosts);
+                        break;
+                    default:
+                    case "ascending":
+                        asort($sorted_hosts);
+                        break;
+                }
+
+                $trd = new TarchRrdGraph( $clustername, $host );
+                $rrd_dirs = $trd->getRrdDirs( $period_start, $period_stop );
+
+                $metric_menu = "<B>Metric</B>&nbsp;&nbsp;"
+                    ."<SELECT NAME=\"m\" OnChange=\"toga_form.submit();\">\n";
+
+                $archive_reports = array( "load_report", "cpu_report", "network_report", "packet_report", "mem_report" );
+                $file_metrics = $trd->dirList( $rrd_dirs[0] );
+
+                $archive_metrics = array();
+
+                foreach( $file_metrics as $k )
+                {
+                    $a_metric_file = explode( '.', $k);
+                    $archive_metrics[] = $a_metric_file[0];
+                }
+
+                $sorted_metrics = array_merge( $archive_reports, $archive_metrics );
+
+                asort( $sorted_metrics );
+
+                $selected_metric = $conf['default_metric'];
+
+                if( $metricname != '')
+                {
+                    $selected_metric = $metricname;
+                }
+
+                foreach( $sorted_metrics as $k )
+                {
+                    $url = rawurlencode($k);
+                    $metric_menu .= "<OPTION VALUE=\"$url\" ";
+                    if ($k == $selected_metric )
+                        $metric_menu .= "SELECTED";
+                    $metric_menu .= ">$k\n";
+                }
+                $metric_menu .= "</SELECT>\n";
+
+                $tpl_data->assign("metric_menu", $metric_menu );
+
+                # First pass to find the max value in all graphs for this
+                # metric. The $start,$end variables comes from get_context.php,
+                # included in index.php.
+                #list($min, $max) = find_limits($sorted_hosts, $metricname);
+
+                $sorted_loop = array();
+                # Second pass to output the graphs or metrics.
+                $i = 1;
+                foreach ( $sorted_hosts as $host=>$value  ) {
+                    $sorted_list = array();
+                    $host_url = rawurlencode($host);
+                    $cluster_url = rawurlencode($clustername);
+
+                    $textval = "";
+                    $val = $metrics[$host][$metricname];
+                    $class = "metric";
+                    $host_link="\"?j_view=host&self=$self&c=$cluster_url&h=$host_url&job_start=$job_start&job_stop=$job_stop&period_start=$period_start&period_stop=$period_stop\"";
+
+                    if ($val['TYPE']=="timestamp" or $always_timestamp[$metricname]) {
+                        $textval = date("r", $val['VAL']);
+                    } elseif ($val['TYPE']=="string" or $val['SLOPE']=="zero" or $always_constant[$metricname] or ($max_graphs > 0 and $i > $max_graphs )) {
+                        $textval = $val['VAL']." ".$val['UNITS'];
+                    } else {
+                        $graphargs = "z=medium&c=$cluster_url&m=$metricname&h=$host_url&v=".$val['VAL']."&job_start=$job_start&job_stop=$job_stop&period_start=$period_start&period_stop=$period_stop";
+                    }
+                    if ($textval) {
+                        $cell="<td class=$class>".  "<b><a href=$host_link>$host</a></b><br>".  "<i>$metricname:</i> <b>$textval</b></td>";
+                    } else {
+                        $cell="<td><a href=$host_link>".  "<img src=\"./graph.php?$graphargs\" ".  "alt=\"$host\" border=0></a></td>";
+                    }
+
+                    $sorted_list["metric_image"]= $cell;
+                    if (! ($i++ % $hostcols) )
+                         $sorted_list["br"]= "</tr><tr>";
+
+                    $sorted_loop[]=$sorted_list;
+                }
+                $tpl_data->assign("sorted_list", $sorted_loop );
+            }
+        }
+
+    }
+    $dwoo->output($tpl, $tpl_data);
+}
+?>
Index: /branches/1.0/web/addons/job_monarch/styles.css
===================================================================
--- /branches/1.0/web/addons/job_monarch/styles.css	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/styles.css	(revision 825)
@@ -0,0 +1,284 @@
+#monarchimage img {
+    background-color:#FFF;
+    background-image:url(ajax-loader.gif);
+    background-repeat:no-repeat;
+    background-position: center center;
+    overflow:auto;
+}
+
+A:link
+{
+   color: rgb(32,41,204);
+   text-decoration: none;
+}
+
+A:visited
+{
+   color: rgb(51,51,204);
+   text-decoration: none;
+}
+
+A:active
+{
+   color: rgb(204,205,226);
+   text-decoration: none;
+}
+
+h1 
+{
+   font: bold 18pt/22pt helvetica, sans-serif;
+   color: rgb(12,17,142);
+   margin-bottom: 2mm;
+}
+
+h2
+{
+   font: bold 14pt/16pt helvetica;
+   color: rgb(12,17,142); 
+   margin-left: 0.1em;
+   margin-right: 0.1em;
+}
+
+h3
+{
+   font: bold 12pt/15pt helvetica, sans-serif;
+   text-align: left;
+   margin-left: 3mm;
+   color: rgb(247,142,14);
+}
+
+h4
+{
+   font: bold 12pt/14pt helvetica;
+   color: rgb(12,17,142); 
+   margin-left: 0.1em;
+   margin-right: 0.1em;
+}
+
+small
+{
+   font: 10pt/12pt arial, helvetica, sans-serif;
+}
+
+td.title
+{
+   font-size: larger;
+   font-weight: normal;
+   background: rgb(238,238,238);
+   text-align: center;
+}
+
+td.metric
+{
+   background: rgb(217,218,224);
+   padding: 2mm;
+}
+
+td.cluster
+{
+   background: rgb(238,238,238);
+   /* padding: 1mm; */
+}
+
+td.grid
+{
+   vertical-align: top;
+   background: rgb(204,204,255);
+}
+
+td.self
+{
+   vertical-align: top;
+   text-align: center;
+   background: rgb(255,153,153);
+}
+
+td.even
+{
+   background: rgb(201,205,234);
+}
+
+td.odd
+{
+   background: rgb(217,218,224);
+}
+
+td.small
+{
+   font: 10pt/12pt times, serif;
+}
+
+td.L1
+{
+   background: rgb(184,249,119);
+}
+
+td.L2
+{
+   background: rgb(213,249,119);
+}
+
+td.L3
+{
+   background: rgb(228,249,119);
+}
+
+td.L4
+{
+   background: rgb(245,249,119);
+}
+
+td.L5
+{
+   background: rgb(249,239,119);
+}
+
+td.L6
+{
+   background: rgb(249,219,119);
+}
+
+td.L7
+{
+   background: rgb(249,202,119);
+}
+
+td.L8
+{
+   background: rgb(249,182,119);
+}
+
+td.L9
+{
+   background: rgb(249,152,119);
+}
+
+td.L10
+{
+   background: rgb(249,124,119);
+}
+
+td.down
+{
+   background: rgb(255,153,153);
+}
+
+th 
+{
+   font: bold 12pt/14pt helvetica, sans-serif;
+   color: rgb(238,238,238);
+   background: rgb(80,80,80);
+   border: thin solid rgb(47,47,47);
+}
+
+tr.even
+{
+   background: rgb(255,255,255);
+}
+
+tr.odd
+{
+   background: rgb(240,240,240);
+}
+
+tr.inactive
+{
+   color: rgb(210,210,210);
+}
+
+table.overview
+{
+   border-collapse: collapse;
+}
+
+*#kol1
+{
+   border-right-style: solid;
+   border-right-color: rgb(204,204,204);
+}
+
+*#selected
+{
+   background: rgb(153,204,255);
+}
+
+tr.overview_header
+{
+   font: bold 10pt/12pt helvetica, sans-serif;
+   text-align: right;
+   border-collapse: collapse;
+   border-bottom-style: solid; 
+   border-bottom-color: rgb(0,0,0);
+}
+
+tr.overview_line
+{
+   font: 10pt/12pt helvetica, sans-serif;
+   text-align: right;
+   border-collapse: collapse;
+   border-bottom-style: dotted;
+   border-bottom-color: rgb(204,204,204);
+}
+
+tr.overview td
+{
+   font: 10pt/12pt helvetica, sans-serif;
+   text-align: right;
+}
+
+tr.monarch th
+{
+   font: bold 12pt/14pt helvetica, sans-serif;
+   color: rgb(10,10,10);
+   background: rgb(240,240,240);
+   border: thin solid rgb(47,47,47);
+   padding: 2mm;
+   margin: 1mm;
+}
+
+.jobname_hint
+{
+   border-bottom: thin dotted;
+   border-bottom-color: rgb(255,0,0);
+}
+
+.red
+{
+	color: rgb(204,51,0);
+}
+.blue
+{
+	color: rgb(0,0,153);
+}
+.brown
+{
+	color: rgb(102,102,0);
+}
+.gray
+{
+	color: rgb(204,204,204);
+}
+.green
+{
+	color: rgb(0,204,102);
+}
+
+td em
+{
+   font: 8pt/10pt helvetica, sans-serif;
+   color: rgb(31,31,112);
+   font-style: normal;
+}
+
+td strong
+{
+   color: rgb(31,31,112);
+   font-style: normal;
+}
+
+.footer
+{
+      font: 10pt/12pt helvetica, sans-serif;
+      color: rgb(100,100,100);
+}
+
Index: /branches/1.0/web/addons/job_monarch/templates/footer.tpl
===================================================================
--- /branches/1.0/web/addons/job_monarch/templates/footer.tpl	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/templates/footer.tpl	(revision 825)
@@ -0,0 +1,16 @@
+<HR>
+<CENTER>
+<FONT SIZE="-1" class=footer>
+<A HREF="https://oss.trac.surfsara.nl/jobmonarch/">Job Monarch</A> version {$monarchversion}
+<A HREF="https://oss.trac.surfsara.nl/jobmonarch/">Check for Updates.</A><BR>
+
+Ganglia web version {$webfrontendversion}<BR>
+
+Downloading and parsing ganglia's XML tree took {$parsetime}.<BR>
+Images created with <A HREF="http://www.rrdtool.org/">RRDTool</A>.<BR>
+</FONT>
+</CENTER>
+
+</FORM>
+</BODY>
+</HTML>
Index: /branches/1.0/web/addons/job_monarch/templates/header.tpl
===================================================================
--- /branches/1.0/web/addons/job_monarch/templates/header.tpl	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/templates/header.tpl	(revision 825)
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<HEAD>
+<TITLE>Ganglia :: {$longpage_title}</TITLE>
+<META http-equiv="Content-type" content="text/html; charset=utf-8">
+<META http-equiv="refresh" content="{$refresh}{$redirect}" >
+<LINK rel="stylesheet" href="./styles.css" type="text/css">
+<SCRIPT LANGUAGE="javascript" SRC="ts_picker.js"></SCRIPT>
+<SCRIPT LANGUAGE="javascript">
+
+        function setPeriodTimestamps() {
+
+                document.{$form_name}.period_start.value = document.{$form_name}.period_start_pick.value;
+                document.{$form_name}.period_stop.value = document.{$form_name}.period_stop_pick.value;
+        }
+
+</SCRIPT>
+</HEAD>
+<BODY BGCOLOR="#FFFFFF">
+
+<FORM ACTION="./" METHOD="GET" NAME="{$form_name}">
+<INPUT TYPE="HIDDEN" NAME="c" VALUE="{$cluster}">
+<INPUT TYPE="HIDDEN" NAME="self" VALUE="{$self}">
+<INPUT TYPE="HIDDEN" NAME="j_view" VALUE="{$view}">
+<TABLE WIDTH="100%">
+<TR>
+  <TD ROWSPAN="2" WIDTH="150">
+  <A HREF="https://subtrac.sara.nl/oss/jobmonarch/">
+  <IMG SRC="./jobmonarch.gif" 
+      ALT="Ganglia" BORDER="0"></A>
+  </TD>
+  <TD VALIGN="TOP">
+
+  <TABLE WIDTH="100%" CELLPADDING="8" CELLSPACING="0" BORDER=0>
+  <TR BGCOLOR="#DDDDDD">
+     <TD BGCOLOR="#DDDDDD">
+     <FONT SIZE="+1">
+     <B>{$page_title} for {$date}</B>
+     </FONT>
+     </TD>
+     <TD BGCOLOR="#DDDDDD" ALIGN="RIGHT">
+     </TD>
+     <TD></TD>
+  </TR>
+  <TR>
+     <TD COLSPAN=1>
+     {$metric_menu} &nbsp;&nbsp;
+     {$range_menu}&nbsp;&nbsp;
+     {$sort_menu}&nbsp;&nbsp;
+{if "$timeperiod" == "yes"}
+    <INPUT TYPE="HIDDEN" NAME="period_start" VALUE="{$period_start}">
+    <INPUT TYPE="HIDDEN" NAME="period_stop" VALUE="{$period_stop}">
+    <INPUT TYPE="HIDDEN" NAME="h" VALUE="{$hostname}">
+    <BR><BR><B>Graph/ from
+    <INPUT TYPE="text" NAME="period_start_pick" VALUE="{$period_start}" ALT="Start time" DISABLED="TRUE">
+    <a href="javascript:show_calendar('document.{$form_name}.period_start_pick', document.{$form_name}.period_start_pick.value);" alt="Click to select a date/time" title="Click to select a date/time">
+    <img src="cal.gif" width="16" height="16" border="0"></a>
+    <a href="#" onClick="javascript: document.{$form_name}.period_start_pick.value=''" alt="Click here to clear field" title="Click here to clear field">
+    <IMG SRC="redcross.jpg" BORDER=0></A>
+    to <INPUT TYPE="text" NAME="period_stop_pick" VALUE="{$period_stop}" ALT="Stop time" DISABLED="TRUE">
+    <a href="javascript:show_calendar('document.{$form_name}.period_stop_pick', document.{$form_name}.period_stop_pick.value);" alt="Click to select a date/time" title="Click to select a date/time">
+    <img src="cal.gif" width="16" height="16" border="0"></a>
+    <a href="#" onClick="javascript: document.{$form_name}.period_stop_pick.value=''" alt="Click here to clear field" title="Click here to clear field">
+    </B>
+    <IMG SRC="redcross.jpg" BORDER=0></A>
+
+{if "$hostview" == "yes"}
+    <INPUT TYPE="HIDDEN" NAME="job_start" VALUE="{$job_start}">
+    <INPUT TYPE="HIDDEN" NAME="job_stop" VALUE="{$job_stop}">
+{/if}
+    <INPUT TYPE="submit" onClick="setPeriodTimestamps();" VALUE="Refresh graphs">
+{/if}
+     </TD>
+     <TD>
+      <B>{$alt_view}</B>
+     </TD>
+
+  </TR>
+  </TABLE>
+
+{if "$search" == "yes"}
+     <TD align="right"><CENTER>
+       <A HREF="./?c={$cluster_url}&j_view=search&self={$self}">
+       <B><I>Jobarchive</I></B><BR>
+       <IMG SRC="./document_archive.jpg" HEIGHT=100 ALT="Search the archive for {$cluster}" TITLE="Search the archive for {$cluster}" BORDER=0></A></CENTER>
+     </TD>
+{/if}
+
+  </TD>
+</TR>
+</TABLE> 
+
+<FONT SIZE="+1">
+{$node_menu}
+</FONT>
+
+<HR SIZE="1" NOSHADE>
Index: /branches/1.0/web/addons/job_monarch/templates/host_view.tpl
===================================================================
--- /branches/1.0/web/addons/job_monarch/templates/host_view.tpl	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/templates/host_view.tpl	(revision 825)
@@ -0,0 +1,91 @@
+<TABLE BORDER="0" WIDTH="100%">
+<TR>
+  <TD COLSPAN="2" BGCOLOR="#EEEEEE" ALIGN="CENTER">
+  <FONT SIZE="+2">{$host} Overview</FONT>
+  </TD>
+</TR>
+
+<TR>
+ <TD ALIGN="LEFT" VALIGN="TOP">
+
+<IMG SRC="{$node_image}" HEIGHT="60" WIDTH="30" ALT="{$host}" BORDER="0">
+{$node_msg}
+<P>
+
+<TABLE BORDER="0" WIDTH="100%">
+<TR>
+  <TD COLSPAN="2" CLASS=title>Time and String Metrics</TD>
+</TR>
+
+{loop $string_metric_info}
+<TR>
+ <TD CLASS=footer WIDTH=30%>{$name}</TD><TD>{$value}</TD>
+</TR>
+{/loop}
+
+<TR><TD>&nbsp;</TD></TR>
+
+<TR>
+  <TD COLSPAN=2 CLASS=title>Constant Metrics</TD>
+</TR>
+
+{loop $const_metric_info}
+<TR>
+ <TD CLASS=footer WIDTH=30%>{$name}</TD><TD>{$value}</TD>
+</TR>
+{/loop}
+
+<TR><TD>&nbsp;</TD></TR>
+
+<TR>
+ <TD COLSPAN=2 CLASS=title>
+ <a href="../../host_gmetrics.php?c={$cluster_url}&h={$host}">Gmetrics</a>
+ </TD>
+</TR>
+</TABLE>
+
+<a href="./?c={$cluster}&h={$host}">
+<IMG SRC="./image.php?c={$cluster}&h={$host}&j_view=hostimage" BORDER=0>
+</a>
+<hr>
+
+</TD>
+
+<TD ALIGN="CENTER" VALIGN="TOP" WIDTH="395">
+<div id="monarchimage">
+<IMG ALT="{$cluster_url} LOAD" SRC="./graph.php?g=load_report&z=overview-medium&c={$cluster_url}&{$graphargs}">
+<IMG ALT="{$cluster_url} MEM" SRC="./graph.php?g=mem_report&z=overview-medium&c={$cluster_url}&{$graphargs}">
+<IMG ALT="{$cluster_url} CPU" SRC="./graph.php?g=cpu_report&z=overview-medium&c={$cluster_url}&{$graphargs}">
+<IMG ALT="{$cluster_url} NETWORK" SRC="./graph.php?g=network_report&z=overview-medium&c={$cluster_url}&{$graphargs}">
+<IMG ALT="{$cluster_url} PACKETS" SRC="./graph.php?g=packet_report&z=overview-medium&c={$cluster_url}&{$graphargs}">
+</div>
+</TD>
+</TR>
+</TABLE>
+<BR>
+<BR>
+<TABLE BORDER="0" WIDTH="100%">
+<TR>
+  <TD CLASS=title>
+  {$host} <strong>graphs</strong>
+  last <strong>{$range}</strong>
+  sorted <strong>{$sort}</strong>
+  </TD>
+</TR>
+</TABLE>
+
+<CENTER>
+<TABLE>
+<TR>
+ <TD>
+
+<div id="monarchimage">
+{loop $vol_metric_info}
+<IMG ALT="{$alt}" SRC="./graph.php?{$graphargs}" WIDTH=381 HEIGHT=148>
+{/loop}
+</div>
+
+ </TD>
+</TR>
+</TABLE>
+</CENTER>
Index: /branches/1.0/web/addons/job_monarch/templates/overview.tpl
===================================================================
--- /branches/1.0/web/addons/job_monarch/templates/overview.tpl	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/templates/overview.tpl	(revision 825)
@@ -0,0 +1,255 @@
+<CENTER>
+<TABLE cellpadding="15">
+<TR>
+
+  <TD>
+
+
+<TABLE ALIGN=CENTER class="overview" cellpadding="5">
+
+<COL id="kol1">
+<COL id="kol2">
+<COL id="kol3">
+<COL id="kol4">
+
+<TR class="overview_header">
+<TD>Batch</TD>
+<TD>Jobs</TD>
+<TD>Nodes</TD>
+<TD>Cpus</TD>
+</TR>
+
+{if "$na_nodes" == "yes"}
+<TR class="overview">
+<TD class="gray">
+Unavailable
+</TD>
+<TD class="gray">
+{$na_jobs}
+</TD>
+<TD class="gray">
+{$na_nodes}
+</TD>
+<TD class="gray">
+{$na_cpus}
+</TD>
+</TR>
+{/if}
+
+<TR class="overview_line">
+<TD class="blue">
+Capacity
+</TD>
+<TD class="blue">
+</TD>
+<TD class="blue">
+{$avail_nodes}
+</TD>
+<TD class="blue">
+{$avail_cpus}
+</TD>
+</TR>
+
+
+
+<TR class="overview">
+<TD class="red">
+Running
+</TD>
+<TD class="red">
+{$running_jobs}
+</TD>
+<TD class="red">
+{$running_nodes}
+</TD>
+<TD class="red">
+{$running_cpus}
+</TD>
+</TR>
+
+<TR class="overview_line">
+<TD class="gray">
+Queued
+</TD>
+<TD class="gray">
+{$queued_jobs}
+</TD>
+<TD class="gray">
+{$queued_nodes}
+</TD>
+<TD class="gray">
+{$queued_cpus}
+</TD>
+</TR>
+
+<TR class="overview">
+<TD class="brown">
+Total
+</TD>
+<TD class="brown">
+{$total_jobs}
+</TD>
+<TD class="brown">
+{$total_nodes}
+</TD>
+<TD class="brown">
+{$total_cpus}
+</TD>
+</TR>
+
+
+<TR class="overview">
+<TD class="green">
+Free
+</TD>
+<TD class="green">
+</TD>
+<TD class="green">
+{$free_nodes}
+</TD>
+<TD class="green">
+{$free_cpus}
+</TD>
+</TR>
+
+<TR class="overview" id="selected">
+<TD>
+View
+</TD>
+<TD>
+{$view_jobs}
+</TD>
+<TD>
+{$view_nodes}
+</TD>
+<TD>
+{$view_cpus}
+</TD>
+</TR>
+
+</TABLE>
+
+<BR>
+<div id="monarchimage">
+{$rjqj_graph}
+</div>
+
+  <TD ALIGN="CENTER"><CENTER>
+<!-- INCLUDESCRIPT BLOCK : ci_script -->
+    <div id="monarchimage">
+    <IMG SRC="{$clusterimage}" USEMAP="#MONARCH_CLUSTER_BIG" BORDER="0">
+    </div>
+    <MAP NAME="MONARCH_CLUSTER_BIG">
+    {$node_area_map}
+    </MAP>
+    <BR>
+<FONT class="footer">Last updated: {$report_time}</FONT></CENTER>
+  </TD>
+
+  </TD>
+  <TD ALIGN="CENTER">
+    <IMG SRC="{$pie}">
+  </TD>
+</TR>
+</TABLE>
+
+<BR>
+
+<SCRIPT TYPE="text/javascript" SRC="libtoga.js"></SCRIPT>
+<NOSCRIPT><P>[Sorting by column header requires JavaScript]<BR><BR></P></NOSCRIPT>
+
+<INPUT TYPE="HIDDEN" NAME="sortby" VALUE="{$sortby}">
+<INPUT TYPE="HIDDEN" NAME="sortorder" VALUE="{$sortorder}">
+<INPUT TYPE="HIDDEN" NAME="c" VALUE="{$clustername}">
+<INPUT TYPE="HIDDEN" NAME="queue" VALUE="{$queue}">
+<INPUT TYPE="HIDDEN" NAME="state" VALUE="{$state}">
+<INPUT TYPE="HIDDEN" NAME="owner" VALUE="{$owner}">
+<INPUT TYPE="HIDDEN" NAME="id" VALUE="{$id}">
+<INPUT TYPE="HIDDEN" NAME="filterorder" VALUE="{$order}">
+
+<TABLE WIDTH="100%" CELLPADDING="2" CELLSPACING="2" BORDER=0>
+<TR CLASS="monarch">
+<TH><B><A HREF="#" onClick="setSort( 'id' )" ALT="Jobid" TITLE="Jobid">Id</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'state' )" ALT="State" TITLE="State">S</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'owner' )">Owner</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'queue' )">Queue</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'name' )" ALT="Jobname" TITLE="Jobname">Name</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'req_cpu' )" ALT="Requested CPU Time (walltime)" TITLE="Requested CPU Time (walltime)">Req. CPU time</A></B></TH>
+{if "$column_header_req_mem" == "yes"}
+<TH><B><A HREF="#" onClick="setSort( 'req_mem' )" ALT="Requested Memory" TITLE="Requested Memory">Req. Memory</A></B></TH>
+{/if}
+<TH><B><A HREF="#" onClick="setSort( 'nodes' )" ALT="Nodes" TITLE="Nodes">N</A>/<A HREF="#" onClick="setSort( 'cpus' )" ALT="Processors" TITLE="Processors">P</A></B></TH>
+{if "$column_header_queued" == "yes"}
+<TH><B><A HREF="#" onClick="setSort( 'queued' )">Queued</A></B></TH>
+{/if}
+{if "$column_header_nodes" == "yes"}
+<TH WIDTH="11%"><B><A HREF="#" onClick="setSort( 'nodes' )" ALT="Nodes" TITLE="Nodes">Nodes</A></B></TH>
+{/if}
+<TH><B><A HREF="#" onClick="setSort( 'start' )">Started</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'runningtime' )">Runningtime</A></B></TH>
+</TR>
+
+{loop $node_list}
+  <TR CLASS="{$nodeclass}">
+    <TD><A HREF="#" onClick="setFilter( 'id', '{$id}' )">{$id}</A></TD>
+    <TD><A HREF="#" onClick="setFilter( 'state', '{$state}' )" ALT="{$fullstate}" TITLE="{$fullstate}">{$state}</A></TD>
+    <TD><A HREF="#" onClick="setFilter( 'owner', '{$owner}' )">{$owner}</A></TD>
+    <TD><A HREF="#" onClick="setFilter( 'queue', '{$queue}' )">{$queue}</A></TD>
+    <TD ALT="{$fulljobname}" TITLE="{$fulljobname}">
+{if "$jobname_hint_start" == "yes"}
+    <FONT CLASS="jobname_hint">
+{/if}
+    {$name}
+{if "$jobname_hint_end" == "yes"}
+    </FONT>
+{/if}
+    </TD>
+    <TD>{$req_cpu}</TD>
+{if "$column_req_mem" == "yes"}
+    <TD>{$req_memory}</TD>
+{/if}
+    <TD>{$nodes}/{$cpus}</TD>
+{if "$column_queued" == "yes"}
+    <TD>{$queued}</TD>
+{/if}
+{if "$column_nodes" == "yes"}
+    <TD>{$nodes_hostnames}</TD>
+{/if}
+    <TD>{$started}</TD>
+    <TD>{$runningtime}</TD>
+  </TR>
+{/loop}
+</TABLE>
+</CENTER>
+
+{if "$showhosts" == "yes"}
+<TABLE BORDER="0" WIDTH="100%">
+<TR>
+  <TD CLASS=title COLSPAN="2">
+  <FONT SIZE="-1">
+  Show Hosts:
+  yes<INPUT type=radio name="sh" value="1" OnClick="toga_form.submit();" {$checked1}>
+  no<INPUT type=radio name="sh" value="0" OnClick="toga_form.submit();" {$checked0}>
+  </FONT>
+  </TD>
+</TR>
+   
+</TABLE>
+
+<CENTER>
+<TABLE>
+<TR>
+<div id="monarchimage">
+{loop $sorted_list}
+{$metric_image}{$br}
+{/loop}
+</div>
+</TR>
+</TABLE>
+
+<p>
+(Nodes colored by 1-minute load) | <A HREF="../../node_legend.html" ALT="Node Image egend">Legend</A>
+
+</CENTER>
+
+{/if}
Index: /branches/1.0/web/addons/job_monarch/templates/search.tpl
===================================================================
--- /branches/1.0/web/addons/job_monarch/templates/search.tpl	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/templates/search.tpl	(revision 825)
@@ -0,0 +1,304 @@
+<CENTER>
+
+<SCRIPT LANGUAGE="javascript">
+function setSort( sortbyval ) {
+
+        if( sortbyval != document.archive_search_form.sortby.value ) {
+
+                document.archive_search_form.sortby.value = sortbyval;
+                document.archive_search_form.sortorder.value = "asc";
+
+        } else {
+
+                if( document.archive_search_form.sortorder.value == "desc" )
+                        document.archive_search_form.sortorder.value = "asc";
+                else if( document.archive_search_form.sortorder.value == "asc" )
+                        document.archive_search_form.sortorder.value = "desc";
+        }
+
+        document.forms['archive_search_form'].submit();
+}
+
+function setFilter( filtername, filterval ) {
+
+        var myfilterorder = document.archive_search_form.elements['filterorder'].value;
+
+        if( document.archive_search_form.elements[filtername] ) {
+                document.archive_search_form.elements[filtername].value = filterval;
+                if( myfilterorder != '')
+                        myfilterorder = myfilterorder + "," + filtername;
+                else
+                        myfilterorder = filtername;
+
+        }
+        document.archive_search_form.elements['filterorder'].value = myfilterorder;
+
+        //setTimeout( "document.forms['archive_search_form'].submit();", 1000 );
+
+        document.forms['archive_search_form'].submit();
+}
+</SCRIPT>
+<SCRIPT LANGUAGE="javascript">
+
+	function setSearchTimestamps() {
+
+		document.archive_search_form.start_from_time.value = document.archive_search_form.start_from_pick.value; 
+		document.archive_search_form.start_to_time.value = document.archive_search_form.start_to_pick.value;
+		document.archive_search_form.end_from_time.value = document.archive_search_form.end_from_pick.value; 
+		document.archive_search_form.end_to_time.value = document.archive_search_form.end_to_pick.value;
+	}
+
+</SCRIPT>
+
+<!-- <FORM NAME="archive_search_form" ACTION="./"> -->
+
+<!-- <INPUT TYPE="hidden" NAME="view" VALUE="search"> -->
+
+<BR><BR>
+
+<TABLE WIDTH="100%">
+
+<TR>
+  <TD CLASS=title COLSPAN="2">
+  <B>Search job archive</B>
+  </TD>
+</TR>
+
+</TABLE>
+
+<BR><BR>
+
+<TABLE WIDTH="90%">
+
+<TR>
+  <TD></TD>
+  <TD></TD>
+  <TD></TD>
+  <TD></TD>
+  <TD></TD>
+  <TD></TD>
+</TR>
+
+<TR>
+
+  <TD>
+  Id:
+  </TD>
+  <TD>
+  <INPUT TYPE="TEXT" NAME="id" VALUE="{$id_value}">
+  </TD>
+
+</TR>
+
+<TR>
+
+  <TD>
+  User:
+  </TD>
+  <TD>
+  <INPUT TYPE="TEXT" NAME="owner" VALUE="{$owner_value}">
+  </TD>
+
+  <TD>
+  Queue:
+  </TD>
+  <TD>
+  <INPUT TYPE="TEXT" NAME="queue" VALUE="{$queue_value}">
+  </TD>
+
+  <TD>
+  Name:
+  </TD>
+  <TD>
+  <INPUT TYPE="TEXT" NAME="name" VALUE="{$name_value}">
+  </TD>
+
+</TR>
+
+<TR>
+
+  <TD></TD>
+  <TD>
+  Job <B>start</B>
+  </TD>
+
+  <TD>
+  From:
+  </TD>
+  <TD>
+  <INPUT TYPE="HIDDEN" NAME="start_from_time" VALUE="{$start_from_value}">
+  <INPUT TYPE="TEXT" NAME="start_from_pick" VALUE="{$start_from_value}" DISABLED="TRUE"> 
+  <A HREF="javascript:show_calendar('document.archive_search_form.start_from_pick', document.archive_search_form.start_from_pick.value );">
+  <IMG SRC="cal.gif" width="16" height="16" border="0" title="Click to select a date/time" alt="Click to select a date/time"></a> 
+  <a href="#" onClick="document.archive_search_form.start_from_pick.value=''" alt="Click here to clear field" title="Click here to clear field"><IMG SRC="redcross.jpg" BORDER=0></A>
+  </TD>
+  
+  <TD>
+  To:
+  </TD>
+  <TD>
+  <INPUT TYPE="HIDDEN" NAME="start_to_time" VALUE="{$start_to_value}">
+  <INPUT TYPE="TEXT" NAME="start_to_pick" VALUE="{$start_to_value}" DISABLED="TRUE">
+  <a href="javascript:show_calendar('document.archive_search_form.start_to_pick', document.archive_search_form.start_to_pick.value );"> 
+  <img src="cal.gif" width="16" height="16" border="0" title="Click to select a date/time" alt="Click to select a date/time"></a> 
+  <a href="#" onClick="document.archive_search_form.start_to_pick.value=''" alt="Click here to clear field" title="Click here to clear field">
+  <IMG SRC="redcross.jpg" BORDER=0></A>
+  </TD>
+  
+</TR>
+
+<TR>
+
+  <TD></TD>
+  <TD>
+  Job <B>finish</B>
+  </TD>
+
+  <TD>
+  From:
+  </TD>
+  <TD>
+  <INPUT TYPE="HIDDEN" NAME="end_from_time" VALUE="{$end_from_value}">
+  <INPUT TYPE="TEXT" NAME="end_from_pick" VALUE="{$end_from_value}" DISABLED="TRUE"> 
+  <A HREF="javascript:show_calendar('document.archive_search_form.end_from_pick', document.archive_search_form.end_from_pick.value );">
+  <IMG SRC="cal.gif" width="16" height="16" border="0" title="Click to select a date/time" alt="Click to select a date/time"></a> 
+  <a href="#" onClick="document.archive_search_form.end_from_pick.value=''" alt="Click here to clear field" title="Click here to clear field">
+  <IMG SRC="redcross.jpg" BORDER=0></A>
+  </TD>
+  
+  <TD>
+  To:
+  </TD>
+  <TD>
+  <INPUT TYPE="HIDDEN" NAME="end_to_time" VALUE="{$end_to_value}">
+  <INPUT TYPE="TEXT" NAME="end_to_pick" VALUE="{$end_to_value}" DISABLED="TRUE">
+  <a href="javascript:show_calendar('document.archive_search_form.end_to_pick', document.archive_search_form.end_to_pick.value );"> 
+  <img src="cal.gif" width="16" height="16" border="0" title="Click to select a date/time" alt="Click to select a date/time"></a> 
+  <a href="#" onClick="document.archive_search_form.end_to_pick.value=''" alt="Click here to clear field" title="Click here to clear field">
+  <IMG SRC="redcross.jpg" BORDER=0></A>
+  </TD>
+  
+</TR>
+
+<TR>
+
+  <TD></TD>
+  <TD></TD>
+  <TD></TD>
+  <TD></TD>
+  <TD></TD>
+  <TD>
+  <INPUT TYPE="submit" VALUE="Search archive" onClick="setSearchTimestamps();">
+  </TD>
+
+</TR>
+
+</TABLE>
+{$form_error_msg}<BR><BR>
+
+{if "$search_results" == "yes" }
+
+<INPUT TYPE="HIDDEN" NAME="sortby" VALUE="{$sortby}">
+<INPUT TYPE="HIDDEN" NAME="sortorder" VALUE="{$sortorder}">
+<INPUT TYPE="HIDDEN" NAME="filterorder" VALUE="{$order}">
+
+<TABLE WIDTH="100%" CELLPADDING="2" CELLSPACING="2" BORDER=0>
+<TR CLASS="monarch">
+<TH><B><A HREF="#" onClick="setSort( 'id' )" ALT="Jobid" TITLE="Jobid">Id</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'state' )" ALT="State" TITLE="State">S</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'owner' )">Owner</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'queue' )">Queue</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'name' )" ALT="Jobname" TITLE="Jobname">Name</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'req_cpu' )" ALT="Requested CPU Time (walltime)" TITLE="Requested CPU Time (walltime)">Req. CPU time</A></B></TH>
+{if "$column_header_req_mem" == "yes"}
+<TH><B><A HREF="#" onClick="setSort( 'req_mem' )" ALT="Requested Memory" TITLE="Requested Memory">Req. Memory</A></B></TH>
+{/if}
+<TH><B><A HREF="#" onClick="setSort( 'nodes' )" ALT="Nodes" TITLE="Nodes">N</A>/<A HREF="#" onClick="setSort( 'cpus' )" ALT="Processors" TITLE="Processors">P</A></B></TH>
+{if "$column_header_nodes" == "yes"}
+<TH><B><A HREF="#" onClick="setSort( 'nodes' )" ALT="Nodes" TITLE="Nodes">Nodes</A></B></TH>
+{/if}
+<TH><B><A HREF="#" onClick="setSort( 'start' )">Started</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'finished' )">Finished</A></B></TH>
+<TH><B><A HREF="#" onClick="setSort( 'runningtime' )">Runningtime</A></B></TH>
+</TR>
+
+{loop $node_list}
+  <TR CLASS="{$nodeclass}">
+    <TD><A HREF="#" onClick="setFilter( 'id', '{$id}' )">{$id}</A></TD>
+    <TD><A HREF="#" onClick="setFilter( 'state', '{$state}' )">{$state}</A></TD>
+    <TD><A HREF="#" onClick="setFilter( 'owner', '{$owner}' )">{$owner}</A></TD>
+    <TD><A HREF="#" onClick="setFilter( 'queue', '{$queue}' )">{$queue}</A></TD>
+    <TD ALT="{$fulljobname}" TITLE="{$fulljobname}">
+{if "$jobname_hint_start" == "yes"}
+    <FONT CLASS="jobname_hint">
+{/if}
+    {$name}
+{if "$jobname_hint_end" == "yes"}
+    </FONT>
+{/if}
+    </TD>
+    <TD>{$req_cpu}</TD>
+{if "$column_req_mem" == "yes"}
+    <TD>{$req_memory}</TD>
+{/if}
+    <TD>{$nodes}/{$cpus}</TD>
+{if "$column_nodes_hostnames" == "yes"}
+    <TD>{$nodes_hostnames}</TD>
+{/if}
+    <TD>{$started}</TD>
+    <TD>{$finished}</TD>
+    <TD>{$runningtime}</TD>
+  </TR>
+{/loop}
+</TABLE>
+</CENTER>
+
+{if "$showhosts" == "yes" }
+<TABLE BORDER="0" WIDTH="100%">
+<TR>
+  <TD CLASS=title COLSPAN="2">
+  <FONT SIZE="-1">
+  Show Hosts:
+  yes<INPUT type=radio name="sh" value="1" OnClick="archive_search_form.submit();" {$checked1}>
+  no<INPUT type=radio name="sh" value="0" OnClick="archive_search_form.submit();" {$checked0}>
+  </FONT>
+{if "$timeperiod" == "yes"}
+    <INPUT TYPE="HIDDEN" NAME="period_start" VALUE="{$period_start}">
+    <INPUT TYPE="HIDDEN" NAME="period_stop" VALUE="{$period_stop}">
+    <BR><B>Graph {$metric_menu} from
+    <INPUT TYPE="text" NAME="period_start_pick" VALUE="{$period_start}" ALT="Start time" DISABLED="TRUE">
+    <a href="javascript:show_calendar('document.{$form_name}.period_start_pick', document.{$form_name}.period_start_pick.value);" alt="Click to select a date/time" title="Click to select a date/time">
+    <img src="cal.gif" width="16" height="16" border="0"></a>
+    <a href="#" onClick="javascript: document.{$form_name}.period_start_pick.value=''" alt="Click here to clear field" title="Click here to clear field">
+    <IMG SRC="redcross.jpg" BORDER=0></A>
+    to <INPUT TYPE="text" NAME="period_stop_pick" VALUE="{$period_stop}" ALT="Stop time" DISABLED="TRUE">
+    <a href="javascript:show_calendar('document.{$form_name}.period_stop_pick', document.{$form_name}.period_stop_pick.value);" alt="Click to select a date/time" title="Click to select a date/time">
+    <img src="cal.gif" width="16" height="16" border="0"></a>
+    <a href="#" onClick="javascript: document.{$form_name}.period_stop_pick.value=''" alt="Click here to clear field" title="Click here to clear field">
+    </B>
+    <IMG SRC="redcross.jpg" BORDER=0></A>
+
+{if "$hostview" == "yes"}
+    <INPUT TYPE="HIDDEN" NAME="job_start" VALUE="{$job_start}">
+    <INPUT TYPE="HIDDEN" NAME="job_stop" VALUE="{$job_stop}">
+{/if}
+    <INPUT TYPE="submit" onClick="setPeriodTimestamps();" VALUE="Refresh graphs">
+{/if}
+  </TD>
+</TR>
+
+</TABLE>
+
+<CENTER>
+
+<div id="monarchimage">
+{loop $sorted_list}
+{$metric_image}
+{/loop}
+</div>
+
+</CENTER>
+
+{/if}
+{/if}
+</CENTER>
Index: /branches/1.0/web/addons/job_monarch/ts_picker.js
===================================================================
--- /branches/1.0/web/addons/job_monarch/ts_picker.js	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/ts_picker.js	(revision 825)
@@ -0,0 +1,149 @@
+// Title: Timestamp picker
+// Description: See the demo at url
+// URL: http://us.geocities.com/tspicker/
+// Script featured on: http://javascriptkit.com/script/script2/timestamp.shtml
+// Version: 1.0
+// Date: 12-05-2001 (mm-dd-yyyy)
+// Author: Denis Gritcyuk <denis@softcomplex.com>; <tspicker@yahoo.com>
+// Notes: Permission given to use this script in any kind of applications if
+//    header lines are left unchanged. Feel free to contact the author
+//    for feature requests and/or donations
+//
+// Additional changes for Toga by Ramon Bastiaans:
+// - Time validation
+// - Setup of str_target on load of calendar (if it's empty)
+// - Commit new time when changed (not just when date is clicked)
+
+function show_calendar(str_target, str_datetime) {
+	var arr_months = ["January", "February", "March", "April", "May", "June",
+		"July", "August", "September", "October", "November", "December"];
+	var week_days = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
+	var n_weekstart = 1; // day week starts from (normally 0 or 1)
+
+	var dt_datetime = (str_datetime == null || str_datetime =="" ?  new Date() : str2dt(str_datetime));
+	var dt_prev_month = new Date(dt_datetime);
+	dt_prev_month.setMonth(dt_datetime.getMonth()-1);
+	var dt_next_month = new Date(dt_datetime);
+	dt_next_month.setMonth(dt_datetime.getMonth()+1);
+	var dt_firstday = new Date(dt_datetime);
+	dt_firstday.setDate(1);
+	dt_firstday.setDate(1-(7+dt_firstday.getDay()-n_weekstart)%7);
+	var dt_lastday = new Date(dt_next_month);
+	dt_lastday.setDate(0);
+	
+	// html generation (feel free to tune it for your particular application)
+	// print calendar header
+	var str_buffer = new String (
+		"<html>\n"+
+		"<head>\n"+
+		"	<title>Calendar</title>\n"+
+		"</head>\n"+
+		"<SCRIPT LANGUAGE=\"javascript\" SRC=\"ts_validatetime.js\"></SCRIPT>\n"+
+		"<body bgcolor=\"White\" onLoad=\"validateTime( '"+str_target+ "', '"+str_datetime+"' ); if( window.opener."+str_target+ ".value == '' ) window.opener."+str_target+ ".value='"+dt2dtstr( dt_datetime )+" '+document.cal.time.value;\" onUnload=\"validateTime( '"+str_target+ "', '"+str_datetime+"' ); if( window.opener."+str_target+ ".value == '' ) window.opener."+str_target+ ".value='"+dt2dtstr( dt_datetime )+"'+document.cal.time.value;\">\n"+
+		"<table class=\"clsOTable\" cellspacing=\"0\" border=\"0\" width=\"100%\">\n"+
+		"<tr><td bgcolor=\"#4682B4\">\n"+
+		"<table cellspacing=\"1\" cellpadding=\"3\" border=\"0\" width=\"100%\">\n"+
+		"<tr>\n	<td bgcolor=\"#4682B4\"><a href=\"javascript:window.opener.show_calendar('"+
+		str_target+"', '"+ dt2dtstr(dt_prev_month)+"'+document.cal.time.value);\">"+
+		"<img src=\"prev.gif\" width=\"16\" height=\"16\" border=\"0\""+
+		" alt=\"previous month\"></a></td>\n"+
+		"	<td bgcolor=\"#4682B4\" colspan=\"5\">"+
+		"<font color=\"white\" face=\"tahoma, verdana\" size=\"2\">"
+		+arr_months[dt_datetime.getMonth()]+" "+dt_datetime.getFullYear()+"</font></td>\n"+
+		"	<td bgcolor=\"#4682B4\" align=\"right\"><a href=\"javascript:window.opener.show_calendar('"
+		+str_target+"', '"+dt2dtstr(dt_next_month)+"'+document.cal.time.value);\">"+
+		"<img src=\"next.gif\" width=\"16\" height=\"16\" border=\"0\""+
+		" alt=\"next month\"></a></td>\n</tr>\n"
+	);
+
+	var dt_current_day = new Date(dt_firstday);
+	// print weekdays titles
+	str_buffer += "<tr>\n";
+	for (var n=0; n<7; n++)
+		str_buffer += "	<td bgcolor=\"#87CEFA\">"+
+		"<font color=\"white\" face=\"tahoma, verdana\" size=\"2\">"+
+		week_days[(n_weekstart+n)%7]+"</font></td>\n";
+	// print calendar table
+	str_buffer += "</tr>\n";
+	while (dt_current_day.getMonth() == dt_datetime.getMonth() ||
+		dt_current_day.getMonth() == dt_firstday.getMonth()) {
+		// print row heder
+		str_buffer += "<tr>\n";
+		for (var n_current_wday=0; n_current_wday<7; n_current_wday++) {
+				if (dt_current_day.getDate() == dt_datetime.getDate() &&
+					dt_current_day.getMonth() == dt_datetime.getMonth())
+					// print current date
+					str_buffer += "	<td bgcolor=\"#FFB6C1\" align=\"right\">";
+				else if (dt_current_day.getDay() == 0 || dt_current_day.getDay() == 6)
+					// weekend days
+					str_buffer += "	<td bgcolor=\"#DBEAF5\" align=\"right\">";
+				else
+					// print working days of current month
+					str_buffer += "	<td bgcolor=\"white\" align=\"right\">";
+
+				if (dt_current_day.getMonth() == dt_datetime.getMonth())
+					// print days of current month
+					str_buffer += "<a href=\"javascript:window.opener."+str_target+
+					".value='"+dt2dtstr(dt_current_day)+"'+document.cal.time.value; window.close();\">"+
+					"<font color=\"black\" face=\"tahoma, verdana\" size=\"2\">";
+				else 
+					// print days of other months
+					str_buffer += "<a href=\"javascript:window.opener."+str_target+
+					".value='"+dt2dtstr(dt_current_day)+"'+document.cal.time.value; window.close();\">"+
+					"<font color=\"gray\" face=\"tahoma, verdana\" size=\"2\">";
+				str_buffer += dt_current_day.getDate()+"</font></a></td>\n";
+				dt_current_day.setDate(dt_current_day.getDate()+1);
+		}
+		// print row footer
+		str_buffer += "</tr>\n";
+	}
+	// print calendar footer
+	str_buffer +=
+		"<form name=\"cal\" onsubmit=\"var mf = window.opener."+str_target+".value.split( ' ' );window.opener."+str_target+".value=mf[0]+' '+document.cal.time.value; window.close();\">\n<tr><td colspan=\"7\" bgcolor=\"#87CEFA\">"+
+		"<font color=\"White\" face=\"tahoma, verdana\" size=\"2\">"+
+		"Time: <input type=\"text\" name=\"time\" value=\""+dt2tmstr(dt_datetime)+
+		"\" size=\"8\" maxlength=\"8\" onChange=\"validateTime('"+str_target+"', '"+str_datetime+"');\"></font></td></tr>\n</form>\n" +
+		"</table>\n" +
+		"</tr>\n</td>\n</table>\n" +
+		"</body>\n" +
+		"</html>\n";
+
+	var vWinCal = window.open("", "Calendar", 
+		"width=200,height=250,status=no,resizable=yes,top=200,left=200");
+	vWinCal.opener = self;
+	var calc_doc = vWinCal.document;
+	calc_doc.write (str_buffer);
+	calc_doc.close();
+}
+// datetime parsing and formatting routimes. modify them if you wish other datetime format
+function str2dt (str_datetime) {
+	var re_date = /^(\d+)\-(\d+)\-(\d+)\s+(\d+)\:(\d+)\:(\d+)$/;
+	if (!re_date.exec(str_datetime))
+		return alert("Invalid Datetime format: "+ str_datetime);
+	return (new Date (RegExp.$3, RegExp.$2-1, RegExp.$1, RegExp.$4, RegExp.$5, RegExp.$6));
+}
+function dt2dtstr (dt_datetime) {
+	return (new String (
+			dt_datetime.getDate()+"-"+(dt_datetime.getMonth()+1)+"-"+dt_datetime.getFullYear()+" "));
+}
+function dt2tmstr (dt_datetime) {
+
+	if( dt_datetime.getHours() < 10 )
+		var hours = '0' + dt_datetime.getHours();
+	else
+		var hours = dt_datetime.getHours();
+
+	if( dt_datetime.getMinutes() < 10 )
+		var minutes = '0' + dt_datetime.getMinutes();
+	else
+		var minutes = dt_datetime.getMinutes();
+
+	if( dt_datetime.getSeconds() < 10 )
+		var seconds = '0' + dt_datetime.getSeconds();
+	else
+		var seconds = dt_datetime.getSeconds();
+		
+	return (new String (
+			hours+":"+minutes+":"+seconds));
+}
+
Index: /branches/1.0/web/addons/job_monarch/ts_validatetime.js
===================================================================
--- /branches/1.0/web/addons/job_monarch/ts_validatetime.js	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/ts_validatetime.js	(revision 825)
@@ -0,0 +1,116 @@
+function validateTime( str_target, str_datetime ) {
+
+        var time = document.cal.time.value.split( ':' );
+        var invalid = 0;
+	var dt_datetime = (str_datetime == null || str_datetime =="" ?  new Date() : str2dt(str_datetime));
+
+        if( time.length <= 1 && invalid == 0 ) {
+
+                document.cal.time.value = dt2tmstr( dt_datetime );
+                alert( 'Invalid time specified (needs to be HH:MM:SS), reset to current time.' );
+                invalid = 1;
+
+        } else if( time.length == 2 ) {
+
+                if( time[1] != '' ) {
+
+                        document.cal.time.value = document.cal.time.value + ':00';
+
+                } else if( invalid == 0 ) {
+
+                        document.cal.time.value = dt2tmstr( dt_datetime );
+                        alert( 'Invalid time specified (needs to be HH:MM:SS), reset to current time.' );
+                        invalid = 1;
+                }
+
+        } else if( time.length == 3 ) {
+
+                newtime = '';
+
+                for( var i=0; i<time.length; i++) {
+
+                        time_field = time[i];
+			if( i == 0 )
+				var sep = '';
+			else
+				var sep = ':';
+				
+
+                        if( time_field.length == 0 && invalid == 0 ) {
+
+                                document.cal.time.value = dt2tmstr( dt_datetime );
+                                alert( 'Invalid time specified (needs to be HH:MM:SS), reset to current time.' );
+                                invalid = 1;
+
+                        } else if( time_field.length == 1 ) {
+
+                                newtime = newtime + sep + '0' + time_field;
+                        } else {
+                                newtime = newtime + sep + time_field;
+                        }
+
+                        for( var l=0; l<time_field.length; l++ ) {
+                                var mydigit = time_field.charAt(l)
+
+                                if(( mydigit < "0" || mydigit > "9" ) && invalid == 0 ) {
+
+                                        document.cal.time.value = dt2tmstr( dt_datetime );
+                                        alert( 'Invalid time specified (needs to be HH:MM:SS), reset to current time.' );
+                                        invalid = 1;
+
+                                }
+                        }
+                }
+
+                if( invalid == 0 ) {
+
+                        document.cal.time.value = newtime;
+                }
+
+        }
+
+        if( invalid == 0 ) {
+
+		target = str_target.split('.');
+		frm = target[1];
+		elm = target[2];
+                var mf = window.opener.document.forms[frm].elements[elm].value.split( ' ' );
+
+		if( mf[0] != '' )
+			window.opener.document.forms[frm].elements[elm].value=mf[0]+' '+document.cal.time.value;
+		else
+			window.opener.document.forms[frm].elements[elm].value=dt2dtstr( new Date() ) +document.cal.time.value;
+        }
+}
+
+// datetime parsing and formatting routimes. modify them if you wish other datetime format
+function str2dt (str_datetime) {
+        var re_date = /^(\d+)\-(\d+)\-(\d+)\s+(\d+)\:(\d+)\:(\d+)$/;
+        if (!re_date.exec(str_datetime))
+                return alert("Invalid Datetime format: "+ str_datetime);
+        return (new Date (RegExp.$3, RegExp.$2-1, RegExp.$1, RegExp.$4, RegExp.$5, RegExp.$6));
+}
+function dt2dtstr (dt_datetime) {
+        return (new String (
+                        dt_datetime.getDate()+"-"+(dt_datetime.getMonth()+1)+"-"+dt_datetime.getFullYear()+" "));
+}
+function dt2tmstr (dt_datetime) {
+
+        if( dt_datetime.getHours() < 10 )
+                var hours = '0' + dt_datetime.getHours();
+        else
+                var hours = dt_datetime.getHours();
+
+        if( dt_datetime.getMinutes() < 10 )
+                var minutes = '0' + dt_datetime.getMinutes();
+        else
+                var minutes = dt_datetime.getMinutes();
+
+        if( dt_datetime.getSeconds() < 10 )
+                var seconds = '0' + dt_datetime.getSeconds();
+        else
+                var seconds = dt_datetime.getSeconds();
+
+        return (new String (
+                        hours+":"+minutes+":"+seconds));
+}
Index: /branches/1.0/web/addons/job_monarch/version.php
===================================================================
--- /branches/1.0/web/addons/job_monarch/version.php	(revision 825)
+++ /branches/1.0/web/addons/job_monarch/version.php	(revision 825)
@@ -0,0 +1,25 @@
+<?php
+/*
+ *
+ * This file is part of Jobmonarch
+ *
+ * Copyright (C) 2006  Ramon Bastiaans
+ *
+ * Jobmonarch 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * Jobmonarch 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+$monarchversion = "0.4+SVN";
+?>
Index: /branches/1.0/web/templates/job_monarch/cluster_extra.tpl
===================================================================
--- /branches/1.0/web/templates/job_monarch/cluster_extra.tpl	(revision 825)
+++ /branches/1.0/web/templates/job_monarch/cluster_extra.tpl	(revision 825)
@@ -0,0 +1,4 @@
+<HR>
+<A HREF="./addons/job_monarch/?c={$cluster}">
+<IMG SRC="./addons/job_monarch/image.php?c={$cluster}&j_view=small-clusterimage" BORDER=0>
+</A>
Index: /branches/1.0/web/templates/job_monarch/host_extra.tpl
===================================================================
--- /branches/1.0/web/templates/job_monarch/host_extra.tpl	(revision 825)
+++ /branches/1.0/web/templates/job_monarch/host_extra.tpl	(revision 825)
@@ -0,0 +1,4 @@
+<a href="./addons/job_monarch/?c={$cluster}&h={$host}">
+<IMG SRC="./addons/job_monarch/image.php?c={$cluster}&h={$host}&j_view=hostimage" BORDER=0>
+</a>
+<hr>
