<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>AtWillys.de</title>
	<atom:link href="http://www.atwillys.de/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.atwillys.de</link>
	<description></description>
	<lastBuildDate>Wed, 25 Jan 2012 22:51:54 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Force nullmailer to use a fixed &#8220;from&#8221; address</title>
		<link>http://www.atwillys.de/uncategorized/force-nullmailer-to-use-a-fixed-from-address/2012-01-25/</link>
		<comments>http://www.atwillys.de/uncategorized/force-nullmailer-to-use-a-fixed-from-address/2012-01-25/#comments</comments>
		<pubDate>Wed, 25 Jan 2012 22:51:54 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[force from]]></category>
		<category><![CDATA[force sender]]></category>
		<category><![CDATA[nullmailer]]></category>
		<category><![CDATA[rewrite from]]></category>
		<category><![CDATA[rewrite sender]]></category>
		<category><![CDATA[ubuntu]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1268</guid>
		<description><![CDATA[As small replacement for postfix and the like, nullmailer has established on the one and other Linux home server &#8211; such as mine. My specific problem with the provider/email relay: They check if the sender email address matches the relay &#8230; <a href="http://www.atwillys.de/uncategorized/force-nullmailer-to-use-a-fixed-from-address/2012-01-25/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>As small replacement for postfix and the like, nullmailer has established on the one and other Linux home server &#8211; such as mine. My specific problem with the provider/email relay: They check if the sender email address matches the relay account email address to prevent spam. That&#8217;s a good feature, but nullmailer uses <b>actual user@host</b>, where <b>host</b> defaults to the content of /etc/mailname (e.g. root@myserver.dyndns.org or www-data@myserver.dyndns.org). Nullmailer has no rewrite functionality based on config files, but in the specific case a simple (but not completely accurate) solution can be applied:</p>
<pre class="brush: bash; title: ; notranslate">
# 1) Rename the original sendmail binary:
$ mv /usr/sbin/sendmail /usr/sbin/sendmail-bin

# 2) Create and edit a script called sendmail:
$ touch /usr/sbin/sendmail
$ chmod 755 /usr/sbin/sendmail
</pre>
<p>Edit the sendmail script file and paste this content:</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/bash
/usr/sbin/sendmail-bin $@ -f `cat /etc/nullmailer/forced-from` &lt;/dev/stdin
</pre>
<p>The script simply forwards all arguments and the STDIN to the original sendmail, but adds the <b>-f</b> argument to replace the <b>from</b> address. In my case, I save the <i><b>full sender address</b></i> of the relay account in the file <b>/etc/nullmailer/forced-from</b>. That&#8217;s it. </p>
<p>There are two things I did not test yet:</p>
<ul>
<li>After done a test with PHP and &#8220;sendmail -f www-data&#8221; in the php.ini, I assume that the last &#8220;-f SENDER-EMAIL&#8221; argument that occurs will be the used one. No idea if this will be that way for all time. The argument list will have to be filtered then.</li>
<li>What does the package manager/aptitude do with this when removing, updating, etc. (That&#8217;s why I say not really accurate :-) )</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/uncategorized/force-nullmailer-to-use-a-fixed-from-address/2012-01-25/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Mount a FTP connection in your file system on Ubuntu</title>
		<link>http://www.atwillys.de/uncategorized/mount-a-ftp-connection-in-your-file-system-on-ubuntu/2011-09-19/</link>
		<comments>http://www.atwillys.de/uncategorized/mount-a-ftp-connection-in-your-file-system-on-ubuntu/2011-09-19/#comments</comments>
		<pubDate>Mon, 19 Sep 2011 00:05:18 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[FTP]]></category>
		<category><![CDATA[mount]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[Ubuntu 10.04]]></category>
		<category><![CDATA[umount]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1245</guid>
		<description><![CDATA[I was just looking for an easy and strict way to process files and directories via FTP (in a shell script) and stumbled over the CurlFtpFS, which allows you to mount an FTP connection as part of your file system. &#8230; <a href="http://www.atwillys.de/uncategorized/mount-a-ftp-connection-in-your-file-system-on-ubuntu/2011-09-19/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>I was just looking for an easy and strict way to process files and directories via FTP (in a shell script) and stumbled over the <a href="http://curlftpfs.sourceforge.net/">CurlFtpFS</a>, which allows you to mount an FTP connection as part of your file system. Extremely nice and elaborated solution these guys implemented. You can either mount it using sudo e.g. in /mnt/ftp/hostname or simply in your home directory, e.g. &#8220;~/ftp&#8221;. As Bob Ross used to say: &#8220;And it&#8217;s really that simple&#8221;.</p>
<h3>Installation</h3>
<pre class="brush: bash; title: ; notranslate">
sudo aptitude install curlftpfs
</pre>
<h3>Mount</h3>
<pre class="brush: bash; title: ; notranslate">
# Create a directory to have a mount point
mkdir ~/ftp

# To directly connect with username and password in the command line:
sudo curlftpfs username:password@host.or.domain ~/ftp

# If you want to type the password in the shell:
sudo curlftpfs -o user=&quot;username&quot; host.or.domain ~/ftp

# To check if's mounted
mount
# You get a list that should include the line like:
#   curlftpfs#.... on ..../ftp type fuse (rw,nosuid,nodev,user=....)
</pre>
<h3>Unnount</h3>
<pre class="brush: bash; title: ; notranslate">
sudo umount ~/ftp
</pre>
<p><b>Annotation:</b> I added sudo because you very likely cannot unmount the drive without root permissions, even not in your home folder.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/uncategorized/mount-a-ftp-connection-in-your-file-system-on-ubuntu/2011-09-19/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Boot camp with Windows 7 and a SSD</title>
		<link>http://www.atwillys.de/uncategorized/boot-camp-with-windows-7-and-an-ssd/2011-09-17/</link>
		<comments>http://www.atwillys.de/uncategorized/boot-camp-with-windows-7-and-an-ssd/2011-09-17/#comments</comments>
		<pubDate>Sat, 17 Sep 2011 20:37:00 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Boot Camp]]></category>
		<category><![CDATA[Dieable Defragmentation]]></category>
		<category><![CDATA[Disable Drive Letters]]></category>
		<category><![CDATA[Disable Hybernation]]></category>
		<category><![CDATA[Disable Indexing]]></category>
		<category><![CDATA[Disable System Restore Points]]></category>
		<category><![CDATA[Disk Cleanup]]></category>
		<category><![CDATA[MacBook Pro]]></category>
		<category><![CDATA[MacOS]]></category>
		<category><![CDATA[OCZ Vertex 2]]></category>
		<category><![CDATA[Save disk space]]></category>
		<category><![CDATA[SSD]]></category>
		<category><![CDATA[Trim page file size]]></category>
		<category><![CDATA[Windows 7]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1201</guid>
		<description><![CDATA[As not all programs are available on MacOS 10.7, you may want to install Windows 7 either on a virtual machine or, to get the whole performance of your machine, on a Boot Camp partition. As described in the last &#8230; <a href="http://www.atwillys.de/uncategorized/boot-camp-with-windows-7-and-an-ssd/2011-09-17/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>As not all programs are available on MacOS 10.7, you may want to install Windows 7 either on a virtual machine or, to get the whole performance of your machine, on a Boot Camp partition. As described in the last article, I switched to a Vertex 2 SSD, which is much faster then the HDD before, but provides with 240GB only half of the space compared to the replaced hard disk. As a very result, I didn&#8217;t want to waste space on a second partition that needs already more than 20GB for the operating system (we can forgive Steve Jobs that Lion has a similar disk usage, but for any reason we can&#8217;t forgive Steve Ballmer). So, 50GB was my choice for the Boot Camp partition, and the Windows 7 space had to be trimmed somehow. The major issue was to prevent that the disk usage grows too much after the installation (due to indexing, backups, shadow images, memory dumps etc). The updates will increase the required space enough anyway. Furthermore, SSDs and convendtional HDDs require different handling, e.g. SSD do not need to be defragmented. Here are the steps that helped me keeping the system clean and speedy. I hope it helps you, too. Note: Every change has its side effects, so checkout if you need a feature or not.</p>
<h3>Cleanup and removing the disk shadow copy</h3>
<p>A simple way of freeing space is to use the normal disk cleanup. This package also contains the functionality to remove a disk shadow copy.</p>
<ol>
<li>Go to START &gt; Computer</li>
<li>Right click on drive C:, select &#8220;Properties&#8221;</li>
<li>Switch to tab &#8220;General&#8221; and click the &#8220;Disk Cleanup&#8221; button</li>
<li>If the button &#8220;Cleanup system files&#8221; is shown, click it</li>
<li>The tab &#8220;More options&#8221; should be visible, switch to it</li>
<li>Under &#8220;System Restore and Shadow Copy&#8221;, click &#8220;Cleanup&#8221; and confirm the deletion</li>
</ol>
<h3>Disable Hybernation</h3>
<p>Hybernation is principally a good thing. However, it has the side effect that a file is created, which contains the RAM state during hybernation. Depending on the RAM size, this preserves gigabytes of disk space (in my case a 1/6 of the disk space!), and the file is not deleted after hybernation. Disabling it is easy (the file will be automatically removed). Side effect: The system can only switch to sleep mode, which still requires battery. If latter is empty Windows would normally hybernate, but can&#8217;t. So it will either force a shutdown or just go off.</p>
<ol>
<li>Click START, search &#8220;cmd.exe&#8221;, and run it as admin (don&#8217;t hit RETURN, but right click on the found item &#8220;cmd.exe&#8221;)</li>
<li>Enter &#8220;powercfg -h off&#8221; in the shell window</li>
</ol>
<h3>Disabling Indexing service</h3>
<p>The indexing service is the Windows-Spotlight background task and uses disk space as well. Side effects: Finding programs and files still works, but more detailled searches, such as words in mails or PDFs does not work.</p>
<ol>
<li>Go START:, search for &#8220;services.msc&#8221;</li>
<li>Locate &#8220;Windows Search Service&#8221;, right click: Properties</li>
<li>Select &#8220;disabled&#8221; in the dropdown &#8220;Startup type&#8221;</li>
</ol>
<h3>Trimming page file size</h3>
<p>The page file is the virtual memory and similar to the scratch partition. It can grow quite a lot. Side effects: You could run out out of memory. This never happend to me up to now, but if you use Software, which needs huge amount of RAM &#8230; well &#8211; could happen that the program crashes or throws an allocation error.</p>
<ol>
<li>Go START</li>
<li>Right click on &#8220;Computer&#8221;, select &#8220;Properties&#8221; from menu</li>
<li>Select &#8220;Advanced System Settings&#8221; on the left side menu</li>
<li>Under &#8220;Performance&#8221;, click on the &#8220;settings&#8221; button</li>
<li>Click the &#8220;Advanced tab&#8221;</li>
<li>In section &#8220;Virtual memory&#8221; click on &#8220;Change&#8221;</li>
<li>Uncheck &#8220;Automatically manage paging file size [...]&#8220;</li>
<li>Click option &#8220;Custom size&#8221; enter &#8220;500&#8243; under Initial size and what you want (e.g. 1000 for 1GB) for maximum size.</li>
</ol>
<h3>Disable System Restore</h3>
<p>The System Restore Points are principally a good Idea, but they need space. Here how to disable this feature. Side effects: You don&#8217;t have a direct undo operation for your system setup (I use incremental network backups to get around this problem). </p>
<ol>
<li>Go START, search for &#8220;gpedit.msc&#8221; and hit return</li>
<li>Browse to the folder &#8220;Local Computer Policy&#8221;/&#8221;Administrative Templates&#8221;/&#8221;Windows Components&#8221;/&#8221;Backup&#8221;/&#8221;Client&#8221;</li>
<li>Double click &#8220;Turn off the ability to back up data files&#8221; and select the &#8220;enabled&#8221; option. (Yes, it is ENABLED), click OK</li>
<li>Double click &#8220;Turn off restore functionality&#8221; and select the &#8220;enabled&#8221; option. Click OK</li>
<li>Double click &#8220;Turn off the ability to create a system image&#8221; and select the &#8220;enabled&#8221; option. Click OK</li>
</ol>
<h3>Disable Defrag schedule</h3>
<p>SSDs don&#8217;t need to be defragmented, indeed it is a waste of resources and decreases the life time unnecessarily.</p>
<ol>
<li>Go to START/All Programs/Accessories/System Tools/Disk Defragmenter</li>
<li>Click the &#8220;Configure Schedule&#8221; button</li>
<li>Untick the checkbox &#8220;Run on a schedule (recommended)&#8221;</li>
</ol>
<h3>Disable MacOS X partition visibility</h3>
<p>If you don&#8217;t want that windows programs can access your Mac partition, you can unassign its drive letter. This is not a &#8220;really secure security feature&#8221;, but it should help a bit, because admin permissions are required to reassign a drive letter.</p>
<ol>
<li>Click START, search for &#8220;Administrative Tools&#8221;</li>
<li>Doubleclick on &#8220;Computer Management&#8221;</li>
<li>Click in the side bar on &#8220;Disk Management&#8221; (in folder &#8220;Storage&#8221;)</li>
<li>Right click on the partition, which file system is HFS and select from the context menu &#8220;Change drive letter and paths&#8221;</li>
<li>Remove the drive letter by clicking the &#8220;Remove&#8221; button and ignore the warning that programs may rely on this drive.
</li>
</ol>
<h3>References</h3>
<ul>
<li><a href="http://www.addictivetips.com/windows-tips/6-ways-to-free-disk-space-in-windows-7/">addictivetips: 6 Ways To Free Disk Space In Windows 7</a></li>
<li><a href="http://www.sevenforums.com/tutorials/819-hibernate-enable-disable.html">SevenForums: Windows 7 &#8211; Hibernate &#8211; Enable or Disable</a></li>
<li><a href="http://www.itechtalk.com/thread3238.html">itechtalk: How to disable or enable Backup and Restore functionality in Windows 7</a></li>
<li><a href="http://www.sevenforums.com/tutorials/618-backup-files-schedule-turn-off.html">SevenForums: Windows 7 &#8211; Backup Files Schedule &#8211; Turn On or Off</a></li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/uncategorized/boot-camp-with-windows-7-and-an-ssd/2011-09-17/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Migrate Mac OS 10.7 Lion on a OCZ Vertex SSD in a unibody  MacBook Pro</title>
		<link>http://www.atwillys.de/uncategorized/migrate-mac-os-10-7-lion-on-a-ocz-vertex-ssd-in-a-unibody-macbook-pro/2011-09-16/</link>
		<comments>http://www.atwillys.de/uncategorized/migrate-mac-os-10-7-lion-on-a-ocz-vertex-ssd-in-a-unibody-macbook-pro/2011-09-16/#comments</comments>
		<pubDate>Fri, 16 Sep 2011 22:58:55 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Install MacOS 10.7]]></category>
		<category><![CDATA[Install MacOS X Lion]]></category>
		<category><![CDATA[Lion Recovery Disk Assistant]]></category>
		<category><![CDATA[MacBookPro6 2]]></category>
		<category><![CDATA[OCZ Vertex 2]]></category>
		<category><![CDATA[Restore From Time Machine Backup]]></category>
		<category><![CDATA[Solid State Drive]]></category>
		<category><![CDATA[SSD]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1176</guid>
		<description><![CDATA[To speed up my unibody MacBook Pro (6,2) I decided to replace the internal hard disk with a SSD (OCZ-Vertex 2, 240GB, SATA2/3Gbps, Rev. 1.33). After browsing loads of tutorials I found another really easy way to put Lion on &#8230; <a href="http://www.atwillys.de/uncategorized/migrate-mac-os-10-7-lion-on-a-ocz-vertex-ssd-in-a-unibody-macbook-pro/2011-09-16/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>To speed up my unibody MacBook Pro (6,2) I decided to replace the internal hard disk with a SSD (OCZ-Vertex 2, 240GB, SATA2/3Gbps, Rev. 1.33). After browsing loads of tutorials I found another really easy way to put Lion on the blank disk. I have no confirmation if this is really the &#8220;master way&#8221; and that complications or side effects are impossible, but it works without 3rd party software, without shell commands, but only with Apple software and the tools you use anyway.</p>
<h3>I assume you have already have &#8230; </h3>
<ul>
<li>an installed and functioning system in on your internal hard disk. If this is a MacOS 10.6 Snow Leopard installation, <b>you must upgrade to Lion</b>, because the Recovery Disk Assistant will be used, which is not available under 10.6 yet.</li>
<li>a Time Machine disk</li>
<li>a &#8220;scrap&#8221; USB stick or old USB hard disk. The Apple guys say it must have at least 1GB space.</li>
</ul>
<h2>Installation sequence</h2>
<ol>
<li>Make Time Machine backup</li>
<li>Remove large files (videos, music, photos, etc) from your internal hard disk until the used disk space is less than the disk size of the SSD. (I simply removed almost all media files and had 90GB disk usage after this cleanup. No applications or settings in the Library deleted.) A software which helped me quite well finding the space wastes was <a href="http://whatsizemac.com/">WhatSize</a>. Probably you could use some cleanup programs as well to reduce the used disk space first (I didn&#8217;t). I would definitely propose to remove more rather than less. You can get the stuff back from the backup anyway.</li>
<li>Make a Time Machine backup <b>again</b>. This backup will be the one to migrate later on.</li>
<li>Download the <a href="http://support.apple.com/kb/DL1433">Lion Recovery Disk Assistant</a>. Additional information about this Apple software can be found in the article <a href="http://support.apple.com/kb/HT4848">OS X Lion: About Lion Recovery Disk Assistant</a>.</li>
<li>Connect the USB disk and format it with a HFS+ filesystem using the the Disk Utility. Then run downloaded Recovery Disk Assistant, which will detect the USB disk (&#8220;follow the instructions :-)&#8221;. In my case it was a 8GB USB stick (one of the d**ned Cuiser sticks), I repartitioned it with one partition, Journaled HFS+, partition name: &#8220;LionRecovery&#8221;).</li>
<li>Replace the internal HDD with the SSD. A good Tutorial by <i>mefouryou</i> how to do this can be found at YouTube: <a href="http://www.youtube.com/watch?v=CBbvRHS8TJA">How to Install an SSD Into a Unibody MacBook Pro</a>.</li>
<li>Switch on your notebook, press the option-key until the boot menu shows up, and boot from your Lion Recovery disk.</li>
<li>Plug in the Time Machine backup disk and choose the &#8220;Restore From Time Machine Backup&#8221; option. In my case the process took about 1 hour. After finishing the restore procedure, the notebook restarted and I could log in as usual.</li>
</ol>
<h3>Conclusion and annotations</h3>
<p>After restoring on the SSD, the system worked out of the box. All settings I checked, such as saved passwords in Safari, Bookmarks, and the like were unchanged. Mail asked to import already existing emails and did this without any side effects. However, some Adobe programs had trouble with the licensing, so I had to uninstall (inclusively removing the settings in ~/Library) and reinstall them. Word 2011 wanted the CD key again (probably because the Hardware ID changed). The processor load was quite high after the installation due to the Spotlight indexing process. The next Time Machine backup, which I did directly after the migration, had a size of 75GB.</p>
<h2>Some SSD related settings</h2>
<p>As they are some differences between SSDs and HDDs, here some further things you may want to do:</p>
<ol>
<li><b>Disable sudden motion sensor</b>, which protects the disk mechanics if you drop your notebook. As SSDs have no mechanical parts, disable it in the terminal: <code>sudo pmset -a sms 0</code></li>
<li><b>Enable TRIM support</b>. Lion enables TRIM for Apple SSDs, but not for Vertex 2. I don&#8217;t like to present a solution on this site because this issue and its solution is a quite dynamic and not long time tested affaire at the moment. One of the referred discussions is <a href="http://notebooksnews.com/apple/enable-trim-mac-os-x-lion/">http://notebooksnews.com/apple/enable-trim-mac-os-x-lion/</a>. I have no idea if this will work for future versions, so search the internet for the actual solutions!</li>
<li><b>Disable hard drive sleep</b>: It&#8217;s no advantage to put SSDs into sleep mode. You can switch this off in the System preferences section &#8220;Energy Saver&#8221; by unticking the &#8220;Put hard disks into sleep when possible&#8221; checkbox for both Battery and Power tabs.</li>
<li>You can free some disk space by disabling the hibernation mode. (This is not directly related to the handling of the SSDs, but general disk usage). Setting the sleep mode settings from 3 to 0 does this: <code>sudo pmset -a hibernatemode 0</code>. You can then remove the sleep image: <code>sudo rm /var/vm/sleepimage</code>.</li>
</ol>
<p><br/><br />
<b>Summa Summarum: After determining what to do, the whole migration took effectively about 2.5 hours of my lifetime &#8211; which is less than fixing the last compilation issue with MacPorts &#8230;</b></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/uncategorized/migrate-mac-os-10-7-lion-on-a-ocz-vertex-ssd-in-a-unibody-macbook-pro/2011-09-16/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Mantis installation script for Ubuntu 10.04</title>
		<link>http://www.atwillys.de/programming/mantis-installation-script-for-ubuntu-10-04/2011-08-25/</link>
		<comments>http://www.atwillys.de/programming/mantis-installation-script-for-ubuntu-10-04/2011-08-25/#comments</comments>
		<pubDate>Thu, 25 Aug 2011 18:52:54 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[install script]]></category>
		<category><![CDATA[issue tracking]]></category>
		<category><![CDATA[Mantis]]></category>
		<category><![CDATA[MantisBT]]></category>
		<category><![CDATA[setup script]]></category>
		<category><![CDATA[Ubuntu 10.04]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1161</guid>
		<description><![CDATA[The MantisBT is a PHP/MySQL based issue tracking system, licensed under the GPL and quite popular in the open source community to deal with software bugs. There are different ways to install MantisBT on your Ubuntu 10.04 server (e.g. aptitude), &#8230; <a href="http://www.atwillys.de/programming/mantis-installation-script-for-ubuntu-10-04/2011-08-25/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>The <a href="http://www.mantisbt.org/">MantisBT</a> is a PHP/MySQL based <a href="http://en.wikipedia.org/wiki/Issue_tracking_system">issue tracking system</a>, licensed under the GPL and quite popular in the open source community to deal with software bugs. There are different ways to install MantisBT on your Ubuntu 10.04 server (e.g. <a href="http://en.wikipedia.org/wiki/Aptitude_(software)">aptitude</a>), but if you want a one-shot preconfigured system in your <code>/var/www/</code> folder, take a look at this small blog article.<br />
<br />
Sequence:</p>
<ul>
<li>If not yet installed, install GIT using <code>sudo aptitude install git</code> (GIT is good anyway :) ). This is needed because the script gets the latest repository snapshot with all the nice bug fixed.</li>
<li>Download and extract the ZIP <a href="/public/download/mantis-setup.zip">archive</a> in your server home directory or /tmp/&#8221;,</li>
<li>Run in the shell <code>./mantis-setup/setup install &lt;mysql root password&gt; &lt;database name&gt; &lt;database user&gt; &lt;database password&gt;</code> (I used myself database name=&#8221;mantisbt&#8221;, database user=mantisbt). Well, please don&#8217;t choose a user name or database name that already exists, I did not check what happens then. It&#8217;s anyway better to have all database stuff this separated.</li>
</ul>
<h3>What happens then?</h3>
<ul>
<li>The script downloads MantisBT with the plugins source-integration and meta using GIT,</li>
<li>modifies the <code>config_inc.php</code> file of the MantisBT,</li>
<li>adapts the file/directory permissions for Apache2 (www-data, if you have another user/group, then modify the script file header, ou can&#8217;t miss it)</li>
<li>generates the database and the database user (the database contents are copied from the SQL file &#8220;copytables.sql&#8221;),</li>
<li>generates an individual SALT value for your BT system encryption</li>
<li>moves the whole folder structure to /var/www/mantisbt.</li>
</ul>
<h3>Result</h3>
<ul>
<li>You can access the bugtracker using http://yourserver.wtf/mantisbt</li>
<li>Login: &#8220;administrator&#8221;, password: &#8220;root&#8221;. Don&#8217;t forget to change it directly.</li>
<li>Installed plugins: &#8220;meta&#8221; and &#8220;source-integration&#8221;</li>
</ul>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Installation script source code</h3>
</div>
<pre class="brush: bash; title: ; notranslate">
#!/bin/bash

#########################################################################################
## Settings
#########################################################################################

APTITUDE='/usr/bin/aptitude'
WWW_ROOT=&quot;/var/www&quot;
APACHE_USER=&quot;www-data&quot;
APACHE_GROUP=&quot;www-data&quot;
TEMP_DIR=&quot;/tmp/mantis-setup&quot;
GIT=&quot;/usr/bin/git&quot;
MANTIS_GITHUB_URL=&quot;https://github.com/mantisbt/mantisbt.git&quot;
MANTIS_GIT_PLUGIN_SOURCE_URL=&quot;git://git.mantisforge.org/source-integration.git&quot;
MANTIS_GIT_PLUGIN_META_URL=&quot;git://git.mantisforge.org/meta.git&quot;
WWW_MANTIS_DIRNAME='mantis'
DB_MYSQL_ROOT_PASS=&quot;$2&quot;
DB_NAME=&quot;$3&quot;
DB_USER=&quot;$4&quot;
DB_PASS=&quot;$5&quot;
DB_HOST='localhost'
CRYPTO_SALT=&quot;$(cat /dev/urandom | head -c 64 | base64 -w 1000)&quot;

pushd . &gt; /dev/null
cd &quot;$(dirname $0)&quot;
SCRIPT_DIR=&quot;$(pwd)&quot;
popd &gt; /dev/null

#########################################################################################
## Program starts here
#########################################################################################

# functions
function echo_error()  { echo -e &quot;&#92;&#48;33[0;31m[FAILED] $@&#92;&#48;33[0m&quot;; }
function echo_ok()     { echo -e &quot;&#92;&#48;33[0;32m[OK] $@&#92;&#48;33[0m&quot;; }
function echo_info()   { echo -e &quot;&#92;&#48;33[0;33m$@&#92;&#48;33[0m&quot;; }

function set_php_var() {
	sudo grep -Fq &quot;${1}&quot; &quot;${3}&quot;;
	if [ $? -eq 0 ]; then
		sudo sed -i &quot;s,${1}.*,${1} = ${2};,&quot; &quot;${3}&quot;;
	else
		sudo sed -i &quot;\$a\\${1} = ${2};&quot; &quot;${3}&quot;;
	fi;
}

function print_usage() {
	echo &quot;Usage:&quot;
	echo &quot;&quot;
	echo &quot; To install:&quot;
	echo &quot;   $SCRIPT_DIR install &lt;mysql root password&gt; &lt;database name&gt; &lt;database user&gt; &lt;database password&gt;&quot;
	echo &quot;&quot;
	echo &quot; To uninstall:&quot;
	echo &quot;   $SCRIPT_DIR uninstall --really&quot;
	echo &quot;&quot;
	exit
}

function chmod_chown_wwwdata() {
	echo_info &quot;Changing file modes/owner/group to make them suitable for Apache&quot;
	sudo chown -R $APACHE_USER:$APACHE_GROUP $TEMP_DIR/mantisbt
	sudo find . -type d -exec chmod 550 {} \;
	sudo find . -type f -exec chmod 440 {} \;
}

# uninstall process
if [ &quot;$1&quot; = &quot;uninstall&quot; ]; then
	if [ &quot;$2&quot; != &quot;--really&quot; ]; then
		echo_error 'You must say &quot;setup uninstall --really&quot;'
		exit
	fi
	echo_error &quot;TODO: IMPLEMENT UNINSTALLER&quot;
	sudo echo &quot;&quot;
	echo_info &quot;----------------------------------------------------------------------&quot;
	echo_info &quot;UNINSTALL&quot;
	echo_info &quot;----------------------------------------------------------------------&quot;
	echo_info &quot;remove mantis ...&quot;

	if [ ! -d $WWW_ROOT ]; then
		echo_error &quot;Web root directory not there ($WWW_ROOT)&quot;
		exit
	elif [ ! -d $WWW_ROOT/$WWW_MANTIS_DIRNAME ]; then
		echo_error &quot;Mantis is not installed in $WWW_ROOT/$WWW_MANTIS_DIRNAME&quot;
		exit
	fi

	sudo rm -rf $WWW_ROOT/$WWW_MANTIS_DIRNAME

	echo_info &quot;NOTE: DATABASE WILL NOT BE DELETED&quot;
	echo_ok &quot;uninstalled&quot;
	exit
elif [ &quot;$1&quot; = &quot;install&quot; ]; then
	# mark sudo
	sudo echo &quot;&quot;

	if [ ! -d $WWW_ROOT ]; then
		echo_error &quot;Web root directory not there ($WWW_ROOT)&quot;
		exit
	elif [ &quot;$4&quot; = &quot;&quot; ]; then
		print_usage
	elif [ -d $WWW_ROOT/$WWW_MANTIS_DIRNAME ]; then
		echo_error &quot;Mantis is already installed in $WWW_ROOT/$WWW_MANTIS_DIRNAME&quot;
		exit
	fi

	if [ &quot;$DB_NAME&quot; = &quot;&quot; -o &quot;$DB_USER&quot; = &quot;&quot; -o &quot;$DB_PASS&quot; = &quot;&quot; ]; then
		echo_error &quot;You must set database name, user and password.&quot;
	fi

	echo_info &quot;Preparing temporary direcory&quot;
	pushd . &amp;&gt; /dev/null
	mkdir $TEMP_DIR
	cd $TEMP_DIR

	echo_info &quot;Cloning mantis from github&quot;
	if [ ! -d &quot;$TEMP_DIR/mantisbt&quot; ]; then
		# get mantis
		$GIT clone $MANTIS_GITHUB_URL
		echo_info &quot;(To ensure compatibility when generating the database structure,&quot;
		echo_info &quot; we checkout release-1.2.6, can be updated later ...)&quot;
		cd $TEMP_DIR/mantisbt
		$GIT checkout release-1.2.6
		rm -rf .git

		# get source-integration
		cd $TEMP_DIR/mantisbt/plugins
		$GIT clone &quot;$MANTIS_GIT_PLUGIN_SOURCE_URL&quot;
		if [ ! -d &quot;$TEMP_DIR/mantisbt/plugins/source-integration&quot; ]; then
			echo_error &quot;Failed to get source-integration plugin from $MANTIS_GIT_PLUGIN_META_URL&quot;
		else
			mv $TEMP_DIR/mantisbt/plugins/source-integration/Source* $TEMP_DIR/mantisbt/plugins
			rm -rf $TEMP_DIR/mantisbt/plugins/source-integration
		fi

		# get meta
		cd $TEMP_DIR/mantisbt/plugins
		$GIT clone &quot;$MANTIS_GIT_PLUGIN_META_URL&quot;
		if [ ! -d &quot;$TEMP_DIR/mantisbt/plugins/meta&quot; ]; then
			echo_error &quot;Failed to get meta plugin from $MANTIS_GIT_PLUGIN_META_URL&quot;
		else
			mv $TEMP_DIR/mantisbt/plugins/meta/Meta $TEMP_DIR/mantisbt/plugins/
			rm -rf $TEMP_DIR/mantisbt/plugins/meta
		fi

		# finish git clone stuff
		cd $TEMP_DIR/mantisbt
	fi

	if [ ! -d &quot;$TEMP_DIR/mantisbt&quot; ]; then
		echo_error &quot;Mantis was not cloned from git&quot;
	fi

	echo_info &quot;Processing $TEMP_DIR/mantisbt/config_inc.php&quot;
	sudo chown -R $USER:$USER $TEMP_DIR/mantisbt
	sudo rm $TEMP_DIR/mantisbt/config_inc.php &amp;&gt; /dev/null
	sudo cp -f $TEMP_DIR/mantisbt/config_inc.php.sample $TEMP_DIR/mantisbt/config_inc.php
	set_php_var '$g_hostname' '&quot;'&quot;$DB_HOST&quot;'&quot;' $TEMP_DIR/mantisbt/config_inc.php
	set_php_var '$g_db_username' '&quot;'&quot;$DB_USER&quot;'&quot;'  $TEMP_DIR/mantisbt/config_inc.php
	set_php_var '$g_db_password' '&quot;'&quot;$DB_PASS&quot;'&quot;'  $TEMP_DIR/mantisbt/config_inc.php
	set_php_var '$g_database_name' '&quot;'&quot;$DB_NAME&quot;'&quot;'  $TEMP_DIR/mantisbt/config_inc.php
	set_php_var '$g_allow_signup' 'false'  $TEMP_DIR/mantisbt/config_inc.php
	set_php_var '$g_allow_anonymous_login' 'false'  $TEMP_DIR/mantisbt/config_inc.php
	set_php_var '$g_crypto_master_salt' '&quot;'&quot;$CRYPTO_SALT&quot;'&quot;'  $TEMP_DIR/mantisbt/config_inc.php
	#sudo cat $TEMP_DIR/mantisbt/config_inc.php

	echo_info &quot;Changing file modes/owner/group to make them suitable for Apache&quot;
	sudo chown -R $APACHE_USER:$APACHE_GROUP $TEMP_DIR/mantisbt
	sudo find . -type d -exec chmod 550 {} \;
	sudo find . -type f -exec chmod 440 {} \;

	echo_info &quot;Generating database&quot;
	sudo cp &quot;$SCRIPT_DIR/dbcreate.sql&quot; &quot;$TEMP_DIR/dbcreate.sql&quot;
	sudo chmod 666 &quot;$TEMP_DIR/dbcreate.sql&quot;
	sudo sed -i &quot;s/@@@mantisbt_db/$DB_NAME/&quot; &quot;$TEMP_DIR/dbcreate.sql&quot;
	sudo sed -i &quot;s/@@@mantisbt_user/$DB_USER/&quot; &quot;$TEMP_DIR/dbcreate.sql&quot;
	sudo sed -i &quot;s/@@@mantisbt_pass/$DB_PASS/&quot; &quot;$TEMP_DIR/dbcreate.sql&quot;
	sudo cat &quot;$SCRIPT_DIR/copytables.sql&quot; &gt;&gt; &quot;$TEMP_DIR/dbcreate.sql&quot;
	sudo cat &quot;$SCRIPT_DIR/dbcreate-finish.sql&quot; &gt;&gt; &quot;$TEMP_DIR/dbcreate.sql&quot;

	echo_info &quot;Running mysql, you must enter the mysql root password now&quot;
	#cat &quot;$TEMP_DIR/dbcreate.sql&quot;
	if [ &quot;$DB_MYSQL_ROOT_PASS&quot; = &quot;&quot; ]; then
		mysql --user=&quot;root&quot; -p &lt; &quot;$TEMP_DIR/dbcreate.sql&quot;
	else
		mysql --user=&quot;root&quot; --password=&quot;$DB_MYSQL_ROOT_PASS&quot; &lt; &quot;$TEMP_DIR/dbcreate.sql&quot;
	fi
	sudo rm &quot;$TEMP_DIR/dbcreate.sql&quot;

	# Finish install
	echo_info &quot;Moving to www root&quot;
	sudo mv $TEMP_DIR/mantisbt $WWW_ROOT/$WWW_MANTIS_DIRNAME
	echo_info &quot;Renaming mantis admin path ($WWW_ROOT/$WWW_MANTIS_DIRNAME/admin)&quot;
	sudo mv $WWW_ROOT/$WWW_MANTIS_DIRNAME/admin $WWW_ROOT/$WWW_MANTIS_DIRNAME/.admin
	popd &amp;&gt; /dev/null
	echo_info &quot;Removing temp directory&quot;
	sudo rm -rf $TEMP_DIR &amp;&gt; /dev/null

	echo_info &quot;NOTE: You can now login as administrator under /$WWW_MANTIS_DIRNAME/index.php&quot;
	echo_info &quot;      login name: administrator&quot;
	echo_info &quot;      login pass: root&quot;
	echo_info &quot;      AND YOU SHOULD CHANGE THIS PASSWORD IMMEDIATELY&quot;

	echo_ok &quot;Installed&quot;
else
	echo 'You mus say &quot;install&quot; or &quot;uninstall&quot;'
	exit
fi
</pre>
<h3>Output</h3>
<p>The script output should look similar to this (just in color):</p>
<pre class="brush: plain; title: ; notranslate">
foo@host:~/ubuntu-setup-scripts/mantis$ ./setup install &quot;*******************&quot; mantisbt mantisbt &quot;****************&quot;

Preparing temporary direcory
Cloning mantis from github
Initialized empty Git repository in /tmp/mantis-setup/mantisbt/.git/
remote: Counting objects: 64350, done.
remote: Compressing objects: 100% (11204/11204), done.
remote: Total 64350 (delta 52967), reused 63836 (delta 52508)
Receiving objects: 100% (64350/64350), 22.87 MiB | 1.10 MiB/s, done.
Resolving deltas: 100% (52967/52967), done.
(To ensure compatibility when generating the database structure,
 we checkout release-1.2.6, can be updated later ...)
Note: checking out 'release-1.2.6'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 89738d5... Bump
Initialized empty Git repository in /tmp/mantis-setup/mantisbt/plugins/source-integration/.git/
remote: Counting objects: 2442, done.
remote: Compressing objects: 100% (651/651), done.
remote: Total 2442 (delta 1693), reused 2442 (delta 1693)
Receiving objects: 100% (2442/2442), 369.03 KiB, done.
Resolving deltas: 100% (1693/1693), done.
Initialized empty Git repository in /tmp/mantis-setup/mantisbt/plugins/meta/.git/
remote: Counting objects: 9, done.
remote: Compressing objects: 100% (7/7), done.
remote: Total 9 (delta 0), reused 9 (delta 0)
Receiving objects: 100% (9/9), 12.59 KiB, done.
Processing /tmp/mantis-setup/mantisbt/config_inc.php
Changing file modes/owner/group to make them suitable for Apache
Generating database
Running mysql, you must enter the mysql root password now
Moving to www root
Renaming mantis admin path (/var/www/mantis/admin)
Removing temp directory
NOTE: You can now login as administrator under /mantis/index.php
      login name: administrator
      login pass: root
      AND YOU SHOULD CHANGE THIS PASSWORD IMMEDIATELY
[OK] Installed
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/mantis-installation-script-for-ubuntu-10-04/2011-08-25/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Connect Drupal 7 to SabreDAV</title>
		<link>http://www.atwillys.de/programming/connect-drupal-7-to-sabredav/2011-08-25/</link>
		<comments>http://www.atwillys.de/programming/connect-drupal-7-to-sabredav/2011-08-25/#comments</comments>
		<pubDate>Thu, 25 Aug 2011 07:34:01 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1145</guid>
		<description><![CDATA[One of the best Apache2 compatible WebDAV server implementations is the object oriented PHP implementation SabreDAV. If you use a Drupal for your content management and like to add a WebDAV file storage, you can connect SabreDAV to the Drupal &#8230; <a href="http://www.atwillys.de/programming/connect-drupal-7-to-sabredav/2011-08-25/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>One of the best Apache2 compatible WebDAV server implementations is the object oriented PHP implementation SabreDAV. If you use a Drupal for your content management and like to add a WebDAV file storage, you can connect SabreDAV to the Drupal user database. You can to this by using the Drupal module DP UserConnector (<a href="http://drupal.org/project/udc">http://drupal.org/project/udc</a>, the project is used in the implementation of the <code>Sabre_DAV_Auth_IBackend</code> interface, as shown in this source code:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>SabreDAV BasicAuth implementation and server run</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
require_once 'Sabre/autoload.php';

/**
 * DP connected user authentification
 */
class Sabre_DAV_Auth_Backend_Basic_DrupalConnected implements Sabre_DAV_Auth_IBackend {

    /**
     * @var DrupalUserAuth
     */
    public $dpu = null;

    /**
     * Returns information about the currently logged in username.
     * If nobody is currently logged in, this method should return null.
     * @return string|null
     */
    public function getCurrentUser() {
        return !$this-&gt;dpu ? null : $this-&gt;dpu-&gt;name;
    }

    /**
     * Authenticates the user based on the current request.
     * If authentication is succesful, true must be returned.
     * If authentication fails, an exception must be thrown.
     * @throws Sabre_DAV_Exception_NotAuthenticated
     * @return bool
     */
    public function authenticate(Sabre_DAV_Server $server,$realm) {

        // Assuming we only accept https for basic auth
        if(!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != 'on') {
            throw new Sabre_DAV_Exception_NotAuthenticated('HTTPS only');
        }

        // Prepare SabreDav auth instances, get user &amp; password pair
        $auth = new Sabre_HTTP_BasicAuth();
        $auth-&gt;setHTTPRequest($server-&gt;httpRequest);
        $auth-&gt;setHTTPResponse($server-&gt;httpResponse);
        $auth-&gt;setRealm($realm);
        $userpass = $auth-&gt;getUserPass();

        // Pre check
        if(!$userpass) {
            $auth-&gt;requireLogin();
            throw new Sabre_DAV_Exception_NotAuthenticated('No basic authentication headers were found');
        }

        // Assuming DRUPAL ROOT = $_SERVER['DOCUMENT_ROOT']
        require_once($_SERVER['DOCUMENT_ROOT'] . '/sites/all/modules/udc/client/DrupalUserAuth.class.inc');

        // The token used to connect to DP
        DrupalUserBase::setToken(&quot;---my---token---here---&quot;);

        // Authentification request
        $dpu = new DrupalUserAuth();
        $dpu-&gt;request(
            $userpass[0],    // user name
            $userpass[1],     // pass
            null,            // email
            true,            // active users only
            null            // no profile fields to fetch
        );

        // Validation
        if (!$dpu-&gt;valid) {
            $auth-&gt;requireLogin();
            throw new Sabre_DAV_Exception_NotAuthenticated('Username or password does not match');
        } else {
            $this-&gt;dpu = $dpu;
            return true;
        }
    }
}

// Directory structure of this sample code:
//
// drwx  - document root (www-data)
// dr-x     - webdav
// -r--        - index.php
// drwx        - davfiles
//                 (DAV FILES ARE IN HERE)
// drw-        - data
// -rw-           - locks
// dr-x        - Sabre
//
$rootDirectory = new Sabre_DAV_FS_Directory('davfiles');
$server = new Sabre_DAV_Server($rootDirectory);
$server-&gt;setBaseUri('/webdav/');
// The lock manager is reponsible for making sure users don't overwrite each others changes.
// Change 'data' to a different directory, if you're storing your data somewhere else.
$lockBackend = new Sabre_DAV_Locks_Backend_File('data/locks');
$lockPlugin = new Sabre_DAV_Locks_Plugin($lockBackend);
$server-&gt;addPlugin($lockPlugin);
$authBackend = new Sabre_DAV_Auth_Backend_Basic_DrupalConnected();
$authPlugin = new Sabre_DAV_Auth_Plugin($authBackend,'WebDAV area');
$server-&gt;addPlugin($authPlugin);
$server-&gt;exec(); // All we need to do now, is to fire up the server
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/connect-drupal-7-to-sabredav/2011-08-25/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Gitosis Installation Script For Ubuntu 10.04</title>
		<link>http://www.atwillys.de/programming/gitosis-installation-script-for-ubuntu-10-4/2011-08-01/</link>
		<comments>http://www.atwillys.de/programming/gitosis-installation-script-for-ubuntu-10-4/2011-08-01/#comments</comments>
		<pubDate>Mon, 01 Aug 2011 19:28:02 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[Programming]]></category>
		<category><![CDATA[Version Control]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[git]]></category>
		<category><![CDATA[git server]]></category>
		<category><![CDATA[gitosis]]></category>
		<category><![CDATA[gitweb]]></category>
		<category><![CDATA[shell script]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[version control]]></category>
		<category><![CDATA[versioning]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1102</guid>
		<description><![CDATA[If you like programming, you might like version control as well, means you might like GIT and your own repository server at home to &#8220;sync&#8221; with your friends and fellows. That&#8217;s what I wanted as well for my Ubuntu (10.4) &#8230; <a href="http://www.atwillys.de/programming/gitosis-installation-script-for-ubuntu-10-4/2011-08-01/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>If you like programming, you might like version control as well, means you might like <a href="http://en.wikipedia.org/wiki/Git_(software)">GIT</a> and your own repository server at home to &#8220;sync&#8221; with your friends and fellows. That&#8217;s what I wanted as well for my Ubuntu (10.4) system, and I have to admit: Setting up <a href="http://en.wikibooks.org/wiki/Git/Gitosis">gitosis</a> can take some time, and there are some nice &#8220;pitfallish&#8221; details on the path as well, so I condensed the various tutorials in the web into a shell script, which installs gitosis, <a href="https://git.wiki.kernel.org/index.php/Gitweb">gitweb</a>, <a href="http://help.ubuntu.com/community/Git">git-daemon</a> and optionally <a href="http://viewgit.sourceforge.net/">viewgit</a>. The mainly used howtos are straight from the ubuntu docs (<a href="http://help.ubuntu.com/community/Git">http://help.ubuntu.com/community/Git</a>). I hope is saves you some time.<br />
<br />
Sequence:</p>
<ul>
<li>Download and extract the ZIP <a href="/public/download/gitosis-setup.zip">archive</a> in your server home directory or /tmp/&#8221;,</li>
<li>Log into your server using SSH</li>
<li>Run in the shell <code>./gitosis-setup/setup install</code></li>
<li>Optionally before <code>./gitosis-setup/setup --help</code></li>
<li>Follow the instructions. You need this only if you don&#8217;t have a SSH key file, e.g. ~/.ssh/id_rsa, otherwise the script will run without asking questions</li>
</ul>
<h3>What happens then?</h3>
<ul>
<li>The script checks if the actual user has a ~/.ssh/id_rsa key file, which is required for gitosis. If not it generates one for you usind <code>ssh-keygen</code> (encryption 4096 bits). You will have to enter a passphrase for this keyfile.</li>
<li>The script installs git gitosis apache2 libapache2-mod-php5 php-geshi gitweb using aptitude</li>
<li>The gitosis setup generates a user &#8220;gitosis&#8221;. The script renames this user to &#8220;git&#8221;</li>
<li>The script initializes gitosis with your key file</li>
<li>The script adds the git-daemon to the services and adds an allow rule for the git port in the firewall (ufw) configuration.</li>
</ul>
<h3>Result</h3>
<ul>
<li>Your gitosis data are in /srv/gitosis.</li>
<li>You have a new user &#8220;git&#8221; on your machine, this user cannot login to the shell, cannot login via password and is strictly bound to the gitosis system (see /srv/gitosis/.ssh/authorized_keys, there is for each registered user the command that is executed when someone connects via SSH).</li>
<li>If you did not have it yet, you have now a running apache2 server with the document root <code>/var/www/</code>. You can access gitweb using <code>http[s]://yourserver.whatever/gitweb</code>, at least at <code>http://localhost/gitweb</code>. Note: The script does not change your document root if already existing. Gitweb is a perl construction that is added to the apache2 using a location alias.</li>
<li><b>The account you installed gitosis with is not the &#8220;admin&#8221; account of gitosis</b>.</li>
</ul>
<p>Now you configure gitosis with your server account, add projects and users:</p>
<pre class="brush: bash; title: ; notranslate">
cd ~/
git clone git@localhost:gitosis-admin.git
cd ~/gitosis-admin
nano gitosis.conf
cp ~/a-friends-public-key-file.pub ~/gitosis-admin/keydir/
etc.
etc.
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Source code</h3>
</div>
<pre class="brush: bash; title: ; notranslate">
#!/bin/bash

# locals
APTITUDE='/usr/bin/aptitude'
APTITUDE_INSTALL=&quot;$APTITUDE -q -y install&quot;
TEMP_DIR='/tmp/setup-gitsvr'
GITHOST=localhost
ID_RSA_NAME=&quot;id_rsa&quot;
LINE_INDENT=&quot;&quot;
WWW_ROOT=&quot;/var/www&quot;
APACHE_USER=&quot;www-data&quot;
APACHE_GROUP=&quot;www-data&quot;

# functions
function echo_error() { echo -e &quot;&#92;&#48;33[0;31m[FAILED] $@&#92;&#48;33[0m&quot;; }
function echo_ok()    { echo -e &quot;&#92;&#48;33[0;32m[OK] $@&#92;&#48;33[0m&quot;; }
function echo_info()  { echo -e &quot;&#92;&#48;33[0;33m$@&#92;&#48;33[0m&quot;; }

function echo_usage() {
	echo &quot;Usage:&quot;
	echo &quot;&quot;
	echo &quot; To install gitosis with doxygen and gitweb:&quot;
	echo &quot;   $(basename $0) install&quot;
	echo &quot;   This will install git, gitosis. It will configure your server, so that:&quot;
	echo &quot;&quot;
	echo &quot;     - The repository is in /srv/gitosis (made by aptitude).&quot;
	echo &quot;     - The git user is 'git' (renamed from 'gitosis').&quot;
	echo &quot;     - The account you run it from is the gitosis admin (use 'sudo setup install'&quot;
	echo &quot;       and do not 'sudo su; setup install'.&quot;
	echo &quot;       - If this account does not yet have an 'id_rsa' key file in ~/.ssh, we will&quot;
	echo &quot;         generate one during the setup process.&quot;
	echo &quot;&quot;
	echo &quot;   After the setup you can directly edit the gitosis config using:&quot;
	echo &quot;&quot;
	echo &quot;       $ cd ~/ &quot;
	echo -e &quot;       $ &#92;&#48;33[0;31mgit clone git@localhost:gitosis-admin.git&#92;&#48;33[0m&quot;
	echo &quot;       $ cd gitosis-admin&quot;
	echo &quot;       $ [vim/nano] gitosis.conf&quot;
	echo &quot;       $ cp &lt;path to new user key file&gt;.pub ~/gitosis-admin/keydir/&lt;name of the user&gt;.pub&quot;
	echo -e &quot;       $ &#92;&#48;33[0;31mgit commit -am 'Added &lt;name of the user&gt;, did something in the config'&quot;
	echo -e &quot;       $ &#92;&#48;33[0;31mgit push origin master&#92;&#48;33[0m&quot;
	echo &quot;&quot;
	echo &quot;&quot;
	echo &quot;&quot;
	echo &quot; To uninstall gitosis and gitweb:&quot;
	echo &quot;   $(basename $0) uninstall --really&quot;
	echo &quot;   This will remove gitosis and gitweb. Other the other packages have to be removed&quot;
	echo &quot;   manually using aptutude remove.&quot;
	echo &quot;&quot;
	echo &quot; To install gitweb:&quot;
	echo &quot;   $(basename $0) install-gitweb&quot;
	echo &quot;&quot;
	echo &quot; To uninstall gitweb&quot;
	echo &quot;   $(basename $0) uninstall-gitweb&quot;
	echo &quot;&quot;
	echo &quot; To install viewgit:&quot;
	echo &quot;   $(basename $0) install-viewgit&quot;
	echo &quot;&quot;
	echo &quot; To uninstall viewgit:&quot;
	echo &quot;   $(basename $0) uninstall-viewgit&quot;
	echo &quot;&quot;
	exit
}

# uninstall process
if [ &quot;$1&quot; = &quot;uninstall&quot; ]; then
	if [ &quot;$2&quot; != &quot;--really&quot; ]; then
		echo_error 'You must say &quot;setup uninstall --really&quot;'
		echo &quot;&quot;
		echo_usage
	fi
	sudo echo &quot;&quot;
	echo_info &quot;----------------------------------------------------------------------&quot;
	echo_info &quot;UNINSTALL&quot;
	echo_info &quot;----------------------------------------------------------------------&quot;

	echo_info &quot;remove git-demon ...&quot;
	if [ -f /etc/init.d/git-daemon ]; then
	echo_info &quot;Stopping service git-daemon&quot;
	sudo service git-daemon stop
	echo_info &quot;Removing git port (9418) from firewall rules (ufw)&quot;
	sudo ufw delete allow in 9418/tcp
	echo_info &quot;Removing /etc/init.d/git-daemon&quot;
	sudo update-rc.d -f git-daemon remove
	sudo rm /etc/init.d/git-daemon
	fi

	echo_info &quot;remove gitosis ...&quot;
	sudo $APTITUDE -q -y remove gitosis gitweb
	echo_info &quot;purge gitosis ...&quot;
	sudo $APTITUDE -q -y purge gitosis gitweb
	echo_info &quot;remove repository ...&quot;
	sudo rm -rf /srv/gitosis &amp;&gt; /dev/null
	echo_info &quot;Ensure user/group gitosis is gone ...&quot;
	sudo groupdel git &amp;&gt; /dev/null
	sudo userdel git &amp;&gt; /dev/null
	echo_ok &quot;uninstalled&quot;
	exit
elif [ &quot;$1&quot; = &quot;install&quot; ]; then
	# mark sudo
	sudo echo &quot;&quot;

	# initial checks
	if [ -d /srv/gitosis ]; then
		echo_error &quot;gitosis already setup (in /srv/gitosis/).&quot;
		exit;
	fi

	if [ `dirname $TEMP_DIR` != &quot;/tmp&quot; ]; then
		echo_error &quot;temp dir is not a subdir of /tmp/&quot;
		exit;
	fi

	if [ ! -f ~/.ssh/$ID_RSA_NAME ]; then
		echo_info &quot;----------------------------------------------------------------------&quot;
		echo_info &quot;You don't have a personal user key file yet, we generate one now ...&quot;

		if [ ! -d ~/.ssh ]; then
			mkdir ~/.ssh
			chmod 700 ~/.ssh
			echo_info &quot;Created ~/.ssh with 700&quot;
		fi

		echo_info &quot;Keygen - now enter a passphrase for your private id key file ...&quot;
		ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa
		chmod 600 ~/.ssh/id_rsa
		echo_info &quot;~/.ssh/id_rsa set to mod 600&quot;
		echo_info &quot;Now we register you at the local machine for ssh keyfile login&quot;
		echo_info &quot;    (ssh-copy-id $USER@$GITHOST), you may need to enter your normal login password&quot;
		echo_info '    +++ IF IT SAYS NOW &quot;The authenticity of host ... cant be established&quot;, JUST SAY &quot;yes&quot; +++'
		echo &quot;&quot;
		ssh-copy-id $USER@$GITHOST
		echo_info &quot;OK, let's go on with the normal gitosis installation ...&quot;
		echo_info &quot;----------------------------------------------------------------------&quot;
	fi

	if [ ! -f ~/.ssh/$ID_RSA_NAME.pub ]; then
		echo_error &quot;Public key file not there&quot;
		exit
	fi

	# aptitude
	echo_info &quot;Installing with aptitude: git gitosis&quot;
	sudo $APTITUDE_INSTALL git gitosis

	# Change user to &quot;git&quot; as clone &quot;git@myhost&quot; is shorter and seems reasonable
	sudo killall -u gitosis
	sudo id gitosis
	sudo usermod -l git gitosis
	sudo groupmod -n git gitosis
	sudo id git
	sudo chown -R git:git /srv/gitosis

	# initialize gitosis
	echo_info &quot;Initializing git (/srv/gitosis)&quot;
	sudo -H -u git gitosis-init &lt; ~/.ssh/$ID_RSA_NAME.pub

	# setup git daemon
	if [ ! -f /etc/init.d/git-daemon ]; then
		echo_info &quot;Copying git-daemon to /etc/init.d/git-daemon&quot;
		sudo cp &quot;$(dirname $0)/git-daemon&quot; /etc/init.d/
		sudo chmod +x /etc/init.d/git-daemon
		sudo update-rc.d git-daemon defaults
		echo_info &quot;Allowing git port (9418) in firewall (ufw)&quot;
		sudo ufw allow in 9418/tcp
		echo_info &quot;Starting service git-daemon&quot;
		sudo service git-daemon start
	fi

	echo &quot;&quot;
	echo_info &quot;----------------------------------------------------------------------&quot;
	echo_info &quot;Now you can clone the gitosis config using: &quot;
	echo_ok &quot;    $ git clone git@$GITHOST:gitosis-admin.git&quot;

elif [ &quot;$1&quot; = &quot;install-gitweb&quot; ]; then
	# mark sudo
	sudo echo &quot;&quot;
	echo_info &quot;Installing with aptitude: apache2 libapache2-mod-php5 php-geshi&quot;
	sudo $APTITUDE_INSTALL apache2 libapache2-mod-php5 php-geshi gitweb
	sudo adduser $APACHE_USER git

elif [ &quot;$1&quot; = &quot;uninstall-gitweb&quot; ]; then

	sudo $APTITUDE remove php-geshi gitweb

elif [ &quot;$1&quot; = &quot;install-viewgit&quot; ]; then

	sudo echo &quot;&quot;
	if [ ! -d $WWW_ROOT ]; then
		echo_error &quot;Web root directory not there ($WWW_ROOT)&quot;
		exit
	fi
	if [ -d $WWW_ROOT/viewgit ]; then
		echo_error &quot;Already installed in ($WWW_ROOT/viewgit)&quot;
		exit
	fi

	pushd &amp;&gt; /dev/null
	cd $WWW_ROOT
	echo_info &quot;Getting web interface, will be then in $WWW_ROOT/viewgit&quot;
	sudo git clone git://repo.or.cz/viewgit.git
	echo_info &quot;Changing viewgit's user/group to $APACHE_USER:$APACHE_GROUP&quot;
	sudo chown -R $APACHE_USER:$APACHE_GROUP viewgit
	echo_info &quot;Preparing config file ($WWW_ROOT/viewgit/inc/localconfig.php)&quot;
	cd $WWW_ROOT/viewgit/inc
	sudo cp config.php localconfig.php
	sudo chown $APACHE_USER:$APACHE_GROUP localconfig.php
	popd &amp;&gt; /dev/null
	echo_info &quot;Adding apache user to gitosis group&quot;
	sudo adduser $APACHE_USER git
	echo_ok &quot;You can now edit the config file $WWW_ROOT/viewgit/inc/localconfig.php&quot;

elif [ &quot;$1&quot; = &quot;uninstall-viewgit&quot; ]; then

	sudo echo &quot;&quot;
	if [ ! -d $WWW_ROOT/viewgit ]; then
		echo_error &quot;Not yet installed in ($WWW_ROOT/viewgit)&quot;
		exit
	fi
	pushd &amp;&gt; /dev/null
	sudo rm -rf $WWW_ROOT/viewgit &amp;&gt;/dev/null
	sudo adduser $APACHE_USER git
	popd &amp;&gt; /dev/null

else
	echo &quot;&quot;
	echo_usage
	exit
fi
</pre>
<h3>Daemon script</h3>
<div class='en' style='' lang='en' dir='ltr'>
<p>This script is one to one the daemon on the Ubuntu gitosis manual page.</p>
</div>
<pre class="brush: bash; title: ; notranslate">
# Taken from here: http://pastie.org/227647

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
NAME=git-daemon
PIDFILE=/var/run/$NAME.pid
DESC=&quot;the git daemon&quot;
DAEMON=/usr/lib/git-core/git-daemon
DAEMON_OPTS=&quot;--base-path=/srv/gitosis/repositories --export-all --verbose --syslog --detach --pid-file=$PIDFILE --user=gitosis --group=nogroup&quot;

test -x $DAEMON || exit 0

[ -r /etc/default/git-daemon ] &amp;&amp; . /etc/default/git-daemon

. /lib/lsb/init-functions

start_git() {
  start-stop-daemon --start --quiet --pidfile $PIDFILE \
    --startas $DAEMON -- $DAEMON_OPTS
}

stop_git() {
  start-stop-daemon --stop --quiet --pidfile $PIDFILE
  rm -f $PIDFILE
}

status_git() {
  start-stop-daemon --stop --test --quiet --pidfile $PIDFILE &gt;/dev/null 2&gt;&amp;1
}

case &quot;$1&quot; in
  start)
  log_begin_msg &quot;Starting $DESC&quot;
  start_git
  log_end_msg 0
  ;;
  stop)
  log_begin_msg &quot;Stopping $DESC&quot;
  stop_git
  log_end_msg 0
  ;;
  status)
  log_begin_msg &quot;Testing $DESC: &quot;
  if status_git
  then
    log_success_msg &quot;Running&quot;
    exit 0
  else
    log_failure_msg &quot;Not running&quot;
    exit 1
  fi
  ;;
  restart|force-reload)
  log_begin_msg &quot;Restarting $DESC&quot;
  stop_git
  sleep 1
  start_git
  log_end_msg 0
  ;;
  *)
  echo &quot;Usage: $0 {start|stop|restart|force-reload|status}&quot; &gt;&amp;2
  exit 1
  ;;
esac

exit 0
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/gitosis-installation-script-for-ubuntu-10-4/2011-08-01/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>RSS 2.0 rendering class</title>
		<link>http://www.atwillys.de/programming/php/rss2-feed-rendering-class/2010-09-04/</link>
		<comments>http://www.atwillys.de/programming/php/rss2-feed-rendering-class/2010-09-04/#comments</comments>
		<pubDate>Sat, 04 Sep 2010 19:53:05 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[rss]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1088</guid>
		<description><![CDATA[It can be so simple to generate RSS 2.0 feeds in PHP. The Rss2Feed class allows to render one or more channels in one RSS feed. All you have to do is to &#8220;feed&#8221; it with you data by overloading &#8230; <a href="http://www.atwillys.de/programming/php/rss2-feed-rendering-class/2010-09-04/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>It can be so simple to generate RSS 2.0 feeds in PHP. The <code>Rss2Feed</code> class allows to render one or more channels in one RSS feed. All you have to do is to &#8220;feed&#8221; it with you data by overloading the <code>onLoad()</code> method. The results can be saved in a .rss file or directly sent to the Browser. The example show how:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
// Before doing anything else, we include the library
include_once('swlib/swlib.class.php');

// Start the library with configuration
swlib::start(array(
    // path to library var directory, we use an one one
    // The var path must be writable for the server, it is NOT the
    // directory &quot;/var&quot; on Unix-Like systems, but a directory to serialize
    // &quot;variables&quot; in.
    'var_path' =&gt; sys_get_temp_dir(),
));

// Our class to customize the feed channel
class MyRss2Feed extends Rss2Feed {

    // That's the method we have to overload:
    protected function onLoad($identifier='', $numOfItems='') {
        // Instead of loading items from a database, we generate
        // a static array for this example:
        return array(
            'title' =&gt; 'CHANNEL TITLE',
            'description' =&gt; 'Channel description',
            'link' =&gt; '', // will be replaced with your $_SERVER[HTTP_HOST]
            'items' =&gt; array(
                array(
                    'title' =&gt; 'Item 1 title',
                    'description' =&gt; 'Item 1 description',
                    'link' =&gt; 'http://item1.link/',
                ),
                array(
                    'title' =&gt; 'Item 2 title &amp;', // this will be escaped (&quot;&amp;&quot;)
                    'description' =&gt; 'Item 2 description',
                    'link' =&gt; 'http://item2.link/',
                ),
                array(
                    'title' =&gt; 'Item 2 title with an &quot;ü&quot;',
                    'description' =&gt; '', // No content
                    'link' =&gt; '', // link will be replaced
                ),
            )
        );
    }
}

// Generate the feed with 100 items, only the default channel
try {
    $rss = new MyRss2Feed();
    $xml = $rss-&gt;renderFeed(array(), 100);
} catch(Exception $e) {
    $exception = $e;
}

// Script output
if(!isset($exception) &amp;&amp; isset($_GET['rss'])) {
    // View the RSS in a reader
    OutputBuffer::purge();
    Tracer::disable();
    print $xml;
} else {
    // View the RSS XML code in the browser
    print '&lt;html&gt;&lt;body&gt;&lt;pre&gt;';
    print htmlspecialchars($xml);
    print '&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;';
}
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
</div>
<pre class="brush: xml; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&gt;
&lt;rss version=&quot;2.0&quot;
  xmlns:content=&quot;http://purl.org/rss/1.0/modules/content/&quot;
  xmlns:wfw=&quot;http://wellformedweb.org/CommentAPI/&quot;
  xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot;
  xmlns:atom=&quot;http://www.w3.org/2005/Atom&quot;
  xmlns:sy=&quot;http://purl.org/rss/1.0/modules/syndication/&quot;
  xmlns:slash=&quot;http://purl.org/rss/1.0/modules/slash/&quot;
  &gt;
 &lt;channel&gt;
  &lt;title&gt;CHANNEL TITLE&lt;/title&gt;
  &lt;link&gt;http://wp.localhost/&lt;/link&gt;
  &lt;description&gt;&lt;/description&gt;
  &lt;generator&gt;swlib&lt;/generator&gt;
  &lt;ttl&gt;60&lt;/ttl&gt;
  &lt;item&gt;
   &lt;title&gt;Item 1 title&lt;/title&gt;
   &lt;description&gt;Item 1 description&lt;/description&gt;
   &lt;link&gt;http://item1.link/&lt;/link&gt;
  &lt;/item&gt;
  &lt;item&gt;
   &lt;title&gt;&lt;![CDATA[Item 2 title &amp;]]&gt;&lt;/title&gt;
   &lt;description&gt;Item 2 description&lt;/description&gt;
   &lt;link&gt;http://item2.link/&lt;/link&gt;
  &lt;/item&gt;
  &lt;item&gt;
   &lt;title&gt;&lt;![CDATA[Item 2 title ü]]&gt;&lt;/title&gt;
   &lt;description&gt;&lt;/description&gt;
   &lt;link&gt;http://wp.localhost/&lt;/link&gt;
  &lt;/item&gt;
 &lt;/channel&gt;
&lt;/rss&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Provides rendering one or more RSS channels into a RSS XML text.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 */
class Rss2Feed {

    /**
     * That's how the channel specfication looks like:
     * @staticvar array
     */
    private static $dataTemplate = array(
        'title' =&gt; '',              // Main title if the feed, should contain the website url
        'link' =&gt; '',               // Link to the main web page of the channel
        'description' =&gt; '',        // Text description of the feed
        'category' =&gt; '',           // One or more category names
        'language' =&gt; '',           // Channel language
        'copyright' =&gt; '',          // Copyright information
        'generator' =&gt; 'swlib',     // Feed generator
        'managingeditor' =&gt; '',     // Email address of he managing editor
        'webMaster' =&gt; '',          // Email address of the admin
        'pubDate' =&gt; '',            // RFC822 time when the content was published
        'lastbuilddate' =&gt; '',      // RFC822 time when the channel was modified
        'docs' =&gt; '',               // RSS specification documentation link
        'cloud' =&gt; '',              // Cloud service interface provider
        'ttl' =&gt; '60',              // Time to live
        'image' =&gt; array(           // Channel image:
            'url' =&gt; '',            // Url to GIF, JPEG or PNG
            'title' =&gt; '',          // Like alt tag in HTML
            'link' =&gt; '',           // Page link (use link of the channel itself)
            'width' =&gt; '',          // Optional width, mad 144
            'height' =&gt; '',         // Optional height, mad 400
            'description' =&gt; '',    // Optional like HTML title=&quot;&quot;
        ),
        'rating' =&gt; '',             //
        'textinput' =&gt; array(       // Input text box:
            'title' =&gt; '',          // The label of the Submit button in the text input area.
            'description' =&gt; '',    // Explains the text input area
            'name' =&gt; '',           // The name of the text object in the text input area
            'link' =&gt; ''            // The URL of the CGI script that processes text input requests
        ),
        'skiphours' =&gt; '',          // Hours to skip update: int 0 to 23
        'skipdays' =&gt; '',           // Days to skip update: Monday, Tuesday, Wednesday, ...
        'items' =&gt; array(),         // THESE ARE THE ITEMS OF THE CHANNEL
    );

    /**
     * That's how the item specfication looks like:
     * @staticvar array
     */
    private static $itemTemplate = array(
        'title' =&gt; '',              // Title of the Item
        'link' =&gt; '',               // Link to the related HTML page
        'description' =&gt; '',        // Text description
        'author' =&gt; '',             // Author of the item
        'category' =&gt; '',           // Item category
        'comments' =&gt; '',           // URI to item comments
        'source' =&gt; '',             // RSS channel where the content of this item is taken from
        'enclosure' =&gt; array(       // Attached media data (array contents are XML attributes)
            'url' =&gt; '',            // Link to the resource
            'type' =&gt; '',           // MIME type
            'length' =&gt; ''          // Content length in bytes
        ),
        'guid' =&gt; '',               // Global unique identifier
        'pubDate' =&gt; '',            // RFC822 time when the item was published
    );

    /**
     * The text encoding of the feed
     * @var string
     */
    private $encoding = &quot;UTF-8&quot;;

    /**
     * The channel data retrieved from the database or other sources
     * @var array
     */
    private $data = array();    

    /**
     * Items, extracted form the data array returned by onLoad.
     * @var array
     */
    public $items = array();

    /**
     * Loads the data which are necessary to render an RSS feed.
     * OVERLOAD THIS FUNCTION TO CUSTOMIZE YOUR RSS CHANNELS.
     * Returns a reference to the result array, which must contain the
     * keys &quot;title&quot;, &quot;description&quot; and &quot;item&quot;
     *
     * @param mixed $identifier
     * @param int $numOfItems
     * @return array
     */
    protected function onLoad($identifier='', $numOfItems=0) {
        return array();
    }

    /**
     * XML text escaping
     * @param string $text
     * @return string
     */
    private static function xmlEscape($text) {
        if(empty($text) || trim($text) == '') { // !empty for array()/null
            return '';
        } else if(!preg_match('/([^\x01-\x7f]|[&amp;&lt;&gt;])/', $text)) {
            return $text;
        } if(strpos($text, ']]&gt;') === false) {
            return &quot;&lt;![CDATA[$text]]&gt;&quot;;
        } else {
            return str_replace(array('&amp;','&quot;',&quot;'&quot;,'&lt;','&gt;'), array('&amp;amp;','&amp;quot;','&amp;apos;','&amp;lt;','&amp;gt;'), $text);
        }
    }

    /**
     * Constructor
     * @param string $encoding
     */
    public function __construct($encoding=&quot;UTF-8&quot;) {
        $this-&gt;encoding = $encoding;
    }

    /**
     * Data access get
     * @param string $name
     * @return mixed
     */
    public function &amp; __get($name) {
        $name = strtolower($name);
        return isset($this-&gt;data[$name]) ? $this-&gt;data[$name] : '';
    }

    /**
     * Data access set
     * @param string $name
     * @param mixed $value
     */
    public function __set($name,  $value) {
        $name = strtolower($name);
        if(!isset($this-&gt;data[$name])) {
            throw new Exception('No such RSS property to set');
        } else {
            $this-&gt;data[$name] = $value;
        }
    }

    /**
     * Loads the data using the overloadable onLoad() method.
     * @param mixed $identifier
     * @param int $numOfItems
     */
    public function load($identifier='', $numOfItems=0) {
        $this-&gt;items = null;
        $this-&gt;data = array_change_key_case(array_merge(self::$dataTemplate, $this-&gt;onLoad($identifier, $numOfItems)), CASE_LOWER);
        $this-&gt;items = &amp;$this-&gt;data['items'];
        unset($this-&gt;data['items']);

        if($this-&gt;title == '') {
            throw new Exception('RSS channel title must be specified');
        }

        if(empty($this-&gt;link)) {
            $this-&gt;link = &quot;http://{$_SERVER['HTTP_HOST']}/&quot;;
        }

        foreach($this-&gt;items as $key =&gt; $item) {
            $this-&gt;items[$key] = array_change_key_case($item, CASE_LOWER);
            if(empty($item['title'])) {
                Tracer::trace(&quot;RSS item $key removed, no title&quot;);
                $this-&gt;items[$key] = false;
            } else if(empty($item['link'])) {
                Tracer::trace(&quot;RSS item $key ({$item['title']}) added channel link&quot;);
                $this-&gt;items[$key]['link'] = $this-&gt;link;
            }
        }

        if(count($this-&gt;items) == 0) {
            throw new Exception('No RSS channel items defined');
        }
    }

    /**
     * Renders the channel
     * @return string
     */
    public function renderChannel($numOfItems=0) {
        $o = &quot; &lt;channel&gt;\n&quot;;

        foreach($this-&gt;data as $tag =&gt; $value) {
            if(!empty($value) || $tag == 'title' || $tag == 'description' || $tag == 'link') {
                if(!is_array($value)) {
                    $o .= &quot;  &lt;$tag&gt;&quot; . self::xmlEscape($value) . &quot;&lt;/$tag&gt;\n&quot;;
                } else if(strlen(trim(implode('', $value), &quot; \n\r\t&quot;)) &gt; 0) {
                    $o .= &quot; &lt;$tag&gt;&quot;;
                    foreach($value as $itag =&gt; $ivalue) {
                        $o .= &quot;   &lt;$itag&gt;&quot; . self::xmlEscape($ivalue) . &quot;&lt;/$itag&gt;\n&quot;;
                    }
                    $o .= &quot; &lt;/$tag&gt;&quot;;
                }
            }
        }

        $count = 0;
        foreach($this-&gt;items as $item) {
            if($numOfItems &gt; 0 &amp;&amp; ++$count &gt; $numOfItems) {
                break;
            } else {
                $o .= &quot;  &lt;item&gt;\n&quot;;
                foreach($item as $tag =&gt; $value) {
                    if(!is_array($value)) {
                        $o .= &quot;   &lt;$tag&gt;&quot; . self::xmlEscape($value) . &quot;&lt;/$tag&gt;\n&quot;;
                    } else if(strlen(trim(implode('', $value), &quot; \n\r\t&quot;)) &gt; 0) {
                        if($tag == 'enclosure') {
                            $o .= &quot;&lt;enclosure url=\&quot;{$value['url']}\&quot; length=\&quot;{$value['length']}\&quot; type=\&quot;{$value['type']}\&quot; /&gt;&quot;;
                        } else if(!empty($value) || $tag == 'title' || $tag == 'description' || $tag == 'link') {
                            $o .= &quot;  &lt;$tag&gt;&quot;;
                            foreach($value as $itag =&gt; $ivalue) {
                                $o .= &quot;    &lt;$itag&gt;&quot; . self::xmlEscape($ivalue) . &quot;&lt;/$itag&gt;\n&quot;;
                            }
                            $o .= &quot;  &lt;/$tag&gt;\n&quot;;
                        }
                    }
                }
                $o .= &quot;  &lt;/item&gt;\n&quot;;
            }
        }
        $o .= &quot; &lt;/channel&gt;\n&quot;;
        return $o;
    }

    /**
     * Renders a list of channels in one feed
     * @param array $channelIdentifiers
     * @param int $numOfItems
     * @return string
     */
    public function renderFeed(array $channelIdentifiers=array(), $numOfItems=0) {
        $o =  '&lt;?xml version=&quot;1.0&quot; encoding=&quot;' . $this-&gt;encoding . '&quot; ?&gt;'
            . &quot;\n&quot; . '&lt;rss version=&quot;2.0&quot;'
            . &quot;\n&quot; . '  xmlns:content=&quot;http://purl.org/rss/1.0/modules/content/&quot;'
            . &quot;\n&quot; . '  xmlns:wfw=&quot;http://wellformedweb.org/CommentAPI/&quot;'
            . &quot;\n&quot; . '  xmlns:dc=&quot;http://purl.org/dc/elements/1.1/&quot;'
            . &quot;\n&quot; . '  xmlns:atom=&quot;http://www.w3.org/2005/Atom&quot;'
            . &quot;\n&quot; . '  xmlns:sy=&quot;http://purl.org/rss/1.0/modules/syndication/&quot;'
            . &quot;\n&quot; . '  xmlns:slash=&quot;http://purl.org/rss/1.0/modules/slash/&quot;'
            . &quot;\n&quot; . &quot;  &gt;\n&quot;;
        foreach($channelIdentifiers as $identifier) {
            try {
                $this-&gt;load($identifier, $numOfItems);
                $o .= $this-&gt;renderChannel($numOfItems);
            } catch(Exception $e) {
                Tracer::traceException($e);
            }
        }
        $o .= '&lt;/rss&gt;';
        return $o;
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/rss2-feed-rendering-class/2010-09-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>User verification image class</title>
		<link>http://www.atwillys.de/programming/php/user-verification-image-class/2010-09-01/</link>
		<comments>http://www.atwillys.de/programming/php/user-verification-image-class/2010-09-01/#comments</comments>
		<pubDate>Wed, 01 Sep 2010 17:37:34 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[user verification]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1057</guid>
		<description><![CDATA[To prevent bots form submitting online-forms, it is a commonly used technique to verify &#8220;human user&#8221; with a code that has to be entered by reading letters and numbers in a noisy image. This class is a simple way to &#8230; <a href="http://www.atwillys.de/programming/php/user-verification-image-class/2010-09-01/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>To prevent bots form submitting online-forms, it is a commonly used technique to verify &#8220;human user&#8221; with a code that has to be entered by reading letters and numbers in a noisy image. This class is a simple way to do this:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
// Before doing anything else, we include the library
include_once('swlib/swlib.class.php');

// Start the library with configuration
swlib::start(array(
    // path to library var directory, we use an one one
    // The var path must be writable for the server, it is NOT the
    // directory &quot;/var&quot; on Unix-Like systems, but a directory to serialize
    // &quot;variables&quot; in.
    'var_path' =&gt; sys_get_temp_dir(),
));

// This is the entry key we use for the $_SESSION for this sample code
$sessionKey = 'swlib-verification-image';

if(isset($_GET['img'])) {
    //
    // This section sends the imgage (we use the same script for sending HTML
    // and the binary image.
    //
    Tracer::disable();
    OutputBuffer::purge();

    // Check referrer and valid session
    if(isset($_SERVER['HTTP_REFERER']) &amp;&amp; strpos(strtolower($_SERVER['HTTP_REFERER']),
            strtolower($_SERVER['HTTP_HOST'])) !== false
            &amp;&amp; isset($_SESSION[$sessionKey])
            &amp;&amp; is_array($_SESSION[$sessionKey])
            &amp;&amp; isset($_SESSION[$sessionKey]['text'])
            &amp;&amp; strlen(trim($_SESSION[$sessionKey]['text'])) &gt; 0
    ){
        // Retrieve the data saved in the session and set the corresponding
        // instance properties
        $cfg = &amp;$_SESSION[$sessionKey];
        $vi = new UserVerificationImage(trim($cfg['text']));
        if(isset($cfg['width'])) try { $vi-&gt;setWidth($cfg['width']); } catch(Exception $e) { ; }
        if(isset($cfg['height'])) try { $vi-&gt;setHeight($cfg['height']); } catch(Exception $e) { ; }
        if(isset($cfg['noise'])) try { $vi-&gt;setNoise($cfg['noise']); } catch(Exception $e) { ; }

        // Finally send the image as binay code to the browser and exit.
        $vi-&gt;sendImage();
    }

    /*
     *
     * Alternatively, you can use the static function to create an image
     * and save the corresponding SHA1 hash code in a cookie:
     *
     *      UserVerificationImage::sendValidationImageAndExit();
     *
     *  This method is much easier to use, but less configurable.
     *
     */

    // exit if the $vi-&gt;sendImage() method did not do this yet.
    exit();
}

////////////////////////////////////////////////////////////////////////////////
// This section is the normal HTML script:
//

// Create the instance
$vi = new UserVerificationImage();

// Check if the user has entered the code
if(isset($_GET['input'])) {

    try {
        // Verify the code saved in the session with the entered code.
        $vi-&gt;setText($_SESSION[$sessionKey]['text']);
        if($vi-&gt;verifyInput($_GET['input'])) {
            $inputCorrect = &quot;Input is correct&quot;;
        } else {
            $inputCorrect = &quot;Input is not correct&quot;;
        }
    } catch(Exception $e) {
        $inputCorrect = &quot;Exception:&quot; . $e-&gt;getMessage();
    }

    /*
     * Alternatively:
     *
     *  if(UserVerificationImage::verifyValidationImage($_GET['input'])) {
     *      $inputCorrect = &quot;Input is correct&quot;;
     *  } else {
     *      $inputCorrect = &quot;Input is not correct&quot;;
     *  }
     *
     * Use this static function to verify images sent using the function
     * UserVerificationImage::sendValidationImageAndExit();
     */
}

// Set a new random code with 8 characters from the character list
$vi-&gt;setRandomText(8, 'ABCDEFGHIJ0123456789klmnopqr?-');

// Write the session for the image lookup
$_SESSION[$sessionKey] = array('text' =&gt; $vi-&gt;getText());

// And print it all ...
$text = $vi-&gt;getText();

print &lt;&lt;&lt;HERE
    &lt;html&gt;&lt;body&gt;
    &lt;style&gt;
        body { font-family: monospace; }
        table { border-collapse:collapse; border: solid 1px black; padding: 2px; }
        td { border: solid 1px black; padding: 5px; }
    &lt;/style&gt;
        &lt;table&gt;
            &lt;tr&gt;&lt;td&gt;Original text&lt;/td&gt;&lt;td&gt;$text&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Image&lt;/td&gt;&lt;td&gt;&lt;img src=&quot;{$_SERVER['PHP_SELF']}?img&quot; /&gt;&lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Enter here&lt;/td&gt;&lt;td&gt;
            &lt;form method=&quot;GET&quot; action=&quot;{$_SERVER['PHP_SELF']}&quot;&gt;
                &lt;input type=&quot;text&quot; name=&quot;input&quot; value=&quot;&quot; /&gt;
                &lt;input type=&quot;submit&quot; name=&quot;validate&quot; value=&quot;validate&quot;&gt;
            &lt;/form&gt;
            &lt;/td&gt;&lt;/tr&gt;
            &lt;tr&gt;&lt;td&gt;Last value was ok:&lt;/td&gt;&lt;td&gt;$inputCorrect&lt;/td&gt;&lt;/tr&gt;
        &lt;/table&gt;
    &lt;/body&gt;&lt;/html&gt;
HERE;
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
<p>The output will be a simple table, which contains the plain text code, the image (showing the same code), a form where you can enter the code and a row, which shows if the entered code was correct.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Creates a verification image for human user verification.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 */
class UserVerificationImage {

    /**
     * Class configuration
     * @var array
     */
    private static $config = array(
    );

    /**
     * The width of the image, 0=auto
     * @var int
     */
    private $width = 0;

    /**
     * The height of the image
     * @var int
     */
    private $height = 15;

    /**
     * The text of the image.
     * @var string
     */
    private $text = '';

    /**
     * Noise factor
     * @var double
     */
    private $noise = 0.1;

    /**
     * Sends a verification image to the browser (including a SHA1 hash cookie).
     * The function ::verifyValidationImage() can be used to validate the user
     * input generated in this function.
     * @return void
     * @param bool $saveInSession=false
     * @param int $textLength
     */
    public static function sendValidationImageAndExit($saveInSession=true, $textLength=8) {
        Tracer::disable();
        OutputBuffer::purge();
        if(isset($_SERVER['HTTP_REFERER']) &amp;&amp; strpos(strtolower($_SERVER['HTTP_REFERER']), strtolower($_SERVER['HTTP_HOST'])) !== false) {
            try {
                $vi = new self();
                if(isset($_SESSION[$k]['width'])) $vi-&gt;setWidth($_SESSION[$k]['width']);
                if(isset($_SESSION[$k]['height'])) $vi-&gt;setHeight($_SESSION[$k]['height']);
                if(isset($_SESSION[$k]['noise'])) $vi-&gt;setNoise($_SESSION[$k]['noise']);
                $vi-&gt;setRandomText($textLength);
                setcookie(&quot;swlibvimage&quot;, sha1(strtoupper($vi-&gt;getText())), time() + 30);
                $vi-&gt;sendImage();
                $key = strtolower(__CLASS__);
                if($saveInSession) {
                    $_SESSION[$key] = $vi-&gt;getText();
                } else if(isset($_SESSION[$key])) {
                    unset($_SESSION[$key]);
                }
            } catch(Exception $e) {
                // Send error image?
            }
        }
        exit();
    }

    /**
     * Hard-validates the text of a validation image that the user has entered.
     * There is no tolerance according to O&lt;--&gt;0 etc. The function uses cookies.
     * @param string $input
     * @return book
     */
    public static function verifyValidationImage($input) {
        if(!isset($_COOKIE['swlibvimage']) || empty($_COOKIE['swlibvimage'])) {
            Tracer::trace(&quot;No cookie set (swlibvimage)&quot;);
            return false;
        } else if($_COOKIE['swlibvimage'] == sha1(strtoupper(trim($input)))) {
            Tracer::trace(&quot;Cookie validation OK (swlibvimage)&quot;);
            return true;
        } else {
            $key = strtolower(__CLASS__);
            if(isset($_SESSION[$key])) {
                $vi = new self();
                $vi-&gt;setText($_SESSION[$key]);
                unset($_SESSION[$key]);
                return $vi-&gt;verifyInput($input);
                Tracer::trace(&quot;Session saved vcode is: $text&quot;);
            } else {
                Tracer::trace(&quot;No alternative session key saved ($key)&quot;);
            }
        }
        return false;
    }

    /**
     * Constructor
     * @param string $text
     */
    public function __construct($text='') {
        if(!empty($text)) $this-&gt;setText(trim($text));
    }

    /**
     * Returns the image width
     * @return int
     */
    public function getWidth() {
        return $this-&gt;width;
    }

    /**
     * Sets the image width
     * @param int $width
     */
    public function setWidth($width) {
        if(!is_numeric($width)) {
            throw new Exception(&quot;Invalid image width: '$width'&quot; );
        } else {
            $width = intval($width);
            if($width &lt; 10 || $width &gt; 500) $width = 0;
            $this-&gt;width = $width;
        }
    }

    /**
     * Returns the image height
     * @return int
     */
    public function getHeight() {
        return $this-&gt;height;
    }

    /**
     * Sets the image height
     * @param int $height
     */
    public function setHeight($height) {
        if(!is_numeric($height)) {
            throw new Exception(&quot;Invalid image height: '$height'&quot; );
        } else if(intval($height) &lt; 20) {
            throw new Exception(&quot;Invalid image height: '$height' (min width is 20)&quot; );
        } else {
            $this-&gt;height = intval($height);
        }
    }

    /**
     * Returns the image text
     * @return string
     */
    public function getText() {
        if(empty($this-&gt;text)) {
            $this-&gt;setRandomText();
        }
        return $this-&gt;text;
    }

    /**
     * Sets the text to display
     * @param string $text
     */
    public function setText($text) {
        if(!is_scalar($text)) {
            throw new Exception(&quot;Invalid image text (must be a scalar value)&quot;);
        } else if(empty($text) || strlen(trim($text)) == 0) {
            $this-&gt;text = '';
        } else {
            $this-&gt;text = strval($text);
        }
    }

    /**
     * Returns the noise factor
     * @return double
     */
    public function getNoise() {
        return $this-&gt;noise;
    }

    /**
     * Sets the noise factor
     * @param double $noise
     */
    public function setNoise($noise) {
        if(!is_numeric($noise)) {
            throw new Exception('Invalid image noise (not numeric)');
        } else if($noise &gt; 1 || $noise &lt; 0) {
            throw new Exception('Invalid image noise (must be between 0 and 1)');
        } else {
            $this-&gt;noise = $noise;
        }
    }

    /**
     * Sets a random text
     * @param int $size
     * @param string charset
     */
    public function setRandomText($size=8, $charset='abcdefghkmnpqrtwxyzABCDEFGHIJKLMNPRTUVWXYZ2346789') {
        $charset = preg_replace('/[\s]/', '', $charset);
        if(!is_numeric($size)) {
            throw new Exception('Invalid random text size (&quot;' . $size . '&quot;)');
        } else if(empty($charset)) {
            throw new Exception('Invalid character set for random text (empty string)');
        } else {
            $l = strlen($charset)-1;
            $o = '';
            while(strlen($o) &lt; $size) {
                $o .= substr($charset, rand(0, $l), 1);
            }
            $this-&gt;text = $o;
            return $o;
        }
    }

    /**
     * Generates the png image, returns the local file path
     * @return string
     */
    public function generateImage() {
        $yPositionRange = 2;
        $fontSize = $this-&gt;getHeight() - (2 * $yPositionRange);
        $charWidth = imagefontwidth($fontSize) + 3;
        $charHeight = imagefontheight($fontSize);
        $textWidth = $charWidth * strlen($this-&gt;getText());
        if($this-&gt;getWidth() == 0) $this-&gt;width = $textWidth + (2 * $yPositionRange);
        $this-&gt;width = 10 * ceil($this-&gt;width / 10);
        $noisePixels = $this-&gt;getWidth() * $this-&gt;getHeight() * $this-&gt;getNoise();
        $x0 = ceil(($this-&gt;getWidth() - $textWidth) / 2);
        $y0 = ceil(($this-&gt;getHeight() - $charHeight) / 2);

        $img = imagecreatetruecolor($this-&gt;getWidth(), $this-&gt;getHeight());
        imagefill($img, 0, 0, 0xffffff);

        // Noise
        $w = $this-&gt;getWidth()-1;
        $h = $this-&gt;getHeight()-1;
        for($i=0; $i&lt;$noisePixels; $i++) {
            $c = 0x000000;
            imagesetpixel($img, rand(0, $w), rand(0, $h), $c);
        }

        $text = $this-&gt;getText();
        for($i=0; $i&lt;strlen($text); $i++) {
            $ch = substr($text, $i, 1);
            imagestring($img, $fontSize, $x0+$i*$charWidth, $y0, $ch, 0x000000);
        }

        for($i=floor($noisePixels); $i&lt;$noisePixels; $i++) {
            $c = 0xffffff;
            imagesetpixel($img, rand(0, $w), rand(0, $h), $c);
        }

        $file = FileSystem::getTempFileName();
        imagepng($img, $file);
        imagedestroy($img);
        return $file;
    }

    /**
     * Sends an image as PNG, flushes the output buffer.
     */
    public function sendImage() {
        $file = $this-&gt;generateImage();
        if(!empty($file)) {
            header('Content-type: image/png');
            header('Content-Length: ' . FileSystem::getFileSize($file));
            header('Cache-Control:no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
            header('Pragma:no-cache');
            header('Connection:Close');
            Tracer::disable();
            OutputBuffer::purge();
            try { print FileSystem::readFile($file); } catch(Exception $e) { ; }
            try { FileSystem::delete($file);  } catch(Exception $e) { ; }
        }
    }

    /**
     * Checks the input against the $text instance variable. One spelling
     * error is accepted for characters like O&lt;--&gt;0 and S&lt;--&gt;5.
     * @param string $input
     * @return bool
     */
    public function verifyInput($input) {
        $input = strtoupper(trim($input, &quot; \n\r\t&quot;));
        $text  = strtoupper(trim($this-&gt;getText(), &quot; \n\r\t&quot;));
        if(empty($input)) {
            Tracer::trace('Verification failed: No input');
            return false;
        } else if(strlen($input) != strlen($text)) {
            Tracer::trace('Verification failed: Text lengths differ');
            return false;
        } else if($input == $text) {
            Tracer::trace('Verification OK: Texts are equal');
            return true;
        } else if(levenshtein($input, $text) &gt; 1) {
            Tracer::trace('Verification failed: More than one character difference');
            return false;
        } else {
            $t1 = $text;
            $t2 = $input;
            $t1 = str_replace(array('0','Q','5'), array('O','O','S'), $t1);
            $t2 = str_replace(array('0','Q','5'), array('O','O','S'), $t2);
            if($t1 == $t2) {
                Tracer::trace('Verification OK, after compensation of optical redundant characters');
                return true;
            } else {
                Tracer::trace('Verification failed: Even after correction of optical redundant characters');
                return false;
            }
        }
        return false;
    }

}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/user-verification-image-class/2010-09-01/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Version checking and publishing class</title>
		<link>http://www.atwillys.de/programming/php/version-checking-and-publishing-class/2010-08-24/</link>
		<comments>http://www.atwillys.de/programming/php/version-checking-and-publishing-class/2010-08-24/#comments</comments>
		<pubDate>Tue, 24 Aug 2010 09:37:05 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[versioning]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1034</guid>
		<description><![CDATA[Version checks and auto-updates become more and more important for PHP applications and CMS. The class presented in this article contains methods and static functions to parse and structure (Java-Style) documentation comments of class- and interface definitions. Furthermore, MD5 and &#8230; <a href="http://www.atwillys.de/programming/php/version-checking-and-publishing-class/2010-08-24/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>Version checks and auto-updates become more and more important for PHP applications and CMS. The class presented in this article contains methods and static functions to parse and structure (Java-Style) documentation comments of class- and interface definitions. Furthermore, MD5 and SHA1 checksums are built for all files, as well as the (comment/whitespace) stripped source codes. This allows to determine if an update is really necessary or if only changes in comments were made.<br />
These features are used in two static main functions: <code>Versioning::publishModule()</code> and <code>Versioning::checkModuleVersion()</code>. <code>Versioning::publishModule()</code> compresses a package/module/library (located in a specified folder) to a ZIP archive located in a publishing path. With this download-archive, other information files are saved in the target directory, such as checksums, the <code>version</code> file, the <code>readme</code> and <code>license</code> info file. E.g. the &#8220;repository&#8221; folder of the SwLibrary (URI <a href="/repository/swlib/" target="_blank">http://www.atwillys.de/repository/swlib/</a>) contains the following files:</p>
<ul>
<li><i>swlib.zip</i>:  Package contents (download file)</li>
<li><i>swlib.zip.md5</i>: MD5 checksum of the ZIP file</li>
<li><i>swlib.zip.sha1</i>: SHA1 checksum of the ZIP file</li>
<li><i>swlib.checksum</i>: Checksums of all recognized source codes in the package</li>
<li><i>swlib.version</i>: Copy of the file &#8220;version&#8221;</li>
<li><i>swlib.license</i>: Copy of the file &#8220;license&#8221;</li>
<li><i>swlib.readme</i>: Copy of the file &#8220;readme&#8221;</li>
<li><i>swlib.content</i>: Details the package contents in JSON format</li>
</ul>
<p>Other packages have other file names, respectively. These information are used by the function <code>Versioning::checkModuleVersion()</code>, which performs a remote (HTTP) lookup in a published repository to check for updates. First the package checksum is checked and compared with the checksum of the local package. If the checksum is different, then other files, such as the version file and the contents-JSON, are checked to determine if an update is &#8220;urgently&#8221; necessary or if only changes in documentation or white-spaces are made.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
    require_once(&quot;swlib/swlib.class.php&quot;);
    define('TRACER_DEFAULT_LEVEL', 5);
    swlib::start(array(
        'var_path' =&gt; sys_get_temp_dir(),
        'tmp_path' =&gt; sys_get_temp_dir(),
    ));
}

print '&lt;html&gt;&lt;body&gt;&lt;pre&gt;';

// Instantiate the object
$ver = new Versioning();

// Get all files in library path of the SwLibrary (we use the swlib as example,
// there are plenty of classes in this path :)
$files = FileSystem::find(swlib::getLibPath());
// ... But we use only the first three files - it's the same for all other files
$files = array($files[0], $files[1], $files[2]);

// Scan all the files that you can scan
$ver-&gt;readFiles($files, swlib::getLibPath());

// Print the results we obtained:
print 'Classes = ' . print_r($ver-&gt;getClasses(), true) . &quot;\n&quot;;
print 'SHA1 checksums = ' . print_r($ver-&gt;getFileSha1s(), true) . &quot;\n&quot;;
print 'MD5 checksums = ' . print_r($ver-&gt;getFileMd5s(), true) . &quot;\n&quot;;
print 'Stripped code checksums MD5) = ' . print_r($ver-&gt;getSourceCodeMd5s(), true) . &quot;\n&quot;;
print 'File details = ' . print_r($ver-&gt;getFileDetails(), true) . &quot;\n\n&lt;hr&gt;&lt;\n&gt;&quot;;

// Example for publishing a package:
Versioning::publishModule('swlib', swlib::getLibPath(), dirname(__FILE__) . '/swlib');

// Check a version of a package
print_r(Versioning::checkModuleVersion('swlib', swlib::getLibPath(), 'http://htx.my/htx/tmp'));

// This is the function provided in the swlib core class, which uses this class.
print_r(swlib::checkForUpdates());

print '&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;';
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
</div>
<pre class="brush: php; title: ; notranslate">
Classes = Array (
    [arrayfilter] =&gt; 1.0
    [cache] =&gt; 0.1
    [eexception] =&gt; 1.0
)

SHA1 checksums = Array (
    [ArrayFilter.class.php] =&gt; d313140fe1fd6cceccbe6e15185d999436cbcb49
    [Cache.class.php] =&gt; 0019328b74c32588bee35a8a0a2f837e95581f97
    [EException.class.php] =&gt; 5cd19b2abf98121ed59b15b934b5f458e2c0cbe6
)

MD5 checksums = Array (
    [ArrayFilter.class.php] =&gt; c1ea4599c43538fd84dbd7475a7a6789
    [Cache.class.php] =&gt; e532897b6b92e1ae6f89da12a3236790
    [EException.class.php] =&gt; 5ea9311e0cffb3fd1a0c6ffd941d8838
)

Stripped code checksums MD5) = Array (
    [ArrayFilter.class.php] =&gt; 49d70bd2637838221feb0a6f130f3508
    [Cache.class.php] =&gt; 8515aef2fda110d63aac3ec3c6e44924
    [EException.class.php] =&gt; 971e89b8a1811240cafecabe97f214f2
)

File details = Array (
    [ArrayFilter.class.php] =&gt; Array (
            [file_sha1] =&gt; d313140fe1fd6cceccbe6e15185d999436cbcb49
            [file_md5] =&gt; c1ea4599c43538fd84dbd7475a7a6789
            [source_md5] =&gt; 49d70bd2637838221feb0a6f130f3508
            [classes] =&gt; Array (
                    [arrayfilter] =&gt; Array (
                            1 =&gt; Provides static functions for simple array filtering and finding tasks.
                            [package] =&gt; de.atwillys.sw.php.swLib
                            [author] =&gt; Stefan Wilhelm
                            [copyright] =&gt; Stefan Wilhelm, 2007-2010
                            [license] =&gt; GPL
                            [version] =&gt; 1.0
                            [type] =&gt; class
                            [name] =&gt; ArrayFilter
                        )
                )
        )

    [Cache.class.php] =&gt; Array (
            [file_sha1] =&gt; 0019328b74c32588bee35a8a0a2f837e95581f97
            [file_md5] =&gt; e532897b6b92e1ae6f89da12a3236790
            [source_md5] =&gt; 8515aef2fda110d63aac3ec3c6e44924
            [classes] =&gt; Array (
                    [cache] =&gt; Array (
                            1 =&gt; Cache management class with GZ compression and uncompress caching of
contents and files. Package caches are stored in sub directories. Each
resource is defined by the SHA1 checksum.
NOTE: THIS CLASS IS BEING DEVELOPED
                            [package] =&gt; de.atwillys.sw.php.swLib
                            [author] =&gt; Stefan Wilhelm
                            [copyright] =&gt; Stefan Wilhelm, 2010
                            [license] =&gt; GPL
                            [version] =&gt; 0.1
                            [type] =&gt; class
                            [name] =&gt; Cache
                        )
                )
        )

    [EException.class.php] =&gt; Array (
            [file_sha1] =&gt; 5cd19b2abf98121ed59b15b934b5f458e2c0cbe6
            [file_md5] =&gt; 5ea9311e0cffb3fd1a0c6ffd941d8838
            [source_md5] =&gt; 971e89b8a1811240cafecabe97f214f2
            [classes] =&gt; Array(
                    [eexception] =&gt; Array(
                            1 =&gt; Implements global error, assertion and exception handling. Errors, warnings
and messages are categorized and either thrown as exception or only traced
(e.g. warnings, messagses). A global exception handler catches uncaught
exceptions, traces the details and prints a HTML error text (without details,
as a MySqlException('You have an error near SELECT * form users where password=...')
is nothing to be seen by the user. Assertions are only traced.
                            [package] =&gt; de.atwillys.sw.php.swLib
                            [author] =&gt; Stefan Wilhelm
                            [copyright] =&gt; Stefan Wilhelm, 2006-2010
                            [license] =&gt; GPL
                            [version] =&gt; 1.0
                            [uses] =&gt; Array (
                                    [exception] =&gt; Exception
                                    [(optional) tracer] =&gt; (optional) Tracer
                                )
                            [type] =&gt; class
                            [name] =&gt; EException
                            [extends] =&gt; Exception
                        )
                )
        )
)
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Library version checking and reporting class. Scans a package source
 * directory for files, builds SHA1 and MD5 checksums for of each file and
 * (if PHP) fetches additional information about documented classes and
 * interfaces. The static function publishModule() allows to zip a package
 * source directory and generate info files containing the checksums, details,
 * version, readme and license and publish all this in a reporitory folder
 * (which has to be writable). The function checkModuleVersion() performs a
 * HTTP lookup to a repository generated by the function publishModule(),
 * compares version, checksum and details and reports a recommendation if the
 * the package has to be updated or not.
 *
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 */
final class Versioning {

    /**
     * Contains the SHA1 checksum over all scanned PHP code files.
     * @var string
     */
    private $checksum = '';

    /**
     * Contains the versions classes and interfaces that were found in the files.
     * This array is an associative array, where the keys are the lower case
     * classes and the values the version strings of these classes/interfaces.
     * The versions are determined using the @version entry.
     * @var array
     */
    private $versions = array();

    /**
     * Contains the files that were checked
     * @var array
     */
    private $files = array();

    /**
     * Contains the exceptions
     * @var array
     */
    private $exceptions = array();

    /**
     * Constructor
     */
    public final function __construct() {
    }

    /**
     * Destructor
     */
    public final function __destruct() {
    }

    /**
     * Returns the files which were searched
     * @return array
     */
    public final function getFileDetails() {
        return $this-&gt;files;
    }

    /**
     * Returns the classes that were found in the searched files
     * @return array
     */
    public final function getClasses() {
        return $this-&gt;versions;
    }

    /**
     * Returns the SHA1 checksum of all scanned code files (*.php).
     * @return string
     */
    public final function getChecksum() {
        return $this-&gt;checksum;
    }

    /**
     * Returns the file-MD5 checksums of the scanned files as associative array,
     * where the keys are the file paths/sub-paths and the values the checksums.
     * @return array.
     */
    public final function getFileMd5s() {
        $cs = array();
        foreach($this-&gt;files as $file =&gt; $desc) {
            $cs[$file] = $desc['file_md5'];
        }
        return $cs;
    }

    /**
     * Returns the file-SHA1 checksums of the scanned files as associative array,
     * where the keys are the file paths/sub-paths and the values the checksums.
     * @return array.
     */
    public final function getFileSha1s() {
        $cs = array();
        foreach($this-&gt;files as $file =&gt; $desc) {
            $cs[$file] = $desc['file_sha1'];
        }
        return $cs;
    }

    /**
     * Returns the MD5 checksums of the whitespace-strupped codes of the scanned
     * files as associative array, where the keys are the file paths/sub-paths
     * and the values the checksums.
     * @return array.
     */
    public final function getSourceCodeMd5s() {
        $cs = array();
        foreach($this-&gt;files as $file =&gt; $desc) {
            $cs[$file] = $desc['source_md5'];
        }
        return $cs;
    }

    /**
     * Returns the information about a selected class.
     * @return array
     */
    public final function getClass($class) {
        return isset($this-&gt;versions[$class]) ? $this-&gt;versions[$class] : array();
    }

    /**
     * Returns information about classes and interfaces in an assoc. array.
     * The class names (lower case) are saved in the array keys, the array
     * values are the information about the class:
     * array(
     *  'type' =&gt;   Can be 'class' or 'interface'
     *  'name' =&gt;   The name of the class or interface
     *  &lt;OTHER&gt; =&gt;  Dynamic entries dependent on the @-comments (like @author or
     *              @final etc). These values are automatically extended from
     *              the class/interface definition line, e.g:
     *
     *                  final class B extends A implements I1, I2
     *
     *              would generate automatically without special comments:
     *
     *              array (
     *                  'type' =&gt; 'class'
     *                  'name' =&gt; 'B'
     *                  'final' =&gt; true
     *                  'extends' =&gt; 'A'
     *                  'implements' =&gt; array('i1'=&gt;'I1', 'i2'=&gt;'I2')
     *              )
     * )
     * @param string $source
     * @return array
     */
    public final function getPhpClassDocumentations($source) {
        // Conditionize
        $source = str_replace(array(&quot;\r&quot;, &quot;\t&quot;), array(&quot;\n&quot;, ' '), $source);
        while(strpos($source, &quot;\n\n&quot;) !== false) {
            $source = str_replace(&quot;\n\n&quot;, &quot;\n&quot;, $source);
        }

        // Parse the source
        $DOC_START = '/**';
        $DOC_END = '*/';
        $documented = array();
        $p0 = strpos($source, $DOC_START);
        while($p0 !== false) {
            $p1 = strpos($source, $DOC_END, $p0+1);
            if($p1 &lt;= $p0) break; // on not found
            $doc = substr($source, $p0+strlen($DOC_START), $p1-$p0-strlen($DOC_START)-strlen($DOC_END));
            $doc = trim(preg_replace('/[\n\s]*\*([^\n]*)[\n]?/i', &quot;$1\n&quot;, $doc), &quot;\n &quot;);
            $p2 = $p1 + strlen($DOC_END);
            while($p2 &lt; strlen($source) &amp;&amp; ctype_space(substr($source, $p2, 1))) $p2++;
            if($p2 &gt;= strlen($source)) break; // on end of text
            $p3 = strpos($source, &quot;\n&quot;, $p2+1);
            if($p3 &lt;= $p2) break; // on not found
            $ref = trim(substr($source, $p2, $p3-$p2+1), &quot;\n &quot;);
            $documented[] = array(
                'for' =&gt; $ref,
                'doc' =&gt; $doc
            );
            $p0 = strpos($source, $DOC_START, $p1+1);
        }

        // Filter information
        $docs = array();

        foreach($documented as $key =&gt; $entry) {
            $exp = array('text' =&gt; '');

            // Parse the comment
            $doc = explode(&quot;\n&quot;, str_replace(&quot;\r&quot;, &quot;\n&quot;, $entry['doc']));
            foreach($doc as $line) {
                $line = trim($line, &quot;\t\n\r &quot;);
                if(!empty($line)) {
                    if(strpos($line, '@') === 0) {
                        $line = explode(' ', $line, 2);
                        $docKey = trim(strtolower(reset($line)), '@ ');
                        $docValue = count($line) &gt; 1 ? trim(end($line)) : '';
                        switch($docKey) {
                            case 'uses':
                                if(!isset($exp[$docKey])) {
                                    $exp[$docKey] = array();
                                }
                                $exp[$docKey][strtolower($docValue)] = $docValue;
                                break;
                            case 'see':
                            case 'todo':
                                if(!isset($exp[$docKey])) {
                                    $exp[$docKey] = array();
                                }
                                $exp[$docKey][] = $docValue;
                                break;
                            default:
                                $exp[$docKey] = $docValue;
                        }

                    } else {
                        $exp['text'] .= $line . &quot;\n&quot;;
                    }
                }
            }
            $exp['text'] = trim($exp['text'], &quot;\n\r\t &quot;);

            // Parse the line
            $for = $entry['for'];
            if(strpos($for, '(') &gt; 1) {
                $for = substr($for, 0, strpos($for, '('));
            }
            $check = preg_split('/[\W]+/i', trim(strtolower($for), &quot; ;{&quot;), -1, PREG_SPLIT_NO_EMPTY);
            $for = preg_split('/[\W]+/i', trim($for, &quot; ;{&quot;), -1, PREG_SPLIT_NO_EMPTY);

            if(in_array('interface', $check)) {
                $exp['type'] = 'interface';
                $exp['name'] = end($for); // overwrite @name if specified
            } else if(in_array('class', $check)) {
                $exp['type'] = 'class';
                while(!empty($for)) {
                    $desc = strtolower(array_shift($for));
                    switch($desc) {
                        case 'abstract':
                        case 'final':
                            $exp[strtolower($desc)] = true;
                            break;
                        case 'class';
                            $exp['name'] = array_shift($for); // overwrite @name if specified
                            break;
                        case 'extends':
                            $exp['extends'] = array_shift($for);
                            break;
                        case 'implements':
                            $exp['implements'] = array();
                            while(!empty($for)) {
                                $desc = trim(array_shift($for), ' ,');
                                $exp['implements'][strtolower($desc)] = $desc;
                            }
                            break;
                        default:
                            Tracer::trace(&quot;Unrecognized descriptor: $desc&quot;);
                    }
                }

            }

            // Add
            if(isset($exp['name'])) {
                $docs[strtolower($exp['name'])] = $exp;
            }
        }
        return $docs;
    }

    /**
     * Searches the files for documented classes and extracts information from
     * the documentation. Undocumented classes will be ignored as they cannot
     * be explicitly versioned. The method generates md5 and sha1 checksums for
     * all file contents, where the documentations are ignored (as they do not
     * influence the functionality.
     * @param array $files
     * @param string $rootDirectory
     */
    public final function readFiles(array $files, $rootDirectory='') {
        $rootDirectory = trim(rtrim($rootDirectory, ' /'));
        $rootDirectory = rtrim($rootDirectory != '' ? $rootDirectory : $_SERVER['DOCUMENT_ROOT'], ' /') . '/';

        $this-&gt;files = array();
        $this-&gt;versions =  array();
        $this-&gt;exceptions = array();
        $this-&gt;checksum = '';

        foreach($files as $file) {
            $this-&gt;files[str_replace($rootDirectory, '', $file)] = array();
            $f = &amp;$this-&gt;files[str_replace($rootDirectory, '', $file)];

            if(FileSystem::isFile($file) &amp;&amp; FileSystem::isReadable($file)) {
                $f['file_sha1'] = sha1_file($file);
                $f['file_md5'] = md5_file($file);
                $f['source_md5'] = md5(@php_strip_whitespace($file));
                $f['classes'] = array();
                $this-&gt;checksum .= basename($file) . $f['file_sha1'];
                try {
                    if(FileSystem::getExtension($file) == 'php') {
                        $f['classes'] = $this-&gt;getPhpClassDocumentations(FileSystem::readFile($file));
                        foreach($f['classes'] as $class =&gt; $desc) {
                            $this-&gt;versions[$class] = isset($desc['version']) ? $desc['version'] : '';
                        }
                    }
                } catch(Exception $e) {
                    $this-&gt;exceptions[$file] = $e-&gt;getMessage();
                }
            } else if(!FileSystem::isFile($file)) {
                $this-&gt;exceptions[$file] = 'File does not exist';
            } else {
                $this-&gt;exceptions[$file] = 'File is not readable';
            }
        }
        $this-&gt;checksum = sha1($this-&gt;checksum);
    }

    /**
     * Returns information about version, readme, license, main checksum, file
     * checksums, and file and class details.
     * @param string $sourceDirectory
     * @return array
     */
    public static final function readLocalModuleVersion($sourceDirectory) {
        $sourceDirectory = trim(rtrim($sourceDirectory, ' /'));
        if(!FileSystem::isDirectory($sourceDirectory)) {
            throw new Exception('Source directory read does not exist: ' . $sourceDirectory);
        } else {
            $ver = new self();
            $ver-&gt;readFiles(FileSystem::find($sourceDirectory), $sourceDirectory);
            $return = array(
                'version' =&gt; '0.0',
                'readme' =&gt; '',
                'license' =&gt; '',
                'checksum' =&gt; $ver-&gt;getChecksum(),
                'details' =&gt; $ver-&gt;getFileDetails()
            );

            if(FileSystem::isFile(&quot;$sourceDirectory/version&quot;)) {
                $return['version'] = trim(FileSystem::readFile(&quot;$sourceDirectory/version&quot;), &quot;\n\r\t &quot;);
            } else if(FileSystem::isFile(&quot;$sourceDirectory/VERSION&quot;)) {
                $return['version'] = trim(FileSystem::readFile(&quot;$sourceDirectory/VERSION&quot;), &quot;\n\r\t &quot;);
            }
            if(FileSystem::isFile(&quot;$sourceDirectory/readme&quot;)) {
                $return['readme'] = trim(FileSystem::readFile(&quot;$sourceDirectory/readme&quot;), &quot;\n\r&quot;);
            } else if(FileSystem::isFile(&quot;$sourceDirectory/README&quot;)) {
                $return['readme'] = trim(FileSystem::readFile(&quot;$sourceDirectory/README&quot;), &quot;\n\r&quot;);
            }
            if(FileSystem::isFile(&quot;$sourceDirectory/license&quot;)) {
                $return['license'] = trim(FileSystem::readFile(&quot;$sourceDirectory/license&quot;), &quot;\n\r&quot;);
            } else if(FileSystem::isFile(&quot;$sourceDirectory/LICENSE&quot;)) {
                $return['license'] = trim(FileSystem::readFile(&quot;$sourceDirectory/LICENSE&quot;), &quot;\n\r&quot;);
            }
        }
        return $return;
    }

    /**
     * Creates a zip file containing all files in the source directory. The zip
     * has the name $moduleName.zip. Additionally
     *      -   a file $moduleName.version is created, which contains the main
     *          SHA1 checksum over all single files checksums
     *      -   a file $moduleName.content is written, which contains details
     *          about documented PHP classes in JSON format
     *      -   a file $moduleName.zip.sha1 is written, which contains the SHA1
     *          checksum of the zip file
     *      -   a file $moduleName.zip.md5 is written, which contains the MD5
     *          checksum of the zip file
     *
     * @param &lt;type&gt; $moduleName
     * @param &lt;type&gt; $sourceDirectory
     * @param &lt;type&gt; $publicDirectory
     */
    public static final function publishModule($moduleName, $sourceDirectory, $publicDirectory) {
        $sourceDirectory = trim(rtrim($sourceDirectory, ' /'));
        $publicDirectory = trim(rtrim($publicDirectory, ' /'));
        $moduleName = strtolower(trim($moduleName));
        if(empty($moduleName) || strpos($moduleName, '/') !== false || strpos($moduleName, ' ') !== false) {
            throw new Exception('Module name is invalid: &quot;' . $moduleName . '&quot;');
        } else if(empty($sourceDirectory)) {
            throw new Exception('No source directory specified to publish');
        } else if(!FileSystem::isDirectory($sourceDirectory)) {
            throw new Exception('Source directory to publish does not exist: ' . $sourceDirectory);
        } else if(empty($publicDirectory)) {
            throw new Exception('No public directory specified to publish');
        } else if(!FileSystem::isDirectory($publicDirectory)) {
            throw new Exception('No such publishing directory: ' . $publicDirectory);
        } else if(!FileSystem::isWritable($publicDirectory)) {
            throw new Exception('Directory to publish in is not writable: ' . $publicDirectory);
        } else if(strpos($publicDirectory, $_SERVER['DOCUMENT_ROOT']) === false) {
            throw new Exception('Directory to publish in is not a sub-directory of this web server htdocs: ' . $publicDirectory);
        } else {
            $libzip  = $publicDirectory . '/' . $moduleName . '.zip';
            $chksum  = $publicDirectory . '/' . $moduleName . '.checksum';
            $version = $publicDirectory . '/' . $moduleName . '.version';
            $content = $publicDirectory . '/' . $moduleName . '.content';
            $license = $publicDirectory . '/' . $moduleName . '.license';
            $readme  = $publicDirectory . '/' . $moduleName . '.readme';

            // Remove actual published files
            if(FileSystem::isFile($libzip)) FileSystem::delete($libzip);
            if(FileSystem::isFile($chksum)) FileSystem::delete($chksum);
            if(FileSystem::isFile($content)) FileSystem::delete($content);
            if(FileSystem::isFile($version)) FileSystem::delete($version);
            if(FileSystem::isFile($license)) FileSystem::delete($license);
            if(FileSystem::isFile($readme)) FileSystem::delete($readme);

            // Write source folder contents as zip, checksum file, details and
            // zip file verification checksums (for auto updater)
            $ver = new self();
            $ver-&gt;readFiles(FileSystem::find($sourceDirectory), $sourceDirectory);
            ZipFile::compress($sourceDirectory, $libzip);
            FileSystem::writeFile(&quot;$libzip.sha1&quot;, sha1_file($libzip));
            FileSystem::writeFile(&quot;$libzip.md5&quot;, md5_file($libzip));
            FileSystem::writeFile($chksum, $ver-&gt;getChecksum());
            FileSystem::writeFile($content, json_encode($ver-&gt;getFileDetails()));

            // Copy info file contents
            if(FileSystem::isFile(&quot;$sourceDirectory/version&quot;)) {
                FileSystem::writeFile($version, FileSystem::readFile(&quot;$sourceDirectory/version&quot;));
            } else if(FileSystem::isFile(&quot;$sourceDirectory/VERSION&quot;)) {
                FileSystem::writeFile($version, FileSystem::readFile(&quot;$sourceDirectory/VERSION&quot;));
            }
            if(FileSystem::isFile(&quot;$sourceDirectory/license&quot;)) {
                FileSystem::writeFile($license, FileSystem::readFile(&quot;$sourceDirectory/license&quot;));
            } else if(FileSystem::isFile(&quot;$sourceDirectory/LICENSE&quot;)) {
                FileSystem::writeFile($license, FileSystem::readFile(&quot;$sourceDirectory/LICENSE&quot;));
            }
            if(FileSystem::isFile(&quot;$sourceDirectory/readme&quot;)) {
                FileSystem::writeFile($readme, FileSystem::readFile(&quot;$sourceDirectory/readme&quot;));
            } else if(FileSystem::isFile(&quot;$sourceDirectory/README&quot;)) {
                FileSystem::writeFile($readme, FileSystem::readFile(&quot;$sourceDirectory/README&quot;));
            }
        }
    }

    /**
     * Compares the version of a specified module with the version information
     * files on a rempte server (&quot;repository&quot;) using HTTP lookup request. The
     * $sourceDirectory is the local folder containing the module source files,
     * the $referenceUri is the URL where the module is published (conform to the
     * Versioning::publishModule(...) method). Furthermore, a diff stat and
     * details about all documented files is generated, each for local and remote.
     * The following result types are possible:
     *
     *  (1) Versions are identical
     *  (2) Update available. (If new files are added at the remote server or only
     *      documentation/whitespace has changed, but not the algorighms
     *  (3) Update recommended. At least one file is different in the interpreted
     *      source code - which could be a security bug fix.
     *
     *  Results are presented in an associative array(
     *       'checked' =&gt; true if version was successfully checked
     *       'identical' =&gt; true if all files are identical
     *       'code_identical' =&gt; true if the local algorighms are up to date
     *       'text' =&gt; String representation of the result
     *       'diff' =&gt; Array containing all files (keys) information if they are
     *                 different
     *       'local' =&gt; array containing details about the local versions
     *       'remote' =&gt; array containing details about the remote versions
     *  )
     *
     * @param string $moduleName
     * @param string $sourceDirectory
     * @param string $referenceUri
     * @return array
     */
    public static final function checkModuleVersion($moduleName, $sourceDirectory, $referenceUri) {
        $moduleName = strtolower(trim($moduleName));
        $sourceDirectory = trim(rtrim($sourceDirectory, ' /'));
        $return = array(
            'checked' =&gt; false,
            'version_identical' =&gt; false,
            'update_recommended' =&gt; false,
            'update_available' =&gt; false,
            'new_features_available' =&gt; false,
            'local_untracked_features' =&gt; false,
            'identical' =&gt; false,
            'code_identical' =&gt; false,
            'text' =&gt; 'Checksum not checked: ',
            'diff' =&gt; array(),
            'local'  =&gt; array('checksum' =&gt; '', 'version' =&gt; '0.0', 'details' =&gt; array()),
            'remote' =&gt; array('checksum' =&gt; '', 'version' =&gt; '0.0', 'details' =&gt; array())
        );
        if(!FileSystem::isDirectory($sourceDirectory)) {
            throw new Exception(&quot;Local source directory does not exist: $sourceDirectory&quot;);
        } else if(empty($moduleName)) {
            throw new Exception(&quot;You did not specify a module name&quot;);
        } else {

            // Get the local statistics
            $return['local'] = self::readLocalModuleVersion($sourceDirectory);

            // Process checksum file
            $rq = new HttpRequest();
            if($rq-&gt;request(&quot;$referenceUri/$moduleName.checksum&quot;)-&gt;getResponseCode() != 200) {
                $return['text'] .= 'HTTP lookup failed: ' . &quot;$referenceUri/$moduleName.checksum&quot;;
            } else {
                $return['remote']['checksum'] = trim($rq-&gt;getOutput(), &quot;\n\r\t &quot;);
                if($return['remote']['checksum'] == $return['local']['checksum']) {
                    $return['checked'] = true;
                    $return['identical'] = true;
                    $return['code_identical'] = true;
                    $return['version_identical'] = true;
                    $return['text'] = 'Versions are identical';
                    $return['remote']['details'] = $return['local']['details'];
                    $return['remote']['version'] = $return['local']['version'];
                } else if($rq-&gt;request(&quot;$referenceUri/$moduleName.content&quot;)-&gt;getResponseCode() != 200) {
                    $return['checked'] = true;
                    $return['identical'] = false;
                    $return['code_identical'] = false;
                    $return['text'] .= 'HTTP lookup failed:&quot;' . &quot;$referenceUri/$moduleName.content&quot;;
                } else {
                    $return['checked'] = true;
                    $return['identical'] = false;
                    $return['code_identical'] = false;
                    $return['text'] = 'Update available';
                    $return['remote']['details'] = @json_decode($rq-&gt;getOutput(), true);
                }
                unset($ver, $rq);

                if($return['checked']) {
                    // Build diff
                    $fkeys = array_merge(array_keys($return['local']['details']), array_keys($return['remote']['details']));
                    $files = array();
                    foreach($fkeys as $f) { $files[strtolower($f)] = $f; }
                    unset($fkeys);

                    $local = array_change_key_case($return['local']['details'], CASE_LOWER);
                    $remote = array_change_key_case($return['remote']['details'], CASE_LOWER);
                    $return['text'] = 'No update required';

                    if(!empty($remote) &amp;&amp; !empty($local)) {
                        // Match local file against the remote files, shrink the
                        // arrays for each match
                        while(!empty($local)) {
                            reset($local);
                            $file = key($local);
                            $return['diff'][$files[$file]] = array(); // Use the original file name
                            $f = &amp;$return['diff'][$files[$file]];
                            $f['local'] = true;
                            if(!isset($remote[$file])) {
                                $return['local_untracked_features'] = true;
                                $f['remote'] = false;
                                $f['identical'] = false;
                                $f['code_identical'] = false;
                                $f['text'] = 'Only local';
                            } else if($remote[$file]['file_sha1'] == $local[$file]['file_sha1']) {
                                $f['remote'] = true;
                                $f['identical'] = true;
                                $f['code_identical'] = true;
                                $f['text'] = 'Files are identical';
                            } else if($remote[$file]['source_md5'] == $local[$file]['source_md5']) {
                                $return['update_available'] = true;
                                $f['remote'] = true;
                                $f['identical'] = false;
                                $f['code_identical'] = true;
                                $f['text'] = 'Source codes are identical, but docs/whitespaces different';
                            } else {
                                $return['update_available'] = true;
                                $return['update_recommended'] = true;
                                $f['remote'] = true;
                                $f['identical'] = false;
                                $f['code_identical'] = false;
                                $f['text'] = 'Files are different';
                            }
                            unset($f); // unlink reference
                            array_shift($local); // implicit next local
                            if(isset($remote[$file])) unset($remote[$file]);
                        }

                        // All other files are only remote (&quot;new files&quot;)
                        foreach($remote as $file =&gt; $desc) {
                            $return['update_available'] = true;
                            $return['new_features_available'] = true;
                            $return['diff'][$files[$file]] = array(
                                'local' =&gt; false,
                                'remote' =&gt; true,
                                'identical' =&gt; false,
                                'code_identical' =&gt; false,
                                'text' =&gt; 'Only remote'
                            );
                        }
                        unset($local, $remote, $f, $file, $desc);

                        // On updates check the remote version file
                        if($return['update_available']) {
                            // Get version file
                            $rq = new HttpRequest();
                            if($rq-&gt;request(&quot;$referenceUri/$moduleName.version&quot;)-&gt;getResponseCode() == 200) {
                                $return['remote']['version'] = trim($rq-&gt;getOutput(), &quot;\n\r\t &quot;);
                                if($return['remote']['version'] == $return['local']['version']) {
                                    $return['version_identical'] = true;
                                }
                            }
                        }

                        // Final text specification
                        if($return['update_recommended']) {
                            $return['text'] = &quot;Update recommended to version &quot; . $return['remote']['version'];
                        } else if($return['update_available']) {
                            $t = array();
                            if(!$return['code_identical']) {
                                $t[] = 'some changes in documentation/whitespace';
                            }
                            if($return['new_features_available']) {
                                $t[] = 'new features added';
                            }
                            if($return['local_untracked_features']) {
                                $t[] = 'features removed or you added stuff locally';
                            }
                            $t = implode(', ', $t);
                            if(!empty($t)) $t = &quot;($t)&quot;;
                            $return['text'] = trim(&quot;Update available (remote version {$return['remote']['version']}), $t&quot;);
                        } else if($return['local_untracked_features']) {
                            $return['text'] = &quot;No update required (Version is {$return['remote']['version']}, only a remote file was removed or you added a file locally)&quot;;
                        } else if($return['identical']) {
                            $return['text'] = &quot;No update available (Version is {$return['local']['version']}. The modules are absolutely identical)&quot;;
                        } else {
                            $return['text'] = &quot;No update available (Version is {$return['local']['version']})&quot;;
                        }
                    }
                }
            }
        }
        return $return;
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/version-checking-and-publishing-class/2010-08-24/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>HTTP request class</title>
		<link>http://www.atwillys.de/programming/php/http-request-class/2010-08-24/</link>
		<comments>http://www.atwillys.de/programming/php/http-request-class/2010-08-24/#comments</comments>
		<pubDate>Tue, 24 Aug 2010 07:59:31 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[http]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=1023</guid>
		<description><![CDATA[This is a simple class that sends a HTTP request to web server and fetches the response headers and the content of the response. You can decide if exceptions shall be thrown if the server responds with an error message &#8230; <a href="http://www.atwillys.de/programming/php/http-request-class/2010-08-24/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>This is a simple class that sends a HTTP request to web server and fetches the response headers and the content of the response. You can decide if exceptions shall be thrown if the server responds with an error message (e.g. 404). The sample code explains more than a thousand words:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
// Before doing anything other, we include the library
include_once('swlib/swlib.class.php');

// The class source code automatically loads (requires) EException.class.php,
// Tracer.class.php, OutputBuffer.class.php and Session.class.php. These
// classes must be in the same directory.

// Start the library with configuration
swlib::start(array(
    // use output buffering, automatically start (this is default)
    'use_ob' =&gt; true,
    // use session, automatically start (this is default)
    'use_session' =&gt; true,
    // path to library var directory, we use an one one
    // The var path must be writable for the server, it is NOT the
    // directory &quot;/var&quot; on Unix-Like systems, but a directory to serialize
    // &quot;variables&quot; in.
    'var_path' =&gt; sys_get_temp_dir(),
    // path to library tmp directory, we use an one one
    // The tmp path must be writable for the server
    'tmp_path' =&gt; sys_get_temp_dir(),
    // extensions for variable export/import files (this is default)
    'var_extension' =&gt; '.tmp',
    // The include paths to search for classes
    'include_paths' =&gt; array()
));

Tracer::setLevel(5);

print '&lt;html&gt;&lt;body&gt;&lt;pre&gt;';

// Create a request object
$rq = new HttpRequest();

// Set the timeout to 5 seconds, default is 10 seconds
$rq-&gt;setTimeout(5);

// Set the port (80 is already the default port)
$rq-&gt;setPort(80);

// We want an exceptino on 404 etc.
$rq-&gt;setThrowExceptionOnErrorResponse(true);

try {
    // Let's get a page, in this case the version file of the library
    $rq-&gt;request('http://www.atwillys.de/repository/swlib/swlib.version');

    // This would be the check if exceptions are switched off.
    if(!$rq-&gt;isResponseOk()) {
        throw new Exception(&quot;...&quot;);
    }

    // Print the headers
    print &quot;&lt;b&gt;Headers&lt;/b&gt;:\n&quot;;
    foreach($rq-&gt;getResponseHeaders() as $header =&gt; $value) {
        print &quot;   &lt;i&gt;$header&lt;/i&gt;=$value\n&quot;;
    }

    // Print the content output:
    print &quot;&lt;b&gt;Content:&lt;/b&gt;\n&quot;;
    print htmlspecialchars($rq-&gt;getOutput());

} catch(Exception $e) {
    print $e-&gt;getMessage();
}

print '&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;';
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
</div>
<pre class="brush: php; title: ; notranslate">
Headers:
   date=Tue, 24 Aug 2010 08:54:46 GMT
   server=Apache
   last-modified=Fri, 20 Aug 2010 10:39:21 GMT
   etag=&quot;2861c7a0-3-48e3ee9601b18&quot;
   accept-ranges=bytes
   content-length=3
   connection=close
   content-type=text/plain; charset=UTF-8

Content:
1.0
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Http socket request wrapper
 * @package de.atwillys.sw.php.swLib
 * @class HttpRequest
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2008-2010
 * @license GPL
 * @version 1.0
 */
class HttpRequest {

    /**
     * The port to connect to
     * @var int
     */
    private $port = 80;

    /**
     * The URL to connect to
     * @var string
     */
    private $uri = array();

    /**
     * Connection timeout in seconds
     * @var int
     */
    private $timeout = 10;

    /**
     * The request sent to the server
     * @var string
     */
    public $request = '';

    /**
     * The request headers, will be sent with
     * the request. Format: $requestHeaders['key'] = 'value'
     * will result in the header line &quot;key: value&quot; (+CRLF)
     * @var array
     */
    private $requestHeaders = array();

    /**
     * The response without the response headers
     * @var string
     */
    private $output = '';

    /**
     * Response code (200 is OK etc ...)
     * @var int
     */
    private $responseCode = 0;

    /**
     * Contains the response headers as assoc
     * array.
     * @var array
     */
    private $responseHeaders = array();

    /**
     * Throw an exception if server response is 404 or the like
     * @var bool
     */
    private $throwExceptionOnErrorResponse = false;

    /**
     * Returns the port to connect to
     * @return int
     */
    public function getPort() {
        return $this-&gt;port;
    }

    /**
     * Sets the port to connect to
     * @param int $port
     */
    public function setPort($port) {
        if(!settype($port, 'integer')) {
            throw new Exception('Port no integer: &quot;' . $port . '&quot;');
        } else {
            $this-&gt;port = $port;
        }
    }

    /**
     * Returns the URL to connect to
     * @return string
     */
    public function getUri() {
        return $this-&gt;uri;
    }

    /**
     * Sets the URL to connect to
     * @param string $uri
     */
    public function setUri($uri) {
        $uri = trim($uri);
        if(empty($uri)) {
            throw new Exception('URI is empty');
        } else {
            $this-&gt;uri = parse_url($uri);
            if($this-&gt;uri['path'] == '') {
                $this-&gt;uri['path'] = '/';
            }
        }
    }

    /**
     * Returns the connection and stream timeout in seconds
     * @return int
     */
    public function getTimeout() {
        return $this-&gt;timeout;
    }

    /**
     * Sets the connection and stream timeout in seconds
     * @param int $seconds
     */
    public function setTimeout($seconds) {
        if(!settype($seconds, 'integer')) {
            throw new Exception('Timeout no integer: &quot;' . $seconds . '&quot;');
        } else {
            $this-&gt;timeout = $seconds &gt; 0 ? $seconds : 0;
        }
    }

    /**
     * Set if an exception shall be thrown by request() if the server
     * responds with an error message, such as 404
     * @param bool $throw
     */
    public function setThrowExceptionOnErrorResponse($throw) {
        $this-&gt;throwExceptionOnErrorResponse = (bool) $throw;
    }

    /**
     * Returns if an exception shall be thrown by request() if the server
     * responds with an error message, such as 404
     * @return bool
     */
    public function getThrowExceptionOnErrorResponse() {
        return $this-&gt;throwExceptionOnErrorResponse;
    }

    /**
     * Returns the response without headers
     * @return string
     */
    public function getOutput() {
        return $this-&gt;output;
    }

    /**
     * Returns an associative array containing the
     * response headers.
     * @return array
     */
    public function getResponseHeaders() {
        return $this-&gt;responseHeaders;
    }

    /**
     * Returns the server response code.
     * (200=OK ...)
     * @return int
     */
    public function getResponseCode() {
        return $this-&gt;responseCode;
    }

    /**
     * Returns if the request was successful
     * @return bool
     */
    public function isResponseOk() {
       return floor($this-&gt;responseCode / 100) == 2 || in_array($this-&gt;responseCode, array(302, 307));
    }

    /**
     * Start the request, parse the response.
     * You can also specify the URL by using
     * setURI(...) instead of using the optional
     * parameter $uri.
     * @param string $uri
     * @param int $port
     */
    public function request($uri=null, $port=null) {
        $this-&gt;output = '';
        $this-&gt;responseHeaders = array();
        $this-&gt;responseCode = 0;
        $this-&gt;request = '';
        if(!empty($uri)) $this-&gt;setURI($uri);
        if(!empty($port)) $this-&gt;setPort($port);
        $uri = $this-&gt;uri;
        $errno = 0;
        $errstr = '';
        $socket = fsockopen($uri['host'], $this-&gt;port, $errno, $errstr, 5);
        if(!$socket) {
            throw new Exception('Failed to connect to server ' . $uri['host'] . ':' . $errstr);
        } else {
            $this-&gt;requestHeaders['Host'] = $uri['host'];
            $this-&gt;requestHeaders['Connection'] = 'close';
            $this-&gt;request = 'GET ' . $uri['path'] .
                (!empty($uri['query']) ? ('?' . $uri['query']) : '' ) .
                (!empty($uri['fragment']) ? ('#' . $uri['fragment']) : '' ) .
                &quot; HTTP/1.1&quot;;
            $request = $this-&gt;request . &quot;\r\n&quot;;
            foreach($this-&gt;requestHeaders as $key =&gt; $value) {
               $request .= $key . ': ' . $value . &quot;\r\n&quot;;
            }
            $request .= &quot;\r\n&quot;;
            stream_set_timeout($socket, $this-&gt;timeout);
            fputs($socket, $request);
            while(!feof($socket)) {
                $this-&gt;output .= fgets($socket, 4096);
            }
            fclose($socket);

            if($this-&gt;output != '') {
                $header = explode(&quot;\r\n\r\n&quot;, $this-&gt;output, 2);
                $this-&gt;output = end($header);
                $header = array_filter(explode(&quot;\n&quot;, str_replace(&quot;\r&quot;,&quot;\n&quot;, reset($header))));
                if(count($header) &gt; 0) {
                    $this-&gt;responseCode = explode(' ', array_shift($header));
                    $responseCodeMessage = array_pop($this-&gt;responseCode);
                    $this-&gt;responseCode = intval(array_pop($this-&gt;responseCode));
                    foreach($header as $rc) {
                        $rc = explode(':', $rc, 2);
                        if(count($rc) == 2) {
                            $this-&gt;responseHeaders[strtolower(trim($rc[0]))] = trim($rc[1]);
                        } else {
                            $this-&gt;responseHeaders[strtolower(trim($rc[0]))] = '';
                        }
                    }
                    if(isset($this-&gt;responseHeaders['transfer-encoding']) &amp;&amp; $this-&gt;responseHeaders['transfer-encoding'] == 'chunked') {
                        if(strlen( ltrim($this-&gt;output)) &gt; 0) {
                            $nl = &quot;\r\n&quot;;
                            $t = $this-&gt;output;
                            $this-&gt;output = '';
                            do {
                                $t = ltrim($t);
                                $p = strpos($t, $nl);
                                if($p === false) {
                                    throw new Exception('Chunked HTTP response content invalid.');
                                } else {
                                    $l = hexdec(substr($t, 0, $p));
                                    if(!is_numeric($l) || $l &lt; 0) {
                                        throw new Exception('Chunked HTTP response content invalid.');
                                    } else {
                                        $this-&gt;output .= substr($t, ($p + strlen($nl)), $l);
                                        $t  = substr($t, ($l + $p + strlen($nl)));
                                    }
                                }
                            }
                            while(strlen(trim($t)) &gt; 0);
                            unset($t);
                        }
                    }
                }
            }
        }

        if($this-&gt;throwExceptionOnErrorResponse &amp;&amp; !$this-&gt;isResponseOk()) {
            throw new Exception(&quot;HTTP request failed: $responseCodeMessage&quot;, $this-&gt;responseCode);
        }
        return $this;
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/http-request-class/2010-08-24/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL administration class</title>
		<link>http://www.atwillys.de/programming/php/mysql-administration-class/2010-08-05/</link>
		<comments>http://www.atwillys.de/programming/php/mysql-administration-class/2010-08-05/#comments</comments>
		<pubDate>Thu, 05 Aug 2010 16:15:56 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[MySql]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=786</guid>
		<description><![CDATA[I implemented this class for administrative MySQL tasks. It is based on MySql and provides additional functionalities like retrieving CREATE and DROP queries for existing tables or the database. These functions are used by to &#8220;main methods&#8221;: backup() and restore(). &#8230; <a href="http://www.atwillys.de/programming/php/mysql-administration-class/2010-08-05/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>I implemented this class for administrative MySQL tasks. It is based on <code>MySql</code> and provides additional functionalities like retrieving CREATE and DROP queries for existing tables or the database. These functions are used by to &#8220;main methods&#8221;: <code>backup()</code> and <code>restore()</code>. <code>backup()</code> writes the whole database (or only a part of it) in a zip file, including the creation queries and a backup info file. <code>restore()</code> can read and interpret these files and regenerates the database structure including the contents. The table backup/restore works with direct file I/O of the MySQL server, so if you have a webspace that has a distributed system of web server and MySQL server, then the web server might not be able to access the generated files (as they are not stored in the local file system of the web server). Simply check it if it works (maybe I will have published a modified version of this class soon to address this possible issue).</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
$dba = new MySqlAdministration('test', 'root', '', 'localhost');

// Get the name of the database
$r = $dba-&gt;getDatabaseName();
print '$db-&gt;getDatabaseName() = ' . print_r($r, true) . '&lt;br/&gt;';

// This would be the query to delete the database
$r = $dba-&gt;getDatabaseDropQuery('test');
print '$db-&gt;getDatabaseDropQuery() = ' . print_r($r, true) . '&lt;br/&gt;';

// This would be the query to delete the table &quot;test&quot;
$r = $dba-&gt;getTableDropQuery('test');
print '$db-&gt;getTableDropQuery(...) = ' . print_r($r, true) . '&lt;br/&gt;';

// This would be the query to create the database
$r = $dba-&gt;getDatabaseCreationQuery();
print '$db-&gt;getDatabaseCreationQuery() = ' . print_r($r, true) . '&lt;br/&gt;';

// This would be the query to create all the tables (structure) as they are now
$r = $dba-&gt;getTableCreationQuery();
print '$db-&gt;getTableCreationQuery() = ' . print_r($r, true) . '&lt;br/&gt;';

// This would be the query to create the table 'test'
$r = $dba-&gt;getTableCreationQuery('test');
print '$db-&gt;getTableCreationQuery(&quot;test&quot;) = ' . print_r($r, true) . '&lt;br/&gt;';

// Lock the table, so that we can query multiple requests
$readlock = array('test');
$writelock = array('test2');
$dba-&gt;lock($readlock, $writelock);
// ... Here we do some stuff ...
// Unlock the table, this will be automatically done on destruction
$dba-&gt;unlock();

// Get table information (this is a method derived from MySql)
$r = $dba-&gt;getTableStatus('test');
print '$db-&gt;getTableStatus(...) = ' . print_r($r, true) . '&lt;br/&gt;';

// Make a backup of the database in a zip file
$tablePrefix = '';  // We don't have a table prefix here
$tables = null;     // null or array() means ALL tables
$comments = 'This is my backup BLA BLA ...'; // A comment for the info file
$dropTablesBeforeInsert = true; // We drop the tables before we recreate them
$dropAndRecreateDatabase = false; // We do not drop the whole database

$r = $dba-&gt;backup($tablePrefix, $tables, $comments, $dropTablesBeforeInsert, $dropAndRecreateDatabase);
print '$db-&gt;backup(...) = ' . print_r($r, true) . '&lt;br/&gt;';

// This is the file you can download then
$zipArchive = $r['zipfile']; 

// Restore the database:
$tablePrefix = '';  // We don't change the table prefix, but we could
$r = $dba-&gt;restore($zipArchive, $tablePrefix);
print '$db-&gt;restore(...) = ' . print_r($r, true) . '&lt;br/&gt;';

// Delete the zip file ...
FileSystem::delete($zipArchive);
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
</div>
<pre class="brush: php; title: ; notranslate">

$db-&gt;getDatabaseName() = test

$db-&gt;getDatabaseDropQuery() = DROP DATABASE IF EXISTS `test`;

$db-&gt;getTableDropQuery(...) = DROP TABLE IF EXISTS `test`;

$db-&gt;getDatabaseCreationQuery() = CREATE DATABASE IF NOT EXISTS `test` /*!40100 DEFAULT CHARACTER SET utf8 */;

$db-&gt;getTableCreationQuery() = CREATE TABLE IF NOT EXISTS `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rid` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;

CREATE TABLE IF NOT EXISTS `test2` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'The primary key',
  `name` varchar(128) NOT NULL COMMENT 'Name of the person',
  `email` text NOT NULL COMMENT 'email address of the person',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COMMENT='An example table for emails';

$db-&gt;getTableCreationQuery(&quot;test&quot;) = CREATE TABLE IF NOT EXISTS `test` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rid` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;

$db-&gt;getTableStatus(...) = Array (
    {test} =&gt; Array (
        {name} =&gt; test
        {engine} =&gt; MyISAM
        {version} =&gt; 10
        {row_format} =&gt; Fixed
        {rows} =&gt; 0
        {avg_row_length} =&gt; 0
        {data_length} =&gt; 0
        {max_data_length} =&gt; 2533274790395903
        {index_length} =&gt; 1024
        {data_free} =&gt; 0
        {auto_increment} =&gt; 30
        {create_time} =&gt; 2010-08-05 16:34:55
        {update_time} =&gt; 2010-08-05 16:34:55
        {check_time} =&gt;
        {collation} =&gt; utf8_general_ci
        {checksum} =&gt;
        {create_options} =&gt;
        {comment} =&gt;
    )
    {test2} =&gt; Array (
        {name} =&gt; test2
        {engine} =&gt; MyISAM
        {version} =&gt; 10
        {row_format} =&gt; Dynamic
        {rows} =&gt; 0
        {avg_row_length} =&gt; 0
        {data_length} =&gt; 0
        {max_data_length} =&gt; 281474976710655
        {index_length} =&gt; 2048
        {data_free} =&gt; 0
        {auto_increment} =&gt; 1
        {create_time} =&gt; 2010-08-05 16:34:55
        {update_time} =&gt; 2010-08-05 16:34:55
        {check_time} =&gt; 2010-08-05 16:34:55
        {collation} =&gt; utf8_general_ci
        {checksum} =&gt;
        {create_options} =&gt;
        {comment} =&gt; An example table for emails
    )
)

$db-&gt;backup(...) = Array (
    {zipfile} =&gt; /Applications/XAMPP/xamppfiles/temp/2010-8-5-MySqlBackup.zip
    {backupinfo} =&gt; /mysqlbackup.test.1281022607.create.sql
    {comments} =&gt; This is my backup BLA BLA ...
    {time} =&gt; 2010-08-05 15:36:47 GMT
    {database} =&gt; test
    {prefix} =&gt;
    {create} =&gt; /mysqlbackup.test.1281022607.create.sql
    {tables} =&gt; Array (
        {test} =&gt; Array (
            {name} =&gt; test
            {file} =&gt; /mysqlbackup.test.1281022607.test.tdata
        )
        {test2} =&gt; Array (
            {name} =&gt; test2
            {file} =&gt; /mysqlbackup.test.1281022607.test2.tdata
        )
    )
)

$db-&gt;restore(...) =
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * MySQL backup / restore and administrative tools. Backups the whole database
 * (or only a part) into a ZIP file, including an info file. The restore function
 * accepts the ZIP file and is able to restore the database (with table creations).
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2009-2010
 * @license GPL
 * @version 1.0
 * @uses Tracer
 * @uses MySql
 * @uses MySqlException
 * @uses FileSystem
 * @uses ZipFile
 */
class MySqlAdministration extends MySql {

    /**
     * MySQL wrapper class constructor
     * @param string $database
     * @param string $user
     * @param string $password
     * @param string $server
     */
    public final function __construct($database='', $user='', $password='', $server='') {
        parent::__construct($database, $user, $password, $server);
    }

    /**
     * Locks the specified tables (array with the table names)
     * in the database. If both read and write lock is not specified,
     * all tables in the database will be write locked.
     * CAUTION: USE unlock() method when you leave the critical section.
     * @param array $readLockTables
     * @param array $writeLockTables
     */
    public final function lock($readLockTables=null, $writeLockTables=null) {
        if(empty($readLockTables) &amp;&amp; empty($readLockTables)) {
            // No parameters: write lock all
            $writeLockTables = $this-&gt;getTableNames();
        } else if((!is_array($readLockTables) &amp;&amp; !empty($readLockTables) ) || ( !is_array($writeLockTables)) &amp;&amp; !empty($writeLockTables) ) {
            // No array and not null, array(), false: invalid.
            throw new MySqlException('The arguments for the tables to lock is incorrect.');
        }

        // conditionize
        if(!empty($readLockTables)) {
            foreach($readLockTables as $key =&gt; $table) {
                $readLockTables[$key] = !in_array($table, $writeLockTables) ? self::escape(trim($table)) : false;
            }
            $readLockTables = array_filter($readLockTables);
            $readLockTables = !empty($readLockTables) ? (implode(' READ,', $readLockTables) . ' READ') : '';
        } else {
            $readLockTables = '';
        }
        if(!empty($writeLockTables)) {
            foreach($writeLockTables as $key =&gt; $table) {
                $writeLockTables[$key] = self::escape(trim($table));
            }
            $writeLockTables = '' . implode(' WRITE,', $writeLockTables) . ' WRITE';
        } else {
            $writeLockTables = '';
        }

        self::trace(&quot;Lock tables&quot;);
        $this-&gt;query('LOCK TABLES ' . $readLockTables . (($readLockTables!='' &amp;&amp; $writeLockTables!='') ? ',' : '') . $writeLockTables);
    }

    /**
     * Unlocks all table locks
     */
    public final function unlock() {
        self::trace('Unlock all tables');
        try {
            $this-&gt;query(&quot;UNLOCK TABLES&quot;);
        } catch(MySqlException $e) {
            self::trace('Falied to unlock tables');
        }
    }

    /**
     * Returns the SQL query for creating the database in the same
     * manner as the actual one is. The query always includes an
     * &quot;IF NOT EXISTS&quot; clause.
     * @return string
     */
    public final function getDatabaseCreationQuery() {
       $r = reset($this-&gt;query(&quot;SHOW CREATE DATABASE `&quot; . $this-&gt;getDatabaseName() . &quot;`&quot; ));
       $r = array_change_key_case($r, CASE_LOWER);
       $r = explode(&quot;\n&quot;, $r['create database'] . ';', 2);
       $r[0] = str_ireplace('CREATE DATABASE', 'CREATE DATABASE IF NOT EXISTS', $r[0]);
       $r = implode(&quot;\n&quot;, $r);
       return $r;
    }

    /**
     * Returns the SQL query for creating a table that is identical
     * to the existing table. If no table is specified, the method
     * generates a creation query for alltables in the database.
     * @param string $table
     * @param bool $ifNotExists
     * @return string
     */
    public final function getTableCreationQuery($table=null, $ifNotExists=true) {
       if(is_null($table)) {
           $tables = $this-&gt;getTableNames();
       } else {
           $tables = array(self::escape($table));
       }

       $queries = array();
       foreach($tables as $table) {
           $r = reset($this-&gt;query(&quot;SHOW CREATE TABLE `&quot; . $table . &quot;`&quot; ));
           $r = array_change_key_case($r, CASE_LOWER);
           $r = $r['create table'] . ';';
           if($ifNotExists) {
               $r = explode(&quot;\n&quot;, $r, 2);
               $r[0] = str_ireplace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS', $r[0]);
               $r = implode(&quot;\n&quot;, $r);
           }
           $queries[] = $r;
           $queries[] = '';
       }

       return implode(&quot;\n&quot;, $queries);
    }

    /**
     * Returns the SQL query for removing a table from database
     * @param string $table
     * @return string
     */
    public function getTableDropQuery($table) {
        return &quot;DROP TABLE IF EXISTS `&quot; . self::escape($table) . &quot;`;&quot;;
    }

    /**
     * Returns the SQL query for deleting the whole database
     * @return string
     */
    public function getDatabaseDropQuery() {
        return &quot;DROP DATABASE IF EXISTS `&quot; . self::escape($this-&gt;getDatabaseName()) . &quot;`;&quot;;
    }

    /**
     * Backups the table structure as SQL file and all tables as query output,
     * if the $tablePrefix (if spexified) matches the begin of the table name
     * (case insensitive). Returns an array containing information about the
     * backup files of all tables, the prefixes, and the table names without
     * prefix, as well as a boolean flag that indicated weather the table was
     * backuped or not.
     * @param string $tablePrefix
     * @param array $tables
     * @param string $comments
     * @param bool $dropTablesBeforeInsert
     * @param bool $dropAndRecreateDatabase
     * @return array
     */
    public final function backup($tablePrefix='', $tables=null, $comments='', $dropTablesBeforeInsert=true, $dropAndRecreateDatabase=false) {
        // Conditionize and check arguments
        if(empty($tables)) {
            $tables = $this-&gt;getTableNames();
        } else if(!is_array($tables)) {
            throw new MySqlException('If specified, then the tables (names) to backup must be pass as array');
        }

        if(!settype($comments, 'string')) {
            throw new MySqlException('Comment must be a string');
        }

        $date = new UtcDate();
        $backupFolder = FileSystem::getTempDirectory() . &quot;/mysqlbackup&quot;;

        $return = array(
            'zipfile' =&gt; '',
            'backupinfo' =&gt; '',
            'comments' =&gt; $comments,
            'time' =&gt; $date-&gt;toString(),
            'database' =&gt; $this-&gt;getDatabaseName(),
            'prefix' =&gt; $tablePrefix,
            'create' =&gt; '',
            'tables' =&gt; array()
        );

        // Delete backup folder to have a new folder with proper write conditions
        if(FileSystem::exists($backupFolder)) {
            FileSystem::delete($backupFolder) ;
        }
        FileSystem::mkdir($backupFolder);
        $filebase =  $backupFolder . '/mysqlbackup.' . $this-&gt;getDatabaseName() . '.' . $date-&gt;getTimeStamp() . '.';

        // Main backup in critical section surrounded by lock(), try {} catch() {}, unlock()
        $ee = null;
        {
            // Lock all tables READ/WRITE
            $structureData = '';
            $this-&gt;lock();
            try {
                // Backup structure
                if($dropAndRecreateDatabase) {
                    $structureData .= str_ireplace('`' . $this-&gt;getDatabaseName() . '`', '&lt;&lt;DATABASE&gt;&gt;', $this-&gt;getDatabaseDropQuery()) . &quot;\n\n&quot;;
                }
                $structureData .=  str_ireplace('`' . $this-&gt;getDatabaseName() . '`','&lt;&lt;DATABASE&gt;&gt;', $this-&gt;getDatabaseCreationQuery()) . &quot;\n\n&quot;;

                // Iterate tables
                foreach($tables as $table) {
                    $table = trim($table);
                    if(empty($tablePrefix) || stripos($table, $tablePrefix) === 0) {
                        $name = empty($tablePrefix) ? $table : str_ireplace($tablePrefix, '', $table);
                        $file =  $filebase . $name . '.tdata';
                        $this-&gt;query(&quot;SELECT * FROM `&quot; . self::escape($table) . &quot;` INTO OUTFILE '$file'&quot;);
                        // Save data for further usage
                        $return['tables'][$name] = array(
                            'name' =&gt; $name,
                            'file' =&gt; str_ireplace($backupFolder, '', $file)
                        );

                        // If desired, drop original table
                        if($dropTablesBeforeInsert) {
                            $dt = $this-&gt;getTableDropQuery($table);
                            if(!empty($tablePrefix)) {
                                $dt = str_replace($table, '&lt;&lt;PREFIX&gt;&gt;'. $name, $dt);
                            }
                            $structureData .= $dt . &quot;\n\n&quot;;
                            unset($dt);
                        }

                        // Create table:
                        $ct = $this-&gt;getTableCreationQuery($table);
                        if(!empty($tablePrefix)) {
                            $ct = explode(&quot;\n&quot;, $ct, 2);
                            $ct[0] = str_replace($table, '&lt;&lt;PREFIX&gt;&gt;'. $name, $ct[0]);
                            $ct = implode(&quot;\n&quot;, $ct);
                        }
                        $structureData .=  $ct . &quot;\n\n&quot;;
                        unset($ct);
                    }

                    // Export structure to file
                    $file = $filebase . &quot;create.sql&quot;;
                    if(@file_put_contents($file, $structureData) === false) {
                        throw new Exception('Could not write structure file');
                    } else {
                        $return['create'] = str_ireplace($backupFolder, '', $file);;
                    }
                }

                // Generate backup info
                $return['backupinfo'] = str_ireplace($backupFolder, '', $file);
                $backupInfo = json_encode($return);
                $file = $filebase . &quot;backup.json&quot;;
                if(@file_put_contents($file, $backupInfo) === false) {
                    throw new Exception('Could not write backup info file');
                }
            } catch(Exception $e) {
                Tracer::traceException($e);
                $ee = $e;
            }
            // unlock the tables
            $this-&gt;unlock();
        }

        // Exception handling after unlock()
        if(!is_null($ee)) {
            try {
                FileSystem::delete($backupFolder);
            } catch(Exception $e) {
            }
            throw $ee;
        }
        // Compress backup folder
        $zip = FileSystem::getTempDirectory() . '/' . $date-&gt;getYear() . '-' . $date-&gt;getMonth() .  '-' . $date-&gt;getDay() . '-MySqlBackup.zip';
        ZipFile::compress($backupFolder, $zip);
        FileSystem::delete($backupFolder);
        $return['zipfile'] = $zip;
        return $return;
    }

    /**
     * Restores the database content from zip archive. If $tablePrefix
     * is not spexified, the argument will be read from the backup info file in
     * the zip archive. The database is the database actually selected in this
     * object.
     * @param string $zipArchive
     * @param string $tablePrefix
     */
    public final function restore($zipArchive, $tablePrefix=null) {
        $backupFolder = FileSystem::getTempDirectory() . &quot;/mysqlbackup&quot;;
        if(FileSystem::exists($backupFolder)) {
            FileSystem::delete($backupFolder);
        }
        try {
            ZipFile::extract($zipArchive,  FileSystem::getTempDirectory());
            if(!FileSystem::exists($backupFolder)) {
                throw new Exception('Backup folder does not exist after extracting zip archive');
            } else {
                $info = FileSystem::find($backupFolder, &quot;*.backup.json&quot;);
                if(count($info) == 0) {
                    throw new Exception('Could not find a backup info file in zip archive');
                } else if(count($info) &gt; 1) {
                    throw new Exception('Threr are more than one backup info files in zip archive. Do not know which to use');
                } else {
                    // Get arguments or default arguments from info file
                    $info = json_decode(FileSystem::readFile(reset($info)), true);

                    if(is_null($tablePrefix)) {
                        if(isset($info['prefix']) &amp;&amp; strlen(trim($info['prefix'])) &gt; 0) {
                            $tablePrefix = $info['prefix'];
                        } else {
                            throw new Exception('No table prefix specified and no prefix in the backup info file');
                        }
                    }

                    if(!isset($info['create'])) {
                        throw new Exception('Missing &quot;create&quot; entry in info file');
                    } else if(!isset($info['tables']) || !is_array($info['tables'])) {
                        throw new Exception('Missing tables entry in info file');
                    } else if(empty($info['tables'])) {
                        throw new Exception('Tables entry in info file empty, no tables to restore registered.');
                    } else {
                        $tablePrefix = addslashes(trim($tablePrefix));
                        $create = FileSystem::readFile($backupFolder . '/'. trim($info['create'], &quot; /&quot;));
                        $create = str_replace('&lt;&lt;PREFIX&gt;&gt;', $tablePrefix, $create);
                        $create = str_replace('&lt;&lt;DATABASE&gt;&gt;', $this-&gt;getDatabaseName(), $create);
                        $create = str_replace(&quot;\n&quot;, &quot;&quot;, str_replace(&quot;\r&quot;,&quot;&quot;, $create));
                        $tables = array();
                        Tracer::trace_r($info['tables'], '$info[&quot;tables&quot;]');
                        foreach($info['tables'] as $table) {
                            $tables[$tablePrefix . $table['name']] = $backupFolder . '/'. trim($table['file'], &quot; /&quot;);
                        }

                        Tracer::trace_r($tables, '$tables');
                        foreach(explode(';', $create) as $query) {
                            $query = trim($query);
                            if(!empty($query)) {
                                Tracer::trace($query);
                                $this-&gt;query($query);
                            }
                        }
                        foreach($tables as $tableName =&gt; $tableFile) {
                            @chmod($tableFile, 0777);
                            $query = &quot;LOAD DATA INFILE '$tableFile' INTO TABLE `&quot; . self::escape($tableName) . &quot;`&quot;;
                            if(!empty($query)) {
                                Tracer::trace($query);
                                $this-&gt;query($query);
                            }
                        }
                    }
                }
            }
        } catch(Exception $e) {
            FileSystem::delete($backupFolder);
            throw $e;
        }
        FileSystem::delete($backupFolder);
    }
};
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/mysql-administration-class/2010-08-05/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL wrapper class</title>
		<link>http://www.atwillys.de/programming/php/mysql-wrapper-class/2010-08-05/</link>
		<comments>http://www.atwillys.de/programming/php/mysql-wrapper-class/2010-08-05/#comments</comments>
		<pubDate>Thu, 05 Aug 2010 13:46:37 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[MySql]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=761</guid>
		<description><![CDATA[One of the older classes, but nonetheless worth to publish as it may be of use. The MySQL wrapper contains methods to obtain information about the database, tables, as well as normal queries, queries to objects or arrays and key-value &#8230; <a href="http://www.atwillys.de/programming/php/mysql-wrapper-class/2010-08-05/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>One of the older classes, but nonetheless worth to publish as it may be of use. The MySQL wrapper contains methods to obtain information about the database, tables, as well as normal queries, queries to objects or arrays and key-value based requests. Multiple instances are possible, as each object has an own link to its database connection. The instances automatically connect to the database and throw exceptions (<code>MySqlException</code>) on error. It is also the base class for a MySQL administration class, which is published on this site as well.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
// This example referrs to a table, which was generated usin the query:
//
//    SET SQL_MODE=&quot;NO_AUTO_VALUE_ON_ZERO&quot;;
//    CREATE TABLE IF NOT EXISTS `test` (
//      `id` int(11) NOT NULL AUTO_INCREMENT,
//      `rid` int(11) NOT NULL,
//      PRIMARY KEY (`id`)
//    ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
//
//
// (it is just a renamed relation table of on of my projects)

// The database object
$db = new MySql('test', 'root', '', 'localhost');

// Returns information about the database
$r = $db-&gt;getStatus();
print '$db-&gt;getStatus() = ' . print_r($r, true) . '&lt;br/&gt;';

// Returns information about the status and fields of all tables
$r = $db-&gt;getTableStatus();
print '$db-&gt;getTableStatus() = ' . print_r($r, true) . '&lt;br/&gt;';

// Returns information about the fields of a table
$r = $db-&gt;getTableFields('test');
print '$db-&gt;getTableFields() = ' . print_r($r, true) . '&lt;br/&gt;';

// Query an insert, where the fields are defined as array keys and the values
// as the array values. The return value is the id if the just inserted row.
$id = $db-&gt;queryInsertInto('test', array('rid' =&gt; rand(0, 1000)));
print '$db-&gt;queryInsertInto(...) = ' . print_r($id, true) . '&lt;br/&gt;';

// Query the fields of one entry. This method returns directly the content
// if the filter is an ID (numeric). Otherwise it returns an array containing
// the rows with associative values (even if there is only one matching row).
$r = $db-&gt;queryFields('test', array('id','rid'), $id);
print '$db-&gt;queryFields(' . $id . ') = ' . print_r($r, true) . '&lt;br/&gt;';

// This method works with SQL syntax and returns an array of objects
$r = $db-&gt;queryObjects('SELECT * FROM test WHERE id=' . $id);
print '$db-&gt;queryObjects(...) = ' . print_r($r, true) . '&lt;br/&gt;';

// This method works with SQL syntax and returns an array of field-arrays
$r = $db-&gt;queryArrays('SELECT * FROM test WHERE id=' . $id);
print '$db-&gt;queryArrays(...) = ' . print_r($r, true) . '&lt;br/&gt;';

// Change a row
$r = $db-&gt;queryUpdateTable('test', array('id' =&gt; $id, 'rid' =&gt; rand(0, 1000)));
print '$db-&gt;queryUpdateTable(...) = ' . print_r($r, true) . '&lt;br/&gt;';

// Delete the row
$r = $db-&gt;queryDeleteFrom('test', $id);
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
</div>
<pre class="brush: php; title: ; notranslate">
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Exception thrown by class MySql and MySqlAdministration
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2007-2010
 * @license GPL
 * @version 1.0
 */
class MySqlException extends Exception {
}
?&gt;

&lt;?php
/**
 * MySQL wrapper, which provides auto connect/disconnect, normal SQL query,
 * escaping, and special query methods like queryInsertInto(), queryDeleteFrom(),
 * queryUpdate(), query to objects, query information about the database or
 * tables - all kind of queries you use regularely.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2007-2010
 * @license GPL
 * @version 1.0
 * @uses MySqlException
 * @uses Tracer
 */
class MySql implements ITracable {

    /**
     * Class configuration
     * @staticvar array
     */
    private static $config = array(
        'server' =&gt; 'localhost',
        'database' =&gt; '',
        'user' =&gt; '',
        'password' =&gt; ''
    );

    /**
     * SQL connection link /id
     * @var unknown
     */
    private $link;

    /**
     * Server connection, usual localhost
     * @var string
     */
    private $server;

    /**
     * SQL server login user
     * @var string
     */
    private $user;

    /**
     * SQL login password
     * @var string
     */
    private $password;

    /**
     * SQL database
     * @var string
     */
    private $database;

    /**
     * Connected flag
     * @var bool
     */
    private $connected;

    /**
     * Defines class defaults
     * @param string $database
     * @param string $user
     * @param string $password
     * @param string $server
     */
    public static final function config($database=null, $user=null, $password=null, $server=null) {
        if(!is_null($database)) self::$config['database'] = (empty($database) ? 'localhost' : $database);
        if(!is_null($server)) self::$config['server'] = trim($server);
        if(!is_null($user)) self::$config['user'] = trim($user);
        if(!is_null($password)) self::$config['password'] = $password;
    }

    /**
     * Escapes the value of a mysql query value text (so that no injection
     * possible and normal text values do not cause an unexpected error)
     * Null is escaped with NULL
     * @param string $query_value
     * @return string
     */
    public static final function escape($query_value) {
        if(empty($query_value)) {
            return is_null($query_value) ? 'NULL' : '';
        } else {
            return mysql_real_escape_string($query_value);
        }
    }

    /**
     * MySQL wrapper class constructor
     * @param string $database
     * @param string $user
     * @param string $password
     * @param string $server
     */
    public function __construct($database='', $user='', $password='', $server='') {
        self::trace(&quot;($database, $user, (pwd), $server)&quot;);
        $this-&gt;server      = $server != '' ? $server : self::$config['server'];
        $this-&gt;user        = $user	!= '' ? $user : self::$config['user'];
        $this-&gt;password    = $password != '' ? $password : self::$config['password'];
        $this-&gt;database    = $database != '' ? $database : self::$config['database'];
        $this-&gt;connected   = false;
        $this-&gt;link        = null;
        try { $this-&gt;connect(); } catch(Exception $e) { Tracer::traceException($e); }
    }

    /**
     * MySQL wrapper class destructor
     */
    public function __destruct() {
        self::trace(&quot;Disconnect ...&quot;);
        $this-&gt;disconnect(true);
    }

    /**
     * Server connected inicator
     * @return bool
     */
    public final function isConnected() {
        return $this-&gt;connected;
    }

    /**
     * Returns the name of the database
     * @return string
     */
    public final function getDatabaseName() {
        return $this-&gt;database;
    }

    /**
     * Connect to server
     */
    public final function connect() {
        if(!$this-&gt;connected) {
            self::trace(&quot;Connecting&quot;);
            $this-&gt;link = @mysql_connect($this-&gt;server, $this-&gt;user, $this-&gt;password);
            if(!$this-&gt;link) {
                throw new MySqlException(&quot;Connecting to database failed&quot;);
            } else if(!mysql_select_db($this-&gt;database)) {
                @mysql_close($this-&gt;link);
                throw new MySqlException(&quot;Failed to select database&quot;);
            } else {
                if(function_exists('mysql_set_charset')) {
                    @mysql_set_charset('utf8', $this-&gt;link);
                } else {
                    @mysql_query(&quot;SET NAMES 'utf8'&quot;, $this-&gt;link);
                }
                $this-&gt;connected = true;
            }
        }
    }

    /**
     * Disconnect MySQL server
     */
    public final function disconnect() {
        @mysql_close($this-&gt;link);
        $this-&gt;link = null;
        $this-&gt;connected = false;
    }

    /**
     * Returns the database structure and status as assoc. array.
     * @return array
     */
    public final function getStatus() {
        $database = array(
            'name' =&gt; $this-&gt;getDatabaseName(),
            'tables' =&gt; array()
        );
        $tables = $this-&gt;getTableStatus();
        foreach($tables as $table) {
            $name = $table['name'];
            $table['fields'] = $this-&gt;getTableFields($name);
            $database['tables'][$name] = $table;
        }
        return $database;
    }

    /**
     * Returns the list of table names in the database
     * @return array
     */
    public final function getTableNames() {
        if(!$this-&gt;isConnected()) {
            $this-&gt;connect();
        }
        $result = array();
        $tables = $this-&gt;query(&quot;SHOW TABLES FROM &quot; . $this-&gt;database);

        foreach($tables as $table) {
            $table = reset($table);
            $result[] = $table;
        }
        return $result;
    }

    /**
     * Returns an assoc. array with information about all tables
     * @return array
     */
    public final function getTableStatus() {
        $tables = array();
        $r = $this-&gt;query(&quot;SHOW TABLE STATUS FROM &quot; . $this-&gt;getDatabaseName() . &quot;;&quot;);
        foreach($r as $table) {
            $table = array_change_key_case($table, CASE_LOWER);
            $tables[$table['name']] = $table;
        }
        return $tables;
    }

    /**
     * Returns the field specifications of the database fields
     * @param string $table
     * @return array
     */
    public final function getTableFields($table) {
        $result = array();
        $table = self::escape($table);
        $fields = $this-&gt;query(&quot;SHOW FULL COLUMNS FROM `$table`&quot;);
        foreach($fields as $field) {
            $field = array_change_key_case($field, CASE_LOWER);
            $result[$field['field']] = $field;
        }
        return $result;
    }

    /**
     * Returns the resource to the query. The result resource has to be
     * set free manually.
     * @param string $query
     * @return resource
     */
    public final function queryResource($query) {
        self::trace(&quot;query=$query&quot;);
        if(empty($query)) {
            throw new MySqlException(&quot;Query failed: no query rule specified.&quot;);
        } else if(!$this-&gt;isConnected()) {
            $this-&gt;connect();
        }
        $result = @mysql_query($query, $this-&gt;link);

        if($result === false) {
            Tracer::trace('MySql ERROR=' . mysql_error());
            throw new MySqlException('MySql query failed');
        } else {
            return $result;
        }
    }

    /**
     * Perform query. Returns array of assoc arrays
     * @param string $query
     * @return array&amp;
     */
    public final function &amp; query($query) {
        $result = $this-&gt;queryResource($query);
        $data = array();
        if(is_resource($result)) {
            while($row = mysql_fetch_assoc($result)) {
                $data[] = $row;
            }
            @mysql_free_result($result);
        }
        return $data;
    }

    /**
     * Query SELECT form the table, where all fields specified by the field names
     * array are fetched into a structure (assoc. array).
     * @param string $table
     * @param array $fieldNames
     * @param string $idOrFilter
     * @param int $numRows
     * @param int $fromRow
     * @return void
     */
    public final function &amp; queryFields($table, $fieldNames=array(), $idOrFilter='', $numRows=0, $fromRow=0) {
        $fromRow = intval($fromRow);
        $numRows = intval($numRows);
        $table = '`' . self::escape(trim($table)) . '`';

        if(empty($fieldNames)) {
            $fields = '*';
        } else if(!is_array($fieldNames)) {
            throw new MySqlException('Specified field names must be passed as array');
        } else {
            $fields = array();
            foreach($fieldNames as $key =&gt; $f) {
                $fields[] = '`' . self::escape($f) . '`';
            }
            $fields = implode(',', $fields);
        }

        $query = &quot;SELECT $fields FROM $table&quot;;

        if(is_numeric($idOrFilter)) {
            $query .= ' WHERE id=' . intval($idOrFilter);
        } else if(!empty($idOrFilter)) {
            $query .= &quot; WHERE $idOrFilter&quot;;
        }

        if($numRows &gt; 0) {
            $query .= ($fromRow &gt; 0) ? (&quot; LIMIT $fromRow, $numRows&quot;) : (&quot; LIMIT $numRows&quot;);
        }

        $r = $this-&gt;query($query);

        return $r;
    }

    /**
     * Query into a result instance.
     * @return array
     */
    public final function &amp; queryArrays($query) {
        $result = $this-&gt;queryResource($query);
        $data = array();
        while($row = mysql_fetch_array($result)) $data[] = $row;
        @mysql_free_result($result);
        return $data;
    }

    /**
     * Query into a result instance.
     * @return array
     */
    public final function &amp; queryObjects($query, $class=null) {
        $result = $this-&gt;queryResource($query);
        if(!is_null($class) &amp;&amp; !class_exists($class) &amp;&amp; !swlib::hasClass($class)) {
            throw new MySqlException('Class does not exist:' . $class);
        } else {
            $data = array();
            if(is_null($class)) {
                while($o = mysql_fetch_object($result)) {
                    $data[] = $o;
                }
            } else {
                while($o = mysql_fetch_object($result, $class)) {
                    $data[] = $o;
                }
            }
            @mysql_free_result($result);
            return $data;
        }
    }

    /**
     * Query UPDATE table, where the SET &lt;key&gt;='&lt;value&gt;' are the array key/value pairs.
     * $filter is according to &quot;WHERE id=&lt;id&gt;&quot; or &quot;WHERE name=&quot;test&quot; LIMIT 1&quot;;
     * @param string $table
     * @param array $keyValuePairs
     * @param string $filter
     * @param int $limit
     * @return void
     */
    public final function queryUpdateTable($table, array $keyValuePairs, $filter='', $limit=1) {
        $table = self::escape($table);
        $limit = (is_numeric($limit) &amp;&amp; $limit &gt; 0) ? &quot; LIMIT &quot; . intval($limit) : '';
        if(empty($filter)) {
            if(isset($keyValuePairs['id'])) {
                $filter = &quot;WHERE id=&quot; . self::escape($keyValuePairs['id']);
                unset($keyValuePairs['id']);
            } else {
                throw new MySqlException('You must set a filter (WHERE anyfield=anyvalue) if you do not have the filed &quot;id&quot;');
            }
        }
        foreach($keyValuePairs as $key =&gt; $value) {
            $keyValuePairs[$key] = &quot;`$key`='&quot; . self::escape($value) . &quot;'&quot;;
        }
        $keyValuePairs = implode(',', $keyValuePairs);
        $query = &quot;UPDATE `$table` SET $keyValuePairs $filter $limit&quot;;
        $this-&gt;query($query);
    }

    /**
     * Query INSERT INTO table, where the SET &lt;key&gt;='&lt;value&gt;' are the array key/value pairs.
     * Returns the complete just inserted row.
     * @param string $table
     * @param array $keyValuePairs
     * @return array
     */
    public final function queryInsertInto($table, array $keyValuePairs) {
        $table = trim($table);
        if(empty($table)) {
            throw new MySqlException('Cannot append data in database without a specified table (empty string)');
        } else {
            $table = '`' . self::escape($table)  . '`';
            $keys = $values = array();

            if(isset($keys['id']) &amp;&amp; empty($keys['id'])) {
                $keys['id'] = null;
            }
            foreach($keyValuePairs as $key =&gt; $value) {
                if(empty($key)) {
                    throw new MySqlException('Database field key is empty');
                } else {
                    $keys[] = '`' . self::escape(trim($key)) . '`';
                    $values[] = &quot;'&quot; . self::escape($value) . &quot;'&quot;;
                }
            }
            $keys = implode(',', $keys);
            $values = implode(',', $values);
            $query = &quot;INSERT INTO $table ($keys) VALUES ($values);&quot;;
            $this-&gt;query($query);
            $r = $this-&gt;query('SELECT LAST_INSERT_ID();');
            if(empty($r)) {
                throw new MySqlException('Could not get autoincrement id of insert action');
            } else {
                $r = reset($r);
                return reset($r);
            }
        }
    }

    /**
     * Query DELETE FROM table. If $idOrFilter is numeric, then the filter
     * will be automatically set to &quot;WHERE id=$idOrFilter&quot;, if filter is a
     * text, then the filter text will be used.
     * @param string $table
     * @param array $idOrFilter
     * @return void
     */
    public final function queryDeleteFrom($table, $idOrFilter) {
        $idOrFilter = trim($idOrFilter);
        if(is_numeric($idOrFilter)) {
            $idOrFilter = &quot;id='$idOrFilter'&quot;;
        } else if(empty($idOrFilter)) {
            throw new MySqlException('No id/filter given to indicate the row to delete');
        }
        $this-&gt;query(&quot;DELETE FROM $table WHERE $idOrFilter&quot;);
    }

    /**
     * Saves the a BLOB in the database in a file in the file system. Write
     * the condition like &quot;id=1&quot;. The WHERE is added by the method.
     * @param string $file
     * @param string $table
     * @param string $field
     * @param string $condition
     */
    public final function queryBlobToFile($file, $table, $field, $condition) {
        $file = trim($file);
        $table = self::escape(trim($table));
        $field = self::escape(trim($field));
        $condition = str_replace(';', '', $condition); // no multiple commands allowed.
        if(file_exists($file)) {
            throw new MySqlException('The file to write the BLOB in already exists');
        } else if(!is_dir(dirname($file))) {
            throw new MySqlException('Parent directory of the file to save does not exist');
        } else if(!is_writable(dirname($file))) {
            throw new MySqlException('Parent directory of the file to save is not writable for you');
        } else {
            $query = &quot;SELECT `$field` FROM `$table` WHERE $condition LIMIT 1 INTO DUMPFILE '$file'&quot;;
            self::trace($query);
            $this-&gt;query($query);
        }
    }

    /**
     * Performs a query to write a BLOB field in the database from a
     * existing file in the file system.
     * @param string $file
     * @param string $table
     * @param string $field
     */
    public final function queryFileToBlob($file, $table, $field, $condition) {
        // All the submethods throw exceptions if something goes wrong.
        $file = trim($file);
        $table = self::escape(trim($table));
        $field = self::escape(trim($field));
        $condition = str_replace(';', '', $condition); // no multiple commands allowed.
        $data = self::escape(FileSystem::readFile($file));
        self::trace(&quot;UPDATE `$table` SET `$field`='[BLOB of &quot; . (strlen($data) &gt;&gt; 10) . &quot;kb]' WHERE $condition&quot;);
        $this-&gt;query(&quot;UPDATE `$table` SET `$field`='$data' WHERE $condition&quot;);
    }

    /**
     * Private tracing method
     * @param string $text
     * @param int $level
     */
    protected function trace($text, $level=1) {
        if(!Tracer::tracedClass(__CLASS__)) return;
        Tracer::trace($text, $level);
    }
};
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/mysql-wrapper-class/2010-08-05/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Configuration access wrapper class</title>
		<link>http://www.atwillys.de/programming/php/configuration-access-wrapper-class/2010-08-05/</link>
		<comments>http://www.atwillys.de/programming/php/configuration-access-wrapper-class/2010-08-05/#comments</comments>
		<pubDate>Thu, 05 Aug 2010 11:20:59 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[config]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=749</guid>
		<description><![CDATA[During a chat with a friend about the convenience of object-style access to PHP configuration, which are defined as structured associative arrays, we came to a lightweight wrapper class that addresses these issues. As the configuration array is already in &#8230; <a href="http://www.atwillys.de/programming/php/configuration-access-wrapper-class/2010-08-05/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>During a chat with a friend about the convenience of object-style access to PHP configuration, which are defined as structured associative arrays, we came to a lightweight wrapper class that addresses these issues. As the configuration array is already in use by other modules, it should not be changed, and a copy to objects would lead to a higher memory usage. So the solution is based on cascaded references to the configuration array. The example shows what it is about:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
// This is an example config array which is e.g. loaded by including
// a config.php or the like.
$theConfigurationStructure = array(
    'ref' =&gt; 'no',
    'database' =&gt; array(
        'server' =&gt; 'unlocalhost',
        'user' =&gt; 'me',
        'password' =&gt; 'none',
        'database' =&gt; 'Example database',
        'tables' =&gt; array(
            'contacts' =&gt; array(
                'id', 'name', 'email'
            ),
            'cache' =&gt; array(
                'id', 'uri', 'chksum', 'expires', 'etag', 'lastmodified'
            ),

        )
    ),
    'aNumber' =&gt; 10,
    'a_text' =&gt; 'nothing in there'
);

// This is the instance of the wrapper class, which &quot;points&quot; to
// the root node of the array
$cfg = new ConfigAccess($theConfigurationStructure);

// Access to a scalar config entry
print 'config.aNumber = ' . $cfg-&gt;aNumber . '&lt;br/&gt;';

// Access to a scalar config entry in a sub-configuration
print 'config.database.user = ' . $cfg-&gt;database-&gt;user . '&lt;br/&gt;';

// Access to an array config entry in a sub-configuration
// Note here we have to escape, because the class returns an object
// instead of an array. If we went the array instead, we call the toArray()
// escape function
print 'config.database.tables.cache = ' . print_r($cfg-&gt;database-&gt;tables-&gt;cache-&gt;toArray(), true) . '&lt;br/&gt;';
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Wrapper class for object-style access to an associative configuration
 * array. Note that the configuration keys must be conform to the convention of
 * variables (word characters and numbers). Otherwise the array key cannot be
 * converted to an object property.
 * @class ConfigAccess
 * @package de.atwillys.php.misc
 * @author Stefan Wilhelm
 * @license GPLq
 */
class ConfigAccess {

    /**
     * Reference to the config array content
     * @var array
     */
    private $__r = null;

    /**
     * Constructor, stores a reference to the root or sub-branch of the
     * assiciative configuration array.
     * @param array $cref
     */
    public final function __construct(array &amp; $cref) {
        $this-&gt;__r = &amp;$cref;
    }

    /**
     * Returns the content of the condiguration array (or sub-array) with the
     * key $p. If this is an array, then a ConfigAccess object will be returned
     * instead, which &quot;points&quot; to the sub-branch of the configuration.
     * @param string $p
     * @return mixed
     */
    public final function __get($p) {
        if(!isset($this-&gt;__r[$p])) {
            throw new Exception(&quot;Config entry '$p' does not exist&quot;);
        }
        return is_array($this-&gt;__r[$p]) ? new self(&amp;$this-&gt;__r[$p]) : $this-&gt;__r[$p];
    }

    /**
     * Config setter, this is NOT allowed. Hence, the method throws an exception.
     * @param string $p
     * @param string $v
     */
    public final function __set($p,  $v) {
        throw new Exception('You cannot modify the configuration');
    }

    /**
     * This method is an escape for the case that the content of a configuration
     * is an array and shall not be represented with a ConfigAccess object.
     * @return array
     */
    public final function toArray() {
        return $this-&gt;__r;
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/configuration-access-wrapper-class/2010-08-05/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Calendar class library</title>
		<link>http://www.atwillys.de/programming/php/calendar-class-library/2010-08-02/</link>
		<comments>http://www.atwillys.de/programming/php/calendar-class-library/2010-08-02/#comments</comments>
		<pubDate>Mon, 02 Aug 2010 15:39:11 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=598</guid>
		<description><![CDATA[Because there are already thousands of calendars in the net, I added my own one as well ;-). This is a highly maintainable and easily extendable bunch of classes to display month and day calendars with events. It consists the &#8230; <a href="http://www.atwillys.de/programming/php/calendar-class-library/2010-08-02/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>Because there are already thousands of calendars in the net, I added my own one as well ;-). This is a highly maintainable and easily extendable bunch of classes to display month and day calendars with events. It consists the following classes:</p>
<ul>
<li>The main calendar class <code>GregorianCalendar</code>, which organizes the bundle and provides the interface to the application/script.</li>
<li>the renderers <code>GregorianMonthCalendarRenderer</code> and <code>GregorianDayCalendarRenderer</code>, which are the base for the visualization. They are fed with preprocessed data.</li>
<li>The events are managed by the <code>GregorianCalendarEventController</code>. This class is to load, filter and save events in files or a data base, or every possible source (that&#8217;s why it is a separate class).</li>
<li>The class <code>GregorianCalendarEvent</code> is the base class for your own events, managed by the controller and used by the other classes.</li>
</ul>
<p>That&#8217;s all. The sample code illustrates that you don&#8217;t need to do much to have a functioning calendar, and that it is very easy to extend it.<br />
The class sources are normally split in more class files and here collected in one block. Normally every class is in a file with the same name as the class and the extension &#8220;.class.php&#8221;.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Sample Gegorian Month Calendar
 * @package de.atwillys.sw.php.cal.sample
 * @author Stefan Wilhelm, 2010
 * @license GPL
 *
 *
 * If you want to make your own renderer, overload these methods:
 *
 */
class SampleMonthCalendarRenderer extends GregorianMonthCalendarRenderer {

    //    public function onStart() {
    //    }
    //
    //    public function onEnd() {
    //    }
    //
    //    public function onWeekStart($weekId) {
    //    }
    //
    //    public function onWeekEnd($weekId) {
    //    }
    //
    //    public function onDay(IDate $day, $isPadding, $isSelected, $events) {
    //    }
}
?&gt;

&lt;?php
/**
 * Sample Gegorian Day Calendar
 * @package de.atwillys.sw.php.cal.sample
 * @author Stefan Wilhelm, 2010
 * @license GPL
 *
 *
 * Here I added the block view of the sample events, it is more or less
 * like a trace dump in the day calendar table:
 *
 */
class SampleDayCalendarRenderer extends GregorianDayCalendarRenderer {

    /**
     * Returns a day calendar Event block representation
     * @param GregorianCalendarEvent $event
     * @return string
     *
     * Principally all you have to do is to overload this method as well:
     */
    protected function renderEvent(GregorianCalendarEvent $event) {
        $tick = $this-&gt;getParent()-&gt;getHourDivision();
        $t1 = new LocalDate($event-&gt;getStart());
        $t2 = new LocalDate($event-&gt;getEnd());
        $o  = $event-&gt;name . &quot; (&quot; . $event-&gt;id . &quot;)&quot;;
        $o .= '&lt;br/&gt;' . sprintf(&quot;%02d:%02d&quot;, $t1-&gt;getHour(), $t1-&gt;getMinute()) .
                ' to ' . sprintf(&quot;%02d:%02d&quot;, $t2-&gt;getHour(), $t2-&gt;getMinute());
        $t1 = $tick * floor($event-&gt;getStart() / $tick);
        $t2 = $tick * floor($event-&gt;getEnd() / $tick);
        $t1 = new LocalDate($t1);
        $t2 = new LocalDate($t2);
        $o .= '&lt;br/&gt;' . sprintf(&quot;%02d:%02d&quot;, $t1-&gt;getHour(), $t1-&gt;getMinute()) .
                ' to ' . sprintf(&quot;%02d:%02d&quot;, $t2-&gt;getHour(), $t2-&gt;getMinute());
        return $o;
    }
}
?&gt;

&lt;?php
/**
 * Sample Gegorian Calendar event
 * @package de.atwillys.sw.php.cal.sample
 * @author Stefan Wilhelm, 2010
 * @license GPL
 *
 * This is a sample event, which is saved in the data base, the table is
 * build with the SQL:
 *
 *    SET SQL_MODE=&quot;NO_AUTO_VALUE_ON_ZERO&quot;;
 *    CREATE TABLE IF NOT EXISTS `events` (
 *      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
 *      `name` text NOT NULL,
 *      `begin` bigint(20) NOT NULL,
 *      `end` bigint(20) NOT NULL,
 *      `type` text NOT NULL,
 *      `data` text NOT NULL COMMENT 'serialized text data',
 *      `flags` set('p','d') NOT NULL DEFAULT 'p' COMMENT 'pending, deleted',
 *      PRIMARY KEY (`id`)
 *    ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 *
 */
class SampleEvent extends GregorianCalendarEvent {

    /**
     * GregorianCalendarEvent constructor
     * @param string $nameOrDBArray=''
     * @param mixed $start=null
     * @param mixed $end=null
     * @param array $data=array()
     * @param string $type=null
     * @param mixed $id=null
     */
    public function __construct($nameOrDBArray='', $start=null, $end=null, array $data=array(), $type=null, $id=null) {
        if(!is_array($nameOrDBArray)) {
            parent::__construct($nameOrDBArray, $start, $end, $data, $type, $id);
        } else {
            $this-&gt;unserialize($nameOrDBArray);
        }
    }

    /**
     * Returns an associatove array to save the event in a database. The
     * data are JSON encoded
     * @return string
     */
    public function serialize() {
        $r = array(
            'id' =&gt; $this-&gt;id,
            'name' =&gt; $this-&gt;name,
            'begin' =&gt; $this-&gt;start,
            'end' =&gt; $this-&gt;end,
            'type' =&gt; $this-&gt;type,
            'data' =&gt; $this-&gt;data
        );
        unset($r['data']['resource']);
        $r['data'] = json_encode($r['data'], JSON_FORCE_OBJECT);
        return $r;
    }

    /**
     * Sets all object variables corresponding to the assoc array returned by
     * serialize().
     * @param array $r
     */
    public function unserialize(array $r) {
        $this-&gt;id = $r['id'];
        $this-&gt;name = $r['name'];
        $this-&gt;start = $r['begin'];
        $this-&gt;end = $r['end'];
        $this-&gt;type = $r['type'];
        $this-&gt;data = empty($r['data']) ? array() : json_decode($r['data'], true);
    }
}
?&gt;

&lt;?php
/**
 * Sample Gegorian Calendar event controller
 * @package de.atwillys.sw.php.cal.sample
 * @author Stefan Wilhelm, 2010
 * @license GPL
 *
 * This is the sample event controller, organizes the database and provides the
 * funtionality to obtain and save data.
 * As mentioned, it belongs to a table build with the SQL query:
 *
 *    SET SQL_MODE=&quot;NO_AUTO_VALUE_ON_ZERO&quot;;
 *    CREATE TABLE IF NOT EXISTS `events` (
 *      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id',
 *      `name` text NOT NULL,
 *      `begin` bigint(20) NOT NULL,
 *      `end` bigint(20) NOT NULL,
 *      `type` text NOT NULL,
 *      `data` text NOT NULL COMMENT 'serialized text data',
 *      `flags` set('p','d') NOT NULL DEFAULT 'p' COMMENT 'pending, deleted',
 *      PRIMARY KEY (`id`)
 *    ) ENGINE=MyISAM  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
 *
 */
class SampleEventController extends GregorianCalendarEventController {

    /**
     * The database settings, for the hackers: this sample is NOT
     * on the atwillys server ...
     * @var array
     */
    private $database = array(
        'database' =&gt; 'samplecalendar',
        'user' =&gt; 'cal',
        'password' =&gt; 'cal',
        'server' =&gt; 'localhost'
    );

    /**
     * Database
     * @return MySql
     */
    protected function db() {
        if(is_array($this-&gt;database)) {
            $this-&gt;database = new MySql($this-&gt;database['database'],
            $this-&gt;database['user'], $this-&gt;database['password'],
            $this-&gt;database['server']);
        }
        return $this-&gt;database;
    }

    /**
     * Returns all events that start and end between a given period.
     * @param mixed $from
     * @param mixed $to
     * @return SampleEvent[]
     */
    public function getEventsBetween($from, $to) {
        $from = new LocalDate($from);
        $to = new LocalDate($to);
        $r = $this-&gt;db()-&gt;query('SELECT * FROM events WHERE begin &gt;= ' .
                Mysql::escape($from-&gt;getTimeStamp(), true)  . ' &amp;&amp; end &lt;' .
                MySql::escape($to-&gt;getTimeStamp(), true) . ' &amp;&amp; NOT FIND_IN_SET(\'d\', flags)' );
        foreach($r as $k =&gt; $v) $r[$k] = new SampleEvent($v);
        return $r;
    }

    /**
     * Returns all events that do not end before and do not start after the given
     * time period.
     * @param mixed $from
     * @param mixed $to
     * @return SampleEvent[]
     */
    public function getEventsWhichMatch($from, $to) {
        $from = new LocalDate($from);
        $to = new LocalDate($to);
        $r = $this-&gt;db()-&gt;query('SELECT * FROM events WHERE NOT end &lt; ' .
                Mysql::escape($from-&gt;getTimeStamp(), true)  . ' &amp;&amp; NOT begin &gt;' .
                MySql::escape($to-&gt;getTimeStamp(), true) . ' &amp;&amp; NOT FIND_IN_SET(\'d\', flags)' );
        foreach($r as $k =&gt; $v) $r[$k] = new SampleEvent($v);
        return $r;
    }

    /**
     * Returns the event by id, null if not found
     * @param int $id
     */
    public function getEvent($id) {
        $r = $this-&gt;db()-&gt;query('SELECT * FROM events WHERE id= ' .
                Mysql::escape($id, true) . ' LIMIT 1');
        if(!empty($r)) {
            $event = new SampleEvent();
            $event-&gt;unserialize(reset($r));
            return $event;
        } else {
            return null;
        }
    }

    /**
     * Saves an event
     * @param SampleEvent $event
     */
    public function saveEvent(SampleEvent $event) {
        $r = $event-&gt;serialize();
        if(!empty($r['id']) &amp;&amp; is_numeric($r['id'])) {
            $this-&gt;db()-&gt;queryUpdateTable('events', $r);
        } else if(empty($r['id'])) {
            $r['id'] = null;
            $this-&gt;db()-&gt;queryInsertInto('events', $r);
        } else {
            throw new Exception('Sample event id is invalid: ' . $id);
        }
    }

}
?&gt;

&lt;?php
/**
 * Here the index.php, which uses the classes above ...
 * Note: GET is not sanatized
 * Note: There is a if(false) {} to add events in your sample MySql table
 */
require_once('./lib/swlib.class.php');

// Start the swlib
swlib::start(array(
    'use_ob' =&gt; true,
    'use_session' =&gt; true,
    'var_path' =&gt; '/tmp',
    'tmp_path' =&gt; '/tmp',
    'var_extension' =&gt; '.tmp',
    'include_paths' =&gt; array(
        dirname(__FILE__) . '/include'
    )
));

// Let's set another time zone for local dates
date_default_timezone_set('Europe/London');

try {

    // Instantize our event controller
    $rec = new SampleEventController();

    // Instantize the main calendar object and make settings ...
    $cal = new GregorianCalendar();
    $cal-&gt;setEventController($rec);         // Set our own event controller
    $cal-&gt;setDayDisplayFrom(&quot;06:00:00&quot;);    // The day cal will ignore 05:59
    $cal-&gt;setDayStartsAt(&quot;08:00:00&quot;);       // From 06:00 to 07:59 is grayed
    $cal-&gt;setDayEndsAt(&quot;19:59:59&quot;);         // From 20:00 to 22:59 is grayed
    $cal-&gt;setDayDisplayTo(&quot;22:59:59&quot;);      // From 23:00 to 23:59 is ignored
    $cal-&gt;setHourDivision(900);             // One tick is 15 minutes (resolution)

    // These are our renderers:
    $monthCal = new SampleMonthCalendarRenderer();
    $dayCal = new SampleDayCalendarRenderer();

} catch(Exception $e) {
    print &quot;exception:&quot; . $e-&gt;getMessage();
    Tracer::traceException($e);
}

try {
    // The day to show in the month calendar, note that you should
    // SANATIZE this before ...
    if(isset($_GET['month-calendar-show'])) {
        $_SESSION['month-calendar-show'] = $_GET['month-calendar-show'];
    }
    if(isset($_SESSION['month-calendar-show'])) {
        $cal-&gt;setDateToShow($_SESSION['month-calendar-show']);
    }
} catch(Exception $e) {
    Tracer::traceException($e);
    $cal-&gt;setDateToShow( $cal-&gt;getToday() );
}

try {
    // The selected day in the month/day calendar, note that you should
    // SANATIZE this before ...
    if(isset($_GET['month-calendar-select'])) {
        $_SESSION['month-calendar-select'] = $_GET['month-calendar-select'];
    }
    if(isset($_SESSION['month-calendar-select'])) {
        $cal-&gt;setSelectedDate($_SESSION['month-calendar-select']);
    }
} catch(Exception $e) {
    Tracer::traceException($e);
    $cal-&gt;setSelectedDate( $cal-&gt;getDateToShow() );
}

if(false)
{
    // Here a piece of code that creates some events in your database
    MySql::traced(true);

    // We round the timestamp to half an our
    $from = new LocalDate(); // new local date without argument is now.
    $from-&gt;setTimeStamp(round($from-&gt;getTimeStamp()/1800)*1800);
    $to = $from-&gt;add(9 * 3600); // events takes place for 9 hurs
    $ev = new SampleEvent('Overlapping 0', $from, $to, array('who' =&gt; uniqid(),
        'test1' =&gt; rand(0, 10)), 'sample' );
    $rec-&gt;saveEvent($ev); // save it in the database
    Tracer::trace(&quot;Overlapping 0 = $ev&quot;); // and trce it ...

    $from = $from-&gt;add(5*1800);
    $to = $from-&gt;add(5*1800);
    $ev = new SampleEvent('Overlapping 1', $from, $to, array('who' =&gt; uniqid(),
        'test1' =&gt; rand(0, 10)), 'sample' );
    $rec-&gt;saveEvent($ev);
    Tracer::trace(&quot;Overlapping 1 = $ev&quot;);

    $from = $from-&gt;add(2435);
    $to = $from-&gt;add(9873);
    $ev = new SampleEvent('Overlapping 2', $from, $to, array('who' =&gt; uniqid(),
        'test1' =&gt; rand(0, 10)), 'sample' );
    $rec-&gt;saveEvent($ev);
    Tracer::trace(&quot;Overlapping 2 = $ev&quot;);

    $from = $from-&gt;add(234);
    $to = $from-&gt;add(4827);
    $ev = new SampleEvent('Overlapping 3', $from, $to, array('who' =&gt; uniqid(),
        'test1' =&gt; rand(0, 10)), 'sample' );
    $rec-&gt;saveEvent($ev);
    Tracer::trace(&quot;Overlapping 3 = $ev&quot;);

    $from = $from-&gt;add(4321);
    $to = $from-&gt;add(12345);
    $ev = new SampleEvent('Overlapping 4', $from, $to, array('who' =&gt; uniqid(),
        'test1' =&gt; rand(0, 10)), 'sample' );
    $rec-&gt;saveEvent($ev);
    Tracer::trace(&quot;Overlapping 4 = $ev&quot;);

}

// The view starts here:

?&gt;&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.01 Transitional//EN&quot;&gt;
&lt;html&gt;&lt;head&gt;
&lt;title&gt;Sample calendar&lt;/title&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;index.css&quot; type=&quot;text/css&quot; /&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;include/gregorian-month-calendar.css&quot; type=&quot;text/css&quot; /&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;include/gregorian-day-calendar.css&quot; type=&quot;text/css&quot; /&gt;
&lt;link rel=&quot;stylesheet&quot; href=&quot;include/sample-calendar.css&quot; type=&quot;text/css&quot; /&gt;
&lt;/head&gt;&lt;body&gt;
&lt;?php

try {
    print '&lt;table id=&quot;sample-calendar&quot;&gt;&lt;tr&gt;';
    print '&lt;td class=&quot;month-calendar-block&quot;&gt;';
    print &quot;\n&quot; . $cal-&gt;renderMonthCalendar($monthCal) . &quot;\n&quot;;
    print $testout . &quot;\n&quot;;
    print '&lt;/td&gt;';
    print '&lt;td class=&quot;day-calendar-block&quot;&gt;';
    print &quot;\n&quot; . $cal-&gt;renderDayCalendar($dayCal) . &quot;\n&quot;;
    print '&lt;/td&gt;';
    print '&lt;/tr&gt;&lt;/table&gt;';
} catch(Exception $e) {
    print &quot;exception:&quot; . $e-&gt;getMessage();
    Tracer::traceException($e);
}
?&gt;
&lt;/body&gt;&lt;/html&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
<p>As the output is not that small, I made a html snapshot of one page, which you can find it <a href="/public/demos/calendar/index.php" target="_blank">here</a>.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Gegorian Calendar management with the ability to render day calendars and
 * month calendars using the corresponding rendering classes. Also manages
 * the events for selected time spans.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 * @uses GregorianCalendarEvent
 * @uses GregorianCalendarEventController
 * @uses GregorianDayCalendarRenderer
 * @uses GregorianMonthCalendarRenderer
 */
class GregorianCalendar {

    /**
     * The date (and time) to show.
     * @var IDate
     */
    private $dateToShow = null;

    /**
     * The (real) today, set this only in the constructor for history purposes
     * @var IDate
     */
    private $today = null;

    /**
     * The (real) date which is selected, e.g. to displad a day calendar
     * @var IDate
     */
    private $dateSelected = null;

    /**
     * The class to instantiate, must be an IDate implementation, normally
     * LocalDate or UtcDate
     * @var IDate
     */
    private $dateClass = 'LocalDate';

    /**
     * Defines if the week starts with Sunday or Monday
     * @var bool
     */
    private $weekStartsWithSunday = false;

    /**
     * A numeric array which contains the weekday indices for using with localized
     * day name PHP functions.
     * @var array
     */
    private $weekDayIndex = array(1,2,3,4,5,6,0);

    /**
     * Contains the weekday abbreviations
     * @var array
     */
    private $weekDayAbbr = array('sun','mon','tue','wed','thu','fri','sat');

    /**
     * Defines the time period of division in a day calendar (in seconds).
     * @var int
     */
    private $hourDivision = 3600;

    /**
     * Defines when the day starts for the calendar (timestamp format in seconds)
     * @var int
     */
    private $dayStartsAt = '00:00:00';

    /**
     * Defines when the day ends for the calendar (timestamp format in seconds)
     * @var int
     */
    private $dayEndsAt = '23:59:59';

    /**
     * Every time between 00:00:00 and $dayDisplayedFrom (les than $dayDisplayedFrom)
     * will not be displayed
     * @var int
     */
    private $dayDisplayedFrom = '00:00:00';

    /**
     * Every time &gt; $dayDisplayedTo to '23:59:59' will not be displayed
     * @var int
     */
    private $dayDisplayedTo = '23:59:59';

    /**
     * Reference to the event controller object
     * @var GregorianCalendarEventController
     */
    private $eventController = null;

    /**
     * Constructor
     * @param IDate $dateToShow
     * @param IDate $today
     * @param string $dateClass
     */
	public function  __construct($dateToShow=null, $today=null, $dateClass='LocalDate') {
        $this-&gt;dateClass = $dateClass;
        $this-&gt;setToday($today);
        $this-&gt;setDateToShow($dateToShow);
        $this-&gt;setWeekStartsOnSunday(false);
        $this-&gt;setDayStartsAt($this-&gt;dayStartsAt);
        $this-&gt;setDayEndsAt($this-&gt;dayEndsAt);
        $this-&gt;setDayDisplayFrom($this-&gt;dayDisplayedFrom);
        $this-&gt;setDayDisplayTo($this-&gt;dayDisplayedTo);
        $this-&gt;setSelectedDate($this-&gt;getToday());
	}

    /**
     * Returns of a time $t is/takes place at the day $day. The day must be normed
     * to 00:00:00h.
     * @param IDate $day
     * @param IDate $t
     * @return bool
     */
    protected final function isSameDay(IDate $day, IDate $t) {
        return ($t-&gt;getTimeStamp() &gt;= $day-&gt;getTimeStamp()) &amp;&amp; ($t &lt; $day-&gt;getTimeStamp() + 86400);
    }

    /**
     * Returns the date specified as today (which is normally today, but can
     * be specified for historic date referencing)
     * @return IDate
     */
    public final function getToday() {
        return $this-&gt;today;
    }

    /**
     * Returns the date specified as selected (e.g. for displaying a day calendar)
     * @return IDate
     */
    public final function getSelectedDate() {
        return $this-&gt;dateSelected;
    }

    /**
     * Returns the date which has to be displayed. This can be a time with hours
     * minues and seconds. The renderers have to adjust the displayed interval.
     * For a month calendar, the calenar will e.g. render the month July 2010 if
     * $dateToShow is 2010-07-15 03:05:30.
     */
    public final function getDateToShow() {
       return $this-&gt;dateToShow;
    }

    /**
     * Returns if the week shall start on Sunday (or Monday)
     * @return bool
     */
    public final function getWeekStartsOnSunday() {
        return $this-&gt;weekStartsWithSunday;
    }

    /**
     * Returns the week day index dependent on $weekStartsWithSunday, either
     * [0,1,2,3,4,5,6] (if Sun first day) or [1,2,3,4,5,6,0] (if Mon first day)
     * @return bool
     */
    public final function getWeekDayIndex() {
        return $this-&gt;weekDayIndex;
    }

    /**
     * Returns the week day abbreviation depending on $weekStartsWithSunday
     * @param int $index
     * @return string
     */
    public final function getWeekDayAbbreviation($index) {
        $index -= 1;
        if(!isset($this-&gt;weekDayIndex[$index])) {
            throw new Exception(&quot;No such week day index: $index&quot;);
        } else {
            return $this-&gt;weekDayAbbr[$this-&gt;weekDayIndex[$index]];
        }
    }

    /**
     * Retuens the time period of on division in a day calendar (in seconds).
     * @return int
     */
    public function getHourDivision() {
        return $this-&gt;hourDivision;
    }

    /**
     * Returns when the day starts for the calendar
     * @return int
     */
    public final function getDayStartsAt() {
        return $this-&gt;dayStartsAt;
    }

    /**
     * Returns when the day ends for the calendar
     * @return int
     */
    public final function getDayEndsAt() {
        return $this-&gt;dayEndsAt;
    }

    /**
     * Every time between 00:00:00 and getDayDisplayedFrom()
     * will not be displayed
     * @return int
     */
    public final function getDayDisplayedFrom() {
        return $this-&gt;dayDisplayedFrom;
    }

    /**
     * Every time &gt; getDayDisplayedTo() to '23:59:59' will not be displayed
     * @return int
     */
    public final function getDayDisplayedTo() {
        return $this-&gt;dayDisplayedTo;
    }

    /**
     * Returns a reference to the event controller, creates a standard controller
     * of no controller instantiated/set.
     * @return GregorianCalendarEventController
     */
    public final function getEventController() {
        if(!$this-&gt;eventController instanceof GregorianCalendarEventController) {
            $this-&gt;eventController = new GregorianCalendarEventController();
        }
        return $this-&gt;eventController;
    }

    /**
     * Returns the date specified as today (which is normally today, but can
     * be specified for historic date referencing)
     * @param mixed $today
     */
    public final function setToday($today) {
        $today = is_null($today) ? (new $this-&gt;dateClass()) : (new $this-&gt;dateClass(strval($today)));
        if(!($today instanceof IDate)) {
            throw new Exception('Date classes used here must implement IDate');
        }
        $this-&gt;today = $today;
        $this-&gt;today-&gt;setSerial(null, null, null, 0, 0, 0);
    }

    /**
     * Sets the date specified as selected (e.g. for displaying a day calendar)
     * @return IDate
     */
    public final function setSelectedDate($selected) {
        $selected = is_null($selected) ? (new $this-&gt;dateClass()) : (new $this-&gt;dateClass(strval($selected)));
        if(!($selected instanceof IDate)) {
            throw new Exception('Date classes used here must implement IDate');
        }
        $this-&gt;dateSelected = $selected;
    }

    /**
     * Stets the date which has to be displayed. This can be a time with hours
     * minues and seconds. The renderers have to adjust the displayed interval.
     * For a month calendar, the calenar will e.g. render the month July 2010 if
     * $dateToShow is 2010-07-15 03:05:30.
     * @param mixed $dateToShow
     */
    public final function setDateToShow($dateToShow) {
        $dateToShow = !is_null($dateToShow) ? (new $this-&gt;dateClass(strval($dateToShow))) : (($this-&gt;today instanceof IDate) ?  clone $this-&gt;today : new $this-&gt;dateClass());
        if(!($dateToShow instanceof IDate)) {
            throw new Exception('Date classes used here must implement IDate');
        }
        $this-&gt;dateToShow = $dateToShow;
    }

    /**
     * Sets if the week shall start on Sunday (or Monday)
     * @param bool $bool
     */
    public final function setWeekStartsOnSunday($bool) {
        $this-&gt;weekStartsWithSunday = $bool ? true : false;
        $this-&gt;weekDayIndex = $this-&gt;weekStartsWithSunday ? array(0,1,2,3,4,5,6) : array(1,2,3,4,5,6,0);
    }

    /**
     * Sets when the day starts for the calendar
     * @param mixed $time
     */
    public final function setDayStartsAt($time) {
        $time = new $this-&gt;dateClass($time);
        $this-&gt;dayStartsAt = 3600*$time-&gt;getHour() + 60*$time-&gt;getMinute() + $time-&gt;getSecond();
    }

    /**
     * Sets when the day ends for the calendar
     * @param mixed $time
     */
    public final function setDayEndsAt($time) {
        $time = new $this-&gt;dateClass($time);
        $this-&gt;dayEndsAt = 3600*$time-&gt;getHour() + 60*$time-&gt;getMinute() + $time-&gt;getSecond();
    }

    /**
     * Every time between 00:00:00 and $time (les than $time)
     * will not be displayed
     * @param mixed $time
     */
    public final function setDayDisplayFrom($time) {
        $time = new $this-&gt;dateClass($time);
        $this-&gt;dayDisplayedFrom = 3600*$time-&gt;getHour() + 60*$time-&gt;getMinute() + $time-&gt;getSecond();
    }

    /**
     * Every time &gt; $time to '23:59:59' will not be displayed
     * @param mixed $time
     */
    public final function setDayDisplayTo($time) {
        $time = new $this-&gt;dateClass($time);
        $this-&gt;dayDisplayedTo = 3600*$time-&gt;getHour() + 60*$time-&gt;getMinute() + $time-&gt;getSecond();
    }

    /**
     * Sets a new event controller
     * @param GregorianCalendarEventController $controller
     */
    public final function setEventController($controller) {
        if(is_null($controller) || $controller instanceof GregorianCalendarEventController) {
            $this-&gt;eventController = $controller;
        } else {
            throw new Exception('Calendar event controllers must be null (to unset) or GregorianCalendarEventController');
        }
    }

    /**
     * Sets the time period of on division in a day calendar (in seconds).
     * @param int $period
     */
    public function setHourDivision($period) {
        if(!is_numeric($period)) {
            throw new Exception('Day division period must be numeric');
        } else {
            $period = intval($period);
            if($period &lt;= 0) {
                throw new Exception('Day division period must be &gt; 0');
            } else if(3600 % $period != 0) {
                throw new Exception('An special amount of division periods must fit exactly 1 hour (3600 MOD period = 0)');
            } else {
                $this-&gt;hourDivision = $period;
            }
        }
    }

    /**
     * Returns a rendered representation of a month calendar
     * @param GregorianMonthCalendarRenderer $renderer
     * @return array
     */
	public final function renderMonthCalendar(GregorianMonthCalendarRenderer $renderer) {
        if(!$renderer instanceof GregorianMonthCalendarRenderer) {
            throw new Exception('You must use a GregorianMonthCalendarRenderer to render this');
        }
        $renderer-&gt;setParent($this);
        $monthStart = new LocalDate($this-&gt;dateToShow-&gt;getYear() . &quot;-&quot; . $this-&gt;dateToShow-&gt;getMonth() . &quot;-01&quot;);
        $monthEnd   = $monthStart-&gt;getNext('month');
        $monthEnd-&gt;setTimeStamp($monthEnd-&gt;getTimeStamp()-1);
        $calStart   = $monthStart-&gt;getLast($this-&gt;weekStartsWithSunday ? 'sunday' : 'monday');
        $calEnd     = $monthEnd-&gt;getNext($this-&gt;weekStartsWithSunday ? 'sunday' : 'monday');

        if($calEnd-&gt;inWeeks() - $calStart-&gt;inWeeks() &lt; 6) {
            $calEnd = $calEnd-&gt;getNext($this-&gt;weekStartsWithSunday ? 'sunday' : 'monday');
        }
        $calEnd-&gt;setTimeStamp($calEnd-&gt;getTimeStamp()-1);

        $selected = clone $this-&gt;getSelectedDate();
        $selected-&gt;setSerial(null,null,null,0,0,0);
        $selected = $selected-&gt;getTimeStamp();

        Tracer::trace('TZ           = ' . $monthEnd-&gt;getTimeZoneName());
        Tracer::trace('Date to show = ' . $this-&gt;dateToShow . '/' . $this-&gt;dateToShow-&gt;getWeekDay());
        Tracer::trace('Month start  = ' . $monthStart . '/' . $monthStart-&gt;getWeekDay());
        Tracer::trace('Month end    = ' . $monthEnd . '/' . $monthEnd-&gt;getWeekDay());
        Tracer::trace('Cal start    = ' . $calStart . '/' . $calStart-&gt;getWeekDay());
        Tracer::trace('Cal end      = ' . $calEnd . '/' . $calEnd-&gt;getWeekDay());
        Tracer::trace('Cal selected = ' . $this-&gt;getSelectedDate() . '/' . $selected);
        Tracer::trace(&quot;Num of weeks = &quot; . ($calEnd-&gt;inWeeks() - $calStart-&gt;inWeeks()));

        $today = clone $this-&gt;today;
        $ts_s = $monthStart-&gt;getTimeStamp();
        $ts_e = $monthEnd-&gt;getTimeStamp();
        $wday = $week = 0;

        $events = array();
        $dayPeriod = 24*3600-1;

        $renderer-&gt;onStart();
        $renderer-&gt;onWeekStart(0);
        for($day = new LocalDate($calStart); $day-&gt;getTimeStamp() &lt;= $calEnd-&gt;getTimeStamp(); $day = $day-&gt;getNext('day')) {
            if($wday &gt; 6) {
                $wday = 0;
                $renderer-&gt;onWeekEnd($week++);
                $renderer-&gt;onWeekStart($week);
            }
            $events = $this-&gt;getEventController()-&gt;getEventsWhichMatch($day, $day-&gt;getTimeStamp()+$dayPeriod);
            $ts = $day-&gt;getTimeStamp();
            $renderer-&gt;onDay(clone $day, $ts &lt; $ts_s || $ts &gt; $ts_e, $ts==$selected, $events);
            $wday++;
        }
        $renderer-&gt;onEnd();
		return $renderer-&gt;getOutput();
	}

    /**
     * Returns a rendered representation of a day calendar
     * @param GregorianDayCalendarRenderer $renderer
     * @param $date=null
     */
    public final function renderDayCalendar(GregorianDayCalendarRenderer $renderer, $date=null) {
        if(!$renderer instanceof GregorianDayCalendarRenderer) {
            throw new Exception('You must use a GregorianDayCalendarRenderer to render this');
        }
        if(!is_null($date)) {
            $date = new $this-&gt;dateClass($date);
        } else {
            $date = clone $this-&gt;getSelectedDate();
        }

        $renderer-&gt;setParent($this);
        $dayStart = new $this-&gt;dateClass($date);
        $dayEnd   = new $this-&gt;dateClass($date);
        $dayStart-&gt;setSerial(null, null, null, 0, 0, 0);
        $dayEnd-&gt;setSerial(null, null, null, 23, 59, 59);
        $dayStartsAt = $dayStart-&gt;getTimeStamp() + $this-&gt;getDayStartsAt();
        $dayEndsAt   = $dayStart-&gt;getTimeStamp() + $this-&gt;getDayEndsAt();
        $displayFrom = $dayStart-&gt;getTimeStamp() + $this-&gt;getDayDisplayedFrom();
        $displayTo   = $dayStart-&gt;getTimeStamp() + $this-&gt;getDayDisplayedTo();
        $tick        = $this-&gt;getHourDivision();

        Tracer::trace('Date to show = ' . $date);
        Tracer::trace('Day start    = ' . $dayStart . '/' . $dayStart-&gt;toTimeString());
        Tracer::trace('Day end      = ' . $dayEnd . '/' . $dayEnd-&gt;toTimeString());
        Tracer::trace('Cal start    = ' . $dayStartsAt . '/' . ($this-&gt;getDayStartsAt()/3600));
        Tracer::trace('Cal end      = ' . $dayEndsAt . '/' . ($this-&gt;getDayEndsAt()/3600));

        $events = $this-&gt;getEventController()-&gt;getEventsWhichMatch($displayFrom, $displayTo);
        $map    = array();

        if(!empty($events)) {
            // Conditionize events: Sort the events by timestamp, assure that keys
            // are the the start timestamps.
            function lmb_sortCallback($a,$b) { return $a-&gt;getStart() &gt; $b-&gt;getStart() ? 1 : -1; }
            usort($events, lmb_sortCallback);
            $ev = $events;
            $events = array();
            foreach($ev as $event) {
                $events[$event-&gt;getId()] = $event;
            }
            unset($ev);

            foreach($events as $event) Tracer::trace(&quot;EVENT: $event&quot;);

            // Generate a 2D map, where the row keys are the tick timestamps
            $map = array();
            for($i=$displayFrom; $i&lt;=$displayTo; $i+=$tick) {
                $map[$i] = array();
            }

            // Place the events in the map, beginning in column 0, first all events
            // that do not overlap (to shrink the loop after that)
            $ev = $events;
            while(!empty($ev)) {
                $event = array_shift($ev);
                $row = $tick * floor($event-&gt;getStart() / $tick);

                if($row &lt; $displayFrom) {
                    $row = $displayFrom;
                    $events[$row] = $event;
                }

                $end = $tick * ceil($event-&gt;getEnd() / $tick);
                $id  = $event-&gt;getId();
                $col = 0;
                // Loop over all known columns to check if there is space ...
                while(isset($map[$row][$col])) {
                    // Check if ther is space ...
                    $fits = true;
                    for($i=$row; $i&lt;=$end; $i+=$tick) {
                        if($map[$i][$col]) {
                            // Not here, try next column
                            $fits = false;
                            break;
                        }
                    }
                    if($fits) {
                        break;
                    } else {
                        $col++;
                    }
                }
                // New column required, create it for the whole day
                if(!isset($fits) || !$fits) {
                    for($i=$displayFrom; $i&lt;=$displayTo; $i+=$tick) {
                        $map[$i][$col] = false;
                    }
                }
                // Place the event in the row and column (and the rows that the
                // event takes place, mark the start row as ID, all other as
                // true (means &quot;busy&quot;)
                $map[$row][$col] = $id;
                for($i=$row+$tick; $i&lt;$end; $i+=$tick) {
                    $map[$i][$col] = true;
                }
            }

            // Cleanup
            unset($ev, $fits, $col, $row, $id, $end, $event, $i, $key, $nCols, $nEvents);

//            // Trace the map
//            foreach($map as $key =&gt; $row) {
//                $o = strftime(&quot;%H:%M:%S&quot;, $key) . &quot; = &quot;;
//                foreach($row as $id) {
//                    if($id === true) {
//                        $o .= ' busy';
//                    } else if($id === false) {
//                        $o .= ' free';
//                    } else {
//                        $o .= &quot; &quot; . sprintf(&quot;%04d&quot;, $id);
//                    }
//                }
//                Tracer::trace_r($o);
//            }
//            unset($o, $key, $row, $id);
        }

        $renderer-&gt;onStart($date, $tick, $displayFrom, $dayStartsAt, $dayEndsAt, $displayTo, &amp;$events);
        for($time = $dayStart-&gt;getTimeStamp(); $time &lt;= $dayEnd-&gt;getTimeStamp(); $time += $tick) {
            $renderer-&gt;onTick(new $this-&gt;dateClass($time), $tick, $time &lt; $dayStartsAt || $time &gt; $dayEndsAt, $time &lt; $displayFrom || $time &gt; $displayTo, &amp;$events, &amp;$map);
        }
        $renderer-&gt;onEnd();
		return $renderer-&gt;getOutput();
    }
}
?&gt;

&lt;?php
/**
 * Gegorian Month Calendar renderer. Overwrite this class if you want to have an
 * alterantive rendered representation of the month calendar.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 * @uses GregorianCalendar
 * @uses GregorianCalendarEvent
 */
class GregorianMonthCalendarRenderer {

    /**
     * Defines the parent calendar
     * @var GregorianCalendar
     */
    private $parent = null;

    /**
     * The output of the rendering process
     * @var string
     */
    protected $output = '';

    /**
     * Constructor
     * @param GregorianCalendar $parentGregorianCalendar
     */
    public function __construct($parentGregorianCalendar = null) {
        $this-&gt;parent = $parentGregorianCalendar;
    }

    /**
     * Sets the parent of the calendar
     * @param GregorianCalendar $calendar
     */
    public final function setParent(GregorianCalendar $calendar) {
        if(!is_null($this-&gt;parent) &amp;&amp; $this-&gt;parent !== $calendar) {
            throw new Exception('You cannot re-assign the parent calendar instance');
        } else {
            $this-&gt;parent = $calendar;
        }
    }

    public final function getParent() {
        return $this-&gt;parent;
    }

    /**
     * Returns the output of the rendering process
     * @return string
     */
    public final function getOutput() {
        return $this-&gt;output;
    }

    /**
     * Callback to start the calendar - every HTML content before the weeks
     * (and days in the weeks). Normally a table, thead, th with weekday names.
     * @return void
     */
    public function onStart() {
        $date = $this-&gt;getParent()-&gt;getDateToShow();
        $o = '';
        $o .= '&lt;table class=&quot;gregorian-month-calendar&quot;&gt;';
        $o .= '&lt;caption&gt;';
        $o .= '&lt;a class=&quot;prev&quot; href=&quot;' . $_SERVER['PHP_SELF'] . '?month-calendar-show=' . $date-&gt;getLast('month')-&gt;toDateString() . '&quot;&gt;' . '&amp;nbsp;' . '&lt;/a&gt;';
        $o .= '&lt;a class=&quot;next&quot; href=&quot;' . $_SERVER['PHP_SELF'] . '?month-calendar-show=' . $date-&gt;getNext('month')-&gt;toDateString() . '&quot;&gt;' . '&amp;nbsp;' . '&lt;/a&gt;';
        $o .= '&lt;a class=&quot;title&quot;&gt;' . ucfirst(strftime(&quot;%B %Y&quot;, $date-&gt;getTimeStamp())) . '&lt;/a&gt;';
        $o .= '&lt;/caption&gt;';
        for($i=1; $i&lt;=7; $i++) {
            $o .= '&lt;col class=&quot;' . $this-&gt;getParent()-&gt;getWeekDayAbbreviation($i) .'&quot; /&gt;';
        }
        $o .= '&lt;tr&gt;';
        foreach($this-&gt;getParent()-&gt;getWeekDayIndex() as $i) {
            $o .= '&lt;th&gt;&lt;a&gt;' . strtoupper(substr(gmstrftime('%A', ($i-4)*24*3600), 0, 1)) .'&lt;/a&gt;&lt;/th&gt;';
        }
        $o .= '&lt;/tr&gt;';
        $this-&gt;output .= $o;
    }

    /**
     * All HTML to close the table, table footer etc.
     * @return void
     */
    public function onEnd() {
        $this-&gt;output .= '&lt;/table&gt;';
    }

    /**
     * Start a new week, normally a &lt;tr&gt; or an additional &lt;td&gt;&lt;/td&gt; with the
     * calendar week (e.g. CW45) etc.
     * @return void
     */
    public function onWeekStart($weekId) {
        $this-&gt;output .= '&lt;tr&gt;';
    }

    /**
     * Finish a new week, normally a &lt;/tr&gt;
     * @return void
     */
    public function onWeekEnd($weekId) {
        $this-&gt;output .= &quot;&lt;/tr&gt;\n&quot;;
    }

    /**
     * Renders a day in the week (normally a &lt;tr&gt;$dateDayOfMonthTwoDigits&lt;/tr&gt;
     * @param IDate $day
     * @param bool isPadding
     * @param bool isSelected
     * @param GregorianMonthCalendarRenderer[] $events
     * @return mixed
     */
    public function onDay(IDate $day, $isPadding, $isSelected, $events) {
        $class = array();
        $class[] = $this-&gt;getParent()-&gt;getWeekDayAbbreviation($day-&gt;getWeekDay());
        switch(Math::signz($day-&gt;getTimeStamp() - $this-&gt;getParent()-&gt;getToday()-&gt;getTimeStamp())) {
            case -1: $class[] = 'passed'; break;
            case 0 : $class[] = 'today'; break;
        }
        if(!empty($events)) $class[] = 'has-events';
        if($isPadding) $class[] = 'padding';
        if($isSelected) $class[] = 'selected';
        $class = 'class=&quot;' . implode(' ', $class) . '&quot;';
        $title = 'title=&quot;' . ucwords(strftime(&quot;%A, %B %e, %Y&quot;, $day-&gt;getTimeStamp())) . (count($events) == 0 ? '' : ' (' . count($events) . ' events)').  '&quot;';
        $date = '&lt;a href=&quot;' . $_SERVER['PHP_SELF'] . '?month-calendar-select=' . $day-&gt;toDateString() . '&quot;&gt;' . sprintf(&quot;%02d&quot;, $day-&gt;getDay()) . '&lt;/a&gt;';
        $day-&gt;isPadding = $isPadding;
        $this-&gt;output .= &quot;&lt;td $class $title&gt;$date&lt;/td&gt;&quot;;
    }
}
?&gt;

&lt;?php
/**
 * Gegorian Day Calendar renderer class. Overwrite this class if you want a
 * different rendered representation.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 * @uses GregorianCalendar
 * @uses GregorianCalendarEvent
 */
class GregorianDayCalendarRenderer {

    /**
     * Defines the parent calendar
     * @var GregorianCalendar
     */
    private $parent = null;

    /**
     * The output of the rendering process
     * @var string
     */
    protected $output = '';

    /**
     * The HTML element id of the table
     * @var string
     */
    protected $tableId = '';

    /**
     * Constructor
     * @param GregorianCalendar $parentGregorianCalendar
     */
    public function __construct($parentGregorianCalendar = null) {
        $this-&gt;parent = $parentGregorianCalendar;
    }

    /**
     * Sets the parent of the calendar
     * @param GregorianCalendar $calendar
     */
    public final function setParent(GregorianCalendar $calendar) {
        if(!is_null($this-&gt;parent) &amp;&amp; $this-&gt;parent !== $calendar) {
            throw new Exception('You cannot re-assign the parent calendar instance');
        } else {
            $this-&gt;parent = $calendar;
        }
    }

    /**
     * Returns the parent Calendar object (which the renderer is for)
     * @return GregorianCalendar
     */
    public final function getParent() {
        return $this-&gt;parent;
    }

    /**
     * Sets the id of the table that contains the day calendar
     * @param string $id
     */
    public final function setTableId($id) {
        if(empty($id)) {
            $this-&gt;tableId = '';
        } else {
            $this-&gt;tableId = trim($id);
        }
    }

    /**
     * Returns the id of the table that contains the day calendar
     * @return string
     */
    public final function getTableId() {
        return $this-&gt;tableId;
    }

    /**
     * Returns the output of the rendering process
     * @return string
     */
    public final function getOutput() {
        return $this-&gt;output;
    }

    /**
     * Callback to start the calendar - every HTML content before the weeks
     * (and days in the weeks). Normally a table, thead, th with weekday names.
     * @param IDate $date
     * @param int $tick
     * @param int $displayFrom
     * @param int $dayStartsAt
     * @param int $dayEndsAt
     * @param int $displayTo
     * @param GregorianCalendarEvent[] &amp;$events
     * @return void
     */
    public function onStart($date, $tick, $displayFrom, $dayStartsAt, $dayEndsAt, $displayTo, &amp;$events) {
        $id = empty($this-&gt;tableId) ? '' : ('id=&quot;' . str_replace('#', '', $this-&gt;tableId) . '&quot; ');
        $o = '';
        $o .= '&lt;table ' . $id . 'class=&quot;gregorian-day-calendar&quot;&gt;';
        $o .= '&lt;caption&gt;' . ucwords(strftime(&quot;%A, %B %e, %Y&quot;, $date-&gt;getTimeStamp())) . '&lt;/caption&gt;';
        $this-&gt;output .= &quot;$o\n&quot;;
    }

    /**
     * All HTML to close the table, table footer etc.
     * @return void
     */
    public function onEnd() {
        $this-&gt;output .= &quot;&lt;/table&gt;\n&quot;;
        $this-&gt;events = array();
    }

    /**
     * Renders one tick with the division period, padding means that the day
     * of the calendar has either not started yet or already ended (according
     * to $dayStartsAt and $dayEndsAt)
     * @param IDate $date
     * @param int $tick
     * @param bool $isPadding
     * @param bool $hidden
     * @param GregorianCalendarEvent[] &amp;$events
     * @param array() &amp;$eventMap
     * @return void
     */
    public function onTick(IDate $date, $tick, $isPadding, $hidden, &amp;$events, &amp;$eventMap) {
        if($hidden) return;
        $class = array();
        if($isPadding) $class[] = 'padding';
        if($date-&gt;getMinute() == 0) {
            $class[] = 'full-hour';
            $fullHourTime = sprintf(&quot;%02d:%02d&quot;, $date-&gt;getHour(), $date-&gt;getMinute());
        } else {
            $fullHourTime = '&amp;nbsp;';
        }        

        $time = sprintf(&quot;%04d-%02d-%02d %02d:%02d&quot;, $date-&gt;getYear(), $date-&gt;getMonth(), $date-&gt;getDay(), $date-&gt;getHour(), $date-&gt;getMinute());

        if(!empty($events)) {
            $ts = $date-&gt;getTimeStamp();
            $row = &amp;$eventMap[$ts];
            $td = '';
            $td .= '&lt;td class=&quot;space&quot;&gt;&lt;/td&gt;' . &quot;\t&quot;;
            foreach($row as $index =&gt; $col) {
                if($col === true) {
                    $td .= '&lt;td class=&quot;space&quot;&gt;&lt;/td&gt;' . &quot;\t&quot;;
                    $td .= '&lt;!-- rowspan --&gt;'; // event running, managed by rowspan
                } else if(is_null($col)) {
                    $td .= '&lt;!-- colspan --&gt;'; // event running, managed by colspan
                } else if($col === false) {
                    $td .= '&lt;td class=&quot;space&quot;&gt;&lt;/td&gt;' . &quot;\t&quot;;
                    $td .= '&lt;td class=&quot;data&quot;&gt;&amp;nbsp;&lt;/td&gt;'; // an empty cell
                } else if(is_numeric($col)) {
                    $td .= '&lt;td class=&quot;space&quot;&gt;&lt;/td&gt;' . &quot;\t&quot;;
                    $event = $events[$col]; // by ID

                    // $rows = (ceil($event-&gt;getEnd() / $tick) - ($ts / $tick));
                    $cols = count($row) - $index; // index starts at 0 --&gt; min cols=1, max=count(...)
                    $rows = 1;
                    for($i=$ts; $i &lt; $ts + ($tick * count($eventMap)); $i+=$tick) {
                        if(isset($eventMap[$i+$tick][$index]) &amp;&amp; $eventMap[$i+$tick][$index] === true) {
                            $rows++;
                        } else {
                            break;
                        }
                    }

                    for($i=$ts; $i &lt; $ts+($tick * $rows); $i+=$tick) {
                        if(!isset($eventMap[$i][$index+1]) || $eventMap[$i][$index+1] !== false) {
                            $cols = 1;
                            break;
                        }
                    }

                    if($cols &gt; 1) {
                        for($i=$ts; $i &lt; $ts+($tick * $rows); $i+=$tick) {
                            for($j=$index; $j &lt; count($row); $j++) {
                                $eventMap[$i][$j] = null;
                            }
                        }
                        $row[$index] = $event-&gt;getId();
                        // Take the spacers into account
                        $cols = 2*$cols-1;
                    }
                    $td .= $this-&gt;renderEvent($event, $rows, $cols);
                }
                $td .= &quot;\n&quot;;
            }
        } else {
            $td = '&lt;td class=&quot;space&quot;&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;';
        }

        $o = '';
        $o .= '&lt;tr value=&quot;' . $time . '&quot; ' .  (empty($class) ? '' : (' class=&quot;' . implode(' ', $class)) . '&quot;') . '&gt;' . &quot;\n&quot;;
        $o .= &quot;\t&quot; . '&lt;td class=&quot;time&quot; title=&quot;' . $time . '&quot; &gt;' . $fullHourTime .  '&lt;/td&gt;' . &quot;\n&quot;;
        $o .= $td;
        $o .= '&lt;/tr&gt;' . &quot;\n&quot;;
        $this-&gt;output .= $o;
    }

    /**
     * Returns a day calendar string representation of an event
     * @param GregorianCalendarEvent $event
     * @param int $rows
     * @params int $cols
     * @return string
     */
    protected function renderEvent(GregorianCalendarEvent $event, $rows, $cols) {
        $td = '&lt;td class=&quot;data event&quot; rowspan=&quot;' . $rows . '&quot; colspan=&quot;' . $cols . '&quot;&gt;';
        $td .= (method_exists($event, 'render')) ? $event-&gt;render($this) : '';
        $td .= &quot;&lt;/td&gt;&quot;;
        return $td;
    }
}
?&gt;

&lt;?php
/**
 * Base class for Gegorian Calendar Events. Events are defined using a unique
 * identifier (e.g. the primary key of a database or file), a start timestamp
 * and an end timestamp. The configuration of the GregorianCalendar class
 * decides if these timestamps are interpreted as local or UTC. Further &quot;fixed&quot;
 * properties are the type of event (e.g. &quot;meeting&quot;, &quot;festival&quot; ...) and an
 * associative data array that contains variable information about the particular
 * event. All keys in the array can be accessed like properties (if e.g.
 *  $data = array(
 *      'where' =&gt; 'there',
 *      'who' =&gt; 'me',
 *      'why' =&gt; 'because'
 *  );
 *
 * Then $event-&gt;where === 'there'.
 * Note that you should choose &quot;PHP-variable-name-conform&quot; array keys for this,
 * alternatively you can use the data getter: $event-&gt;getData(&quot;who&quot;) === &quot;me&quot;.
 *
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 */
class GregorianCalendarEvent {

    /**
     * A unique identifier of the event
     * @var string
     */
    protected $id = '';

    /**
     * The name of the event
     * @var string
     */
    protected $name = 'New Event';

    /**
     * The timestamp when the event starts
     * @var IDate
     */
    protected $start = 0;

    /**
     * The timestamp when the event ends
     * @var IDate
     */
    protected $end = 0;

    /**
     * The type of event
     * @var string
     */
    protected $type = '';

    /**
     *
     * @var array
     */
    protected $data = array();

    /**
     * GregorianCalendarEvent constructor
     * @param string $name=''
     * @param mixed $start=null
     * @param mixed $end=null
     * @param array $data=array()
     * @param string $type=null
     * @param mixed $id=null
     */
    public function __construct($name='', $start=null, $end=null, array $data=array(), $type=null, $id=null) {
        if($start instanceof IDate) {
            $start = $start-&gt;getTimeStamp();
        } else if(empty($start)) {
            $start = time();
        } else if(!is_numeric($start)) {
            throw new Exception('GregorianCalendarEvent start time must be an integer timestamp');
        }

        if($end instanceof IDate) {
            $end = $end-&gt;getTimeStamp();
        } else if(empty($end)) {
            $end = $start + 3600;
        } else if(!is_numeric($end)) {
            throw new Exception('GregorianCalendarEvent end time must be an integer timestamp');
        }

        if($end &lt;= $start) {
            throw new Exception('GregorianCalendarEvent end time must be later than the start time');
        }

        $this-&gt;start = $start;
        $this-&gt;end = $end;

        if(!empty($name)) $this-&gt;name = trim($name);
        if(!empty($id)) $this-&gt;id = $id;
        if(!empty($data)) $this-&gt;data = $data;
        if(!empty($type)) $this-&gt;type = trim($type); // implicit toString
    }

    /**
     * Returns a property if the correcponding getter (get&lt;$ame&gt;)exists OR the
     * key exsts in the data array. Returns null if none of both exists. Note
     * that values stored in the data array are inaccessible if a the key is
     * e.g. &quot;from&quot;, &quot;to&quot;, &quot;name&quot; etc.
     * @param string $name
     */
    public function __get($name) {
        if(method_exists($this, 'get' . $name)) {
            $name = 'get' . $name;
            return $this-&gt;$name();
        } else if(isset($this-&gt;data[$name])) {
            return $this-&gt;data[$name];
        } else {
            return null;
        }
    }

    /**
     * Sets a property if the correcponding setter (set&lt;$ame&gt;) exists. If no
     * setter is found, then the value will be stored in the data array. Note
     * that values stored in the data array are inaccessible if a the key is
     * e.g. &quot;from&quot;, &quot;to&quot;, &quot;name&quot; etc. The setter will be called instead.
     * @param string $name
     * @param mixed $value
     * @return void
     */
    public function __set($name,  $value) {
        if(method_exists($this, 'set' . $name)) {
            $name = 'set' . $name;
            $this-&gt;$name($value);
        } else {
            $this-&gt;data[$name] = $value;
        }
    }

    /**
     * String representation
     * @return string
     */
    public function  __toString() {
        return '['
            . strftime('%Y-%m-%d %H:%M:%S %Z', $this-&gt;start)
            . ' - '
            . strftime('%Y-%m-%d %H:%M:%S %Z', $this-&gt;end)
            . '] '
            . $this-&gt;name
            . ' {id=&quot;' . $this-&gt;id . '&quot;, type=&quot;'. $this-&gt;type
            . '&quot;, data=[&quot;' . implode('&quot;, &quot;', $this-&gt;data) . '&quot;]'
            . '}';
    }

    /**
     * Returns the id of the event
     * @return mixed
     */
    public function getId() {
        return $this-&gt;id;
    }

    /**
     * Returns the name of the event
     * @return string
     */
    public function getName() {
        return $this-&gt;name;
    }

    /**
     * Returns when the event starts
     * @return IDate
     */
    public function getStart() {
        return $this-&gt;start;
    }

    /**
     * Returns when the event ends
     * @return IDate
     */
    public function getEnd() {
        return $this-&gt;end;
    }

    /**
     * Returns the type of event
     * @return string
     */
    public function getType() {
        return $this-&gt;type;
    }

    /**
     * Returns the data array or a valued in the data array specified using
     * $key, null if not found
     * @return mixed
     */
    public function getData($key=null) {
        if(!empty($key)) {
            return isset($this-&gt;data[$key]) ? $this-&gt;data[$key] : null;
        } else {
            return $this-&gt;data;
        }
    }

    /**
     * Sets the id of the event
     * @param mixed $id
     */
    public function setId($id) {
        if(!is_scalar($id)) {
            throw new Exception('An calendar event ID cannot be an object or array');
        } else {
            $this-&gt;id = $id;
        }
    }

    /**
     * Sets the name of the event
     * @param string $name
     */
    public function setName($name) {
        $this-&gt;name = trim($name);
    }

    /**
     * Sets the timestamp when the event starts
     * @param int $from
     */
    public function setFrom($from) {
        if(is_numeric($from)) {
            $this-&gt;start = intval($from);
        } else if($from instanceof IDate) {
            $this-&gt;start = $from-&gt;getTimeStamp();
        } else {
            throw new Exception('&quot;from&quot;-data is no timestamp');
        }
    }

    /**
     * Sets when the event ends
     * @param int $to
     */
    public function setTo($to) {
        if(is_numeric($to)) {
            $this-&gt;end = intval($to);
        } else if($to instanceof IDate) {
            $this-&gt;end = $to-&gt;getTimeStamp();
        } else {
            throw new Exception('&quot;to&quot;-data is no timestamp');
        }
    }

    /**
     * Sets the event type
     * @param string $type
     */
    public function setType($type) {
        $this-&gt;type = trim(strtolower($type));
    }

    /**
     * Sets the custom/user data array
     * @param array $data
     */
    public function setData(array $data) {
        $this-&gt;data = $data;
    }
}
?&gt;

&lt;?php
/**
 * Gegorian Calendar event controller. Overwrite the methods to customize
 * the controller (load from file, from database etc. The class ist not
 * abstract because it will be instantiated by the calendar if no derived
 * controller is specified. This class methods returns empty date, which have
 * no effect.)
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 * @uses GregorianCalendarEvent
 */
class GregorianCalendarEventController {

    /**
     * Returns all events that start and end between a given period. E.g. if
     * $from and $to are the timestamps for 00:00 to 23:59:59 on the same day,
     * then the method would return all events that take place exactly on this
     * day (start and end on this day).
     * @param mixed $from
     * @param mixed $to
     * @return GregorianCalendarEvent[]
     */
    public function getEventsBetween($from, $to) {
        return array();
    }

    /**
     * Returns all events that do not end before and do not start after the given
     * time period. If e.g. $from and $to are timespamps corresponding to 00:00
     * and 23::59:59 on the same day, then this method would return all events
     * that partially or completely take place on this day.
     * @param mixed $from
     * @param mixed $to
     * @return GregorianCalendarEvent[]
     */
    public function getEventsWhichMatch($from, $to) {
        return array();
    }

    /**
     * Returns an event given by its identifier
     * @return GregorianCalendarEvent
     */
    public function getEvent($id) {
        return null;
    }

}
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>CSS recommendation</h3>
</div>
<pre class="brush: css; title: ; notranslate">
/**
 * Common CSS
 * @author Stefan Wilhelm
 * @package de.atwillys.php.swlib.css.std
 */

body, abbr, acronym, address, area, b, base, basefont, big, blockquote, br, button,
caption, center, cite, code, col, colgroup, dd, del, del, dfn, dir, div, dl, dt, em,
fieldset, font, form, h1, h2, h3, h4, h5, hr, i, iframe, img, input, ins, isindex,
kbd, label, li, map, menu, object, ol, option, p, pre, s, samp, select, small, span,
strike, strong, sub, sup, table, tbody, td, textarea, tfoot, th, thead, title, tr,
tt, u, ul, var, a, a:link, a:hover, a:visited, hr, img, code, pre, tt, samp, input,
textarea, select, option {
    font-family: &quot;Helvetica&quot;, &quot;Verdana&quot;, monospace;
    background: transparent;
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    text-decoration: none;
    color: #000000;
    font-size: 11pt;
    line-height: 13pt;
    border: none 0px;
}

body {
    padding:10px 10px 10px 10px;
}

h1 {
    font-size: 17pt;
}

h2 {
    font-size: 16pt;
}

h3 {
    font-size: 14pt;
}

h4, h5 {
    font-size: 11pt;
}

a:link, a:visited {
    font-weight: bold;
    color: #000099;
}

a:hover {
    color: #990000;
}

img {
    margin: 5px 5px 5px 5px;
}

hr {
    color:black;
    background-color:black;
    border:0px none;
    border-bottom:1px solid black;
    width:100%
}

code, pre, tt, samp {
    font-family: monospace;
    color:#000000;
}

form {
    margin:0px;
    padding:2px;
    border:0px none;
}

/**
 * CSS for Gregorian month calendars
 * @author Stefan Wilhelm
 * @package de.atwillys.php.swlib.cal
*/

table.gregorian-month-calendar {
    width: 245px;
    border-collapse: collapse;
    border-spacing: 0px;
}

table.gregorian-month-calendar caption {
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    background: #bbbbbb;
    color: #111111;
    vertical-align: middle;
    text-align: center;
    font-size: 14pt;
    line-height: 18pt;
    margin-bottom: 2px;
    background-color: #eeeeee;
    border: ridge 1px #666666;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}

table.gregorian-month-calendar caption a,
table.gregorian-month-calendar caption a:link,
table.gregorian-month-calendar caption a:visited {
    display:block;
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    background: #bbbbbb;
    color: #111111;
    vertical-align: middle;
    text-align: center;
    font-size: 14pt;
    line-height: 18pt;
    background-color: #eeeeee;
    border: ridge 1px #666666;
    border-radius: 3px;
}

table.gregorian-month-calendar caption a.prev,
table.gregorian-month-calendar caption a.prev:link,
table.gregorian-month-calendar caption a.prev:visited {
    margin: 0px 10px 0px 10px;
    border: none;
    width: 10px;
    float: left;
    background: transparent;
    background-image: url(&quot;prev.png&quot;);
    background-repeat: no-repeat;
    background-position: center center;
}

table.gregorian-month-calendar caption a.next,
table.gregorian-month-calendar caption a.next:link,
table.gregorian-month-calendar caption a.next:visited {
    margin: 0px 10px 0px 10px;
    border: none;
    width: 10px;
    float: right;
    background: transparent;
    background-image: url(&quot;next.png&quot;);
    background-repeat: no-repeat;
    background-position: center center;
}

table.gregorian-month-calendar caption a.title,
table.gregorian-month-calendar caption a.title:link,
table.gregorian-month-calendar caption a.title:visited {
    width:auto;
}

table.gregorian-month-calendar th,
table.gregorian-month-calendar td {
    vertical-align: middle;
    text-align: center;
}

table.gregorian-month-calendar th a,
table.gregorian-month-calendar th a:link,
table.gregorian-month-calendar th a:visited,
table.gregorian-month-calendar th a:hover,
table.gregorian-month-calendar td a,
table.gregorian-month-calendar td a:link,
table.gregorian-month-calendar td a:visited,
table.gregorian-month-calendar td a:hover {
    display: block;
    width: 35px;
    height: 20px;
    vertical-align: middle;
    text-align: center;
    border: ridge 1px #666666;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}

table.gregorian-month-calendar th a,
table.gregorian-month-calendar th a:link,
table.gregorian-month-calendar th a:visited {
    padding: 2px 2px 2px 2px;
    background-color: #979887;
    margin-bottom: 2px;
}

table.gregorian-month-calendar td a,
table.gregorian-month-calendar td a:link,
table.gregorian-month-calendar td a:visited,
table.gregorian-month-calendar td a:hover {
    color: #111111;
    padding: 7px 2px 7px 2px;
    background-color: #bbbbbb;
}

table.gregorian-month-calendar td.passed a,
table.gregorian-month-calendar td.passed a:link,
table.gregorian-month-calendar td.passed a:visited {
    color: #555555;
}

table.gregorian-month-calendar td.padding a,
table.gregorian-month-calendar td.padding a:link,
table.gregorian-month-calendar td.padding a:visited {
    color: #bbbbbb;
    background-color: #eeeeee;
    border: ridge 1px #bbbbbb;
}

table.gregorian-month-calendar td.sat a,
table.gregorian-month-calendar td.sat a:link,
table.gregorian-month-calendar td.sat a:visited,
table.gregorian-month-calendar td.sun a,
table.gregorian-month-calendar td.sun a:link,
table.gregorian-month-calendar td.sun a:visited {
    background-color: #eeee66;
}

table.gregorian-month-calendar td.today a,
table.gregorian-month-calendar td.today a:link,
table.gregorian-month-calendar td.today a:visited {
    color: #ff0077;
}

table.gregorian-month-calendar td.selected a,
table.gregorian-month-calendar td.selected a:link,
table.gregorian-month-calendar td.selected a:visited {
    color: #222222;
    background-color: #66ee66;
    border: ridge 1px #666666;
}

table.gregorian-month-calendar td.has-events a,
table.gregorian-month-calendar td.has-events a:link,
table.gregorian-month-calendar td.has-events a:visited {
    background-image: url('has-events.png');
    background-repeat: no-repeat;
}

table.gregorian-month-calendar td a:hover,
table.gregorian-month-calendar td.selected a:hover,
table.gregorian-month-calendar td.has-events a:hover,
table.gregorian-month-calendar td.sat a:hover,
table.gregorian-month-calendar td.sun a:hover,
table.gregorian-month-calendar td.padding a:hover {
    color: #222222;
    background-color: #44cc44;
    border: ridge 1px #666666;
}

/**
 * CSS for Gregorian day calendars
 * @author Stefan Wilhelm
 * @package de.atwillys.php.swlib.cal
*/

table.gregorian-day-calendar {
    width: 100%;
    border-collapse: collapse;
    border-spacing: 0px;
}

table.gregorian-day-calendar caption {
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    width: 99.9%;
    background: #eeeeee;
    color: #111111;
    vertical-align: middle;
    text-align: center;
    font-size: 14pt;
    line-height: 18pt;
    margin-bottom: 2px;
    border: ridge 1px #666666;
    border-radius: 3px;
    -webkit-border-radius: 3px;
    -moz-border-radius: 3px;
}

table.gregorian-day-calendar td {
    vertical-align: middle;
    padding: 0px 0px 0px 0px;
    text-align: left;
    border-top: dotted 1px #aaaaaa;
    border-bottom: dotted 1px #aaaaaa;
}

table.gregorian-day-calendar td.time {
    text-align: right;
    width: 80px;
    height: 20px;
}

table.gregorian-day-calendar tr.full-hour td.time {
    border-top: solid 1px #000000;
}

table.gregorian-day-calendar tr.padding {
    background-color: #eeeeee;
}

table.gregorian-day-calendar tr td.space {
    width: 4px;
}

table.gregorian-day-calendar tr td.data.event {
    padding: 4px 4px 4px 4px;
    vertical-align: top;
    border: 0px;
    border-collapse: collapse;
    background: #aaaaff;
    box-shadow:0 0 1px #000000;
    -webkit-box-shadow:0 0 1px #000000;
    -moz-box-shadow:0 0 1px #000000;
    border-radius: 10px;
    -webkit-border-radius: 10px;
    -moz-border-radius: 10px;
}

table.gregorian-day-calendar tr td.event div {
}
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/calendar-class-library/2010-08-02/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Growl UDP notification class</title>
		<link>http://www.atwillys.de/programming/php/growl-udp-notification-class/2010-07-10/</link>
		<comments>http://www.atwillys.de/programming/php/growl-udp-notification-class/2010-07-10/#comments</comments>
		<pubDate>Sat, 10 Jul 2010 13:24:17 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[Growl]]></category>
		<category><![CDATA[UDP]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=520</guid>
		<description><![CDATA[Growl is a well known, powerful tool for the Mac (and now for other platforms as well), which displays small notification messages on your screen. Any program can register itself as a source of messages and then send notifications. Additionally, &#8230; <a href="http://www.atwillys.de/programming/php/growl-udp-notification-class/2010-07-10/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>Growl is a well known, powerful tool for the Mac (and now for other platforms as well), which displays small notification messages on your screen. Any program can register itself as a source of messages and then send notifications. Additionally, Growl can listen to a UDP port for the same purpose. The PHP class in this article uses this remote messaging protocol for notifying local or remote computers. The Growl protocol is not really complex, but here a short overview how &#8220;UDP-Growling&#8221; works:</p>
<ul>
<li>Growl wants to know which program sent an incoming notification and which type of notification was received (a warning, error, new email, new Skype message &#8230;). This allows you to set up what to do with these messages in your Growl configuration panel.</li>
<li>Hence, there are two major message types in the UDP protocol: Registration and Notification. The registration packets register a new application and specify which types of notification are sent from this application. The notification messages contain the messages that you want to display.</li>
<li>The notification messages contain title, description, priority, and if they are sticky (keep being displayed until you click them)</li>
<li>For security reasons, Growl has a password protection for UDP remote registrations</li>
</ul>
<p>Risk a glance at the sample source code so see how it works:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
require_once(dirname(&quot;GrowlNotifier.class.php&quot;);
print '&lt;html&gt;&lt;body&gt;&lt;pre&gt;';

// Sets defaults
GrowlNotifier::config(array(
    'server' =&gt; '127.0.0.1',            // default server ip (this computer)
    'password' =&gt; '',                   // default password
    'application' =&gt; 'PhpGrowler',      // default application identifier
    'register' =&gt; true,                 // send registrations?
    'notifications' =&gt; array(           // defaule registered notifications
        'message','warning', 'error',   // default enabled notifications
        'disabled notify' =&gt; false,     // explicity disabled notification
        'enabled notify' =&gt; 'true'      // explicity enabled notification
    )
));

// Instance using default config
$growl = new GrowlNotifier();
$growl-&gt;notify('Hello Growl', &quot;This is a message for you&quot;);

// Instance using config specified in constructor
$growl = new GrowlNotifier('localhost', '');
$growl-&gt;notify('Hello localhost', &quot;This is another message for you&quot;);

print '&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;';
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
<p>To see the output of this example &#8220;out of the box&#8221;, go to your Growl settings pane under &#8220;Network&#8221;, and tick the check boxes &#8220;Listen for incoming notifications&#8221; and &#8220;Allow remote application registration&#8221;. Leave the password field blank. Well &#8211; I assume that Growl is installed and your Apache server is running on your local machine. Probably a system message &#8220;Allow incoming network transfers for GrowlHelper&#8221; will be displayed &#8211; the best is to say &#8220;always allow&#8221;.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Growl notification class
 * @package de.atwillys.php.swLib
 * @version 1.0
 * @author Stefan Wilhelm, 2010
 * @license GPL
 */
class GrowlNotifier {

    /**
     * Class configuration defaults
     * @ststicvar array
     */
    private static $config = array(
        'server' =&gt; '127.0.0.1',            // default server ip
        'password' =&gt; '',                   // default password
        'application' =&gt; 'PhpGrowler',      // default application identifier
        'register' =&gt; true,                 // send registrations?
        'notifications' =&gt; array(           // defaule registered notifications
            'message','warning', 'error',   // default enabled notifications
            //'something else' =&gt; false     // disabled notification
        )
    );

    /**
     * The version of the protocol
     * @const int
     */
    const PROTOCOL_VERSION = 1;

    /**
     * Packacke type registration
     * @const int
     */
    const TYPE_REGISTRATION = 0;

    /**
     * Packacke type notification
     * @const int
     */
    const TYPE_NOTIFICATION = 1;

    /**
     * Growl UDP port
     * @const int
     */
    const UDP_PORT = 9887;

    /**
     * The Growl server/computer IP address
     * @var string
     */
    private $server = '127.0.0.1';

    /**
     * The Growl server/computer password
     * @var string
     */
    private $password = '';

    /**
     * Application, which is sent to identify the message source
     * @var string
     */
    private $application = 'PhpGrowler';

    /**
     * Available notification types
     * @var array
     */
    private $registeredNotifications = array('message');

    /**
     * Helper to achieve that the registration is only sent once.
     * @var bool
     */
    private $hasRegistered = null;

    /**
     * Class configuration. Sets the specified config settings (merges
     * with the existing). Returns the actual configuration after the
     * new array has been merged to the defaults/previous settings.
     * @param array $config
     * @return array
     */
    public static final function config($config=array()) {
        if(!is_array($config)) {
            throw new Exception('GrowlNotifier config is no array');
        } else {
            self::$config = array_merge(self::$config, $config);
        }
        return self::$config;
    }

    /**
     * Sends an UDP application registration package to the specified growl
     * server/computer. The notifications array can be either associative
     * (key is the notification name, value is a boolean value that describes
     * that the notification is enabled) or numerical indexed (key is a number,
     * value is the notification, enabled by default true). $server is the IP
     * address of the server, $password the password of the server, $application
     * the name or slug or any string identifier of the application.
     * @param string $server
     * @param string $password
     * @param string $application
     * @param array $notifications
     */
    private static function sendUdpRegistration($server, $password, $application, array $notifications) {
        $application = utf8_encode($application);
        $encoded = $defaults = '';
        $ne = $nd = 0;
        foreach($notifications as $notification =&gt; $enabled) {
            $enabled = (bool) $enabled;
            $notification = utf8_encode(trim($notification));
            $encoded .= pack('n', strlen($notification)) . $notification;
            $ne++;
            if($enabled !== false) { $defaults .= pack('c', $ne-1); $nd++; }
        }
        $data = pack(
            'c2nc2', self::PROTOCOL_VERSION, self::TYPE_REGISTRATION,
            strlen($application), $ne, $nd
        ) . $application . $encoded . $defaults;
        $data .= pack('H32', md5($data . trim($password)));
        $sock = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        if($sock == false) {
            throw new Exception('Sending registration failed: ' . socket_strerror(socket_last_error()));
        }
        if(@socket_sendto($sock, $data, strlen($data), MSG_EOF, $server, self::UDP_PORT) &lt; 0) {
            @socket_close($sock);
            throw new Exception('Sending registration failed: ' . socket_strerror(socket_last_error()));
        }
        @socket_close($sock);
    }

    /**
     * Sends an UDP application notification package to the specified growl
     * server/computer. $server is the IP address of the server, $password
     * the password of the server, $application the name or slug or any string
     * identifier of the application, $notification one of the registered
     * notification identifiers - rest of parameters are self explaining.
     * @param string $server
     * @param string $password
     * @param string $application
     * @param string $notification
     * @param string $title=''
     * @param string $description=''
     * @param int $priority=0
     * @param bool $sticky=false
     */
    private static function sendUdpNotification($server, $password, $application, $notification, $title='',
                                                $description='', $priority=0, $sticky=false) {
        $application = utf8_encode(trim($application));
        $notification = utf8_encode(trim($notification));
        $description = utf8_encode(trim($description));
        $title = utf8_encode(trim($title));
        $priority = intval($priority);
        $data = pack('c2n5', self::PROTOCOL_VERSION, self::TYPE_NOTIFICATION,
            (2*(intval($priority) &amp; 7)) | (intval($priority) &lt; 0 ? 8 : 0) | ($sticky==true ? 256 : 0),
            strlen($notification), strlen($title), strlen($description), strlen($application)
        ) . $notification . $title . $description . $application;
        $data .= pack('H32', md5($data . trim($password)));
        $sock = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
        if($sock == false) {
            throw new Exception('Sending notification failed: ' . socket_strerror(socket_last_error()));
        }
        if(@socket_sendto($sock, $data, strlen($data), MSG_EOF, $server, self::UDP_PORT) &lt; 0) {
            @socket_close($sock);
            throw new Exception('Sending notification failed: ' . socket_strerror(socket_last_error()));
        }
        @socket_close($sock);
    }

    /**
     * Constructor
     * @param string $server
     * @param string $password
     * @param array $registeredNotifications
     */
    public function __construct($server=null, $password=null, $application=null, $registeredNotifications=array()) {
        $this-&gt;setServer(trim($server) != '' ? $server : self::$config['server']);
        $this-&gt;setPassword(!empty($password) ? $password : self::$config['password']);
        $this-&gt;setApplication(trim($application) != '' ? $application : self::$config['application']);
        $this-&gt;setRegisteredNotifications(!empty($registeredNotifications) ? $registeredNotifications : self::$config['notifications']);
    }

    /**
     * Returns the IP address of the server to send registrations/notifications to.
     * @return string
     */
    public function getServer() {
        return $this-&gt;server;
    }

    /**
     * Sets the new server IP address
     * @param string $server
     */
    public function setServer($server) {
        $this-&gt;server = trim($server);
    }

    /**
     * Returns the password of the server to send registrations/notifications to.
     * @return string
     */
    public function getPassword() {
        return $this-&gt;password;
    }

    /**
     * Sets the new server password
     * @param string $password
     */
    public function setPassword($password) {
        $this-&gt;password = strval($password);
    }

    /**
     * Returns the application identifier, under which the messages are send.
     * @return string
     */
    public function getApplication() {
        return $this-&gt;application;
    }

    /**
     * Sets the new application identified, under which messages are send.
     * @param strig $application
     */
    public function setApplication($application) {
        $this-&gt;application = trim($application);
    }

    /**
     * Returns an assoc. array containing the registered notification types
     * (or the notifications to register). Keys a the notification names, values
     * are the enabled-status (bool).
     * @return array
     */
    public function getRegisteredNotifications() {
        return $this-&gt;registeredNotifications;
    }

    /**
     * Sets the new registerd notifications..
     * Wants an assoc. array containing the registered notification types
     * (or the notifications to register). Keys a the notification names, values
     * are the enabled-status (bool). The first registered notification is the
     * default one, which will be used if the corresponding argumrnt in notify()
     * is empty.
     * @param array $notifications
     */
    public function setRegisteredNotifications($notifications) {
        if(!is_array($notifications)) {
            throw new Exception('Notifications to register must be passed as array');
        } else if(empty($notifications)) {
            throw new Exception('No notifications defined to register');
        } else {
            $sanatizedNotifications = array();
            foreach($notifications as $notification =&gt; $enabled) {
                if(is_numeric($notification) &amp;amp;&amp;amp; !is_bool($enabled)) {
                    $sanatizedNotifications[trim($enabled)] = true;
                } else {
                    $sanatizedNotifications[trim($notification)] = $enabled;
                }
            }
            $this-&gt;registeredNotifications = $sanatizedNotifications;
        }
    }

    /**
     * Sends a notification to the configured server.
     * @param string $notification
     * @param string $title
     * @param string $description=''
     * @param int $priority=0
     * @param bool $sticky=false
     */
    public function notify($title, $description='', $notification=null, $priority=0, $sticky=false) {
        // Send the registration only once
        if($this-&gt;hasRegistered !== false) {
            if(is_null($this-&gt;hasRegistered)) {
                $this-&gt;hasRegistered = self::$config['register'] ? true : false;
            }
            if($this-&gt;hasRegistered) {
                try {
                    self::sendUdpRegistration($this-&gt;getServer(), $this-&gt;getPassword(),
                            $this-&gt;getApplication(), $this-&gt;getRegisteredNotifications());
                    $this-&gt;hasRegistered = true;
                } catch(Exception $e) {
                    throw new Exception('Notification registration failed:' . $e-&gt;getMessage());
                }
            }
        }

        // Default is the first registered one ...
        if(empty($notification)) {
            foreach($this-&gt;getRegisteredNotifications() as $notification =&gt; $enabled) {
                if($enabled) break;
            }
        }

        // send the notification
        self::sendUdpNotification($this-&gt;getServer(), $this-&gt;getPassword(), $this-&gt;getApplication(),
                $notification, $title, $description, $priority, $sticky);
    }

}
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>References</h3>
</div>
<ul>
<li><a href="http://growl.info/documentation/developer/protocol.php" target="_blank">Growl Network Protocol Format</a></li>
</ul>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/growl-udp-notification-class/2010-07-10/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Framework base class: swlib</title>
		<link>http://www.atwillys.de/programming/php/framework-base-class-swlib/2010-07-06/</link>
		<comments>http://www.atwillys.de/programming/php/framework-base-class-swlib/2010-07-06/#comments</comments>
		<pubDate>Tue, 06 Jul 2010 22:47:49 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[autoload]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[swlib]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=471</guid>
		<description><![CDATA[The basic tasks that I want to have included in my scripts are collected in one frame library, the swlib. This class manages the autoloading for me and initializes error exception handling, output buffering, tracer and session. Other functions are &#8230; <a href="http://www.atwillys.de/programming/php/framework-base-class-swlib/2010-07-06/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>The basic tasks that I want to have included in my scripts are collected in one frame library, the <code>swlib</code>. This class manages the autoloading for me and initializes error exception handling, output buffering, tracer and session. Other functions are the import/export of persistent variables, as well as a safe script shutdown (means that is registers static shutdown functions that cause to print the output and trace output even if the script was stopped after an error/uncaught exception or the <code>die()</code> function &#8211; however, if the PHP parser aborts, then there is no way of catching this with no shutdown handling). The class requires the (on this page published) classes <code>EException</code>, <code>OutputBuffer</code>, <code>Tracer</code> and <code>Session</code>.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
// Before doing anything other, we include the library
include_once('./lib/swlib.class.php');

// The class source code automatically loads (requires) EException.class.php,
// Tracer.class.php, OutputBuffer.class.php and Session.class.php. These
// classes must be in the same directory.

// Start the library with configuration
swlib::start(array(
    // use output buffering, automatically start (this is default)
    'use_ob' =&gt; true,
    // use session, automatically start (this is default)
    'use_session' =&gt; true,
    // path to library var directory, we use an one one
    // The var path must be writable for the server, it is NOT the
    // directory &quot;/var&quot; on Unix-Like systems, but a directory to serialize
    // &quot;variables&quot; in.
    'var_path' =&gt; dirname(__FILE__) . '/var',
    // path to library tmp directory, we use an one one
    // The tmp path must be writable for the server
    'tmp_path' =&gt; dirname(__FILE__) . '/tmp',
    // extensions for variable export/import files (this is default)
    'var_extension' =&gt; '.tmp',
    // The include paths to search for classes
    'include_paths' =&gt; array(
        dirname(__FILE__) . '/class'
    )
));

print '&lt;html&gt;&lt;body&gt;&lt;pre&gt;';

// Print some information
print &quot;Lib path = &quot; . swlib::getLibPath() . &quot;\n&quot;;
print &quot;Tmp path = &quot; . swlib::getTmpPath() . &quot;\n&quot;;
print &quot;Var path = &quot; . swlib::getVarPath() . &quot;\n&quot;;
print &quot;\n&quot;;

print &quot;Has class 'Css' = &quot; . (swlib::hasClass('Css') ? 'yes' : 'no') . &quot;\n&quot;;
print &quot;Has class 'Csss' = &quot; . (swlib::hasClass('Csss') ? 'yes' : 'no') . &quot;\n&quot;;

// We add an include path after we configured the library. This will here throw
// an exception because the directory &quot;class2&quot; does not exist:
try {
    swlib::addIncludePath(dirname(__FILE__) . '/class2');
} catch(Exception $e) {
    print &quot;Include failed: &quot; . $e-&gt;getMessage() . &quot;\n&quot;;
}

print &quot;\n&quot;;

// We update the classes manually here, but this is normally not necessary,
// as the library updates the classes if a class is not found. If so, the
// an Exception will be thrown as well (only one time).
// In the very end, a file is created in the var path, which is read at startup
// and contains an associative array with the class names as key and the file
// path as value. Autoloading is well defined then and redundancies can be
// detected.
try {
    swlib::updateAutoloadClasses();
} catch(Exception $e) {
    print &quot;Could not update classes: Exceptoin&quot; . $e-&gt;getMessage();
    Tracer::traceException($e);
}

// Print the array with autoloadable classes, class names are the keys, class
// file paths are the values.
print &quot;Autoloadable classes = &quot; . print_r(swlib::getAutoloadableClasses(), true) . &quot;\n&quot;;

// Autoload some classes
$a = new A();
$b = new B();
$c = new C();

// This method shows the classes that were autoloaded by the swlib. This
// Does not include the EException, Tracer, OutputBuffer, Session, swlib
// because these classes are treated as library core.
print &quot;Autoloaded classes = &quot; . print_r(swlib::getAutoloadedClasses(), true) . &quot;\n&quot;;

// Let's import/export a variable to a file in the var path. The file will be
// named &quot;a.tmp&quot; if it is exported.
try {
    print &quot;Already saved dice = &quot; . print_r(swlib::importVariable('dice'), true) . &quot;\n&quot;;
} catch(Exception $e) {
    // We will get this exceotion the first time we call the script, because
    // the variable was not exported yet, the message is:
    // &quot;Import file for &quot;dice&quot; is not readable or does not exist&quot;.
    print &quot;&quot; . $e-&gt;getMessage();
}

// And here's the export to &quot;a.tmp&quot;:
swlib::exportVariable('dice', array(
    'D6'  =&gt; rand(1, 6),
    'D20' =&gt; rand(1, 20)
));

print '&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;';
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
</div>
<pre class="brush: plain; title: ; notranslate">
Lib path = /.../
Tmp path = /.../tmp
Var path = /.../var

Has class 'Css' = yes
Has class 'Csss' = no
Include failed: Directory to add to class path does not exist:/.../class2

Autoloadable classes = Array
(
    [arrayfilter] =&gt; /.../lib/ArrayFilter.class.php
    [cache] =&gt; /.../lib/Cache.class.php
    1 =&gt; /.../lib/Css.class.php
    [eexception] =&gt; /.../lib/EException.class.php
    [emailcontact] =&gt; /.../lib/EMailContact.class.php
    [filesystem] =&gt; /.../lib/FileSystem.class.php
    [httpauthentification] =&gt; /.../lib/HttpAuthentification.class.php
    [httprequest] =&gt; /.../swlib/lib/HttpRequest.class.php
    [iemailcontact] =&gt; /.../lib/IEMailContact.class.php
    [inifile] =&gt; /.../lib/IniFile.class.php
    [latexrenderer] =&gt; /.../lib/LaTexRenderer.class.php
    [mailer] =&gt; /.../lib/Mailer.class.php
    [math] =&gt; /.../lib/Math.class.php
    [mathexception] =&gt; /.../lib/MathException.class.php
    [menu] =&gt; /.../lib/Menu.class.php
    [menurenderer] =&gt; /.../lib/MenuRenderer.class.php
    [menutreerenderer] =&gt; /.../lib/MenuTreeRenderer.class.php
    [mysql] =&gt; /.../lib/MySql.class.php
    [mysqladministration] =&gt; /.../lib/MySqlAdministration.class.php
    [mysqlexception] =&gt; /.../lib/MySqlException.class.php
    [outputbuffer] =&gt; /.../lib/OutputBuffer.class.php
    [phpcode] =&gt; /.../lib/PhpCode.class.php
    [resourcefile] =&gt; /.../lib/ResourceFile.class.php
    [session] =&gt; /.../lib/Session.class.php
    [shellprocess] =&gt; /.../lib/ShellProcess.class.php
    [swlib] =&gt; /.../lib/swlib.class.php
    [tracer] =&gt; /.../lib/Tracer.class.php
    [unittest] =&gt; /.../lib/UnitTest.class.php
    [utcdate] =&gt; /.../lib/UtcDate.class.php
    [xmlconverter] =&gt; /.../lib/XmlConverter.class.php
    [zipexception] =&gt; /.../lib/ZipException.class.php
    [zipfile] =&gt; /.../lib/ZipFile.class.php
    [a] =&gt; /.../class/A.class.php
    [b] =&gt; /.../class/B.class.php
    1 =&gt; /.../class/C.class.php
)

Autoloaded classes = Array
(
    [0] =&gt; A
    [1] =&gt; B
    [2] =&gt; C
)

Already saved dice = Array
(
    [D6] =&gt; 6
    [D20] =&gt; 5
)
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
// library core includes
require_once(dirname(__FILE__) . &quot;/EException.class.php&quot;);
require_once(dirname(__FILE__) . &quot;/Tracer.class.php&quot;);
require_once(dirname(__FILE__) . &quot;/OutputBuffer.class.php&quot;);
require_once(dirname(__FILE__) . &quot;/Session.class.php&quot;);

/**
 * SW PHP Library Core class. Organizes autoloading, settings import/export,
 * and initializes all classes/modules to enable the features:
 *
 *  - Exception handling
 *  - Tracing
 *  - Session management
 *  - Output bufferung
 *  - Update check of the whole package de.atwillys.sw.php.swLib
 *
 * Furthermore, it normalizes the GET,POST, etc input according to magic quotes
 * (which are ALL removed).
 *
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2005-2010
 * @license GPL
 * @version 1.0
 * @uses Tracer
 * @uses EException
 * @uses OutputBuffer
 * @uses Session
 */
final class swlib {

    /**
     * Library configuration
     * @var array
     */
    private static $config = array(
        'use_ob' =&gt; true,           // use output buffering, automatically start
        'use_session' =&gt; true,      // use session, automatically start
        'var_path' =&gt; '',           // path to library var directory
        'tmp_path' =&gt; '',           // path to library tmp directory
        'var_extension' =&gt; '.tmp',  // extensions for variable export/import files
        'include_paths' =&gt; array(), // The include paths to search for classes
        'rm_gpc_quotes' =&gt; true,    // Removes magic_quotes_gpc stuff
        'rm_gpc_globals' =&gt; true,   // Removes old HTTP_GET_VARS and the like
        'tracer' =&gt; array(),        // Tracer configuration
        'eexception' =&gt; array(),    // EException configuration
    );

    /**
     * Own instance
     * @var swlib
     */
    private static $instance = null;

    /**
     * Registered autoloadable classes
     * @var array
     */
    private static $autoloadableClasses = array();

    /**
     * Loaded autoloadable classes
     * @var array
     */
    private static $autoloadedClasses = array();

    /**
     * Library configuration. Sets the specified config settings (merges
     * with the existing). Returns the actual configuration after the
     * new array has been merged to the defaults/previous settings.
     * @param array $config
     * @return array
     */
    public static final function config($config=array()) {
        if(!is_array($config)) {
            throw new Exception('swlib config is no array');
        } else {
            self::$config = array_merge(self::$config, $config);
        }
        return self::$config;
    }

    /**
     * Library startup, optional with setting the config. See the private
     * $self::config variable for information about whar can be configured.
     * @param array $config
     * @return void
     */
    public static final function start($config=array()) {
        if(is_null(self::$instance) &amp;&amp; !empty($config)) {
            self::config($config);
        }
        self::instance();
    }

    /**
     * Stops the library and flushes the output
     * buffers. Do not call this method, it will
     * be automatically called when the script
     * stops. (using register_shutdown_function())
     */
    public static final function stop() {
       self::$instance = null;
    }

    /**
     * Library singleton instance
     * @return SwLib
     */
    public static final function instance() {
        if(is_null(self::$instance)) {
            self::$instance = new self();
            Tracer::start(self::$config['tracer']);
        }
        return self::$instance;
    }

    /**
     * Returns the sw library path
     * @return string
     */
    public static final function getLibPath() {
        return dirname(__FILE__);
    }

    /**
     * Returns the var path
     * @return string
     */
    public static final function getVarPath() {
        return self::$config['var_path'];
    }

    /**
     * Returns the tmp path
     * @return string
     */
    public static final function getTmpPath() {
        return self::$config['tmp_path'];
    }

    /**
     * Returns registered autoload classes.
     * @return array
     */
    public static final function getAutoloadableClasses() {
        return self::$autoloadableClasses;
    }

    /**
     * Returns actual loaded autoload classes.
     * @return array
     */
    public static final function getAutoloadedClasses() {
        return self::$autoloadedClasses;
    }

    /**
     * Returns if a class exists or can be autoloaded
     * via the swlib.
     * @param string $class
     * @return bool
     */
    public static final function hasClass($class) {
        return isset(self::$autoloadableClasses[strtolower($class)]);
    }

    /**
     * Adds a directory that is recursivly searched for *.class.php files
     * in order to add the class files to the autoloaded classes
     * index. The ignore pattern is a wildcard pattern (for more pattern
     * separate with &quot;;&quot;, e.g.
     * swlib::addIncludePath(
     *      $_SERVER['DOCUMENT_ROOT'] . '/mypath',
     *      'cache;var;tmp*;session*'
     * );
     * @param string $directory
     * @param string $ignore
     */
    public static function addIncludePath($directory, $ignore='') {
        if(!is_dir($directory)) {
            throw new Exception('Directory to add to class path does not exist:' . $directory);
        } else {
            self::$config['include_paths'][$directory] = array(
                'directory' =&gt; $directory,
                'ignore' =&gt; $ignore
            );
        }
    }

    /**
     * Imports a variable value using eval() a loaded file
     * in the var_path. Throws exception if import fails.
     * @param string $name
     * @return mixed
     */
    public static final function importVariable($name) {
        $file = self::getVarPath() . '/' . strtolower(trim($name)) . self::$config['var_extension'];
        $content = @file_get_contents($file);
        if($content === false) {
            if(!is_file($file)) {
                throw new Exception('Variable import file for &quot;' . $name . '&quot; does not exist');
            } if(!is_readable($file)) {
                throw new Exception('Variable import file for &quot;' . $name . '&quot; is not readable');
            } else {
                throw new Exception('Failed to get file contents for variable ' . $name);
            }
        } else if(strlen($content) == 0) {
            return null;
        } else {
            return eval('return ' . $content . ';');
        }
    }

    /**
     * Exports a variable value using var_export() a loaded file
     * in the var_path. Throws exception if export fails.
     * @param string $name
     * @param mixed $value
     * @return void
     */
    public static final function exportVariable($name, $value) {
        $file = self::getVarPath() . '/' . strtolower(trim($name)) . self::$config['var_extension'];
        if(@file_put_contents($file, var_export($value, true)) === false) {
            Tracer::trace('Warning: Failed to export variable (&quot;' . $name . '&quot; to file &quot;' . $file . '&quot;)');
            if(!is_dir(self::getVarPath())) {
                throw new Exception('Swlib &quot;var&quot; path does not exist');
            } else if(!is_writable(self::getVarPath())) {
                throw new Exception('Swlib &quot;var&quot; path does not writable');
            } else if(is_dir($file)) {
                throw new Exception('Swlib Failed to write file in var path: ' . basename($file) . ' is already an existing directory');
            } else if(!is_file($file)) {
                throw new Exception('Swlib Failed to write file in var path: ' . basename($file));
            } else {
                throw new Exception('Swlib Failed to write file in var path: ' . basename($file) . '. Probably the file is locked at the moment');
            }
        }
    }

    /**
     * Autoloads a class
     * @param string $class
     */
    public static final function autoload($class) {
        $class = trim($class);
        if(!class_exists($class)) {
            $file = strtolower($class);

            if(!isset(self::$autoloadableClasses[$file])) {
                Tracer::trace('CLASS &quot;' . $class . '&quot; NOT FOUND, UPDATING CLASS INDEX ...');
                Tracer::backtrace();
                self::updateAutoloadClasses();
            }

            if(isset(self::$autoloadableClasses[$file])) {
                $file = self::$autoloadableClasses[$file];

                if(!is_readable($file)) {
                    if(!is_file($file)) {
                        self::updateAutoloadClasses();
                    }
                    throw new Exception(&quot;Failed to read class file: \&quot;$file\&quot; (not readable or does not exist)&quot;);
                } else {
                    try {
                        include_once $file;
                    } catch(Exception $e) {
                        Tracer::traceException($e);
                        Tracer::trace(&quot;Exception in class file '$file'&quot;);
                        throw $e;
                    }
                    self::$autoloadedClasses[] = $class;
                }
            } else {
                Tracer::trace('CLASS TO AUTOLOAD NOT FOUND: ' . $class);
                throw new Exception('Class not found: ' . $class);
            }
        } else {
            Tracer::trace('Class ' . $class . ' already included.');
        }
    }

    /**
     * Refreshes the library variable settings list of classes
     * for autoload
     * @return bool success
     */
    public static final function updateAutoloadClasses() {
        include_once('FileSystem.class.php');
        $classes = $files = $duplicates = array();
        $files = FileSystem::find(dirname(__FILE__), '*.class.php');

        if(!is_array(self::$config['include_paths'])) {
            throw new Exception('include_paths is not configured');
        }

        foreach(self::$config['include_paths'] as $acp) {
            if(is_array($acp)) {
                if(isset($acp['directory'])) {
                    $dir = $acp['directory'];
                }
                if(isset($acp['ignore'])) {
                    $ignore = $acp['ignore'];
                }
            } else {
                $dir = $acp;
                $ignore = '';
            }
            if(is_dir($dir)) {
                $files = array_merge($files, FileSystem::find($dir, '*.class.php', $ignore));
            } else {
                Tracer::trace('cannot register as class path, dir does not exist:' . $dir);
            }
        }

        foreach($files as $file) {
            $class = strtolower(basename($file, '.class.php'));
            if(isset($classes[$class]) &amp;&amp; $classes[$class] != $file) {
                $duplicates[] = $file;
            } else {
                $classes[$class] = $file;
            }
        }

        // Throw exeption if fails
        self::exportVariable('autoload_classes', $classes);

        if(count($duplicates) &gt; 0) {
            $duplicates = implode(', ', $duplicates);
            throw new Exception('Duplicate classes detected:' . $duplicates);
        }
        self::$autoloadableClasses = $classes;
        Tracer::trace_r(self::$autoloadableClasses, 'New autoload classes:');
        return true;
    }

    /**
     * Removes the GPC magic quotes, can remove deprecated HTTP_GET_VARS etc.
     * Performs this only once.
     * @param bool $removeHTTP_something=true
     */
    private static function compensateGpcMagicQuotes($removeHTTP_something=true) {
        if($removeHTTP_something) {
            foreach(array(
                'HTTP_ENV_VARS','HTTP_POST_VARS', 'HTTP_GET_VARS', 'HTTP_COOKIE_VARS',
                'HTTP_SERVER_VARS', 'HTTP_POST_FILES', 'HTTP_SESSION_VARS'
            ) as $var) {
                if(isset($GLOBALS[$var])) unset($GLOBALS[$var]);
            }
        }
        if(function_exists('get_magic_quotes_gpc') &amp;&amp; get_magic_quotes_gpc()) {
            if(!function_exists('stripslashes_r')) {
                function stripslashes_r($v)  {
                    if(!is_array($v)) return isset($v) ? stripslashes($v) : null;
                    $a = array();
                    foreach($v as $k =&gt; $t) $a[stripslashes($k)] = stripslashes_r($t);
                    return $a;
                }
            }
            $_GET = stripslashes_r($_GET);
            $_POST = stripslashes_r($_POST);
            $_COOKIE = stripslashes_r($_COOKIE);
            $_FILES = stripslashes_r($_FILES);
            $_REQUEST = stripslashes_r($_REQUEST);
            if(!$removeHTTP_something) {
                if(isset($HTTP_GET_VARS)) $HTTP_GET_VARS = &amp;$_GET;
                if(isset($HTTP_POST_VARS)) $HTTP_POST_VARS = &amp;$_POST;
                if(isset($HTTP_COOKIE_VARS)) $HTTP_COOKIE_VARS = &amp;$_COOKIE;
                if(isset($HTTP_POST_FILES)) $HTTP_POST_FILES = &amp;$_FILES;
                if(isset($HTTP_REQUEST_VARS)) $HTTP_REQUEST_VARS = &amp;$_REQUEST;
            }
        }
    }

    /**
     * Checks a (class Versioning compatible) HTTP reporitory for new versions
     * of the library. Default repository is atwilys.de
     * @param string $url
     * @param bool $returnDetails=false
     */
    public static final function checkForUpdates($url='http://www.atwillys.de/repository/swlib', $returnDetails=false) {
        try {
            $r = Versioning::checkModuleVersion('swlib', dirname(__FILE__), $url);
            return $returnDetails ? $r : $r['text'];
        } catch(Exception $e) {
            return &quot;Version check failed due to exception: &quot; . $e-&gt;getMessage();
        }
    }

    /**
     * Main library constructor
     */
    private final function __construct() {
        spl_autoload_register(array(__CLASS__, 'autoload'));
        register_shutdown_function(array(__CLASS__, &quot;stop&quot;), &quot;inside&quot;);
        if(self::$config['var_path'] == '') self::$config['var_path'] = sys_get_temp_dir();
        if(self::$config['tmp_path'] == '') self::$config['tmp_path'] = sys_get_temp_dir();

        if(!is_writable(self::$config['var_path']) || !is_writable(self::$config['tmp_path'])) {
            Tracer::disable();
            OutputBuffer::purge();
            EException::enable(false);
            header('x', true, 500);
            die('&lt;b&gt;Internal Server Error&lt;/b&gt;: swlib: var or tmp path is not writable');
        }

        try {
            self::$autoloadableClasses = self::importVariable('autoload_classes');
        } catch(Exception $e) {
            self::updateAutoloadClasses();
        }
        if(self::$config['use_ob']) OutputBuffer::start();
        if(self::$config['use_session']) Session::start();
        EException::config(self::$config['eexception']);
        EException::enable();
        if(self::$config['rm_gpc_quotes']) self::compensateGpcMagicQuotes(self::$config['rm_gpc_globals']);
    }

    /**
     * Main library cleanup, this method is even called on uncaught error or die();
     */
    public final function __destruct() {
        OutputBuffer::abort();
        Tracer::stop();
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/framework-base-class-swlib/2010-07-06/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Shell execution management class</title>
		<link>http://www.atwillys.de/programming/php/shell-execution-management-class/2010-07-05/</link>
		<comments>http://www.atwillys.de/programming/php/shell-execution-management-class/2010-07-05/#comments</comments>
		<pubDate>Mon, 05 Jul 2010 21:07:47 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[exec]]></category>
		<category><![CDATA[proc_open]]></category>
		<category><![CDATA[shell]]></category>
		<category><![CDATA[shell_exec]]></category>
		<category><![CDATA[system]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=460</guid>
		<description><![CDATA[Shell processes are normally executed using the functions exec, shell_exec, system etc &#8211; depending on which return or output is desired. proc_open and Co have the advantage that every output can be fetched: exit code, standard output (stdout) and error &#8230; <a href="http://www.atwillys.de/programming/php/shell-execution-management-class/2010-07-05/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>Shell processes are normally executed using the functions <code>exec</code>, <code>shell_exec</code>, <code>system</code> etc &#8211; depending on which return or output is desired. <code>proc_open</code> and Co have the advantage that every output can be fetched: exit code, standard output (stdout) and error output (stderr). All this is managed by the <code>ShellProcess</code> class. You can run programs, fetch the outputs just-in-time via inherited callbacks, and send text input (stdin) to the process. The script can stop the process if the browser connection is closed.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
include_once('ShellProcess.class.php');
print '&lt;html&gt;&lt;body&gt;&lt;pre&gt;';

// We overload on&lt;Something&gt; methods of the the ShellProcess class.
// These are used like event methods. In the example, we add the
// fetched text to variables and support methods to get these variables.
// I think you don't need no further description ...
class MyShellProcess extends ShellProcess {

    private $stdout = '';
    private $stderr = '';

    protected function onStdErr($text) {
        $this-&gt;stderr .= $text;
    }

    protected function onStdOut($text) {
        $this-&gt;stdout .= $text;
    }

    protected function onProcessStarted() {
        $this-&gt;stdout .= &quot;[Process started]\n\n&quot;;
    }

    protected function onProcessStopped() {
        $this-&gt;stdout .= &quot;\n\n[Process stopped]\n&quot;;
    }

    public function getOutput() {
        return $this-&gt;stdout;
    }

    public function getErrorOutput() {
        return $this-&gt;stderr;
    }
}

try {
    // Ok, lets run something:
    $sh = new MyShellProcess('ls -lsia');

    // You can set a timeout
    $sh-&gt;setTimeout(10.0);

    // That's default - if the script times out or the connection is aborted,
    // then the child process will be terminated.
    $sh-&gt;setTerminateOnAbort(true);

    // You can change the command (no need to set the command in the constructor)
    $sh-&gt;setCommand('ls -lsia *');

    // And run ...
    $sh-&gt;run();

    // Handle output ...
    if($sh-&gt;getErrorOutput() == '' &amp;&amp; $sh-&gt;getOutput() == '') {
        // We want to see if the script worked but just has no output
        print &quot;THERE IS NO OUTPUT\n&quot;;
    } else {
        // Check if we have errors outputs...
        if($sh-&gt;getOutput() != '') {
            print &quot;[STDOUT]\n&quot; . $sh-&gt;getOutput() . &quot;\n\n&quot;;
        }

        // Check if we have errors outputs...
        if($sh-&gt;getErrorOutput() != '') {
            print &quot;[STDERR]\n&quot; . $sh-&gt;getErrorOutput() . &quot;\n\n&quot;;
        }
    }

} catch(Exception $e) {
    print &quot;EXCEPTION: &quot; . $e-&gt;getMessage() . &quot;\n&quot; . $e-&gt;getTraceAsString();
}

print '&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;';
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
<p>Well, that&#8217;s context dependent, but for me it was:</p>
</div>
<pre class="brush: plain; title: ; notranslate">
[STDOUT]
[Process started]

ArrayFilter.class.php
Cache.class.php
Css.class.php
EException.class.php
EMailContact.class.php
FileSystem.class.php
HttpAuthentification.class.php
HttpRequest.class.php
IEMailContact.class.php
IniFile.class.php
LaTexRenderer.class.php
Mailer.class.php
Math.class.php
MathException.class.php
Menu.class.php
MenuRenderer.class.php
MenuTreeRenderer.class.php
MySql.class.php
MySqlAdministration.class.php
MySqlException.class.php
OutputBuffer.class.php
PhpCode.class.php
ResourceFile.class.php
Session.class.php
ShellProcess.class.php
Tracer.class.php
UnitTest.class.php
UtcDate.class.php
XmlConverter.class.php
ZipException.class.php
ZipFile.class.php
index.php
swlib.class.php

latex:
cache
index.php

pdfindex:
PaperParser.class.php
cache
index.php

[Process stopped]
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Shell execution wrapper. Provides executing a shell program, passing input
 * to the STDIN of the process (virtually typing in the console window), and
 * fetching STDOUT (normal output) and STDERR (error output). Furthermore the
 * child process is terminated if the script is finished. This prevents higher
 * cpu load for no reason. You can specify not to terminate the process as well.
 * The output is passed to the callback methods onStdOut() and onStdErr(), which
 * just print the contents. Overload these methods to handle the output yourself.
 * All overloadable methods:
 *  - onProcessStarted(): Called when the process was just started
 *  - onProcessFinished(): Called when the process exists
 *  - onProcessRunning(): Called regularely when the process is running
 *  - onStdOut(): STDOUT processing
 *  - onStdErr(): STDERR processing
 *
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2009-2010
 * @license GPL
 * @version 1.0
 */
class ShellProcess {

    /**
     * The command to be executed
     * @var string
     */
    private $command = '';

    /**
     * The command to be executed
     * @var string
     */
    private $hProcess = false;

    /**
     * Environment variables for the process
     * @var array
     */
    private $environment = null;

    /**
     * The directory to execute the program in
     * @var string
     */
    private $directory = null;

    /**
     * The pipes for stdin, stdout, stderr
     * @var array
     */
    private $pipes = array();

    /**
     * The process id of the started child process. Only valid during
     * execution (or after that if the process shall continue after
     * connection abort / timeout).
     * @var int
     */
    private $pid = -1;

    /**
     * Child process exit code, null if no exit code determined yet.
     * @var int
     */
    private $exitcode = null;

    /**
     * Process timeout
     * @var int
     */
    private $timeout = 10000;

    /**
     * If true, then the process will be stopped/closed if the http connection
     * is broken
     * @var bool
     */
    private $abortOnConnectionClosed = true;

    /**
     * If true, then the process will terminated after the connection has been
     * closed or the timeout was reached.
     * @var bool
     */
    private $killProcessOnAbort = true;

    /**
     * Contains all process IDs that have to be killed when the script
     * shuts down. The variable will be initialized when the method
     * run() is called.
     * @staticvar array
     */
    private static $processToKillOnAbort = null;

    /**
     * Overload this method to write your own STDERR handler
     * @param string $text
     */
    protected function onStdErr($text) {
        print $text;
    }

    /**
     * Overload this method to write your own STDOUT handler
     * @param string $text
     */
    protected function onStdOut($text) {
        print $text;
    }

    /**
     * Overload this to be informed when the process was started
     * @param string $text
     */
    protected function onProcessStarted() {
    }

    /**
     * Overload this to be informed when the process was stopped
     * @param string $text
     */
    protected function onProcessStopped() {
    }

    /**
     * Overload this to be informed every time the process monitoring loop
     * was executed and want to start the next cycle, but minimum after 250ms.
     * This means it is not sure that the method is called after 250ms, but
     * definitly not earlier.
     * Return false if you want to exit the loop.
     * @return bool
     */
    protected function onProcessRunning() {
    }

    /**
     * Constructor
     * @param string $command
     */
    public final function __construct($command='') {
        $this-&gt;command = trim($command);
    }

    /**
     * Destructor
     */
    public final function __destruct() {
        if(is_resource($this-&gt;hProcess)) {
            @proc_close($this-&gt;hProcess);
        }
        if(is_array($this-&gt;pipes) &amp;&amp; !empty($this-&gt;pipes)) {
            if(is_resource($this-&gt;pipes[0])) @fclose($this-&gt;pipes[0]);
            if(is_resource($this-&gt;pipes[1])) @fclose($this-&gt;pipes[1]);
            if(is_resource($this-&gt;pipes[2])) @fclose($this-&gt;pipes[2]);
        }
    }

    /**
     * Returns the command to be executed
     * @return string
     */
    public final function getCommand() {
        return $this-&gt;command;
    }

    /**
     * Sets the new command to be executed.
     * @param string $command
     */
    public final function setCommand($command) {
        $this-&gt;command = trim($command);
    }

    /**
     * Returns the timeout in seconds
     * @return double
     */
    public final function getTimeout() {
        return $this-&gt;timeout;
    }

    /**
     * Sets the new process timeout in seconds
     * @param double $seconds
     */
    public final function setTimeout($seconds) {
        if(!is_numeric($seconds)) {
            throw new Exception('Timeout must be an integer value in seconds.');
        } else {
            $this-&gt;timeout = intval($seconds);
        }
    }

    /**
     * Returns if the process has to be terminated (killed) if it still runs
     * after a timeout, break or connection abort.
     * @return bool
     */
    public final function getTerminateOnAbort() {
        return $this-&gt;killProcessOnAbort;
    }

    /**
     * Sets if the process has to be terminated (killed) if it still runs
     * after a timeout, break or connection abort.
     * @param bool $terminate
     */
    public final function setTerminateOnAbort($terminate) {
        if(!is_bool($terminate)) {
            throw new Exception('Terminate-on-abort must be a boolean value.');
        } else {
            $this-&gt;killProcessOnAbort = $terminate ? true : false;
        }
    }

    /**
     * Returns the process ID of the child process, -1 if not yet started.
     * @return int
     */
    public final function getPID() {
        return $this-&gt;pid;
    }

    /**
     * Returns the process exit code after the process was stopped, null if
     * process not started or still running.
     * @return int
     */
    public final function getExitCode() {
        return $this-&gt;exitcode;
    }

    /**
     * Returns the process working directory
     * @return string
     */
    public final function getWorkingDirectory() {
        return strval($this-&gt;directory);
    }

    /**
     * Sets the process working directory
     * @param string $dir
     */
    public final function setWorkingDirectory($dir) {
        $dir = trim($dir);
        if(!FileSystem::isDirectory($dir)) {
            throw new Exception(&quot;Cannot set working directory to nonexisting directory&quot;);
        }
        $this-&gt;directory = $dir;
    }

    /**
     * Writes a $text to the STDIN (terminal input) of the process
     * @param string $text
     */
    public final function sendKeys($text) {
        if(!is_resource($this-&gt;hProcess)) {
            throw new Exception('Shell command not running, cannot send characters to STDIN');
        } else if(!fwrite($this-&gt;pipes[0], $text)) {
            throw new Exception('Failed to write to STDIN pipe of shell command');
        }
    }

    /**
     * Terminates the process using the kill command.
     */
    public final function terminate() {
        // That's to ensure that the pid is ok
        $pid = $this-&gt;pid;
        if($pid &gt; 0) {
            Tracer::trace(&quot;KILL executed process $pid&quot;);
            @exec(&quot;ps ax -o  \&quot;%p,%P\&quot;&quot;, $return, $exitcode);
            if(is_array($return)) {
                foreach($return as $cpid) {
                    $cpid = explode(',', $cpid);
                    if(trim($cpid[1]) == $pid) {
                        Tracer::trace(&quot;KILL child process &quot; . $cpid[0]);
                        @exec('kill ' . $cpid[0] . ' 2&gt;/dev/null &gt;&amp;- &gt;/dev/null');
                    }
                }
            }
            @exec('kill ' . $pid . ' 2&gt;/dev/null &gt;&amp;- &gt;/dev/null');
        } else {
            throw new Exception('Could not fetch a valid pid to terminate process', 1001);
        }
    }

    /**
     * Executes the specified command line
     * @return void
     */
    public final function run() {

        $ignoreUserAbort = ignore_user_abort();
        if(!$this-&gt;abortOnConnectionClosed) {
            ignore_user_abort(false);
        }
        set_time_limit(60);
        ini_set('memory_limit','256M');
        if($this-&gt;command == '') {
            throw new Exception('No shell command specified to execute');
        } else {
            $descriptors = array(0 =&gt; array(&quot;pipe&quot;, &quot;r&quot;), 1 =&gt; array(&quot;pipe&quot;, &quot;w&quot;), 2 =&gt; array(&quot;pipe&quot;, &quot;w&quot;));
            $this-&gt;hProcess = proc_open(&quot;exec &quot; . $this-&gt;command, $descriptors, $this-&gt;pipes, $this-&gt;directory, $this-&gt;environment);
            if(!is_resource($this-&gt;hProcess)) {
                $exception = new Exception('Executing shell command failed');
            } else {
                $status = proc_get_status($this-&gt;hProcess);
                if(!is_array($status)) {
                    throw new Exception(&quot;Failed to determine process status of started process&quot;);
                }
                $this-&gt;pid = $status['pid'];

                if($this-&gt;abortOnConnectionClosed) {
                    if(!is_array(self::$processToKillOnAbort)) {
                        self::$processToKillOnAbort = array();
                        register_shutdown_function(array(__CLASS__, 'shutdownTermination'), 'inside');
                    }
                    self::$processToKillOnAbort[strval($this-&gt;pid)] = $this-&gt;pid;
                }

                stream_set_blocking($this-&gt;pipes[1], 0);
                stream_set_blocking($this-&gt;pipes[2], 0);
                $stdoutOpen = $stderrOpen = true;
                $timeout = microtime(true) + $this-&gt;timeout;
                $exception = null;
                $nextOnRunningInterval = 0.1;
                $nextOnRunning = microtime(true) + $nextOnRunningInterval;

                try {
                    $this-&gt;onProcessStarted();
                } catch(Exception $e) {
                    $exception = $e;
                }

                if(!$exception) {

                    while($stdoutOpen || $stderrOpen) {
                        // Check if streams have new data
                        $stream_r = array();
                        $stream_w = null;
                        $stream_x = null;
                        if($stdoutOpen) {
                            $stream_r[] = $this-&gt;pipes[1];
                        }
                        if($stderrOpen) {
                            $stream_r[] = $this-&gt;pipes[2];
                        }

                        $stream_c = stream_select($stream_r, $stream_w, $stream_x, 100);
                        if($stream_c === false) {
                            $exception = new Exception('Failed to check STDOUT and STDERR for new data');
                            break;
                        } else if($stream_c &gt; 0) {

                            // Check STDOUT for new data
                            if($stdoutOpen) {
                                if(!feof($this-&gt;pipes[1])) {
                                    $txt = fread($this-&gt;pipes[1], 4096);
                                    $l = strlen($txt);
                                    if($l &gt; 0) {
                                        try {
                                            $this-&gt;onStdOut($txt);
                                        } catch(Exception $e) {
                                            $exception = $e;
                                            break;
                                        }
                                    }
                                } else {
                                    $stdoutOpen = false;
                                }
                            }

                            // Check STDERR for new data
                            if($stderrOpen) {
                                if(!feof($this-&gt;pipes[2])) {
                                    $txt = fread($this-&gt;pipes[2], 4096);
                                    $l = strlen($txt);
                                    if($l &gt; 0) {
                                        try {
                                            $this-&gt;onStdErr($txt);
                                        } catch(Exception $e) {
                                            $exception = $e;
                                            break;
                                        }
                                    }
                                } else {
                                    $stderrOpen = false;
                                }
                            }
                        }

                        // Check if connection has been closed by the client
                        // and ignore_userabort() was set to true
                        if($this-&gt;abortOnConnectionClosed) {
                            if(connection_aborted()) {
                                $exception = new Exception('Client connection closed');
                                break;
                            }
                        }

                        // Check for script timeout
                        if($this-&gt;timeout &gt; 0) {
                            if(microtime(true) &gt; $timeout) {
                                $exception = new Exception('Script timed out');
                                break;
                            } else {
                                set_time_limit(20);
                            }
                        }

                        // Only to be sure not to block other proceses even if
                        // each loop results new data.
                        usleep(10);

                        if(microtime(true) &gt;= $nextOnRunning) {
                            $nextOnRunning = microtime(true) + $nextOnRunningInterval;
                            if($this-&gt;onProcessRunning() === false) {
                                break;
                            }
                        }

                        $status = proc_get_status($this-&gt;hProcess);
                        if(!is_array($status) || $status['running'] == false) {
                            break;
                        }
                    }
                }

                // Get the status agein
                $status = proc_get_status($this-&gt;hProcess);
                if(!is_array($status)) {
                    $exception = new Exception(&quot;Failed to determine process status&quot;);
                } else if($status['running'] == true) {
                    if($this-&gt;killProcessOnAbort) {
                        try {
                            $this-&gt;terminate();
                        } catch(Exception $e) {
                            if(! $exception instanceof Exception) {
                                $exception = $e;
                            } else {
                                // ok, do it this way, not using $previous ...
                                $exception = new Exception($exception-&gt;getMessage() . ', AND ADITIONALLY: ' . $exception-&gt;getMessage());
                            }
                        }
                    }
                }

                // Get exit code
                if(is_array($status) &amp;&amp; !$status['running']) {
                    $this-&gt;exitcode = $status['exitcode'];
                }

                // clean up
                @proc_close($this-&gt;hProcess);
                if(is_resource($this-&gt;pipes[0])) @fclose($this-&gt;pipes[0]);
                if(is_resource($this-&gt;pipes[1])) @fclose($this-&gt;pipes[1]);
                if(is_resource($this-&gt;pipes[2])) @fclose($this-&gt;pipes[2]);
                $this-&gt;hProcess = null;
                $this-&gt;pipes = array();

                try {
                    $this-&gt;onProcessStopped($this-&gt;exitcode);
                } catch(Exception $e) {
                    $exception = $e;
                }

            }

            // Unregister the process id to kill at shutdown time.
            if(isset (self::$processToKillOnAbort[strval($this-&gt;pid)])) {
                unset(self::$processToKillOnAbort[strval($this-&gt;pid)]);
            }

            // Restore the original ignore_user_abort.
            ignore_user_abort($ignoreUserAbort);

            if($exception != null) {
                throw $exception;
            }
        }
    }

    /**
     * Registered shutdown function, terminates all child processes,
     * which are registered as terminate on abort.
     * @return void
     */
    public static final function shutdownTermination() {
        if(is_array(self::$processToKillOnAbort)) {
            foreach(self::$processToKillOnAbort as $pid) {
                $return = array();
                $exitcode = -1;
                @exec(&quot;ps ax -o  \&quot;%p,%P\&quot;&quot;, $return, $exitcode);
                if(is_array($return)) {
                    foreach($return as $cpid) {
                        $cpid = explode(',', $cpid);
                        if(trim($cpid[1]) == $pid) {
                            Tracer::trace(&quot;KILL child process &quot; . $cpid[0]);
                            @exec('kill ' . $cpid[0] . ' 2&gt;/dev/null &gt;&amp;- &gt;/dev/null');
                        }
                    }
                }
                @exec('kill ' . $pid . ' 2&gt;/dev/null &gt;&amp;- &gt;/dev/null');
            }
        }
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/shell-execution-management-class/2010-07-05/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>LaTex formula rendering class</title>
		<link>http://www.atwillys.de/programming/php/latex-formula-rendering-class/2010-07-05/</link>
		<comments>http://www.atwillys.de/programming/php/latex-formula-rendering-class/2010-07-05/#comments</comments>
		<pubDate>Mon, 05 Jul 2010 19:58:58 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[latex]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=446</guid>
		<description><![CDATA[This class renders a LaTex formula to an image with transparent background. The image files are cached, so that the shell processes are not started twice if the formula was already requested &#8211; that&#8217;s good for the performance. The sample &#8230; <a href="http://www.atwillys.de/programming/php/latex-formula-rendering-class/2010-07-05/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>This class renders a LaTex formula to an image with transparent background. The image files are cached, so that the shell processes are not started twice if the formula was already requested &#8211; that&#8217;s good for the performance. The sample script returns not something like &gt;img src=&#8221;&#8230;&#8221; /%lt;, but directly the image binary. The sample source code shows how to use it. Note that this very likely won&#8217;t work if you have only a webspace. You need Latex installed, ImageMagick, and need the possibility to create restricted user accounts (for security reasons, Latex can execute shell commands). </p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
<p>I use a Mac for the development, so the paths are also MacPorts related (/opt/local/bin/something).</p>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php

// Input check
if(isset($_GET['f'])) {
    if(get_magic_quotes_gpc()) {
        $_GET['f'] = stripslashes($_GET['f']);
    }
    $_GET['f'] = trim($_GET['f'],&quot; \t\n\r&quot;);
    require_once($_SERVER['DOCUMENT_ROOT'] . 'Tracer.class.php');
    require_once($_SERVER['DOCUMENT_ROOT'] . 'ResourceFile.class.php');
    require_once($_SERVER['DOCUMENT_ROOT'] . 'LaTexRenderer.class.php');
    try {
        // Configure the renderer
        // Note that you should run this binaries with another user than your
        // normal web server, one with restricted user. I use a shell script,
        // &quot;sudo_restricted.sh&quot;, which contains a line like
        // &quot;sudo -n -H -u &lt;restricted user&gt; $1 $2 $3 $4 $5 $6 $7 $8 $9&quot;
        // The user is of cause NOT root, but a restricted used registered in
        // the sudoers file. The HOME directory of this user is the current one.
        $sudo = dirname(__FILE__) . '/sudo_restricted.sh ';
        LaTexRenderer::config(array(
            'cache_dir' =&gt; dirname(__FILE__) . '/cache',
            'latex_bin' =&gt; $sudo . '/usr/texbin/latex',
            'dvips_bin' =&gt; $sudo . '/usr/texbin/dvips',
            'convert_bin' =&gt; $sudo . '/opt/local/bin/convert',
            'cache_prefix' =&gt; 'texf_',
            'temp_dir' =&gt; '',
            'font_size' =&gt; 16,
            'packages' =&gt; array(
                'amsmath','amsfonts','amssymb','latexsym','color'
            )
        ));
        $render = new LaTexRenderer();
        $file = dirname(__FILE__) . '/cache/' . $render-&gt;renderFormulaToImage($_GET['f']);
        $rc = new ResourceFile($file, '/');
        $rc-&gt;download();
    } catch(Exception $e) {
        header('501 Internal Server Error');
    }
} else {
// Html header and footer
print &lt;&lt;&lt;HERE
    &lt;!DOCTYPE html PUBLIC &quot;-//W3C//DTD XHTML 1.1//EN&quot; &quot;http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd&quot;&gt;
    &lt;html&gt;&lt;head&gt;&lt;title&gt;LaTeX Equations and Graphics in PHP&lt;/title&gt;&lt;/head&gt;&lt;body&gt;
    &lt;pre&gt;&lt;form action=&quot;{$_SERVER['PHP_SELF']}&quot; method=&quot;get&quot;&gt;
    &lt;textarea rows=&quot;20&quot; cols=&quot;60&quot; name=&quot;f&quot;&gt;{$_GET['f']}&lt;/textarea&gt;&lt;br/&gt;
    &lt;input type=&quot;submit&quot; /&gt;&lt;/form&gt;
    &lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;
HERE;
}
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Renders LaTeX to a png image. The rendering class has an own cache based on
 * the checkums of the fomulas. If a new formula is entered specified, then the
 * rendering process will run, and create the cache file. The callback
 * onTexFilter() can be overloaded to add an own filter, e.g. to prevent too long
 * texts, shell commands or require HTTP authentication to render formulas.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2010
 * @license GPL
 * @version 1.0
 */
class LaTeXRenderer {

    /**
     * Stores the class configuration
     * @staticvar array
     */
    private static $config = array(
        'latex_bin' =&gt; '/usr/texbin/latex',
        'dvips_bin' =&gt; '/usr/texbin/dvips',
        'convert_bin' =&gt; '/opt/local/bin/convert',
        'cache_dir' =&gt; '',
        'cache_prefix' =&gt; 'texf_',
        'temp_dir' =&gt; '',
        'font_size' =&gt; 16,
        'packages' =&gt; array(
            'amsmath','amsfonts','amssymb','latexsym','color'
        )
    );

    /**
     * Sets and returns the class configuration
     * @param array $config
     * @return array
     */
    public static function config($config=null) {
        if(!empty($config)) {
            if(!is_array($config)) {
                throw new Exception('Class '. __CLASS__ . ' must be configured using an array');
            } else {
                self::$config = array_merge(self::$config, $config);
            }
        }
        return self::$config;
    }

    /**
     * Overload this function to filter the input TeX sources, or e.g. using
     * HTTP authentication to enable it. You can return a text that renders
     * an error message.
     * @param string $tex
     * @return string
     */
    protected function onTexFilter($tex) {
        return $tex;
    }

    /**
     * Constructor, automatically initializes the undefined configuration
     * if the temp_directory is empty.
     */
    public function  __construct() {
        if(empty(self::$config['cache_dir'])) self::config(array('cache_dir' =&gt; './cache'));
        if(empty(self::$config['temp_dir'])) self::config(array('temp_dir' =&gt; '/tmp'));
    }

    /**
     * Render a formula to an image
     * @return string
     */
    public function renderFormulaToImage($tex) {
        return $this-&gt;renderToImage(&quot;\\documentclass[10pt]{article}\n\$packages\\pagestyle{empty}\n\\begin{document}\n\\begin{displaymath}\n$tex\n\\end{displaymath}\n\\end{document}\n&quot;);
    }

    /**
     * Renders a LaTeX source to an image (png) if the md5 checksum of the
     * source text already exists, a cached image representation is returned.
     * The images are located in in the cache directory.
     * @param string $tex
     * @return string
     */
    private function renderToImage($tex) {
        $tex = $this-&gt;onTexFilter($tex);
        $fileName = trim(self::$config['cache_prefix'] . md5($tex));
        $cacheFilePath = self::$config['cache_dir'] . &quot;/$fileName.png&quot;;

        if(!is_file($cacheFilePath)) {
            $exception = null;
            try {
                $packages = self::$config['packages'];
                foreach($packages as $key =&gt; $package) {
                    $packages[$key] = &quot;\usepackage{&quot; . $package . &quot;}&quot;;
                }
                $packages = implode(&quot;\n&quot;, $packages);
                $font_size = self::$config['font_size'];
                $tex = str_replace('$packages', $packages, $tex);
                $current_dir = getcwd();
                $tmpDir = self::$config['temp_dir'];
                $cacheDir = self::$config['cache_dir'];

                chdir($tmpDir);
                file_put_contents(&quot;$tmpDir/$fileName.tex&quot;, $tex);

                $output = array();
                exec(self::$config['latex_bin'] . &quot; --interaction=nonstopmode $tmpDir/$fileName.tex&quot;, $output, $return_var);
                @unlink(&quot;$tmpDir/$fileName.tex&quot;);
                @unlink(&quot;$tmpDir/$fileName.aux&quot;);
                @unlink(&quot;$tmpDir/$fileName.log&quot;);

                if(!is_file(&quot;$tmpDir/$fileName.dvi&quot;)) {
                    $error = '';
                    foreach ($output as $line) {
                        $line = trim($line);
                        if(!empty($line)) {
                            if(substr($line, 0, 1) == '!') {
                                $error = trim($line, &quot;!. &quot;);
                            } else if(!empty($error)) {
                                $line = ltrim($line, ' l.0123456789');
                                throw new Exception(&quot;LaTeX: $error: $line&quot;);
                            }
                        }
                    }
                    if(!empty($error)) {
                        throw new Exception('LaTeX:' . $error);
                    } else {
                        throw new Exception('LaTeX did not generate output file');
                    }
                }

                $output = array();
                exec(self::$config['dvips_bin'] . &quot; -E $tmpDir/$fileName.dvi -o $tmpDir/$fileName.ps&quot;, $output, $return_var);
                @unlink(&quot;$tmpDir/$fileName.dvi&quot;);
                if(!is_file(&quot;$tmpDir/$fileName.ps&quot;)) {
                    throw new Exception('LaTeX/dvips conversion failed: ' . implode(&quot;\n&quot;, $output));
                }

                $output = array();
                chmod(&quot;$tmpDir/$fileName.ps&quot;, 0666);
                exec(escapeshellcmd(self::$config['convert_bin'] . &quot; -define registry:temporary-path=$tmpDir -density 120 $tmpDir/$fileName.ps $tmpDir/$fileName.png&quot;));
                @unlink(&quot;$tmpDir/$fileName.ps&quot;);
                if(is_file(&quot;$tmpDir/$fileName.png&quot;)) {
                    copy(&quot;$tmpDir/$fileName.png&quot;, &quot;$cacheDir/$fileName.png&quot;);
                    @unlink(&quot;$tmpDir/$fileName.png&quot;);
                } else {
                    throw new Exception('Conversion from to ps to png failed:' . implode(&quot;\n&quot;, $output));
                }
            } catch(Exception $e) {
                $exception = $e;
            }

            chdir($current_dir);

            // Rethrow the exception after cleanup and directory restored
            if(!empty($exception)) {
                throw $exception;
            }
        }

        return &quot;$fileName.png&quot;;
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/latex-formula-rendering-class/2010-07-05/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Ini file handling class</title>
		<link>http://www.atwillys.de/programming/php/ini-file-handling-class/2010-07-05/</link>
		<comments>http://www.atwillys.de/programming/php/ini-file-handling-class/2010-07-05/#comments</comments>
		<pubDate>Mon, 05 Jul 2010 17:06:35 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[ini file]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=436</guid>
		<description><![CDATA[Sometimes old-fashioned ini-files have to be read or written. This class is a possible interface for these tasks. I use it because I have to convert files sometimes &#8211; with case sensitive keys. Sample source code Output Class source code &#8230; <a href="http://www.atwillys.de/programming/php/ini-file-handling-class/2010-07-05/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>Sometimes old-fashioned ini-files have to be read or written. This class is a possible interface for these tasks. I use it because I have to convert files sometimes &#8211; with case sensitive keys.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
include_once('FileSystem.class.php');
include_once('IniFile.class.php');
print '&lt;html&gt;&lt;body&gt;&lt;pre&gt;';

// Path to a temporary config file
$path = FileSystem::getTempDirectory() . '/test_configfile.ini';
print &quot;Config file pPath = '$path'\n\n&quot;;

FileSystem::writeFile($path, &lt;&lt;&lt;HERE_CONFIG
[section 1]
key1.1=1000
key1.2=A text
[section 2]
key2.1=2000
key2.2=A second text
HERE_CONFIG
);

// Instantiate ...
$cfg = new IniFile($path);

print &quot;\$cfg-&gt;data = &quot; . print_r($cfg-&gt;data, true) . &quot;\n&quot;;

// Modify data ...
$cfg-&gt;data['section 3'] = array(
    'key3.1' =&gt; 2000,
    'key3.2' =&gt; 'A third text'
);

// Save the file
$cfg-&gt;save();

print &quot;New content = &quot; . FileSystem::readFile($path) . &quot;\n\n&quot;;

unlink($path);
print '&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;';
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
</div>
<pre class="brush: plain; title: ; notranslate">
Config file pPath = '/Applications/XAMPP/xamppfiles/temp/test_configfile.ini'

$cfg-&gt;data = Array
(
    [section 1] =&gt; Array
        (
            [key1.1] =&gt; 1000
            [key1.2] =&gt; A text
        )

    [section 2] =&gt; Array
        (
            [key2.1] =&gt; 2000
            [key2.2] =&gt; A second text
        )

)

New content =
 [section 1]
key1.1=1000
key1.2=A text
[section 2]
key2.1=2000
key2.2=A second text
[section 3]
key3.1=2000
key3.2=A third text
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
<p>Note that only the data are saved, comments are not read or saved. The entries and sections are case sensitive.</p>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Initialisation file access. Load/save availability. Sections and
 * keys are accessable using the public data array.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2008-2010
 * @license GPL
 * @version 1.0
 */
class IniFile {

    /**
     * The complete path to the initialisation file
     * @var string
     */
    private $file = '';

    /**
     * Stores the data in an assoc. array in form
     * $config-&gt;data['section']['key']
     * @var array
     */
    public $data = array();

    /**
     * Constructor. If a file specified with the $path argument
     * exists, then the file is directly read and parsed.
     * Throws no exception if the file does not exist. Instead,
     * the path is saged in the $file instance variable for later
     * saving.
     * @param string $path
     */
    public function __construct($path=null) {
        $this-&gt;file = $path;
        if(is_file($path)) {
            // Readfile throws exceptions
            $contents = explode(&quot;\n&quot;, str_replace(&quot;\r&quot;, &quot;\n&quot;, FileSystem::readFile($path)));
            $this-&gt;data = array();
            $actualSection = null;
            foreach($contents as $line) {
                $line = ltrim($line, &quot; \t\v&quot;);
                // Check comments
                if(empty($line)) {
                    continue;
                } else {
                    switch(substr($line,0,1)) {
                        case '*':
                        case '#':
                        case &quot;'&quot;:
                            continue;
                            break;
                        case '[':
                            $line = trim($line, &quot; \t\v[]&quot;);
                            if(!isset($this-&gt;data[$line])) {
                                $this-&gt;data[$line] = array();
                            }
                            $actualSection = &amp;$this-&gt;data[$line];
                            break;
                        default:
                            $line = explode('=', $line, 2);
                            $line[0] = trim($line[0], &quot; \t&quot;);
                            $actualSection[$line[0]] = isset($line[1]) ? $line[1] : '';
                            $actualKey = &amp;$actualSection[$line[0]];
                    }
                }
            }
        }
    }

    /**
     * Returns a new ConfigFile instance containing the loaded and parsed
     * data. Throws an exception if the file does not exist.
     * @param string $path
     */
    public static function load($path){
        $o = new self($path);
        if(!is_file($path)) {
            // The constructor loads a file of it exists in the file system
            // The load method must throw an exception if the file does not exist
            throw new Exception(&quot;Config file to load does not exist: '$path'&quot;);
        }
    }

    /**
     * Saves the content of the assoc. data array in the file specified by
     * $path, or by the already set $file instance variable. Note that comments
     * will be removed.
     * @param string $path
     */
    public function save($path=null) {
        if(!is_array($this-&gt;data)) {
            throw new Exception('Config file data must be an array of arrays (sections of key-value-pairs)');
        } else {
            $r = '';
            foreach($this-&gt;data as $section =&gt; $pairs) {
                if(is_array($pairs)) {
                    $section = trim($section);
                    $r .= &quot;[$section]\n&quot;;
                    foreach($pairs as $key =&gt; $value) {
                        if(!settype($value, 'string')) {
                            throw new Exception(&quot;There must be a string representation of the value of a config entry (value of [$section][$key] )&quot;);
                        } else {
                            $r .= trim($key) . '=' . str_replace(&quot;\r&quot;, '', str_replace(&quot;\n&quot;,'', $value)) . &quot;\n&quot;;
                        }
                    }
                } else {
                    throw new Exception('A config file section must be an assoc. array (pattern is $data[section][key]=value');
                }
            }
            $path = trim($path);
            if(strlen($path) == 0) {
                $path = $this-&gt;file;
            }
            // Will throw an exception if something's wrong
            FileSystem::writeFile($path, $r);
        }
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/ini-file-handling-class/2010-07-05/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP code info and sandboxed executing</title>
		<link>http://www.atwillys.de/programming/php/php-code-info-and-sandboxed-executing/2010-07-05/</link>
		<comments>http://www.atwillys.de/programming/php/php-code-info-and-sandboxed-executing/2010-07-05/#comments</comments>
		<pubDate>Mon, 05 Jul 2010 14:50:06 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[code info]]></category>
		<category><![CDATA[sandbox]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=396</guid>
		<description><![CDATA[This is a collection of the one and other function around PHP source code handling, including sandboxing without the need to install PEAR (I just didn&#8217;t have it &#8211; if you do, then use the Sandbox class, the function here &#8230; <a href="http://www.atwillys.de/programming/php/php-code-info-and-sandboxed-executing/2010-07-05/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>This is a collection of the one and other function around PHP source code handling, including sandboxing without the need to install PEAR (I just didn&#8217;t have it &#8211; if you do, then use the Sandbox class, the function here is slower). Other static functions are checking the overhead of a PHP file and getting some information about a PHP file, such as defined classes, functions or interfaces.</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
include_once('Tracer.class.php');
include_once('OutputBuffer.class.php');
include_once('PhpCode.class.php');
print '&lt;html&gt;&lt;body&gt;&lt;pre&gt;';

// Print out the documentation overhead, which is the
// ratio between stripped and original file.
print &quot;PhpCode::documentationOverhead(...) = &quot; . print_r(
        PhpCode::documentationOverhead(__FILE__), true
        ) . &quot;\n\n&quot;;

// This should show one class &quot;MySql&quot;. Note that this source code info
// does not work for file that are already loaded.
print &quot;PhpCode::getSourceInfo(...) = &quot; . print_r(
        PhpCode::getSourceInfo('MySql.class.php'), true
        ) . &quot;\n\n&quot;;

$result = PhpCode::evaluate(&lt;&lt;&lt;CODE_HERE
    // A new function
    function f() { return true; }

    // Some new classes etc.
    abstract class A { }
    interface I { }
    class B extends A implements I { }

    \$a = 1001;
    \$b = &quot;I'm a text&quot;;
    \$c = new Exception('Throw me');
    print &quot;\$a, \$b, &quot; . \$c-&gt;getMessage();
CODE_HERE
);

// This should show the output, an exception that occured, new added
// functions, classes and interfaces.
print &quot;PhpCode::evaluate(...) = &quot; . print_r($result, true) . &quot;\n\n&quot;;

// This is executed sandboxed, note that savemode must be switched off to
// to this.
$result = PhpCode::evaluateSandboxed(&lt;&lt;&lt;CODE_HERE
   // This is executed sandboxed
   print &quot;&lt;b&gt;Sandboxed eval ...&lt;/b&gt;&quot;;
   print &quot;Arguments = &quot;;
   print_r(\$GLOBALS);
CODE_HERE
, array(
    // Here some arguments
    '_GET' =&gt; array(
        'key1' =&gt; 'value1',
        'key2' =&gt; 'value2',
        'key3' =&gt; 'value3'
    )
));

print &quot;PhpCode::evaluateSandboxed(...) = &quot; . print_r($result, true) . &quot;\n\n&quot;;

print '&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;';
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
<p>The output on my localhost is:</p>
</div>
<pre class="brush: plain; title: ; notranslate">
PhpCode::documentationOverhead(...) = Array
(
    [original_size] =&gt; 1720
    [stripped_size] =&gt; 1208
    [relation_percent] =&gt; 142
    [error] =&gt;
)

PhpCode::getSourceInfo(...) = Array
(
    [output] =&gt;
    [classes] =&gt; Array
        (
            [171] =&gt; MySql
        )

)

PhpCode::evaluate(...) = Array
(
    [exception] =&gt;
    [interfaces] =&gt; Array
        (
            [13] =&gt; I
        )

    [classes] =&gt; Array
        (
            [172] =&gt; A
            [173] =&gt; B
        )

    [functions] =&gt; Array
        (
            [0] =&gt; f
        )

    [output] =&gt; 1001, I'm a text, Throw me
)

PhpCode::evaluateSandboxed(...) = Sandboxed eval ...Arguments = Array
(
    [_GET] =&gt; Array
        (
            [key1] =&gt; value1
            [key2] =&gt; value2
            [key3] =&gt; value3
        )

    [_SERVER] =&gt; Array
        (
            [UNIQUE_ID] =&gt; TDHrOMCoAQsAADS09t8AAAAH
            [HTTP_HOST] =&gt; htx.my
            [HTTP_ACCEPT_ENCODING] =&gt; gzip, deflate
            [HTTP_ACCEPT_LANGUAGE] =&gt; en-us
            [HTTP_USER_AGENT] =&gt; Mozilla/5.0 (Macintosh; U; ...
            [HTTP_ACCEPT] =&gt; application/xml,application/xhtml+xml,...
            [HTTP_CACHE_CONTROL] =&gt; max-age=0
            [HTTP_COOKIE] =&gt; PHPSESSID=f6bc29eab1be0c12564b3a57cb837199
            [HTTP_CONNECTION] =&gt; keep-alive
            [PATH] =&gt; /usr/bin:/bin:/usr/sbin:/sbin
            [SERVER_SIGNATURE] =&gt;
            [SERVER_SOFTWARE] =&gt; Apache/2.2.14 (Unix) DAV/2 mod_ssl/2.2.14 ...
            [SERVER_NAME] =&gt; htx.my
            [SERVER_ADDR] =&gt; ::1
            [SERVER_PORT] =&gt; 80
            [REMOTE_ADDR] =&gt; ::1
            [DOCUMENT_ROOT] =&gt; /Users/.../Sites/htxdocs
            [SERVER_ADMIN] =&gt; ...@atwillys.de
            [SCRIPT_FILENAME] =&gt; /Users/.../Sites/htxdocs/tests/index.php
            [REMOTE_PORT] =&gt; 59737
            [GATEWAY_INTERFACE] =&gt; CGI/1.1
            [SERVER_PROTOCOL] =&gt; HTTP/1.1
            [REQUEST_METHOD] =&gt; GET
            [QUERY_STRING] =&gt;
            [REQUEST_URI] =&gt; /tests/
            [SCRIPT_NAME] =&gt; /tests/index.php
            [PHP_SELF] =&gt; /tests/index.php
            [REQUEST_TIME] =&gt; 1278339896
            [argv] =&gt; Array ( )
            [argc] =&gt; 0
        )
)
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * PHP code information and execution. The evaluateSandboxed() should work
 * even if PEAR is not installed (no class SandBox), but safemode should be
 * disabled because PHP has to be executed in the shell. This means longer
 * processing time as well.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2007-2010
 * @license GPL
 * @version 1.0
 * @uses Tracer
 */
final class PhpCode implements ITracable {

    /**
     * Load and find classes, returns classes difference
     * @return array
     */
    public static function getSourceInfo($phpSourceCodeFile) {
        $c1 = get_declared_classes();
        $buffer = new OutputBuffer();
        self::trace(&quot;Load '$phpSourceCodeFile' by require&quot;);
        try {
            $t = Tracer::getTimer();
            include $phpSourceCodeFile;
            $t = Tracer::getTimer() -$t;
            self::trace(&quot;File loaded, needed time: $t ms&quot;);
        } catch(Exception $e) {
            throw new Exception(&quot;Require '$phpSourceCodeFile' failed&quot;);
        }
        $c2 = get_declared_classes();
        $output['output'] = $buffer-&gt;getOutput();
        $output['classes'] = array_diff($c2, $c1);
        self::trace(&quot;New classes: &quot;. implode(&quot;,&quot;,$output['classes']));
        unset($c1, $c2, $buffer);
        return $output;
    }

    /**
     * Execute php code and returns its print output
     * @return array
     */
    public static function evaluate($phpSourceCode) {
        self::trace(&quot;evaluating '$phpSourceCode'&quot;);
        $c0 = get_declared_classes();
        $f0 = get_defined_functions(); $f0 = $f0['user'];
        $i0 = get_declared_interfaces();
        $t0 = Tracer::getTimer();
        $out = array();
        $out['exception'] = null;
        $ob = new OutputBuffer();
        try {
            eval($phpSourceCode);
        } catch(Exception $e) {
            $out['exception'] = $e;
        }
        $t0 = Tracer::getTimer() - $t0;
        $c1 = get_declared_classes();
        $f1 = get_defined_functions(); $f1 = $f1['user'];
        $i1 = get_declared_interfaces();
        $out['interfaces'] = array_diff($i1, $i0);
        $out['classes'] = array_diff($c1, $c0);
        $out['functions'] = array_diff($f1, $f0);
        $out['output'] = $ob-&gt;getOutput();
        self::trace(&quot;New classes: &quot;. implode(&quot;,&quot;,$out['classes']));
        unset($c0, $c1, $ob);
        return $out;
    }

    /**
     * Executes a string independent from the actual script
     * using command line PHP. Takes more time than normal
     * execution, but cannot crash your script.
     * Normally a Sandbox could be used for this, but this
     * is an alternative also running without PEAR.
     * But again: SLOW.
     * @param string $phpSourceCode
     * @param array $args
     * @return string
     */
    public static function evaluateSandboxed($phpSourceCode, $args=null) {
        if(!empty($args) &amp;&amp; is_array($args)) {
            $argf = tempnam('/tmp' , 'php_evaluate_args_');
            $aop = &quot;&lt;?php &quot;;
            $globs = array();
            if(!isset($args['_SERVER'])) $args['_SERVER'] = $_SERVER;
            foreach($args as $key =&gt; $arg) {
                $aop .= '$' . $key . '=' . var_export($arg, true) . ';';
                $globs[] = '&quot;' . $key . '&quot; =&gt; &amp;$' . $key;
            }

            $aop .= &quot;\n?&gt;&quot;;
            file_put_contents($argf, $aop);
            unset($aop);
            $phpSourceCode = '&lt;?php $_SERVER=array(); $_GET=array(); $_POST=array(); $_SESSION=array(); $_FILES=array(); include(&quot;' . $argf . '&quot;); ' .
            '$GLOBALS=array(' . implode(',', $globs) . '); ' . $phpSourceCode . '?&gt;';
        }
        $file = tempnam('/tmp' , 'php_evaluate_');
        file_put_contents($file, $phpSourceCode);
        $out = shell_exec(&quot;php $file&quot;);
        @unlink($file);
        @unlink($argf);
        return $out;
    }

    /**
     * Returns an array containing information about the
     * &quot;overhead&quot; using documentation and code structuring.
     * @return array
     */
    public static function documentationOverhead($file) {
        $out = array(
            'original_size' =&gt; 0,
            'stripped_size' =&gt; 0,
            'relation_percent' =&gt; 0,
            'error' =&gt; ''
        );
        $file = trim(str_replace(&quot;\\&quot;, &quot;/&quot;, $file)); // windows, windows ;)
        if(!file_exists($file)) $file = str_replace('//','/', $_SERVER['DOCUMENT_ROOT'] . $file);
        if(!file_exists($file)) {
            $out['error'] = &quot;file does not exist&quot;;
        } else if(!is_readable($file)) {
                $out['error'] = &quot;file not readable, no access&quot;;
            } else {
                $out['stripped_size'] = strlen(php_strip_whitespace($file));
                $out['original_size'] = strlen(file_get_contents($file));
                $out['relation_percent'] = intval(100 * $out['original_size'] / $out['stripped_size']);
            }
        return $out;
    }

    /**
     * Private tracing method
     * @param string $text
     * @param int $level
     */
    protected function trace($text, $level=1) {
        if(!Tracer::tracedClass(__CLASS__)) return;
        Tracer::trace($text, $level);
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/php-code-info-and-sandboxed-executing/2010-07-05/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Class for upload/download management</title>
		<link>http://www.atwillys.de/programming/php/class-for-uploaddownload-management/2010-07-04/</link>
		<comments>http://www.atwillys.de/programming/php/class-for-uploaddownload-management/2010-07-04/#comments</comments>
		<pubDate>Sun, 04 Jul 2010 23:28:15 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[download]]></category>
		<category><![CDATA[etag]]></category>
		<category><![CDATA[http range]]></category>
		<category><![CDATA[if-modified-since]]></category>
		<category><![CDATA[upload]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=353</guid>
		<description><![CDATA[Here&#8217;s a class for managing the upload and download of files via PHP. It supports HTTP Range (for partial downloads and continuing), cache control, ETags, if-modified-since, and the usual suspects like forcing the dialog &#8220;Save file as&#8221; for mime types &#8230; <a href="http://www.atwillys.de/programming/php/class-for-uploaddownload-management/2010-07-04/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>Here&#8217;s a class for managing the upload and download of files via PHP. It supports HTTP Range (for partial downloads and continuing), cache control, ETags, if-modified-since, and the usual suspects like forcing the dialog &#8220;Save file as&#8221; for mime types that would be directly shown in the browser (like images or text files). <code>ResourceFile</code> can be used directly or extended to make own constant settings. An example how the downloading works is described in the example implementation here:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
include_once('Tracer.class.php');
include_once('FileSystem.class.php');
include_once('ResourceFile.class.php');

// We create a root path for the ResourceFile class, that makes is safer
$mediaLibraryPath = FileSystem::getTempDirectory() . '/example_medialibrary';
$file = 'file.txt';

if(!FileSystem::isDirectory($mediaLibraryPath)) {
    FileSystem::mkdir($mediaLibraryPath, 0777);
}

// Make a file we want to download ...
FileSystem::writeFile(&quot;$mediaLibraryPath/$file&quot;, &quot;That's a text...&quot;);

// Two examples how to use the class:
if($_GET['inherited']) {

    // Here is an example for setting fixed and unchangable settings by
    // deriving form the original ResourceFile class. Of cause, this is
    // not required ...
    class MyResourceFile extends ResourceFile {

        // Example: Constant root resource directory
        public function getFileRootDirectory() {
            return FileSystem::getTempDirectory() . '/example_medialibrary';
        }

        // Example: Constant expiry time
        public function getExpireTime() {
            return 3600;
        }
    }
    $rc = new MyResourceFile($file);
} else {
    $rc = new ResourceFile(
            &quot;/$file&quot;, // sub path (with respect to the root path below)
            $mediaLibraryPath, // resources root path
            null, // Expiry time in seconds, we leave it at default
            'private', // can be 'private', 'public' or 'nocache'
            false // force that the file is downloaded as file
    );

}

// Some examples what you can get for use in your source code:
$resourceName      = $rc-&gt;getName();
$filesize          = $rc-&gt;getSize();
$lastModified      = $rc-&gt;getLastModified();
$withRespectToRoot = $rc-&gt;getPath();
$wholePath         = $rc-&gt;getFilePath();
$extension         = $rc-&gt;getExtension();
$parentDirectory   = $rc-&gt;getDirectory();
$fileExists        = $rc-&gt;exists();
$mime              = $rc-&gt;getMimeType();
$etag              = $rc-&gt;getETag();
$md5               = $rc-&gt;getMD5();
$cacheControl      = $rc-&gt;getCacheControl();
$expires           = $rc-&gt;getExpireTime();
$mustDownload      = $rc-&gt;getForceSaveFile();

// Ok. let's download it
$rc-&gt;download();
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<p>These are the request/response headers:</p>
</div>
<pre class="brush: plain; title: ; notranslate">
Request Headers
  Accept:application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,...
  Cache-Control:max-age=0
  Referer:http://htx.localhost/tests/
  User-Agent:Mozilla/5.0 (Macintosh; U; Intel ...

Response Headers
  Accept-Ranges:bytes
  Cache-Control:private, must-revalidate, max-age=1278280363
  Connection:close
  Content-Disposition:inline; filename=&quot;file.txt&quot;
  Content-Length:27
  Content-Range:bytes 0-16/16
  Content-Type:text/plain
  Date:Sun, 04 Jul 2010 20:52:43 GMT
  Etag:&quot;8dc4b39f096b4b6823d6722fe0fd7bad&quot;
  Expires:Sun, 04 Jul 2010 21:52:43 GMT
  Last-Modified:Sun, 04 Jul 2010 20:52:43 GMT
  Server:Apache/2.2.14 (Unix) DAV/2 mod_ssl/2.2.14 OpenSSL ...
  X-Powered-By:PHP/5.3.1
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<p>So, and this is the class source code. Some annotations</p>
<ol>
<li>Make sure that you don&#8217;t send any text before, this would mess up the download. Use an output buffer (e.g. the class OutputBuffer in this category).</li>
<li>I extended the class, so that the checksum, date etc are saved in the database. This makes sure that you can see if the file was modified in-between (e.g. by another process or script).</li>
</ol>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Resource file class, implemented to manage downloads and uploads of files.
 * Provides HTTP-Range (partial download), ETAG, If-Modified-Since, auto
 * detection of the mime type, and cache control.
 * Base for downloadable or requestable
 * resources saved in a database or as
 * files. Provides mime type handling
 * and output with http header information.
 *
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2007-2010
 * @license GPL
 * @version 1.0
 * @uses FileSystem
 * @uses Tracer
 */
class ResourceFile implements ITracable {

    /**
     * Stores the possible mime types
     * Use getMimeType() function to
     * access the mime types, the array
     * ist filled with data on first
     * access to getMimeType().
     * @staticvar array
     */
    protected static $mimeTypes = array();

    /**
     * The full path with directory, file name
     * and extension, but not including the
     * root directory.
     * @var string
     */
    protected $path = '';

    /**
     * Defines if the &quot;Save on Disk&quot; Dialog
     * has to be shown, even if the Browser
     * can show the file.
     * @var bool
     */
    private $forceSaveFile = false;

    /**
     * Defines the cache control, acceptet is &quot;private&quot;, &quot;public&quot;, &quot;nocache&quot;,
     * Wrong values are interpreted as nocache.
     * @var string
     */
    private $cacheControl = 'private';

    /**
     * Defines if HTTP Range for partial downloading is enabled
     * @var bool
     */
    private $enableHttpRange = true;

    /**
     * The expiry time in seconds
     * @var int
     */
    private $expireTime = 3600;

    /**
     * Specifies the root path which $path is relative to.
     * This can prevent storing and reading files from a wrong
     * path. The setter of the variable is set-once.
     * @var string
     */
    private $rootDirectory = '';

    /**
     * Assigns a new resource object by specifying
     * the identifying part or the path (e.g.
     * /my_folder/my_file.ext, the real local path
     * might be $_SERVER['DOCUMENT_ROOT']/files/my_folder/my_file.ext,
     * or any prefix in a database, but this is
     * the path that identifies the resource.
     * @param string $subpath
     */
    public function __construct($subpath='', $rootPath=null, $expireTime=null, $cacheControl=null, $forceSaveFile=null) {
        if(strlen($subpath) &gt; 0) $this-&gt;path = $subpath;
        if(!empty($rootPath)) $this-&gt;rootDirectory = $rootPath;
        if(!is_null($expireTime)) $this-&gt;setExpireTime($expireTime);
        if(!empty($cacheControl)) $this-&gt;setCacheControl($cacheControl);
        if(!is_null($forceSaveFile)) $this-&gt;setForceSaveFile($forceSaveFile);
    }

    /**
     * Returns the root directory of the filesystem saved
     * files (if database files it is the cache, if 'normal'
     * files it is the local directory where the resource
     * files are saved in.
     * E.g. $_SERVER['DOCUMENT_ROOT'] . '/files'
     * @return string
     */
    public function getFileRootDirectory() {
        return $this-&gt;rootDirectory;
    }

    /**
     * Returns content expire time in seconds.
     * @return int
     */
    public function getExpireTime() {
        return $this-&gt;expireTime;
    }

    /**
     * Sets the new expiry time interval in seconds.
     * This is not an absolute timestamp (which will be
     * calculated during the download).
     * @param int $seconds
     */
    public function setExpireTime($seconds) {
        if(!is_numeric($seconds)) {
            throw new Exception(&quot;Wrong expiry time specified:$seconds&quot;);
        } else {
            $this-&gt;expireTime = intval($seconds);
        }
    }

    /**
     * Returns the path with resource name and extension
     * in the local root path.
     * @return string
     */
    public final function getPath() {
        return $this-&gt;path;
    }

    /**
     * Returns the path to the server loacal saved
     * file. This is an absolute system file patb.
     * @return string
     */
    public final function getFilePath() {
        return $this-&gt;getFileRootDirectory() . '/' . trim($this-&gt;getPath(), ' /');
    }

    /**
     * Returns the resource name.
     * @return string
     */
    public final function getName() {
        return basename($this-&gt;path);
    }

    /**
     * Returns the resource extension.
     * @return string
     */
    public final function getExtension() {
        $p = strrpos($this-&gt;getName(), '.');
        if($p === false || $p == strlen($this-&gt;getName())) {
            return '';
        } else {
            return substr($this-&gt;getName(), $p+1);
        }
    }

    /**
     * Returns the resource parent directory.
     * @return string
     */
    public final function getDirectory() {
        return dirname($this-&gt;path);
    }

    /**
     * Returns the mime type by parsing the
     * extension.
     * @return string
     */
    public function getMimeType() {
        if(empty(self::$mimeTypes)) {
            self::$mimeTypes = array(
                'txt' =&gt; 'text/plain',
                'htm' =&gt; 'text/html',
                'html' =&gt; 'text/html',
                'php' =&gt; 'text/html',
                'css' =&gt; 'text/css',
                'js' =&gt; 'application/javascript',
                'json' =&gt; 'application/json',
                'xml' =&gt; 'application/xml',
                'swf' =&gt; 'application/x-shockwave-flash',
                'flv' =&gt; 'video/x-flv',
                'png' =&gt; 'image/png',
                'jpe' =&gt; 'image/jpeg',
                'jpeg' =&gt; 'image/jpeg',
                'jpg' =&gt; 'image/jpeg',
                'gif' =&gt; 'image/gif',
                'bmp' =&gt; 'image/bmp',
                'ico' =&gt; 'image/vnd.microsoft.icon',
                'tiff' =&gt; 'image/tiff',
                'tif' =&gt; 'image/tiff',
                'svg' =&gt; 'image/svg+xml',
                'svgz' =&gt; 'image/svg+xml',
                'zip' =&gt; 'application/zip',
                'rar' =&gt; 'application/x-rar-compressed',
                'exe' =&gt; 'application/x-msdownload',
                'msi' =&gt; 'application/x-msdownload',
                'cab' =&gt; 'application/vnd.ms-cab-compressed',
                'mp3' =&gt; 'audio/mpeg',
                'qt' =&gt; 'video/quicktime',
                'mov' =&gt; 'video/quicktime',
                'pdf' =&gt; 'application/pdf',
                'psd' =&gt; 'image/vnd.adobe.photoshop',
                'ai' =&gt; 'application/postscript',
                'eps' =&gt; 'application/postscript',
                'ps' =&gt; 'application/postscript',
                'doc' =&gt; 'application/msword',
                'rtf' =&gt; 'application/rtf',
                'xls' =&gt; 'application/vnd.ms-excel',
                'ppt' =&gt; 'application/vnd.ms-powerpoint',
                'odt' =&gt; 'application/vnd.oasis.opendocument.text',
                'ods' =&gt; 'application/vnd.oasis.opendocument.spreadsheet'
            );
        }
        $ext = $this-&gt;getExtension();
        if(isset(self::$mimeTypes[$ext])) {
            return self::$mimeTypes[$ext];
        } else {
            return 'application/octet-stream';
        }
    }

    /**
     * Returns the file size in bytes
     * @return int
     */
    public function getSize() {
        $s = filesize($this-&gt;getFilePath());
        if($s===false) {
            throw new Exception('Failed to get file size');
        } else {
            return $s;
        }
    }

    /**
     * Returns the unix timestamp when the resource
     * was last modified. Overload this function for
     * database usage.
     * @return int
     */
    public function getLastModified() {
        $t = @filemtime($this-&gt;getFilePath());
        if($t === false) {
            throw new Exception('Getting file last modified time failed');
        } else {
            return $t;
        }
    }

    /**
     * Returns the md5 checksum of the resource.
     * Overload this function for database usage.
     * @return int
     */
    public function getMD5() {
        $t = md5_file($this-&gt;getFilePath());
        if($t === false) {
            throw new Exception('Getting file md5 failed');
        } else {
            return $t;
        }
    }

    /**
     * Returns if the resource exists
     * @return bool
     */
    public function exists() {
        return is_file($this-&gt;getFilePath()) ? true : false;
    }

    /**
     * Returns an ETAG for http header information.
     * @return string
     */
    public function getETag() {
        return md5($this-&gt;getFilePath() . $this-&gt;getLastModified() . $this-&gt;getSize());
    }

    /**
     * Returns if the &quot;Save file dialog&quot; is to bs
     * shown, even if the browser is able to interprete
     * the file.
     * @return bool
     */
    public function getForceSaveFile() {
        return $this-&gt;forceSaveFile;
    }

    /**
     * Sets if the &quot;Save file dialog&quot; is to bs
     * shown, even if the browser is able to interprete
     * the file.
     * @param bool $enable
     */
    public function setForceSaveFile($enable) {
        $this-&gt;forceSaveFile = $enable ? true : false;
    }

    /**
     * Returns the cache control setting. Values are &quot;public&quot;, &quot;private&quot;
     * or &quot;nocache&quot;
     * @return string
     */
    public function getCacheControl() {
        return $this-&gt;cacheControl;
    }

    /**
     * Sets the cache control setting. Values are &quot;public&quot;, &quot;private&quot;
     * or &quot;nocache&quot;
     * @return string
     */
    public function setCacheControl($ctrl) {
        $this-&gt;cacheControl = $this-&gt;cacheControl = trim(strtolower($ctrl));
        switch ($this-&gt;cacheControl) {
            case 'public':
            case 'private':
            case 'nocache':
                break;
            default:
                self::trace(&quot;Warning: wrong cache control set: $ctrl&quot;);
        }
    }

    /**
     * Moves uploaded file to a subfolder of
     * getFileRootDirectory(). $postfile is a
     * string name of a $_FILES['$postfile'] element.
     * $targetFile is subpath and file name. The extension
     * will be automatically added.
     * @param string $postfile
     * @param string $targetFile=''
     * @param bool $overwrite=false
     */
    public function upload($postfile, $targetFile='', $overwrite=false) {
        // Initialize path to &quot;invalid&quot;
        $this-&gt;path = '';
        $postfile = trim(strtolower($postfile));
        $files = array_change_key_case($_FILES);

        if(!isset($files[$postfile])) {
            throw new Exception('No file with this (input form) name posted');
        } else if(!is_array($files[$postfile])) {
            throw new Exception('The the $_POST[] array is empty.');
        } else {
            $postfile = array_change_key_case($files[$postfile], CASE_LOWER);
            unset($files);
            if($postfile['name'] == '') {
                throw new Exception('No file to upload selected');
            } else if(!isset($postfile['error']) || $postfile['error'] != 0) {
                throw new Exception('The uploaded file was not uploaded completly');
            } else if(false &amp;&amp; $postfile['size'] == 0) { // TODO: Hmm, this could also be a normal use case ... must be checked
                throw new Exception('Upload file is empty');
            } else {
                $fname = trim($postfile['name']);
                $ftype = trim($postfile['type']);
                $ftemp = trim($postfile['tmp_name']);
                $fsize = $postfile['size'];
                $fext  = FileSystem::getExtension($fname);

                if($fsize != filesize($ftemp)) {
                    throw new Exception('The file size of the uploaded file does not match the size specified by the client');
                } else {
                    if(empty($targetFile)) {
                        // Generate target file name
                        $targetFile = $this-&gt;getFileRootDirectory() . '/' . $fname;
                    } else {
                        $targetFile = $this-&gt;getFileRootDirectory() . '/' . trim($targetFile, ' /');
                    }
                    if(!$overwrite &amp;&amp; FileSystem::exists($targetFile)) {
                        throw new Exception('The file to upload already exists in the file system');
                    } else {
                        // Move uploaded file
                        if(!move_uploaded_file($ftemp, $targetFile) === null) {
                            if(!is_writable(dirname($targetFile))) {
                                throw new Exception('Target file parent directory is not writable for you.');
                            } else {
                                throw new Exception('Failed to move uploaded file');
                            }
                        } else {
                            $this-&gt;path = trim(str_replace($this-&gt;getFileRootDirectory(), '', $targetFile), ' /');
                        }
                    }
                }
            }
        }
    }

    /**
     * Binary passthrough to the http client, if no
     * file name is specified the resource file name
     * is used. $forSaveOnLocalComputer defines if
     * always the &quot;save file to&quot; is displayed in
     * the browser (attachment disposition).
     * @param string $filename
     * @param bool $forSaveOnLocalComputer
     */
    public function download($fileName='') {
        if($fileName=='') {
            $fileName = $this-&gt;getName();
        }

        // Clear all actual output buffers and drop'em, cause the session
        // to write the session file (to prevent lock errors for large
        // file downloads)
        while(ob_get_level() &gt; 0) {
            @ob_end_clean();
        }
        @session_write_close();
        @set_time_limit(5);

        if((headers_sent($hs_file, $hs_line))) {
            self::trace(&quot;Headers already sent in $hs_file:$hs_line&quot;);
            throw new Exception('Headers already sent');
        } else if(connection_status() != 0) {
            self::trace('Connection was closed');
            throw new Exception('Connection closed');
        } else if($fileName == '') {
            self::trace('File name is empty'  . $this-&gt;getFilePath());
            throw new Exception('File name is empty');
        } else if(!$this-&gt;exists()) {
            self::trace('File not found:' . $this-&gt;getFilePath());
            throw new Exception('File not found');
        } else if(substr($this-&gt;getName(), 0, 1) == '.') {
            self::trace('File is hidden (.anything)' . $this-&gt;getFilePath());
            throw new Exception('File hidden');
        } else {
            self::trace('file exists and not hidden');
            $request_headers  = array_change_key_case(apache_request_headers(), CASE_LOWER);
            $fileMdyf = $this-&gt;getLastModified();
            $fileetag = $this-&gt;getETag();

            if(isset($request_headers['if-modified-since']) &amp;&amp; @strftime(&quot;D, d M Y H:i:s&quot;, $request_headers['if-modified-since']) == $fileMdyf) {
                self::trace('if-modified-since matched, send not modified 304');
                header('Content-Length: 0', true, 304);
            } else if(isset($request_headers['if-none-match']) &amp;&amp; stripos($fileetag, $request_headers['if-none-match']) !== false) {
                self::trace('if-none-match matched, send not modified 304');
                header('Content-Length: 0', true,304);
            } else {
                $fileSize = $this-&gt;getSize();
                $fileMime = $this-&gt;getMimeType();
                $fileExpr = time() + $this-&gt;expireTime;
                $filePath = $this-&gt;getFilePath();

                header('HTTP/1.1 200 OK', true, 200);
                header('Connection: close', true);
                header(&quot;Last-Modified: &quot; . @gmdate(&quot;D, d M Y H:i:s&quot;, $fileMdyf) . &quot; GMT&quot;, true);
                header('Content-Type: ' . $fileMime, true);
                header(&quot;Content-Length: &quot; . $fileSize, true);
                header('Expires: '. @gmdate('D, d M Y H:i:s', $fileExpr) . ' GMT', true);
                header('Etag: &quot;' . $fileetag . '&quot;', true);

                switch($this-&gt;cacheControl) {
                    case 'private':
                        self::trace('Cache control: private');
                        header('Cache-Control: private, must-revalidate, max-age=' . $fileExpr, true);
                        header_remove('Pragma');
                        break;
                    case 'public':
                        self::trace('Cache control: public');
                        header('Cache-Control: public, must-revalidate, max-age=' . $fileExpr, true);
                        header_remove('Pragma');
                        break;
                    default:
                        self::trace('Cache control: nocache');
                        header('Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
                        header('Pragma: no-cache', true);
                }

                if($this-&gt;forceSaveFile) {
                    self::trace('adding the &quot;for local saving headers&quot;');
                    header('Content-Disposition: attachment; filename=&quot;'. $fileName . '&quot;', true);
                    header('Content-Transfer-Encoding: binary', true);
                } else {
                    header('Content-Disposition: inline; filename=&quot;' . $fileName . '&quot;', true);
                }

                // Prepare send
                $fs=0;
                $fe=$fileSize;

                // HTTP Range support
                if($this-&gt;enableHttpRange) {
                    if(isset($_SERVER['HTTP_RANGE']) &amp;&amp; preg_match('/bytes=\h*(\d+)-(\d*)[\D.*]?/i', $_SERVER['HTTP_RANGE'], $matches)) {
                        if(!empty($matches[0]) &amp;&amp; is_numeric($matches[0])) {
                            $fst = intval($matches[0]);
                            if(!empty($matches[1]) &amp;&amp; is_numeric($matches[1])) $fet=intval($matches[1]);
                            if($fst &gt;= 0 &amp;&amp; $fet &gt; $fst &amp;&amp; $fet &lt;= $fileSize) {
                                header('HTTP/1.1 206 Partial Content', true, 206);
                                $fs = $fst;
                                $fe = $fet;
                            }
                        }
                    }
                    header('Accept-Ranges: bytes', true);
                    header('Content-Length:'.($fe-$fs), true);
                    header(&quot;Content-Range: bytes $fs-$fe/$fileSize&quot;, true);
                }

                // Send the file
                if(!($pfile=@fopen($filePath,'rb'))) {
                    header(&quot;HTTP/1.1 505 Internal server error&quot;, true, 505);
                    throw new Exception('Failed to open file for range download: ' . $filePath);
                } else {
                    $fileChunkSize = 1024*16;
                    self::trace('passing file ...');
                    @fseek($pfile,0,0);
                    $fp=$fs;
                    fseek($pfile,$fs,0);
                    while(!feof($pfile) &amp;&amp; $fp &lt; $fe &amp;&amp; (connection_status() == NORMAL)) {
                        print @fread($pfile, min($fileChunkSize, $fe-$fp));
                        $fp+=$fileChunkSize;
                        @set_time_limit(3);
                    }
                }
            }
        }
    }

    /**
     * Private tracing method
     * @param string $text
     * @param int $level
     */
    protected function trace($text, $level=1) {
        if(!Tracer::tracedClass(__CLASS__)) return;
        Tracer::trace($text, $level);
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/class-for-uploaddownload-management/2010-07-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>FileSystem class</title>
		<link>http://www.atwillys.de/programming/php/filesystem-class/2010-07-04/</link>
		<comments>http://www.atwillys.de/programming/php/filesystem-class/2010-07-04/#comments</comments>
		<pubDate>Sun, 04 Jul 2010 20:11:42 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[delete]]></category>
		<category><![CDATA[file system]]></category>
		<category><![CDATA[find recursive]]></category>
		<category><![CDATA[read]]></category>
		<category><![CDATA[write]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=347</guid>
		<description><![CDATA[Well, there is not much to say about it. Its mainly a wrapper for the normal file system functions of PHP, except that it works with exceptions and has recursive functions. Risk a glance what you can do with it. &#8230; <a href="http://www.atwillys.de/programming/php/filesystem-class/2010-07-04/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>Well, there is not much to say about it. Its mainly a wrapper for the normal file system functions of PHP, except that it works with exceptions and has recursive functions. Risk a glance what you can do with it. The usage example is here:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
print &quot;&lt;html&gt;&lt;body&gt;&lt;pre&gt;&quot;;
include_once('FileSystem.class.php');

try {
    $tmpDir = FileSystem::getTempDirectory() . '/testDir';
    $file0  = &quot;$tmpDir/file0.txt&quot;;

    print &quot;Temporaries directory = &quot; . FileSystem::getTempDirectory() . &quot;\n&quot;;
    print &quot;My Temporaries directory = $tmpDir\n&quot;;
    print &quot;\n&quot;;

    try {
        print &quot;Create my temp dir ...\n&quot;;
        // Throws exception if already exists
        FileSystem::mkdir($tmpDir, 0777);
    } catch(Exception $e) {
        print &quot;Exception: &quot; . $e-&gt;getMessage() . &quot;\n&quot;;
    }

    print &quot;Write file ...\n&quot;;
    FileSystem::writeFile($file0, 'test data');

    print &quot;Read file ...\n&quot;;
    print &quot;Written was: \&quot;&quot; . FileSystem::readFile($file0) . &quot;\&quot;\n&quot; ;

    print &quot;File0 = $file0\n&quot;;
    print &quot;File0 dirname = &quot; . FileSystem::getDirname($file0) . &quot;\n&quot;;
    print &quot;File0 basename = &quot; . FileSystem::getBasename($file0) . &quot;\n&quot;;
    print &quot;File0 extension = &quot; . FileSystem::getExtension($file0) . &quot;\n&quot;;
    print &quot;File0 exists? &quot; . (FileSystem::exists($file0) ? &quot;yes&quot; : &quot;no&quot;) . &quot;\n&quot;;
    print &quot;File0 directory? &quot; . (FileSystem::isDirectory($file0) ? &quot;yes&quot; : &quot;no&quot;) . &quot;\n&quot;;
    print &quot;File0 file? &quot; . (FileSystem::isFile($file0) ? &quot;yes&quot; : &quot;no&quot;) . &quot;\n&quot;;
    print &quot;File0 link? &quot; . (FileSystem::isLink($file0) ? &quot;yes&quot; : &quot;no&quot;) . &quot;\n&quot;;
    print &quot;File0 readable? &quot; . (FileSystem::isReadable($file0) ? &quot;yes&quot; : &quot;no&quot;) . &quot;\n&quot;;
    print &quot;File0 writable? &quot; . (FileSystem::isWritable($file0) ? &quot;yes&quot; : &quot;no&quot;) . &quot;\n&quot;;

    // Find files recursively ...
    print &quot;Write other files ...\n&quot;;
    foreach(array(&quot;file1&quot;, &quot;file2&quot;, &quot;file3&quot;, &quot;file4&quot;) as $file) {
        FileSystem::writeFile(&quot;$tmpDir/$file&quot;, 'test');
    }
    FileSystem::mkdir(&quot;$tmpDir/subdir&quot;);
    foreach(array(&quot;file1&quot;, &quot;file2&quot;, &quot;file3&quot;, &quot;file4&quot;) as $file) {
        FileSystem::writeFile(&quot;$tmpDir/subdir/$file&quot;, 'test');
    }

    print &quot;Find * in tmpdir = &quot; . print_r(FileSystem::find($tmpDir, '*'), true) . &quot;\n&quot;;

    // Remove temp directory recursively ...
    print &quot;Delete my temp dir\n&quot;;
    FileSystem::delete($tmpDir);

} catch(Exception $e) {
    print &quot;Exception: &quot; . $e-&gt;getMessage() . &quot;\n&quot; . $e-&gt;getTraceAsString();
}

print &quot;&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&quot;;
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * FileSystem operations (in general wrappers for already existing PHP functions)
 * with exceptions. Implemented static functions are e.g. checking if a file,
 * directory, link exists, recursively find files and folders with filtering,
 * single command file I/O, file basename, dirname, extension, and retrieving
 * system directories and temporary files.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2007-2010
 * @license GPL
 * @version 1.0
 * @uses FileSystemException
 */
class FileSystem {

    /**
     * Searches for recursivly files and/or directories excluding defined whildcard
     * patterns (separated with &quot;;&quot;, e.g. &quot;*.ignore1;*.ignore2&quot;.
     * @param string $directory
     * @param string $pattern
     * @param string $ignore
     * @param bool $onlyFiles
     * @return array
     */
    public static final function find($directory, $pattern='*', $ignore='', $onlyFiles=false) {
        $ignore = (trim($ignore) == '') ? array() : explode(';', $ignore);
        return self::glob_rr($directory, $pattern, $ignore, $onlyFiles);
    }

    private static final function glob_rr($directory, $pattern, $ignore, $onlyFiles) {
        $result = array();
        $files = @glob($directory . '/*', GLOB_NOSORT);
        if(is_array($files) &amp;&amp; !empty($files)) {
            foreach($files as $file) {
                $doignore=false;
                foreach($ignore as $i) {
                    if(fnmatch($i, basename($file))) {
                        $doignore = true;
                        break;
                    }
                }
                if(!$doignore) {
                    if(is_dir($file)) {
                        if(!$onlyFiles &amp;&amp; fnmatch($pattern, basename($file))) {
                            $result[] = $file;
                        }
                        $result = array_merge($result, self::glob_rr($file, $pattern, $ignore, $onlyFiles));
                    } else if(fnmatch($pattern, basename($file))) {
                        $result[] = $file;
                    }
                }
            }
        }
        return $result;
    }

    /**
     * Returns the path for a temporary file. The file is already
     * created when the method returns.
     * @return string
     */
    public static final function getTempFile() {
        return tempnam('', 'php_fs_');
    }

    /**
     * Returns the path for a possible temporary file. The file is NOT yet
     * created when the method returns.
     * @return string
     */
    public static final function getTempFileName() {
        return self::getTempDirectory() . '/php_fs_' . time() . '_' . uniqid() . '.tmp';
    }

    /**
     * Returns the php temp directory
     * @return string
     */
    public static final function getTempDirectory() {
        return rtrim(sys_get_temp_dir(), '/');
    }

    /**
     * Creates a directory recursivly
     * @param string $directory
     * @param int $mode
     */
    public static final function mkdir($directory, $mode=0770) {
        $directory = trim($directory);
        if(is_dir($directory)) {
            throw new FileSystemException('The directory you want to create already exists');
        } else if(is_file($directory)) {
            throw new FileSystemException('The directory you want to create is already an existing file');
        } else if(empty($directory)) {
            throw new FileSystemException('You did not specify a directory to create (empty string given)');
        } else if(!@mkdir($directory, $mode, true)) {
            if(!is_writable(dirname($directory))) {
                throw new FileSystemException('Failed to create directory (parent directory not writable for you)');
            } else {
                throw new FileSystemException('Failed to create directory');
            }
        }
    }

    /**
     * Deletes a file or a directory. Directories are deleted RECURSIVLY.
     * @param string $fileOrDirectory
     */
    public static final function delete($fileOrDirectory) {
        $fileOrDirectory = trim($fileOrDirectory);
        if(is_file($fileOrDirectory)) {
            if(!@unlink($fileOrDirectory)) {
                if(!is_writable($fileOrDirectory)) {
                    throw new FileSystemException('Failed to delete file (not writable for you)');
                } else {
                    throw new FileSystemException('Failed to delete file');
                }
            }
        } else if(is_dir($fileOrDirectory)) {
            if(!is_writable($fileOrDirectory)) {
                // This is to prevent deleting files and subfolders but not the
                // directory itself. This can prevent mistakes.
                throw new FileSystemException('Directory to delete is not writable for you');
            } else {
                // Remove files
                $list = self::find($fileOrDirectory, '*', '', true);
                foreach($list as $file) {
                    // use of this method again to get exceptions thrown
                    self::delete($file);
                }
                // Remove now empty subdirectories
                $list = array_reverse(self::find($fileOrDirectory, '*', '', false));
                foreach($list as $dir) {
                    if(!rmdir($dir)) {
                        throw new FileSystemException('Failed to delete directory');
                    }
                }
                // Remove the directory itself
                if(!@rmdir($fileOrDirectory)) {
                    throw new FileSystemException('Failed to delete directory');
                }

            }
        } else {
            throw new FileSystemException('File or directory to remove does not exist');
        }
    }

    /**
     * Renames a file or directory
     * @param string $oldName
     * @param string $newName
     */
    public static function rename($oldName, $newName) {
        if(!rename($oldName, $newName)) {
            throw new FileSystemException('Failed to rename file &quot;' . $oldName . '&quot; to &quot;' . $newName . '&quot;');
        }
    }

    /**
     * Returns if a file or directory exists
     * @param string $fileOrDirectory
     * @return bool
     */
    public static function exists($fileOrDirectory) {
        return file_exists($fileOrDirectory) ? true : false;
    }

    /**
     * Returns if a directory exists
     * @param string $fileOrDirectory
     * @return bool
     */
    public static function isDirectory($fileOrDirectory) {
        return is_dir($fileOrDirectory) ? true : false;
    }

    /**
     * Returns if a file exists
     * @param string $fileOrDirectory
     * @return bool
     */
    public static function isFile($fileOrDirectory) {
        return is_file($fileOrDirectory) ? true : false;
    }

    /**
     * Returns if a file/directory is executable
     * @param string $fileOrDirectory
     * @return bool
     */
    public static function isExecutable($fileOrDirectory) {
        return is_executable($fileOrDirectory) ? true : false;
    }

    /**
     * Returns if a file/directory is readable
     * @param string $fileOrDirectory
     * @return bool
     */
    public static function isReadable($fileOrDirectory) {
        return is_readable($fileOrDirectory) ? true : false;
    }

    /**
     * Returns if a file/directory is writable
     * @param string $fileOrDirectory
     * @return bool
     */
    public static function isWritable($fileOrDirectory) {
        return is_writable($fileOrDirectory) ? true : false;
    }

    /**
     * Returns if a file/directory path is a link
     * @param string $fileOrDirectory
     * @return bool
     */
    public static function isLink($fileOrDirectory) {
        return is_link($fileOrDirectory) ? true : false;
    }

    /**
     * Returns the content of a file specified by its path.
     * @param string $file
     * @return string
     */
    public static function &amp; readFile($file) {
        $hFile = @fopen($file,'rb');
        if(!$hFile) {
            if(!is_string($file)) {
                throw new FileSystemException('The name of the file to read is invalid (no text)');
            } else if(!is_file($file)) {
                throw new FileSystemException('The file to read does not exist');
            } else if (!is_readable($file)) {
                throw new FileSystemException('The file to read is not readable for you');
            } else {
                throw new FileSystemException('Unknown error opening file');
            }
        } else {
            $data = '';
            while(!@feof($hFile)) {
                $data .= @fread($hFile, 8192);
                if($data === false) {
                    @fclose($hFile);
                    throw new FileSystemException('Failed to read file');
                }
            }
            fclose($hFile);
        }
        return $data;
    }

    /**
     * Writes data in a file
     * @param string $file
     * @param mixed $data
     */
    public static function writeFile($file, $data) {
        if(!@file_put_contents($file, $data, FILE_BINARY)) {
            if(!is_dir(dirname($file))) {
                throw new FileSystemException('File cannot be saved because the parent directory does not exist');
            } else if(is_dir($file)) {
                throw new FileSystemException('File cannot be saved because it already exists as a directory');
            } else if(!is_writable($file)) {
                throw new FileSystemException('File cannot be saved because it is not writable for you');
            } else {
                throw new FileSystemException('File could not be saved because an unknown error occurred');
            }
        }
    }

    /**
     * Returns the extension of a file/directory
     * @param string $filepath
     * @return string
     */
    public static function getExtension($filepath) {
        return trim(pathinfo(basename($filepath), PATHINFO_EXTENSION));
    }

    /**
     * Returns the basename of a file/directory
     * @param string $filepath
     * @return string
     */
    public static function getBasename($filepath) {
        return basename($filepath);
    }

    /**
     * Returns the directory name of a file/directory
     * @param string $filepath
     * @return string
     */
    public static function getDirname($filepath) {
        return dirname($filepath);
    }

    /**
     * Returns the file size of a file in bytes
     * @param string $filepath
     * @return int
     */
    public static function getFileSize($filepath) {
        if(!is_file($filepath)) {
            throw new FileSystemException('File to get the size of does not exist');
        } else {
            return filesize($filepath);
        }
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/filesystem-class/2010-07-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Tracer class</title>
		<link>http://www.atwillys.de/programming/php/tracer-class/2010-07-04/</link>
		<comments>http://www.atwillys.de/programming/php/tracer-class/2010-07-04/#comments</comments>
		<pubDate>Sun, 04 Jul 2010 19:18:52 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[backtrace]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[logger]]></category>
		<category><![CDATA[logging]]></category>
		<category><![CDATA[tracer]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=323</guid>
		<description><![CDATA[If your developing a page and don&#8217;t have the possibility to run a debugger in your IDE, or more likely if you uploaded your project but sometimes get strange errors and have no idea why, then a tracer can help. &#8230; <a href="http://www.atwillys.de/programming/php/tracer-class/2010-07-04/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>If your developing a page and don&#8217;t have the possibility to run a debugger in your IDE, or more likely if you uploaded your project but sometimes get strange errors and have no idea why, then a tracer can help. I wrote this class at a time (once upon a long time) when the debugger capabilities where so useless that it made no sense to use them. Since that time this class was enhanced a little bit, so that I have my trace points where I want, but can get an email from the server if an exception occurs, see which files are loaded, see the defined variables at the beginning and the end of the script execution and so on. Take a look at the sample source code and the corresponding output to see what&#8217;s inside:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
include_once('Tracer.class.php');

// This is optional, good if you are developing your code
define('TRACER_DEFAULT_LEVEL', 4);

// Start the tracer
Tracer::start();

// So you can change the trace level anytime, anywhere in the script
Tracer::setLevel(2);

// Let's create a small context
class A {

    // Traces something
    public function traceHere() {
        Tracer::trace(&quot;I am &quot; . get_class($this));
    }

    // Calls sub functions, one of them fails ...
    public function fail() {
        $this-&gt;fail1(0, 'i called you');
    }

    private function fail1($arg0, $arg1) {
        $this-&gt;fail2($arg0+1, crc32($arg1));
    }

    private function fail2($arg0, $arg1) {
        $this-&gt;fail3(2*$arg0, crypt($arg1));
    }

    private function fail3($arg0, $arg1) {
        $this-&gt;fail4($arg0+10, md5($arg1));
    }

    private function fail4($arg0, $arg1) {
        throw new Exception('Well, that failed');
    }
}

// Just to have an inherited class
class B extends A {
}

// Just to have a function in no class scope
function fail(A $a) {
    $a-&gt;fail();
}

// Implicit traced with trace level 1
Tracer::trace('Over Here');

// Traced with trace level 2
Tracer::trace('Over there', 2);

// Traced with trace level 2
Tracer::trace('This is not traced, because the trace level is 2', 3);

// The class scope is traced as well ...
$a = new A();
$b = new B();

$a-&gt;traceHere(); // Shows &quot;A::traceHere() I am B&quot;
$b-&gt;traceHere(); // Shows &quot;A::traceHere() I am B&quot;, not &quot;B::traceHere() I am B&quot;

// That's the way you trace exceptions if you caught them. The backtrace
// is with function arguments and class scope:
try {
    fail($a);
} catch(Exception $e) {
    Tracer::traceException($e);
}

// Trace a variable (like print_r) with caption &quot;My array&quot; and level 2
$array = array(1 =&gt; 1000, 'key' =&gt; 'value');
Tracer::trace_r($array, 'My array', 2);

// Print out some information, notice that this will be displayed above the
// trace output, because the tracer buffers its own output.
print &quot;Tracer timer = &quot; . Tracer::getTimer() . '&lt;br /&gt;';
print &quot;Trace level  = &quot; . Tracer::getLevel() . '&lt;br /&gt;';
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Output</h3>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<p>That&#8217;s how the output looks like. My format is <code>T&lt;level&gt;-&lt;time in seconds&gt; &lt;message&gt;</code>, you can change this easily in the source code.</p>
</div>
<pre class="brush: xml; title: ; notranslate">
Tracer timer = 0001.425
Trace level = 2

&lt;pre class=&quot;tracer&quot;&gt;
T01-0000.076 Over Here
T02-0000.125 Over there
T01-0000.179 A::traceHere() I am A
T01-0000.221 A::traceHere() I am B
T00-0001.314 Exception(&quot;Well, that failed&quot;) in /tests/index.php@40
  /tests/index.php@36            A::fail4(12,&quot;0055a4361bf12d7d138d649ee555f6cf&quot;)
  /tests/index.php@32            A::fail3(2,&quot;$1$GQfJKSxD$giKXwEGXvqK3jLRqSE07H.&quot;)
  /tests/index.php@28            A::fail2(1,-4462706)
  /tests/index.php@24            A::fail1(0,&quot;i called you&quot;)
  /tests/index.php@53            A::fail()
  /tests/index.php@77            ::fail(A)

T02-0001.400  My array=Array
  (
      [1] =&gt; 1000
      [key] =&gt; value
  )
&lt;/pre&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Defines if a class is ITracable. Implemented in all classes that require
 * the Tracer.
 * @package de.atwillys.sw.php.swLib
 * @interface ITracable
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2006-2010
 * @license GPL
 * @version 1.0
 */
interface ITracable {
}
?&gt;

&lt;?
require_once('ITracable.class.php');

/**
 * Main tracing class, can be extended.
 * Traces to output window when Tracer::stop() is called
 * if the trace level &gt; 0 or an uncaught Exception occured.
 * - Name  of recipient: $GLOBALS['config']['admin.name']
 * - EMail of recipient: $GLOBALS['config']['admin.email']
 *
 * The trace level is automatically set if $_GET['trace'] &gt; 0
 * The trace level is saved in $_SESSION.
 * There is the possibility to set a default trace level using
 * - define('TRACER_DEFAULT_LEVEL', &lt;INT VALUE&gt;). This should be
 *   for debugging only.
 *
 * If you want to force the Tracer being off (not seen by any
 * unauthenticated user) simply call Tracer::disable();
 *
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2007-2010
 * @license GPL
 * @version 1.0
 */
class Tracer {

    /**
     * Tracer class configuration
     * @var array
     */
    public static $config = array(
        'auto_source' =&gt; true,                  // Automatically add file, line, method ...
        'level' =&gt; -1,                          // Default level
        'append_trace_protocol' =&gt; false,       // Defines if the user can see the output
        'trace_to_file' =&gt; '',                  // File path to trace into (''=off)
        'email_on_uncaught_exceptions' =&gt; '',   // Admin email address (''=off)
        'traced_classes' =&gt; array()             // Keys are the names, values (bool) if traced
    );

    /**
     * Contains the context (globals) list if $traceContext == true
     * @staticvar array
     */
    private static $context = array();

    /**
     * Global trace level
     * @staticvar int
     */
    private static $level = -1;

    /**
     * Global tracer object
     * @staticvar tracer
     */
    private static $instance = null;

    /**
     * Global tracer start timer
     * @staticvar long
     */
    private static $timer;

    /**
     * Tracer output
     * @staticvar array
     */
    private static $output = array();

    /**
     * Contains the information about the last traced exception
     * @staticvar Exception[]
     */
    private static $exceptions = array();

    /**
     * Indicator that an uncaught exception occured
     * @staticvar bool
     */
    private static $hasUncaughgtException = false;

    /**
     * Tracer configuration. Sets the specified config settings (merges
     * with the existing). Returns the actual configuration after the
     * new array has been merged to the defaults/previous settings.
     * @param array $config
     * @return array
     */
    public static final function config($config = array()) {
        if(!is_array($config)) {
            throw new Exception('swlib config is no array');
        } else {
            self::$config = array_merge(self::$config, $config);
            if(!isset(self::$config['traced_classes'])) {
                self::$config['traced_classes'] = array();
            }
            self::$config['traced_classes'] = array_change_key_case(self::$config['traced_classes'], CASE_LOWER);
        }
        return self::$config;
    }

    /**
     * Tracer factory, subclass must be, if specified, a tracer derivate
     * @param array $config
     */
    public static final function start($config=array()) {
        if(!is_null(self::$instance)) {
            return self::$instance;
        }

        if(!empty($config)) {
            self::config($config);
        }

        if(!empty(self::$config['trace_to_file'])) {
            $o = &quot;\n----------------------------------------------------------------------------- \n&quot; .
                 &quot;-- &quot; . $_SERVER['REQUEST_URI'] . &quot;\n&quot; .
                 &quot;----------------------------------------------------------------------------- \n&quot;;
            file_put_contents(self::$config['trace_to_file'], $o, FILE_APPEND);
            chmod(self::$config['trace_to_file'], 0666);
        }

        self::$timer = microtime(true);
        self::$instance = new self;
        self::$level = intval(self::$config['level']);

        if(self::$level === 0) {
            if(isset($_SESSION['swlib.tracelevel'])) unset($_SESSION['swlib.tracelevel']);
        } else if(self::$level &gt; -1) {
            $_SESSION[&quot;swlib.tracelevel&quot;] = self::$level;
        } else if(isset($_SESSION[&quot;swlib.tracelevel&quot;])  &amp;&amp; is_numeric($_SESSION['swlib.tracelevel'])) {
            self::$level = $_SESSION['swlib.tracelevel'];
        } else {
            self::$level = 0;
            if(isset($_SESSION['swlib.tracelevel'])) unset($_SESSION['swlib.tracelevel']);
        }

        if(self::$level &gt; 0) {
            self::$context = array(
                'SERVER' =&gt; $_SERVER,
                'AT_STARTUP' =&gt; array(
                    'GET' =&gt; $_GET,
                    'POST' =&gt; $_POST,
                    'FILES' =&gt; $_FILES,
                    'COOKIE' =&gt; $_COOKIE,
                    'SESSION' =&gt; $_SESSION,
                    'REQUEST' =&gt; $_REQUEST,
                    'HTTP_REQUEST_HEADERS' =&gt; apache_request_headers(),
                    'HTTP_RESPONSE_HEADERS' =&gt; headers_list()
                )
            );
            if(isset(self::$context['SERVER']['PHP_AUTH_PW'])) {
                self::$context['SERVER']['PHP_AUTH_PW'] = &quot;(password masked)&quot;;
            }
            self::$context['SERVER']['SERVER_ADMIN'] = '(admin email)';
        }
        return self::$instance;
    }

    /**
     * Disables tracer
     */
    public static final function stop() {
        self::$instance = null;
    }

    /**
     * Construction
     */
    private final function __construct() {
    }

    /**
     * Destruction
     */
    public final function __destruct() {
        if(self::$level &gt; 0 || self::$hasUncaughgtException) {
            $included = array();
            foreach(get_included_files() as $value) {
                $included[] = str_replace($_SERVER['DOCUMENT_ROOT'], '', $value);
            }

            self::$context['AT_SHUTDOWN'] = array(
                    'GET' =&gt; $_GET,
                    'POST' =&gt; $_POST,
                    'FILES' =&gt; $_FILES,
                    'COOKIE' =&gt; $_COOKIE,
                    'SESSION' =&gt; $_SESSION,
                    'REQUEST' =&gt; $_REQUEST,
                    'HTTP_REQUEST_HEADERS' =&gt; apache_request_headers(),
                    'HTTP_RESPONSE_HEADERS' =&gt; headers_list()
                );

            self::$context['INCLUDED_FILES'] = $included;

            if(self::$level &gt; 0 &amp;&amp; self::$config['append_trace_protocol']) {
                $printit = strtolower(str_replace(' ', '', implode(&quot;\n&quot;, headers_list())));
                $printit = strpos($printit, 'content-type') !== false &amp;&amp; strpos($printit, 'html') !== false;
                if($printit) {
                    $o = '';
                    foreach(self::$output as $line) {
                        $o .=  sprintf(&quot;T%02d-%s %s %s\n&quot;, $line[1], $line[0], $line[2], htmlspecialchars($line[3]));
                    }
                    if(!defined('TRACER_NO_CONTEXT') || TRACER_NO_CONTEXT != true) {
                        $o .= &quot;\n&quot; . '&lt;pre class=&quot;trace-context&quot;&gt;'. htmlspecialchars(print_r(self::$context, true)) . '&lt;/pre&gt;';
                    }
                    $o = trim($o, &quot; \n\r\t&quot;);
                    print '&lt;br&gt;&lt;pre class=&quot;tracer&quot;&gt;' . $o . '&lt;/pre&gt;';
                }
            } else if((self::$hasUncaughgtException &amp;&amp; !empty(self::$config['email_on_uncaught_exceptions']))) {
                try {
                    foreach(self::$exceptions as $e) {
                        $o = 'Exception(&quot;' . $e-&gt;getMessage() . '&quot;' . ($e-&gt;getCode()!=0?(' code=' . $e-&gt;getCode()):'') . ') in ' . str_replace($_SERVER['DOCUMENT_ROOT'], '', $e-&gt;getFile()) . &quot;@&quot; . $e-&gt;getLine() .&quot;\n&quot;;
                        $o .= &quot;\n&quot; . self::backtrace($e-&gt;getTrace());
                        $o .= &quot;\n&quot;;
                    }
                    $e = end(self::$exceptions);
                    require_once('Mailer.class.php');
                    $mailer = new Mailer();
                    $mailer-&gt;setSender(new EMailContact(' Tracer of ' . $_SERVER['SERVER_NAME'] , 'noreply@' . $_SERVER['SERVER_NAME']));
                    $mailer-&gt;addRecipient(new EMailContact(self::$config['email_on_uncaught_exceptions'], self::$config['email_on_uncaught_exceptions']));
                    $mailer-&gt;setSubject('Exception on ' . $_SERVER['SERVER_NAME'] . ': ' . $e-&gt;getMessage());
                    $o = 'Exception(&quot;' . $e-&gt;getMessage() . '&quot;' . ($e-&gt;getCode()!=0?(' code=' . $e-&gt;getCode()):'') . ') in ' . str_replace($_SERVER['DOCUMENT_ROOT'], '', $e-&gt;getFile()) . &quot;@&quot; . $e-&gt;getLine() .&quot;\n&quot;;
                    $o .= &quot;\n&quot; . self::backtrace($e-&gt;getTrace());
                    $o .= &quot;\n&quot; . print_r(self::$context, true);
                    $o .= &quot;\n\n+++++ TRACER +++++++\n&quot;;
                    foreach(self::$output as $line) {
                        $o .=  sprintf(&quot;T%02d-%s %s %s\n&quot;, $line[1], $line[0], $line[2], htmlspecialchars($line[3]));
                    }
                    $mailer-&gt;setBody($o);
                    $mailer-&gt;send();
                } catch(Exception $ee) {
                    // ALL WRONG ...
                }
            }
        }
    }

    /**
     * Trace write method.
     * @param string $text
     * @param int $level
     * @param string $source
     */
    public final function write($text, $level=0, $source='') {
        self::$output[] = array(
            self::getTimer(),
            $level,
            $source,
            $text
        );
        $o = sprintf(&quot;T%02d-%s %s %s&quot;, $level, self::getTimer(), $source, $text);
        if(!empty(self::$config['trace_to_file'])) {
            file_put_contents(self::$config['trace_to_file'], $o . &quot;\n&quot;, FILE_APPEND);
        }
    }

    /**
     * Get seconds since tracer factory was called (page was requested)
     * @return double
     */
    public static final function getTimer() {
        return trim(sprintf(&quot;%08.3f&quot;, 1000 * (microtime(true)-self::$timer)));
    }

    /**
     * Main class trace function
     * @param string $text
     * @param int $level
     * @return void
     */
    public static final function trace($text, $level=1) {
        if($level &gt; self::$level || self::$instance==null) return;
        $source = '';
        if(self::$config['auto_source']) {
            $bt = debug_backtrace();
            $d  = null;
            foreach($bt as $a) {
                if(!(strtolower($a['function']) == 'trace' || !isset($a['class']) || $a['class'] == __CLASS__ || $a['class'] == 'EException')) {
                    $d = $a;
                    break;
                }
            }
            unset($bt); unset($a);
            if(is_array($d) &amp;&amp; isset($d['function'])) {
                $c = isset($d['class']) ? $d['class'] : '';
                $source = $c . '::' . $d['function'] . '()';
                if(!empty($c)) {
                    $c = strtolower($c);
                    // Filter explicitly switched off classes
                    if(isset(self::$config['traced_classes'][$c]) &amp;&amp; self::$config['traced_classes'][$c] === false) {
                        return;
                    }
                }
            }
        }
        self::$instance-&gt;write(trim($text), $level, $source);
    }

    /**
     * Exception tracing
     * @param Exception $e
     * @return string
     */
    public static final function traceException(&amp;$e) {
        self::$exceptions[] = $e;
        if(self::$level&lt;1 || self::$instance==null) return;
        $out = 'Exception(&quot;' . $e-&gt;getMessage() . '&quot;' . ($e-&gt;getCode()!=0?(' code=' . $e-&gt;getCode()):'') . ') in ' . str_replace($_SERVER['DOCUMENT_ROOT'], '', $e-&gt;getFile()) . &quot;@&quot; . $e-&gt;getLine() .&quot;\n&quot;;
        $out .= &quot;\n&quot; . self::backtrace($e-&gt;getTrace());
        self::$instance-&gt;write($out, 0, &quot;&quot;);
        return $out;
    }

    /**
     * Traces uncaught exceptions (called in class EException)
     * Uncaught exceptions cause sending an email to the admin.
     * @param Exception $e
     */
    public static final function traceUncaughtException(&amp;$e) {
        self::$hasUncaughgtException = true;
        self::traceException($e);
    }

    /**
     * Recursive tracing
     * @param mixed &amp;$variable
     * @param string&amp; $varname=''
     * @param int $level=1
     * @return void
     */
    public static final function trace_r(&amp;$variable, $varname='', $level=1) {
        if($level &lt;= self::$level &amp;&amp; self::$instance!=null) {
            self::trace(
                &quot;\n&quot; . (
                (empty($varname) ? '' : $varname . '=') .
                print_r($variable, true)
                ) . '' , $level) ;
        }
    }

    /**
     * Returns a backtrace string, if no argument
     * the function uses normal backtrace
     * @param array $bt
     * @return string
     */
    public static function backtrace($bt=null) {
        if($bt==null) $bt = debug_backtrace();
        $out = '';
        foreach($bt as $a) {
            if(isset($a['class']) &amp;&amp; $a['class'] == __CLASS__) {
                continue;
            }
            $location = '';
            if(isset($a['file'])) $location .=  trim(str_replace(': runtime-created function','', str_replace($_SERVER['DOCUMENT_ROOT'], '', $a['file'])));
            if(isset($a['line'])) $location .= '@' . $a['line'];
            $call = '';
            if(isset($a['class']))    $call .= $a['class'];
            if(isset($a['function'])) $call .= '::' . $a['function'];
            $call .= '(';
            if(isset($a['args'])) {
                $args = array();
                foreach($a['args'] as $arg) {
                    switch(gettype($arg)) {
                        case &quot;boolean&quot;:
                            $args[] = $arg ? 'true' : 'false';
                            break;
                        case &quot;integer&quot;:
                            $args[] = $arg;
                            break;
                        case &quot;double&quot;:
                            $args[] = $arg;
                            break;
                        case &quot;string&quot;:
                            if(strlen($arg) &gt; 250) {
                                $args[] = '&quot;' . substr($arg,0,250) . '&quot; [...]';
                            } else {
                                $args[] = '&quot;' . $arg . '&quot;';
                            }
                            break;
                        case &quot;array&quot;:
                            $args[] = &quot;array(&quot; . count($arg) . &quot;)&quot;;
                            break;
                        case &quot;object&quot;:
                            $args[] = get_class($arg);
                            break;
                        case &quot;resource&quot;:
                            $args[] = 'resource(' . get_resource_type($arg) . ')';
                            break;
                        case &quot;NULL&quot;:
                            $args[] = 'null';
                            break;
                        default:
                            $args[] = 'UNKNOWN';
                    }
                }
                $call .= implode(',', $args);
            }
            $call .= ')';
            $out  .= sprintf(&quot;%-30s %s\n&quot;, $location, $call);
        }
        return $out;
    }

    /**
     * Traces user defined variables fetched with function get_defined_vars
     * (no globals, session, getm post ...)
     */
    public static function traceDefinedVariables(array $get_defined_vars) {
        $ignore = array('GLOBALS' =&gt; '','_ENV' =&gt; '','HTTP_ENV_VARS' =&gt; '',
            '_POST' =&gt; '','HTTP_POST_VARS' =&gt; '','_GET' =&gt; '','HTTP_GET_VARS' =&gt; '',
            '_COOKIE' =&gt; '','HTTP_COOKIE_VARS' =&gt; '','_SERVER' =&gt; '','HTTP_SERVER_VARS' =&gt; '',
            '_FILES' =&gt; '','HTTP_POST_FILES' =&gt; '','_REQUEST' =&gt; '','HTTP_SESSION_VARS' =&gt; '',
            '_SESSION' =&gt; ''
        );
        $get_defined_vars = array_diff_key($get_defined_vars, $ignore);
        $out  = '';
        foreach($get_defined_vars as $key =&gt; $value) {
            $out .= $key . '=' . gettype($value) . &quot;&lt;br&gt;&quot;;
        }
        self::trace($out);
    }

    /**
     * Sets the new trace level
     * @param int $level
     */
    public static final function setLevel($level) {
        self::$level = $level;
    }

    /**
     * Returns the actual trace level.
     * @return int
     */
    public static final function getLevel() {
        return self::$level;
    }

    /**
     * Sets if the automatic trace source is automatically fetched by
     * using backtrace.
     * @param bool $enable
     * @return bool
     */
    public static final function autoSourceEnable($enable=null) {
        if(!is_null($enable)) {
            self::$config['auto_source'] = !$enable ? false : true;
        }
        return self::$config['auto_source'];
    }

    /**
     * Disables the tracer
     */
    public static final function disable() {
        self::$level = -1;
        self::$output = array();
        self::$context = array();
    }

    /**
     * Defines/returns if the user can see the trace output on the page (appended
     * in a &lt;pre class=&quot;tracer&quot;&gt; ... &lt;/pre&gt;
     * @param bool $userCanSeeTracingsOnPage
     * @return bool
     */
    public static function appendProtocol($userCanSeeTracingsOnPage=null) {
        if(!is_null($userCanSeeTracingsOnPage)) {
            self::$config['append_trace_protocol'] = $userCanSeeTracingsOnPage ? true : false;
        }
        return self::$config['append_trace_protocol'];
    }

    /**
     * Sets/returns if a class is registered as traced or not. Returns true if
     * explicitly switched on, false if explicitly switched off, null otherwise.
     * @param string $name
     * @param bool $traceIt
     * @return bool
     */
    public static function tracedClass($name, $traceIt=null) {
        $name = strtolower(trim($name));
        if(empty($name)) {
            return null;
        } else if(!is_null($traceIt)) {
            $traceIt = $traceIt ? true : false;
            self::$config['traced_classes'][$name] = $traceIt;
            return $traceIt;
        } else if(isset(self::$config['traced_classes'][$name])) {
            return self::$config['traced_classes'][$name];
        } else {
            return null;
        }
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/tracer-class/2010-07-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Error exception class</title>
		<link>http://www.atwillys.de/programming/php/error-exception-class/2010-07-04/</link>
		<comments>http://www.atwillys.de/programming/php/error-exception-class/2010-07-04/#comments</comments>
		<pubDate>Sun, 04 Jul 2010 16:54:47 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[ErrorException]]></category>
		<category><![CDATA[exception]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=291</guid>
		<description><![CDATA[Attached an Exception-Extension similar to ErrorException. It contains one static control function ::enable() and the required redirections to catch errors. The class has cross-references to the Tracer I implemented, but checks if the tracer class exists, so the tracer is &#8230; <a href="http://www.atwillys.de/programming/php/error-exception-class/2010-07-04/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>Attached an Exception-Extension similar to ErrorException. It contains one static control function <code>::enable()</code> and the required redirections to catch errors. The class has cross-references to the <code>Tracer</code> I implemented, but checks if the tracer class exists, so the tracer is not required per se. Unfortunately not all errors are redirected by PHP (a general problem) and hence not all catchable. Here a sample code how to use it:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
define('TRACER_DEFAULT_LEVEL', 10);
include_once('../htx/lib/Tracer.class.php');
include_once('../htx/lib/EException.class.php');
EException::enable();
Tracer::start();

print &quot;&lt;html&gt;&lt;body&gt;&lt;pre&gt;&quot;;

trigger_error('This should only be seen in the tracer', E_USER_NOTICE);
trigger_error('This should only be seen in the tracer', E_USER_WARNING);

try {
    trigger_error('This should be catched', E_USER_ERROR);
} catch(Exception $e) {
    print &quot;&lt;b&gt;Was catched: &lt;/b&gt;&quot; . $e-&gt;getMessage();
}

// This will be catched by the exception handler.
trigger_error('This should be catched', E_USER_ERROR);

print &quot;&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&quot;;
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<p>In the <code>::enable()</code> function the catchable errors are redirected to the <code>::errorCallback()</code>, which decide if an error shall be only logged in the tracer (vor warnings, notices and assertions) or an exception shall be thrown. Uncaught exceptions are delegated to <code>uncaughtException()</code>, which causes a tracing and stops the script.</p>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Implements global error, assertion and exception handling. Errors, warnings
 * and messages are categorized and either thrown as exception or only traced
 * (e.g. warnings, messagses). A global exception handler catches uncaught
 * exceptions, traces the details and prints a HTML error text (without details,
 * as a MySqlException('You have an error near SELECT * form users where password=...')
 * is nothing to be seen by the user. Assertions are only traced.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2006-2010
 * @license GPL
 * @version 1.0
 * @uses Exception
 * @uses (optional) Tracer
 */
class EException extends Exception {

    /**
     * Defines if an exception shall be thrown if include() fails
     * @var bool
     */
    private static $config = array(
        'error_levels' =&gt; array(
            E_ERROR =&gt; array('level' =&gt; E_ERROR, 'tag' =&gt; 'Error', 'tracelevel' =&gt; 0),
            E_USER_ERROR =&gt; array('level' =&gt; E_ERROR, 'tag' =&gt; 'User error', 'tracelevel' =&gt; 0),
            E_RECOVERABLE_ERROR =&gt; array('level' =&gt; E_ERROR, 'tag' =&gt; 'Recoverable error', 'tracelevel' =&gt; 0),
            E_PARSE =&gt; array('level' =&gt; E_ERROR, 'tag' =&gt; 'Parse error', 'tracelevel' =&gt; 0),
            E_CORE_ERROR =&gt; array('level' =&gt; E_ERROR, 'tag' =&gt; 'Core error', 'tracelevel' =&gt; 0),
            E_COMPILE_ERROR =&gt; array('level' =&gt; E_ERROR, 'tag' =&gt; 'Compile error', 'tracelevel' =&gt; 0),
            E_WARNING =&gt; array('level' =&gt; E_WARNING, 'tag' =&gt; 'Warning', 'tracelevel' =&gt; 3),
            E_CORE_WARNING =&gt; array('level' =&gt; E_WARNING, 'tag' =&gt; 'Core warning', 'tracelevel' =&gt; 3),
            E_USER_WARNING =&gt; array('level' =&gt; E_WARNING, 'tag' =&gt; 'User warning', 'tracelevel' =&gt; 3),
            E_COMPILE_WARNING =&gt; array('level' =&gt; E_WARNING, 'tag' =&gt; 'Compile warning', 'tracelevel' =&gt; 3),
            E_NOTICE =&gt; array('level' =&gt; E_NOTICE, 'tag' =&gt; 'Notice', 'tracelevel' =&gt; 5),
            E_USER_NOTICE =&gt; array('level' =&gt; E_NOTICE, 'tag' =&gt; 'User notice', 'tracelevel' =&gt; 5),
            E_STRICT =&gt; array('level' =&gt; E_NOTICE, 'tag' =&gt; 'Strict', 'tracelevel' =&gt; 5),
            E_DEPRECATED =&gt; array('level' =&gt; E_NOTICE, 'tag' =&gt; 'Deprecated', 'tracelevel' =&gt; 5),
            E_USER_DEPRECATED =&gt; array('level' =&gt; E_NOTICE, 'tag' =&gt; '(User) Deprecated', 'tracelevel' =&gt; 5),
        ),
        'throw_on_include_fail' =&gt; false,
        'throw_on_division_by_zero' =&gt; true,
        'warning_level' =&gt; null,
        'notice_level' =&gt; null,
    );

    /**
     * Class configuration, first parameter can be either the config array
     * (to overwrite the whole config) or a key in the existing config array.
     * In the ladder case, the $value will be set for this key.
     * @param array $config
     * @param mixed $value
     */
    public static function config($config=array(), $value=null) {
        if(is_array($config)) {
            if(isset($config['error_levels'])) {
                $levels = array_merge(self::$config['error_levels'], $config['error_levels']);
            } else {
                $levels = self::$config['error_levels'];
            }
            self::$config = array_merge(self::$config, $config);
            self::$config['error_levels'] = $levels;
        } else if($config == 'warning_level' &amp;&amp; is_numeric($value)) {
            $c = intval($value);
            foreach(self::$config['error_levels'] as $key =&gt; $value) {
                if($value['level'] == E_WARNING) {
                    self::$config['error_levels'][$key]['tracelevel'] = $c;
                }
            }
        } else if($config == 'notice_level' &amp;&amp; is_numeric($value)) {
            $c = intval($value);
            foreach(self::$config['error_levels'] as $key =&gt; $value) {
                if($value['level'] == E_NOTICE) {
                    self::$config['error_levels'][$key]['tracelevel'] = $c;
                }
            }
        }
    }

    /**
     * Generate en eexception if a global error is raised. Dependent on PHP, not
     * all errors will be passed to this callback, but for future these cases
     * are implemented.
     * @param int $errno
     * @param string $errstr
     * @param string $errfile
     * @param string $errline
     * @param string $context
     * @return void
     */
    public static function errorCallback($errno, $errstr, $errfile, $errline, $context) {
        $errfile = str_replace($_SERVER['DOCUMENT_ROOT'],'',$errfile);
        $e = &quot;&quot;;
        if(isset(self::$config['error_levels'][$errno])) {
            if(self::$config['error_levels'][$errno]['type'] == E_ERROR) {
                $e = self::$config['error_levels'][$errno]['tag'];
            } else if($errno == E_WARNING) {
                $etxt = strtolower(trim($errstr));
                if(strpos($etxt, 'function.include') !== false &amp;&amp; self::$config['throw_on_include_fail']) {
                    $errno = E_ERROR;
                    $e = &quot;Error:&quot;;
                    $errstr = &quot;Include failed&quot;;
                } else if(strpos($etxt, 'function.require') !== false) {
                    $errno = E_ERROR;
                    $e = &quot;Error:&quot;;
                    $errstr = &quot;Require failed&quot;;
                } else if(strpos($etxt, 'division by zero') !== false &amp;&amp; self::$config['throw_on_division_by_zero']) {
                    $errno = E_ERROR;
                    $e = &quot;Error:&quot;;
                    $errstr = &quot;Division by zero&quot;;
                    $eclass = 'MathException';
                }
            }

            if(class_exists('Tracer')) {
                Tracer::trace(self::$config['error_levels'][$errno]['tag'] . &quot;: $errstr ($errfile@$errline)&quot;, self::$config['error_levels'][$errno]['tracelevel']);
            }

            if($e != '') {
                $e = isset($eclass) ? new $eclass : new self();
                $e-&gt;line = $errline;
                $e-&gt;file = $errfile;
                $e-&gt;message = strip_tags($errstr);
                $e-&gt;code = $errno;
                throw $e;
            }
        }
    }

    /**
     * Global catches uncaught exceptions
     * @param Exception $e
     * @return void
     */
    public static function uncaughtException(Exception $e) {
        print &quot;&lt;div style='padding:5px; border: solid 1px black; background-color:#ff8888;' class=\&quot;uncaught-exception\&quot;&gt;A server error occoured. &lt;br /&gt;&lt;i&gt;There is an uncaught exception, which is not visible to the user and has to be handled by the developer.&lt;/i&gt;&lt;/div&gt;&quot;;
        if(class_exists('Tracer')) {
            Tracer::traceUncaughtException($e);
        }
        die;
    }

    /**
     * Callback for usage of function assert()
     * @param string $file
     * @param int $line
     * @param string $code
     */
    public static function assertCallback($file, $line, $code='') {
        if(class_exists('Tracer')) {
            Tracer::trace(&quot;Assertion failed in '$file'@$line: $code&quot;);
        }
    }

    /**
     * Initialize and enable class usage.
     * @param bool $enabled
     * @return void
     */
    public static function enable($enabled=true) {
        assert_options(ASSERT_ACTIVE, 1);
        assert_options(ASSERT_WARNING, 0);
        assert_options(ASSERT_QUIET_EVAL, 1);
        restore_error_handler();
        restore_exception_handler();
        if($enabled) {
            assert_options(ASSERT_CALLBACK, array(__CLASS__, &quot;assertCallback&quot;));
            set_exception_handler(array(__CLASS__, &quot;uncaughtException&quot;));
            set_error_handler(array(__CLASS__, &quot;errorCallback&quot;));
        }
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/error-exception-class/2010-07-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Session management wrapper class</title>
		<link>http://www.atwillys.de/programming/php/session-management-wrapper-class/2010-07-04/</link>
		<comments>http://www.atwillys.de/programming/php/session-management-wrapper-class/2010-07-04/#comments</comments>
		<pubDate>Sun, 04 Jul 2010 15:27:31 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[session]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=281</guid>
		<description><![CDATA[The session management is designed to be a singleton, which keeps a reference to the own instance reference in a static class variable. A good reason to have a wrapper for the normal PHP session management is that you can &#8230; <a href="http://www.atwillys.de/programming/php/session-management-wrapper-class/2010-07-04/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>The session management is designed to be a singleton, which keeps a reference to the own instance reference in a static class variable. A good reason to have a wrapper for the normal PHP session management is that you can easily re-implement this class and make it loading the session from memory cache or a MySql memory table. Use the static functions to start or stop the session, and the <code>instance()</code> function to access the created instance. Example:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
require_once('Session.class.php');
Session::start();

$rand = rand(1, 10);

if($rand == 1) {
    Session::instance()-&gt;regrnerateId();
} else if($rand == 2) {
    Session::instance()-&gt;destroy();
}

print &quot;&lt;html&gt;&lt;body&gt;&lt;pre&gt;&quot;;

// Work with a session variable
$_SESSION['test'] = !isset($_SESSION['test']) ? 0 : $_SESSION['test'] + 1;

// Print some data
print &quot;Counter           = &quot; . $_SESSION['test'] . &quot;\n&quot;;
print &quot;Session id        = &quot; . Session::instance()-&gt;id() . &quot;\n&quot;;
print &quot;Session name      = &quot; . Session::instance()-&gt;getName() . &quot;\n&quot;;
print &quot;Session expires   = &quot; . Session::instance()-&gt;getCacheExpireTime() . &quot;\n&quot;;

// This function is more for anaysis purposes
print &quot;Session save path = &quot; . Session::instance()-&gt;getSavePath() . &quot;\n&quot;;

// Conditionally stop the session
if($Condition_that_requires_closing_the_session) {
    Session::stop();
} else {
    // The session will be written when the object is destructed,
    // this is when the script has finished and PHP unsets all
    // variables.
}

if($rand == 1) {
    print &quot;\n(Session id regenerated)\n&quot;;
} else if($rand == 2) {
    print &quot;\n(Session destroyed)\n&quot;;
}

print &quot;&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&quot;;
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Session management wrapper. This class can be used to replace the PHP standard
 * session handling. Principally this class does nothing differemt, but the class
 * can be easily replaced with a database-based session handling or the like.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2005-2010
 * @license GPL
 * @version 1.0
 */
class Session {

    /**
     * The one any only session object
     * @staticvar session
     */
    private static $instance = null;

    /**
     * Session startup, autimatically creates session instance.
     * @return Session
     */
    public static final function start() {
        if(is_null(self::$instance)) {
            self::$instance = new self;
        }
        return self::$instance;
    }

    /**
     * Closes the session, writes the session file
     */
    public static final function stop() {
        self::$instance = null;
    }

    /**
     * Returns the Session singleton instance
     * @return Session
     */
    public static final function instance() {
        return self::$instance;
    }

    /**
     * Session instance constructor.
     * Starts new session.
     * @param string $name
     * @return Session
     */
    private final function __construct($name='') {
        $name = trim($name);
        if($name != '') {
            if(!is_numeric($name) &amp;&amp; !is_alnum($name)) {
                session_name($name);
            } else {
                throw new Exception('Session name is invalid:' . $name);
            }
        }
        session_start();
    }

    /**
     * Session instance destructor.
     */
    public final function __destruct() {
        session_write_close();
    }

    /**
     * Session stop with cockie, vars and cache.
     * @return void
     */
    public final function destroy() {
        $_SESSION = array();
        if(isset($_COOKIE[session_name()])) { setcookie(session_name(), '', time()-42000, '/'); }
        session_unset();
        session_destroy();
    }

    /**
     * Returns session IDs
     * @return string
     */
    public final function id() {
        return session_id();
    }

    /**
     * Generates a new session ID, keeps session data
     */
    public final function regrnerateId() {
        if(!session_regenerate_id()) {
            throw new Exception(&quot;ID regeneration failed.&quot;);
        }
    }

    /**
     * Returns the session name
     * @return string
     */
    public final function getName() {
        return session_name();
    }

    /**
     * Returns the save path for session variables
     * @return string
     */
    public final function getSavePath() {
        return session_save_path();
    }

    /**
     * Returns the session cache expire time in
     * minutes.
     * @return int
     */
    public final function getCacheExpireTime() {
        return session_cache_expire();
    }

    /**
     * Returns an associative array with all saved and not garbaged sessions,
     * where the keys are the session ids and the values are the last file
     * modification time.
     * @return array
     */
    public static final function getActiveSessions() {
        $sessions = array();
        foreach(glob(session_save_path() . &quot;sess_*&quot;) as $key =&gt; $file) {
            $f = pathinfo($file);
            $f = str_replace('sess_', '', $f['filename']);
            $sessions[$f] = filemtime($file);
        }
        return $sessions;
    }

    /**
     * Returns the IP address of the client browser
     * @return string
     */
    public static final function getClientIpAddress() {
        if(isset($_SERVER[&quot;REMOTE_ADDR&quot;])) return $_SERVER[&quot;REMOTE_ADDR&quot;];
        if(isset($_SERVER[&quot;HTTP_CLIENT_IP&quot;])) return $_SERVER[&quot;HTTP_CLIENT_IP&quot;];
        if(getenv(&quot;HTTP_CLIENT_IP&quot;)) return getenv(&quot;HTTP_CLIENT_IP&quot;);
        if(getenv(&quot;HTTP_X_FORWARDED_FOR&quot;)) return getenv(&quot;HTTP_X_FORWARDED_FOR&quot;);
        if(getenv(&quot;REMOTE_ADDR&quot;)) return getenv(&quot;REMOTE_ADDR&quot;);
        return null;
    }

    /**
     *
     */
    public static final function getClientUserAgent() {
        $agent = strtolower($_SERVER['HTTP_USER_AGENT']);
        foreach(array(
            '/chrome/i' =&gt; 'chrome', // chrome contains the text &quot;safari&quot;
            '/safari/i' =&gt; 'safari',
            '/firefox/i' =&gt; 'firefox',
            '/msie/i' =&gt; 'internet-explorer',
            '/opera/i' =&gt; 'opera',
        ) as $key =&gt; $value) {
            if(preg_match($key, $agent)) {
                return $value;
            }
        }
        return null;
    }

}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/session-management-wrapper-class/2010-07-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>HTTP authentication class</title>
		<link>http://www.atwillys.de/programming/php/http-authentication-class/2010-07-04/</link>
		<comments>http://www.atwillys.de/programming/php/http-authentication-class/2010-07-04/#comments</comments>
		<pubDate>Sun, 04 Jul 2010 14:38:43 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[authentication]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[http]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=263</guid>
		<description><![CDATA[This class manages user HTTP authentication, which is a common, safe and simple way of user login. The instance throws an exception if the user or password is wrong. The password is given with a .htaccess-like hash (using crypt()). The &#8230; <a href="http://www.atwillys.de/programming/php/http-authentication-class/2010-07-04/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>This class manages user HTTP authentication, which is a common, safe and simple way of user login. The instance throws an exception if the user or password is wrong. The password is given with a .htaccess-like hash (using <code>crypt()</code>). The static function <code>createPassword()</code> can be used to create a compatible password. Let&#8217;s take a look how to use the class:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
// Example users
$users = array(
    'user1' =&gt; HttpAuthentification::createPassword('pass1'),
    'user2' =&gt; crypt('pass2')
);

// Create instance
$auth = new HttpAuthentification($users);

// Add a user after the object was instanciated
$auth-&gt;addUser('user3', HttpAuthentification::createPassword('pass3'));

// Test if the authentication is ok
try {
    $auth-&gt;requireAuthentication();
    print &quot;Auth succeeded&quot;;
} catch(Exception $e) {
    print &quot;Auth failed: &quot; . $e-&gt;getMessage();
}
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<p>And here the class source code. If you use autoloading, you should move the <code>HttpAuthentificationException</code> class to a separate file.</p>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Http authentification exception, thrown if the authentication fails
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2008-2010
 * @license GPL
 * @version 1.0
 */
class HttpAuthentificationException extends Exception {
}
?&gt;

&lt;?php
/**
 * Http authentification wrapper with external user management. The registered
 * users are passed in the constructor/function addUser in an associative array,
 * where the keys are the user login names and the values the ENCRYPTED passwords.
 * The encryption function is the UNIX crypt(). The static function createPassword()
 * can be used as well. To activate the feature call
 *
 *  try {
 *      HttpAuthentification::requireAuthentication();
 *  } catch(HttpAuthentificationException $e) {
 *      ...
 *  }
 *
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2008-2010
 * @license GPL
 * @uses HttpAuthentificationException
 */
class HttpAuthentification {

    /**
     * List if users and passwords
     * @var array
     */
    private $users = array();

    /**
     * Login name of actual user
     * @var string
     */
    private $actualUser = null;

    /**
     * Returns a encrypted password from a plain text password
     * @param string $unencrypted
     * @return string
     */
    public static function createPassword($unencrypted) {
        return crypt($unencrypted);
    }

    /**
     * Constructor with assoc array:
     * keys are user names, values are crypt(passwords).
     * @param array $users
     */
    public function __construct(array $users=array()) {
        foreach($users as $key =&gt; $value) {
            $this-&gt;addUser($key, $value);
        }
    }

    /**
     * Adds a user to the login list. throws Exception if user exists.
     * @param string $loginName
     * @param string $cryptedPassword
     */
    public function addUser($loginName, $cryptedPassword) {
        $loginName = strtolower($loginName);
        if(isset($this-&gt;users[$loginName])) {
            throw new Exception('User &quot;' . $loginName . '&quot; already exists in login list');
        } else {
            $this-&gt;users[$loginName] = $cryptedPassword;
        }
    }

    /**
     * Returns the current user.
     * @return string
     */
    public function getUser() {
        if(is_null($this-&gt;actualUser)) {
            $user = trim(strtolower($_SERVER['PHP_AUTH_USER']));
            $auth = $_SERVER['PHP_AUTH_PW'];
            if(empty($this-&gt;users[$user]) || trim($auth) == '' || crypt($auth, $this-&gt;users[$user]) != $this-&gt;users[$user]) {
                $this-&gt;actualUser = '';
            } else {
                $this-&gt;actualUser = $user;
            }
        }
        return $this-&gt;actualUser;
    }

    /**
     * Throws an Exception if the user is not authorized
     * and adds the WWW-Authenticate to signal the required
     * auth to the browser.
     */
    public function requireAuthentication() {
        if($this-&gt;getUser() == '') {
          header('WWW-Authenticate: Basic realm=&quot;My Realm&quot;');
          header('HTTP/1.0 401 Unauthorized');
          if(trim($_SERVER['PHP_AUTH_USER']) == '' &amp;&amp; trim($_SERVER['PHP_AUTH_PW']) == '') {
            throw new HttpAuthentificationException('User did not enter data');
          } else if(trim($_SERVER['PHP_AUTH_USER']) == '') {
            throw new HttpAuthentificationException('User name not entered');
          } else if(trim($_SERVER['PHP_AUTH_PW']) == '') {
              throw new HttpAuthentificationException('User password not entered');
          } else {
              throw new HttpAuthentificationException('User entered wrong password (' . $_SERVER['PHP_AUTH_USER'] . ')' );
          }
        }
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/http-authentication-class/2010-07-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Class for sending emails</title>
		<link>http://www.atwillys.de/programming/php/class-for-sending-emails/2010-07-04/</link>
		<comments>http://www.atwillys.de/programming/php/class-for-sending-emails/2010-07-04/#comments</comments>
		<pubDate>Sun, 04 Jul 2010 13:27:37 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[email]]></category>
		<category><![CDATA[sendmail]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=246</guid>
		<description><![CDATA[I wrote this mailer classes to have better maintenance possibilities and can use abstract contacts from wherever I want. Could be interesting for you as well :). I split it in three files (as I autoload the classes when they &#8230; <a href="http://www.atwillys.de/programming/php/class-for-sending-emails/2010-07-04/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>I wrote this mailer classes to have better maintenance possibilities and can use abstract contacts from wherever I want. Could be interesting for you as well :). I split it in three files (as I autoload the classes when they are needed), but you can put them all in one file as well. A usage example could be:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Sample source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
try {
  $mailer = new Mailer();
  $mailer-&gt;setSender(new EMailContact('My site mailer', 'mailer@example.net'));
  $mailer-&gt;setSubject('Test email');
  $mailer-&gt;setBody('Test body');
  // To:
  $mailer-&gt;addRecipient(new EMailContact('You', 'contact@example.net'));
  // Copy To:
  $mailer-&gt;addRecipient(new EMailContact('You', 'contact@example.net'), 'cc');
  // Blind Copy To:
  $mailer-&gt;addRecipient(new EMailContact('You', 'contact@example.net'), 'bcc');
  // Send it
  $mailer-&gt;send();
} catch(Exception $e) {
  print &quot;Exception: &quot; . $e-&gt;getMessage();
}
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<p>The first file is an interface, which is used by the Mailer class. This means you can make every user- or address book class to be used as contact class for the Mailer (see below for further information).</p>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * EMail contact interface, used in class Mailer and implemented in class
 * EMailContact.
 * @package de.atwillys.sw.php.swLib
 * @interface IEMailContact
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2006-2010
 * @license GPL
 * @version 1.0
 */
interface IEMailContact
{   /**
     * This is required to send an email
     * @return string
     */
    public function toEMailAddressString();
}
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<p>The contact class can be anything, even your database user class, as long you add the method <code>toEMailAddressString() {}</code> and say <code>implements IEMailContact</code>. So this class is only a &#8220;default&#8221; contact &#8230;</p>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * EMail contact is a base class for the class Mailer. It can be easily be
 * extended to any other addressbook or user class (Note that it might be
 * be better to write an own user class and implement IEMailContact instead
 * of using this class).
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2006-2010
 * @license GPL
 * @version 1.0
 * @uses IEMailContact
 */
class EMailContact implements IEMailContact
{
    /**
     * Name
     * @var string
     */
    private $name;

    /**
     * Email address
     * @var string
     */
    private $address;

    /**
     * Name getter
     * @return string
     */
    public function getName() {
        return $this-&gt;name;
    }

    /**
     * Name setter
     * @param string $name
     */
    public function setName($name) {
        if(!isset($name) || $name == &quot;&quot;) {
            throw new Exception(&quot;Name of email contact is invalid&quot;);
        }
        $this-&gt;name = $name;
    }

    /**
     * EMail address getter
     * @return string
     */
    public function getEMailAddress() {
        return $this-&gt;address;
    }

    /**
     * EMail address setter
     * @param string $address
     */
    public function setEMailAddress($address) {
        if(!isset($address) || $address == &quot;&quot;) {
            throw new Exception(&quot;EMail addresss is invalid&quot;);
        } else if(!eregi(&quot;^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,3})$&quot;, $address)) {
            throw new Exception(&quot;EMail addresss is invalid&quot;);
        }
        $this-&gt;name = $address;
    }

    /**
     * Nonmagic string representator
     * @return string
     */
    public function toEMailAddressString() {
        return $this-&gt;name . &quot; &lt;&quot; . $this-&gt;address . &quot;&gt;&quot;;
    }

    /**
     * EMailContact constructor
     * @param string $name
     * @param string $address
     */
    public function __construct($name=&quot;&quot;, $address=&quot;&quot;) {
        $this-&gt;name 	= $name;
        $this-&gt;address = $address;
    }

    public function __toString() {
      return $this-&gt;toEMailAddressString();
    }

}
?&gt;
</pre>
<div class='en' style='' lang='en' dir='ltr'>
<p>&#8230; and the mailer class itself. Note that the Mailer does not check if the email addresses are correct, because this should be done in the contact class. Otherwise there could be a discrepancy where the user class says the email is correct but the mailer would throw an exception.</p>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Simple SMTP EMail wrapper class. Nothing special.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2007-2010
 * @license GPL
 * @version 1.0
 * @uses IEMailContact
 */
class Mailer {
    /**
     * Global mailer template settings (set with setTemplate())
     *
     * @var mailer
     */
    private static $template = null;

    /**
     * Server connection ip/port
     *
     * @var string
     */
    private $server = &quot;&quot;;

    /**
     * Login user name
     * @var string
     */
    private $user = &quot;&quot;;

    /**
     * Logon password
     *
     * @var string
     */
    private $password = &quot;&quot;;

    /**
     * Sender
     *
     * @var string
     */
    private $sender = null;

    /**
     * Recipient
     *
     * @var array
     */
    private $recipients = array();

    /**
     * eMail subject
     *
     * @var string
     */
    private $subject = &quot;&quot;;

    /**
     * eMail body
     * @var string
     */
    private $body = &quot;&quot;;

    /**
     * Content type
     * @var string
     */
    private $contentType = '';

    /**
     * The CC/BCC recipients
     * @var array
     */
    private $ccs = array();

    /**
     * Email verification
     * @param string $email
     * @return bool
     */
    public static function validateEmailAddress($email, $lookupDomain=false) {
        $email = explode('@', $email);
        if(count($email) != 2) {
            return false;
        } else {
            $domain = end($email);
            $email  = reset($email);
            if(strlen(trim($domain)) &lt; 2) {
                return false;
            } else if(strlen(trim($email)) &lt; 1) {
                return false;
            } else if(strlen($email) &gt; 64) {
                return false;
            } else if(strlen($domain) &gt; 255) {
                return false;
            } else if(preg_match('/[!\.-]{2}/', $domain)) {
                return false;
            } else if(preg_match('/[!\.-]{2}/', $email)) {
                return false;
            } else if(!preg_match('/^[A-Za-z0-9\\-\\.]+$/', $domain)) {
                return false;
            } else if(!preg_match('/^(\\\\.|[A-Za-z0-9!#%&amp;`_=\\/$\'*+?^{}|~.-])+$/', str_replace(&quot;\\\\&quot;, '', $email)) &amp;&amp; !preg_match('/^&quot;(\\\\&quot;|[^&quot;])+&quot;$/', str_replace(&quot;\\\\&quot;, '', $email))) {
                return false;
            } else if($lookupDomain &amp;&amp; !(checkdnsrr($domain, &quot;MX&quot;) || checkdnsrr($domain, &quot;A&quot;))) {
                return false;
            }
            return true;
        }
    }

    /**
     * Constructor
     */
    public function __construct() {
    }

    /**
     * Get server name/ip
     * @return string
     */
    public function getServer()	{
        return $this-&gt;server;
    }

    /**
     * Sets server name and port
     * @param string $server
     */
    public function setServer($server) {
        $this-&gt;server = $server;
        settype($this-&gt;server, &quot;string&quot;);
    }

    /**
     * Login user getter
     * @return string
     */
    public function getUser() {
        return $this-&gt;user;
    }

    /**
     * Sets the login user
     * @param string $user
     */
    public function setUser($user) {
        $this-&gt;user = $user;
        settype($this-&gt;user, &quot;string&quot;);
    }

    /**
     * Login password getter
     * @return string
     */
    public function getPassword() {
        return $this-&gt;password;
    }

    /**
     * Login password setter
     * @param string $password
     */
    public function setPassword($password) {
        $this-&gt;password = $password;
        settype($this-&gt;password, &quot;string&quot;);
    }

    /**
     * Add recipient, $cc can be 'CC' or 'BCC'
     * @param IEMailContact $contact
     * @param string $cc=''
     */
    public function addRecipient(IEMailContact $contact, $cc='') {
        $cc = empty($cc) ? '' : trim(strtolower($cc));
        if($contact == null) {
            throw new Exception(&quot;Recipient to add is not specified&quot;);
        } else if(!empty($cc) &amp;&amp; $cc != 'cc' &amp;&amp; $cc != 'bcc') {
            throw new Exception('CC/BCC for Recipient to add must be specified with &quot;CC&quot; or &quot;BCC&quot;');
        } else {
            $this-&gt;recipients[] = $contact;
            if(!empty($cc)) {
                $this-&gt;ccs[$contact-&gt;toEMailAddressString()] = $cc;
            }
        }
    }

    /**
     * Recipient getter
     * @return array of IEMailContact
     */
    public function getRecipients() {
        return $this-&gt;recipients;
    }

    /**
     * Recipient getter with index
     * @return IEMailContact
     */
    public function getRecipient($index=0) {
        if(!array_key_exists($index, $this-&gt;recipients)) {
            throw new Exception(&quot;Invalid index '$index'&quot;);
        } else {
            return $this-&gt;recipients[$index];
        }
    }

    /**
     * Sender getter
     * @return IEMailContact
     */
    public function getSender()	{
        return $this-&gt;sender;
    }

    /**
     * Sender setter
     * @param string $name
     */
    public function setSender(IEMailContact $contact)	{
        if($contact == null) {
            throw new Exception(&quot;Argument is not an IEMailContact&quot;);
        } else {
            $this-&gt;sender = $contact;
        }
    }

    /**
     * eMail subject getter
     * @return string
     */
    public function getSubject() {
        return $this-&gt;subject;
    }

    /**
     * eMail subject setter
     * @param string $subject
     */
    public function setSubject($subject) {
        $this-&gt;subject = $subject;
        settype($this-&gt;subject, &quot;string&quot;);
    }

    /**
     * eMail body getter
     * @return string
     */
    public function getBody() {
        return $this-&gt;body;
    }

    /**
     * eMail body setter
     * @param string $body
     */
    public function setBody($body) {
        $this-&gt;body = $body;
        settype($this-&gt;body, &quot;string&quot;);
    }

    /**
     * Content type getter
     * @return string
     */
    public function getContentType() {
        return $this-&gt;contentType;
    }

    /**
     * Sets content type, only text/plain and text/html allowed.
     * @return void
     */
    public function setContentType($type) {
        $type = strtolower($type);
        if($type != &quot;text/plain&quot; &amp;&amp; $type != &quot;text/html&quot;) {
            throw new Exception(&quot;Only 'text/plain' or 'text/html' allowed&quot;);
        } else {
            $this-&gt;contentType = $type;
        }
    }

    /**
     * Use $this as default for all constructed mailers
     * @return void
     */
    public function setAsTemplate() {
        $ta = $this-&gt;recipients;
        $this-&gt;recipients = array();
        self::$template = clone $this;
        $this-&gt;recipients = $ta;
    }

    /**
     * Returns array with header, subject, body
     * @return array
     */
    public function getContents() {
        if(trim($this-&gt;getSubject()) == &quot;&quot;) {
            throw new Exception(&quot;No subject specified.&quot;);
        } else if(trim($this-&gt;getBody()) == &quot;&quot;) {
            throw new exception(&quot;Empty body.&quot;);
        } else if(is_null($this-&gt;getSender())) {
            throw new exception(&quot;Sender not specified&quot;);
        } else if(count($this-&gt;getRecipients()) == 0) {
            throw new exception(&quot;No recipients defined.&quot;);
        } else {
            $from = $this-&gt;getSender()-&gt;toEMailAddressString();
            if(count($this-&gt;getRecipients()) &lt; 2) {
                $to = $this-&gt;getRecipient()-&gt;toEMailAddressString();
                $cc = $bcc = '';
            } else {
                $to  = $cc = $bcc = array();
                foreach($this-&gt;getRecipients() as $recip) {
                    $recip = $recip-&gt;toEMailAddressString();
                    if(isset($this-&gt;ccs[$recip]) &amp;&amp; $this-&gt;ccs[$recip] == 'cc') {
                        $cc[] = $recip;
                    } else if(isset($this-&gt;ccs[$recip]) &amp;&amp; $this-&gt;ccs[$recip] == 'bcc') {
                        $bcc[] = $recip;
                    } else {
                        $to[] = $recip;
                    }
                }
                $to  = implode(', ', $to);
                $cc  = empty($cc)  ? '' : 'Cc: '  . implode(', ', $cc)  . &quot;\r\n&quot;;
                $bcc = empty($bcc) ? '' : 'Bcc: ' . implode(', ', $bcc) . &quot;\r\n&quot;;
            }

            if(empty($to)) {
                throw new Exception('Email has no primary recipient (TO:)');
            } else {
                if(empty($this-&gt;contentType)) {
                    $this-&gt;contentType = 'text/plain';
                }
                $header  = &quot;&quot;;
                $header .= &quot;Content-Type: &quot;. $this-&gt;getContentType() . &quot;; charset=ISO-8859-15; format=flowed&quot; . &quot;\r\n&quot;;
                $header .= &quot;Content-Transfer-Encoding: 8bit\r\n&quot;;
                $header .= &quot;MIME-Version: 1.0&quot; . &quot;\r\n&quot;;
                $header .= &quot;X-Accept-Language: de-de, de, en-us, en&quot; . &quot;\r\n&quot;;
                $header .= &quot;From: $from\r\n&quot;;
                // To is handled in the mail function $header .= &quot;To: $to\r\n&quot;;
                $header .= $cc . $bcc;
                $subject = $this-&gt;getSubject() .&quot;\r\n&quot;;
                $content = array();
                $content[&quot;to&quot;] 	= $to;
                $content[&quot;cc&quot;] 	= $cc;
                $content[&quot;bcc&quot;] = $bcc;
                $content[&quot;header&quot;] 	= $header;
                $content[&quot;subject&quot;]	= $subject;
                $content[&quot;body&quot;]	= $this-&gt;getBody();
                return $content;
            }
        }
    }

    /**
     * Get email content to be sent
     * @return string
     */
    public function dump() { // getMailContent()
        $mail = $this-&gt;getContents();
        return $mail[&quot;header&quot;] . &quot;\n\n&quot; . $mail[&quot;subject&quot;] . &quot;\n\n&quot; . $mail[&quot;body&quot;] . &quot;\n&quot;;
    }

    /**
     * Sends the eMail
     * @return array succeeded posts
     */
    public function send() {
        $mail = $this-&gt;getContents();
        if(!mail($mail['to'], $mail[&quot;subject&quot;], $mail[&quot;body&quot;], $mail[&quot;header&quot;])) {
            throw new Exception(&quot;Could not send mail to recipients.&quot;);
        }
        Tracer::trace(&quot;EMail sent:\n&quot; . print_r($mail, true) .&quot;\n&quot;);
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/class-for-sending-emails/2010-07-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Output buffering class</title>
		<link>http://www.atwillys.de/programming/php/output-buffering-class/2010-07-04/</link>
		<comments>http://www.atwillys.de/programming/php/output-buffering-class/2010-07-04/#comments</comments>
		<pubDate>Sun, 04 Jul 2010 11:38:09 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[output buffer]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=244</guid>
		<description><![CDATA[This is a class is for having the output buffer well organized. Note that it uses the Tracer class, including the ITracable interface. You can remove these functions from the source code if you want &#8230; Class source code This &#8230; <a href="http://www.atwillys.de/programming/php/output-buffering-class/2010-07-04/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>This is a class is for having the output buffer well organized. Note that it uses the Tracer class, including the ITracable interface. You can remove these functions from the source code if you want &#8230;</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Enables auto output buffering by calling ob_start() at construction and
 * ob_end_flush() on destruction. You can overload the onEnd() method to
 * add special processes to the buffered content. It is also possible to
 * create instances, e.g. $ob = new OutputBuffer(); print &quot;Something&quot;;
 * $content = $ob-&gt;getContents();
 *
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2005-2010
 * @license GPL
 * @version 1.0
 * @uses Tracer
 */
class OutputBuffer implements ITracable {

    /**
     * Main instance
     * @staticvar OutputBuffer
     */
    private static $instance;

    /**
     * Defines if buffer is shown or dumped (trashed)
     *
     * @property bool
     */
    private $noOutput = false;

    /**
     * Defines the buffer level when the ob was started.
     * @property int
     */
    private $level = 0;

    /**
     * The callback function, overload this if you want to modify the buffer.
     * This function returns null to indicate that the output buffer callback
     * is not used.
     * $param string $buffer
     * @return string
     */
    public function onEnd($buffer='') {
        return null;
    }

    /**
     * Starts output control
     * @return void
     */
    public static final function start() {
        self::$instance = new self();
        self::trace(&quot;started&quot;);
    }

    /**
     * Aborts all buffers and sents the output
     * @return void
     */
    public static final function abort() {
        while(@ob_get_level() &gt; 0) @ob_end_flush();
    }

    /**
     * Aborts all buffers without sending it. Instead the output
     * is returned as string.
     * @return string
     */
    public static final function purge() {
        $out = '';
        while(@ob_get_level() &gt; 0) {
            $out .= @ob_get_clean();
        }
        return $out;
    }

    /**
     * Aborts all buffers without sending it. Instead the output
     * is returned as string.
     * @return string
     */
    public static final function disable() {
        return self::abort();
    }

    /**
     * Execute a PHP code in a level 2++ buffer. Restores buffering to level 1
     * @param string $code
     * @return string
     */
    public static final function executeBuffered($code) {
        $exception = null;
        $buffer = new self();
        try {
            $result = eval($code);
        } catch(Exception $e) {
            $exception = $e;
            $result = &quot;&lt;h1&gt;Block exception thrown&lt;/h1&gt;&lt;br&gt;&lt;pre&gt;$exception&lt;/pre&gt;&quot;;
        }
        $result .= $buffer-&gt;getOutput();
        unset($buffer);
        if($exception != null) {
            throw $exception;
        } else {
            return $result;
        }
    }

    /**
     * Controller object construction, start level 1 buffer
     * @param bool $noOutput=false
     */
    public final function __construct($noOutput=false) {
        $this-&gt;noOutput = $noOutput;
        if(!is_null($this-&gt;onEnd())) {
            ob_start(array($this, 'onEnd'));
        } else {
            ob_start();
        }
        $this-&gt;level = ob_get_level();
        self::trace(&quot;New buffer, level &quot; . $this-&gt;level, 3);
    }

    /**
     * Controller object destruction, flushes and writes all higher buffer level outputs than level at construction time.
     */
    public final function __destruct() {
        $out = '';
        if(ob_get_level() &gt; $this-&gt;level) {
            self::trace(&quot;Buffer level(&quot; . ob_get_level() . &quot;, construction level=&quot; . $this-&gt;level . &quot;) not ok , flushing &quot;);
            while(ob_get_level() &gt; $this-&gt;level) {
                self::trace(&quot;Level now &quot; . ob_get_level());
                $out .= ob_get_clean();
            }
            self::trace(&quot;Buffer destructed, level &quot; . $this-&gt;level . &quot; actual level &quot; . ob_get_level());
        } else {
            self::trace(&quot;ok, already read level &quot; . $this-&gt;level, 3);
        }
        if($this-&gt;noOutput) print $out; // ob_get_clean
    }

    /**
     * Returns the buffered output text.
     * @return string
     */
    public final function getOutput() {
        if($this-&gt;level != ob_get_level()) {
            self::trace(&quot;Warning: Actual buffer level (&quot; . ob_get_level() . &quot;) is deeper than this construction level (&quot;.$this-&gt;level.&quot;)&quot;);
            return &quot;[Buffer level not ok]&quot;;
        } else {
            return ob_get_clean();
        }
    }

    /**
     * Private tracing method
     * @param string $text
     * @param int $level
     */
    protected function trace($text, $level=1) {
        if(!Tracer::tracedClass(__CLASS__)) return;
        Tracer::trace($text, $level);
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/output-buffering-class/2010-07-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>UTC date class</title>
		<link>http://www.atwillys.de/programming/php/240/2010-07-04/</link>
		<comments>http://www.atwillys.de/programming/php/240/2010-07-04/#comments</comments>
		<pubDate>Sun, 04 Jul 2010 11:27:53 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[date]]></category>
		<category><![CDATA[UTC]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=240</guid>
		<description><![CDATA[Here a &#8220;convenience class cluster&#8221; for UTC dates, local dates and the interface IDate, which is implemented from both UtcDate and LocalDate Class source code This Article is also available in Deutsch.]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>Here a &#8220;convenience class cluster&#8221; for UTC dates, local dates and the interface <code>IDate</code>, which is implemented from both <code>UtcDate</code> and <code>LocalDate</code></p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * UTC/Local date interface. Implemented in class UtcDate, LocalDate.
 * @package de.atwillys.sw.php.swLib
 * @interface IDate
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2005-2010
 * @license GPL
 * @version 1.0
 */
interface IDate {

    /**
     * Returns year
     * @return int
     */
    public function getYear();

    /**
     * Returns month
     * @return int
     */
    public function getMonth();

    /**
     * Returns the day
     * @return int
     */
    public function getDay();

    /**
     * Returns hour
     * @return int
     */
    public function getHour();

    /**
     * Returns minute
     * @return int
     */
    public function getMinute();

    /**
     * Returns second
     * @return int
     */
    public function getSecond();

    /**
     * Returns unix time stamp
     * @return int
     */
    public function getTimeStamp();

    /**
     * Returns weekday
     * @return int
     */
    public function getWeekDay();

    /**
     * Sets year
     * @param int $year
     */
    public function setYear($year);

    /**
     * Sets month
     * @param int $month
     */
    public function setMonth($month);

    /**
     * Sets day
     * @param int $day
     */
    public function setDay($day);

    /**
     * Sets hour
     * @param int $hour
     */
    public function setHour($hour);

    /**
     * Sets minute
     * @param int $minute
     */
    public function setMinute($minute);

    /**
     * Sets second
     * @param int $second
     */
    public function setSecond($second);

    /**
     * Returns unix time stamp
     * @patram int $timestamp
     */
    public function setTimeStamp($timestamp);

    /**
     * Changes all properties != null and updates time stamp
     * @param int $year
     * @param int $month
     * @param int $day
     * @param int $hour
     * @param int $minute
     * @param int $second
     * @return void
     */
    public function setSerial($year=null, $month=null, $day=null, $hour=null, $minute=null, $second=null);

    /**
     * Returns this - another date, object is not affected
     * @param IDate $date
     * @return IDate
     */
    public function substract( $date);

    /**
     * Returns this + another date, object is not affected
     * @param IDate $date
     * @return IDate
     */
    public function add($date);

    /**
     * Returns the date/time from stamp in days
     * @return double
     */
    public function inDays();

    /**
     * Returns the date/time from stamp in hours
     * @return double
     */
    public function inHours();

    /**
     * Returns the date/time from stamp in hours
     * @return double
     */
    public function inMinutes();

    /**
     * Returns the date/time from stamp in seconds
     * @return double
     */
    public function inSeconds();

    /**
     * Returns next 'month','day','hour','minute','second'.
     * Next month of 2008-01-24 13:12:14 returns 2008-02-01 00:00:00
     * @return IDate
     */
    public function getNext($unit='second');

    /**
     * Returns last 'month','day','hour','minute','second'.
     * @return IDate
     */
    public function getLast($unit='second');

}
?&gt;

&lt;?php
/**
 * UTC refered dates and related operations.
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2005-2010
 * @license GPL
 * @version 1.0
 * @uses IDate
 */
class UtcDate implements IDate {

    /**
     * Contains the unix timestamp
     * @var array
     */
    protected $mTimeStamp = 0;

    /**
     * Contains year
     * @var int
     */
    protected $mYear;

    /**
     * Contains month
     * @var int
     */
    protected $mMonth;

    /**
     * Contains day
     * @var int
     */
    protected $mDay;

    /**
     * Contains hour
     * @var int
     */
    protected $mHour;

    /**
     * Contains minute
     * @var int
     */
    protected $mMinute;

    /**
     * Contains second
     * @var int
     */
    protected $mSecond;

    /**
     * Contains day in week
     * @var int
     */
    protected $mWeekDay;

    /**
     * Class constructor
     * @param mixed $datetime
     */
    public function __construct($datetime=null) {
        if(empty($datetime)) {
            $this-&gt;mTimeStamp = strtotime(gmdate(&quot;M d Y H:i:s&quot;, time()));
        } else if($datetime instanceof IDate) {
            $this-&gt;mTimeStamp = $datetime-&gt;getTimeStamp();
        } else if(is_numeric($datetime)) {
            $this-&gt;mTimeStamp = strtotime(gmdate(&quot;M d Y H:i:s&quot;, $datetime));
        } else if(!is_string($datetime)) {
            throw new Exception('Date format is not valid');
        } else {
            $t = strtotime($datetime);
            if($t === false) {
                throw new Exception('Cannot parse the passed date text');
            } else {
                $this-&gt;mTimeStamp = strtotime(gmdate(&quot;M d Y H:i:s&quot;, $t));
            }
        }
        $this-&gt;update();
    }

    /**
     * Updates properties by timestamp
     * @return void
     */
    protected function update() {
        $dt = getdate($this-&gt;mTimeStamp);
        $this-&gt;mSecond 		= $dt['seconds'];
        $this-&gt;mMinute 		= $dt['minutes'];
        $this-&gt;mHour 		= $dt['hours'];
        $this-&gt;mDay 		= $dt['mday'];
        $this-&gt;mMonth 		= $dt['mon'];
        $this-&gt;mYear 		= $dt['year'];
        $this-&gt;mWeekDay 	= $dt['wday'] &lt; 1 ? 7 : $dt['wday'];
    }

    /**
     * Updates timestamp by properties
     * @return void
     */
    protected function updateTimeStamp() {
        $this-&gt;mTimeStamp = mktime($this-&gt;mHour,$this-&gt;mMinute,$this-&gt;mSecond,$this-&gt;mMonth,$this-&gt;mDay,$this-&gt;mYear, 0);
    }

    /**
     * Returns format &quot;YYYY-MM-DD HH:MM:SS GMT&quot;
     * @return string
     */
    public function toString() {
        return sprintf(&quot;%4d-%02d-%02d %02d:%02d:%02d %3s&quot;,
            $this-&gt;getYear(),
            $this-&gt;getMonth(),
            $this-&gt;getDay(),
            $this-&gt;getHour(),
            $this-&gt;getMinute(),
            $this-&gt;getSecond(),
            $this-&gt;getTimeZoneName()
        );
    }

    /**
     * Returns format  &quot;YYYY-MM-DD&quot;
     * @return string
     */
    public function toDateString() {
        return sprintf(&quot;%4d-%02d-%02d&quot;,
            $this-&gt;getYear(),
            $this-&gt;getMonth(),
            $this-&gt;getDay()
        );
    }

    /**
     * Returns format &quot;HH:MM::SS&quot;
     * @return string
     */
    public function toTimeString() {
        return sprintf(&quot;%02d:%02d:%02d&quot;,
            $this-&gt;getHour(),
            $this-&gt;getMinute(),
            $this-&gt;getSecond()
        );
    }

    /**
     * Returns format &quot;YYYY-MM-DD HH:MM GMT&quot;
     * @return string
     */
    public function __toString() {
        return $this-&gt;toString();
    }

    /**
     * Returns year
     * @return int
     */
    public function getYear() {
        return $this-&gt;mYear;
    }

    /**
     * Returns month
     * @return int
     */
    public function getMonth() {
        return $this-&gt;mMonth;
    }

    /**
     * Returns day
     * @return int
     */
    public function getDay() {
        return $this-&gt;mDay;
    }

    /**
     * Returns hour
     * @return int
     */
    public function getHour() {
        return $this-&gt;mHour;
    }

    /**
     * Returns minute
     * @return int
     */
    public function getMinute() {
        return $this-&gt;mMinute;
    }

    /**
     * Returns second
     * @return int
     */
    public function getSecond() {
        return $this-&gt;mSecond;
    }

    /**
     * Returns unix time stamp
     * @return int
     */
    public function getTimeStamp() {
        return $this-&gt;mTimeStamp;
    }

    /**
     * Returns the time zone abbreciation
     * @return string
     */
    public function getTimeZoneName() {
        return &quot;GMT&quot;;
    }

    /**
     * Returns weekday
     * @return int
     */
    public function getWeekDay() {
        return $this-&gt;mWeekDay;
    }

    /**
     * Sets year
     * @param int $year
     */
    public function setYear($year) {
        $this-&gt;mYear = $year;
        $this-&gt;updateTimeStamp();
    }

    /**
     * Sets month
     * @param int $month
     */
    public function setMonth($month) {
        $this-&gt;mMonth = $month;
        $this-&gt;updateTimeStamp();
    }

    /**
     * Sets day
     * @param int $day
     */
    public function setDay($day) {
        $this-&gt;mDay = $day;
        $this-&gt;updateTimeStamp();
    }

    /**
     * Sets hour
     * @param int $hour
     */
    public function setHour($hour) {
        $this-&gt;mHour = $hour;
        $this-&gt;updateTimeStamp();
    }

    /**
     * Sets minute
     * @param int $minute
     */
    public function setMinute($minute) {
        $this-&gt;mMinute = $minute;
        $this-&gt;updateTimeStamp();
    }

    /**
     * Sets second
     * @param int $second
     */
    public function setSecond($second) {
        $this-&gt;mSecond = $second;
        $this-&gt;updateTimeStamp();
    }

    /**
     * Sets the timestamp
     * @param int $timestamp
     */
    public function setTimeStamp($timestamp) {
        $this-&gt;mTimeStamp = $timestamp;
        $this-&gt;update();
    }

    /**
     * Changes all properties != null and updates time stamp
     * @param int $year
     * @param int $month
     * @param int $day
     * @param int $hour
     * @param int $minute
     * @param int $second
     * @return void
     */
    public function setSerial($year=null, $month=null, $day=null, $hour=null, $minute=null, $second=null) {
        if($year  !== null) $this-&gt;mYear   = $year;
        if($month !== null) $this-&gt;mMonth  = $month;
        if($day   !== null) $this-&gt;mDay    = $day;
        if($hour  !== null) $this-&gt;mHour   = $hour;
        if($minute!== null) $this-&gt;mMinute = $minute;
        if($second!== null) $this-&gt;mSecond = $second;
        $this-&gt;updateTimeStamp();
    }

    /**
     * Returns this - another date, object is not affected, $date can also be
     * a timestamp or period in seconds
     * @param IDate $date
     * @return UtcDate
     */
    public function substract($date) {
        $class = get_class($this);
        if(is_numeric($date)) {
            return new $class($this-&gt;getTimeStamp() - intval($date));
        } else if($date instanceof IDate) {
            return new $class($this-&gt;getTimeStamp() - $date-&gt;getTimeStamp());
        } else {
            throw new Exception('Only timestamps or IDate can be substracted from an IDate');
        }
    }

    /**
     * Returns this + another date, object is not affected, $date can also be
     * a timestamp or a period in seconds
     * @param IDate $date
     * @return UtcDate
     */
    public function add($date) {
        $class = get_class($this);
        if(is_numeric($date)) {
            return new $class($this-&gt;getTimeStamp() + intval($date));
        } else if($date instanceof IDate) {
            return new $class($this-&gt;getTimeStamp() + $date-&gt;getTimeStamp());
        } else {
            throw new Exception('Only timestamps or IDate can be added to an IDate');
        }
    }

    /**
     * Returns the date/time from stamp in days
     * @return double
     */
    public function inWeeks() {
        return round($this-&gt;getTimeStamp() / 604800.0, 2);
    }

    /**
     * Returns the date/time from stamp in days
     * @return double
     */
    public function inDays() {
        return round($this-&gt;getTimeStamp() / 86400.0, 2);
    }

    /**
     * Returns the date/time from stamp in hours
     * @return double
     */
    public function inHours() {
        return round($this-&gt;getTimeStamp() / 3600.0, 2);
    }

    /**
     * Returns the date/time from stamp in hours
     * @return double
     */
    public function inMinutes() {
        return round($this-&gt;getTimeStamp() / 60.0, 2);
    }

    /**
     * Returns the date/time from stamp in seconds
     * @return double
     */
    public function inSeconds() {
        return $this-&gt;getTimeStamp();
    }

    /**
     * Returns next 'month','day','hour','minute','second'.
     * Next month of 2008-01-24 13:12:14 returns 2008-02-01 00:00:00
     * @return UtcDate
     */
    public function getNext($unit='second') {
        $unit = strtolower(trim($unit, &quot; \n\r\t&quot;));
        $year=null; $month=null; $day=null; $hour=null; $minute=null; $second=null;
        // Do not insert breaks here ...
        switch($unit) {
            case 'month':
                $day=1;
            case 'week':
            case 'day':
            case 'sunday':
            case 'monday':
            case 'tuesday':
            case 'wednesday':
            case 'thursday':
            case 'friday':
            case 'saturday':
                $hour=0;
            case 'hour':
                $minute=0;
            case 'minute':
                $second=0;
            case 'second':
                break;
            default:
                throw new Exception(&quot;argument unit is not valid: '$unit'&quot;);
        }
        $class = get_class($this);
        $d = new $class(strtotime(&quot;next $unit&quot;, $this-&gt;getTimeStamp()));
        $d-&gt;setSerial($year,$month,$day,$hour,$minute,$second);
        return $d;
    }

    /**
     * Returns last 'month','day','hour','minute','second'.
     * Next last of 2008-01-24 13:12:14 returns 2007-12-01 00:00:00
     * @return UtcDate
     */
    public function getLast($unit='second') {
        $unit = strtolower(trim($unit, &quot; \n\r\t&quot;));
        $year=null; $month=null; $day=null; $hour=null; $minute=null; $second=null;
        // Do not insert breaks here ...
        switch($unit) {
            case 'month':
                $day=1;
            case 'week':
            case 'day':
            case 'sunday':
            case 'monday':
            case 'tuesday':
            case 'wednesday':
            case 'thursday':
            case 'friday':
            case 'saturday':
                $hour=0;
            case 'hour':
                $minute=0;
            case 'minute':
                $second=0;
            case 'second':
                break;
            default:
                throw new Exception(&quot;argument unit is not valid: '$unit'&quot;);
        }
        $class = get_class($this);
        $d = new $class(strtotime(&quot;last $unit&quot;, $this-&gt;getTimeStamp()));
        $d-&gt;setSerial($year,$month,$day,$hour,$minute,$second);
        return $d;
    }

}
?&gt;

&lt;?php
/**
 * Local time refered dates: same as UtcDate for local date/times. It is
 * assumed that $datetime (if numeric) is a local timestamp (form time() )
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2005-2010
 * @license GPL
 * @version 1.0
 * @uses UtcDate
 * @uses IDate
 */
class LocalDate extends UtcDate implements IDate {

    /**
     * Class constructor
     * @param mixed $datetime
     */
    public function __construct($datetime=null) {
        if(empty($datetime)) {
            $this-&gt;mTimeStamp = time();
        } else if(is_numeric($datetime))	{
            $this-&gt;mTimeStamp = intval($datetime);
        } else if($datetime instanceof IDate) {
            $this-&gt;mTimeStamp = $datetime-&gt;getTimeStamp();
        } else if(!is_string($datetime)) {
            throw new Exception('Date format is not valid');
        } else {
            $t = strtotime($datetime);
            if($t === false) {
                throw new Exception('Cannot parse the passed date text');
            } else {
                $this-&gt;mTimeStamp = $t;
            }
        }
        $this-&gt;update();
    }

    /**
     * Returns the time zone abbreciation
     * @return string
     */
    public function getTimeZoneName() {
        return trim(strtoupper(date(&quot;T&quot;, $this-&gt;mTimeStamp)));
    }

    /**
     * Updates timestamp by properties
     * @return void
     */
    protected function updateTimeStamp() {
        $this-&gt;mTimeStamp = mktime($this-&gt;mHour,$this-&gt;mMinute,$this-&gt;mSecond,$this-&gt;mMonth,$this-&gt;mDay,$this-&gt;mYear, -1);
    }
}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/240/2010-07-04/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>SEES source code and MacOS 10.6 binary available</title>
		<link>http://www.atwillys.de/programming/sees/sees-source-code-and-macos-10-6-binary-available/2010-07-01/</link>
		<comments>http://www.atwillys.de/programming/sees/sees-source-code-and-macos-10-6-binary-available/2010-07-01/#comments</comments>
		<pubDate>Thu, 01 Jul 2010 20:12:36 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[SEES]]></category>
		<category><![CDATA[interpreter]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[shell script]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=200</guid>
		<description><![CDATA[The page with the binary, source code, introduction and reference is available: You can find it here.]]></description>
			<content:encoded><![CDATA[<p>The page with the binary, source code, introduction and reference is available: You can find it <a href="/software/sees/">here</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/sees/sees-source-code-and-macos-10-6-binary-available/2010-07-01/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Zip a file or directory</title>
		<link>http://www.atwillys.de/programming/sees/zip-a-file-or-directory/2010-07-01/</link>
		<comments>http://www.atwillys.de/programming/sees/zip-a-file-or-directory/2010-07-01/#comments</comments>
		<pubDate>Thu, 01 Jul 2010 17:22:37 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[SEES]]></category>
		<category><![CDATA[interpreter]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[shell script]]></category>
		<category><![CDATA[zip]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=164</guid>
		<description><![CDATA[]]></description>
			<content:encoded><![CDATA[<pre class="brush: jscript; title: ; notranslate">
function zip(path, zipfile) {
	if(!path || path.toString().trim() == '') {
		throw new Error(&quot;No file/directory to zip given&quot;);
	} else if(!exist(path)) {
		throw new Error(&quot;File/Directory to zip not exist: &quot; + path);
	} else if(!zipfile || zipfile.toString().trim() == '') {
		throw new Error(&quot;No zip file given to compress \&quot;&quot; + path + &quot;\&quot;&quot;);
	} else if(exist(zipfile)) {
		throw new Error(&quot;Zip file already exists:&quot; + zipfile);
	} else {
		var here = pwd();
		try {
			cd(dirname(path));
			var r = exec(&quot;zip&quot; + (isdir(path) ? &quot; -r&quot; : &quot;&quot;) + &quot; -o -9 &quot; + zipfile + &quot;  &quot; + basename(path));
			cd(here);
		} catch(e) {
			cd(here);
			throw e;
		}

		if(r.exitcode != 0) {
			throw new Error(&quot;zip tool failed: &quot; + r.stderr);
		}
	}
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/sees/zip-a-file-or-directory/2010-07-01/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Remove .DS_Store files</title>
		<link>http://www.atwillys.de/programming/sees/remove-ds_store-files/2010-07-01/</link>
		<comments>http://www.atwillys.de/programming/sees/remove-ds_store-files/2010-07-01/#comments</comments>
		<pubDate>Thu, 01 Jul 2010 17:14:29 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[SEES]]></category>
		<category><![CDATA[interpreter]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[shell script]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=161</guid>
		<description><![CDATA[A function to remove .DS_Store files in a given path using SEES. <a href="http://www.atwillys.de/programming/sees/remove-ds_store-files/2010-07-01/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<pre class="brush: jscript; title: ; notranslate">
function removeDS_Store(path) {
	if(!path || path.toString().trim() == '') {
		throw new Error(&quot;No directory to remove .DS_Store given&quot;);
	} else if(!isdir(path)) {
		throw new Error(&quot;Directory to remove .DS_Store does not exist: &quot; + path);
	} else {
		var files = ls(path + '/*', true, true);
		for(var i=0; i&lt;files.length; i++) {
			if(files[i].match(/.DS_Store$/i)) {
				rm(files[i]);
			}
		}
	}
}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/sees/remove-ds_store-files/2010-07-01/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Base64 encoder/decoder class</title>
		<link>http://www.atwillys.de/programming/cpp/base64-encoderdecoder-class/2010-06-25/</link>
		<comments>http://www.atwillys.de/programming/cpp/base64-encoderdecoder-class/2010-06-25/#comments</comments>
		<pubDate>Fri, 25 Jun 2010 20:47:36 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[c++]]></category>
		<category><![CDATA[base64]]></category>
		<category><![CDATA[decoder]]></category>
		<category><![CDATA[encoder]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=30</guid>
		<description><![CDATA[This is a small class for encoding and decoding base64 streams. Header file: Implementation file: This Article is also available in Deutsch.]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>This is a small class for encoding and decoding base64 streams.</p>
</div>
<p>Header file:</p>
<pre class="brush: cpp; title: ; notranslate">
#ifndef __BASE_64_STREAM__HPP__
#define __BASE_64_STREAM__HPP__

#include &lt;string&gt;
#include &lt;iostream&gt;
#include &lt;sstream&gt;

namespace sw {
    namespace codec {

        class base64 {
        public:

            size_t max_decode_size(const std::string &amp; s) const;
            bool encode(const std::string &amp; s_in, std::string &amp; s_out) const;
            bool decode(const std::string &amp; s_in, std::string &amp; s_out) const;
            bool encode(const void* si, size_t len, std::string &amp; s) const;
            bool decode(const std::string &amp; s, void* so, size_t max_so, size_t *n_so_converted) const;

        private:

            inline bool encode3to4(const unsigned char* c3, char* c4) const {
                c4[0] = _emap[ (unsigned char) (((c3[0] &amp; 0xfc) &gt;&gt; 2)) ];
                c4[1] = _emap[ (unsigned char) (((c3[0] &amp; 0x03) &lt;&lt; 4) + ((c3[1] &amp; 0xf0) &gt;&gt; 4)) ];
                c4[2] = _emap[ (unsigned char) (((c3[1] &amp; 0x0f) &lt;&lt; 2) + ((c3[2] &amp; 0xc0) &gt;&gt; 6)) ];
                c4[3] = _emap[ (unsigned char) (((c3[2] &amp; 0x3f))) ];
                return true;
            }

            inline bool decode4to3(const char* c4i, unsigned char* c3) const {
                unsigned char c4[4];
                for (int i = 0; i &lt; 4; i++) {
                    c4[i] = _dmap[ (unsigned char) c4i[i]];
                } // printf(&quot;%c-&gt;%02x|&quot;, c4i[i], c4[i], c4[i]);
                c3[0] = (((c4[0] &lt;&lt; 2)) + ((c4[1] &amp; 0x30) &gt;&gt; 4));
                c3[1] = (((c4[1] &amp; 0xf) &lt;&lt; 4) + ((c4[2] &amp; 0x3c) &gt;&gt; 2));
                c3[2] = (((c4[2] &amp; 0x3) &lt;&lt; 6) + ((c4[3])));
                return c4[0] &lt; 0xfa &amp;&amp; c4[1] &lt; 0xfa &amp;&amp; c4[2] &lt; 0xfa &amp;&amp; c4[3] &lt; 0xfa;
            }

            static const char _emap[64];
            static const unsigned char _dmap[256];
        };
    }
};

#endif
</pre>
<p>Implementation file:</p>
<pre class="brush: cpp; title: ; notranslate">
#include &lt;sw/private/base64.hpp&gt;

const char sw::codec::base64::_emap[64] = {
    0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,
    0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x61,0x62,0x63,0x64,0x65,0x66,
    0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0x73,0x74,0x75,0x76,
    0x77,0x78,0x79,0x7a,0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x2b,0x2f
};

const unsigned char sw::codec::base64::_dmap[256] = {
    0xfc,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xfe,0xfe,0xfe,0xfe,0xfe,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xfe,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x3e,0xff,0xff,0xff,0x3f,
    0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0xff,0xff,0xff,0xfd,0xff,0xff,
    0xff,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,
    0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0xff,0xff,0xff,0xff,0xff,
    0xff,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,
    0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
    0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff
};

size_t sw::codec::base64::max_decode_size(const std::string &amp; s) const
{
    return s.length() * 3 / 4 + 1;
}

bool sw::codec::base64::encode(const std::string &amp; s_in, std::string &amp; s_out) const
{
    return encode(s_in.c_str(), s_in.length()-1, s_out);
}

bool sw::codec::base64::decode(const std::string &amp; s_in, std::string &amp; s_out) const
{
    if(s_in.empty()) {
        s_out = &quot;&quot;;
        return true;
    } else {
        size_t len = max_decode_size(s_in)+1;
        char* s_c = new char[len];
        if(s_c) {
            if(decode(s_in, s_c, len, &amp;len)) {
                s_c[len] = '&#92;&#48;';
                s_out = s_c;
                delete [] s_c;
                return true;
            } else {
                s_out = &quot;&quot;;
                delete [] s_c;
                return false;
            }
        } else {
                return false;
        }
    }
}

bool sw::codec::base64::encode(const void* si, size_t len, std::string &amp; s) const
{
    if(!si) {
            return false;
    } else  if(len==0) {
            return true;
    } else {
        std::stringstream so;
        char c4[4];
        const unsigned char* pi = (unsigned char*) si;					// byte stepped pointer
        const unsigned char* pe = (unsigned char*) si+len;				// end pointer
        const unsigned char* pm = (unsigned char*) si+(len-(len%3));	// loop end pointer
        unsigned int  i=0;
        while(pi&lt;pm) {
            encode3to4(pi,c4);
            pi+=3;
            for(int i=0; i&lt;4; i++) so &lt;&lt; c4[i];
        }
        if(pe&gt;pm) {
            unsigned char c3[3];
            for(i=0; i&lt;(len%3); i++) c3[i] = *(pi++);
            while(i&lt;4) c3[i++] = '&#92;&#48;';
            encode3to4(c3,c4);
            for(i=0; i&lt;(len%3)+1; i++) so &lt;&lt; c4[i];
            while(i++&lt;4) so &lt;&lt; &quot;=&quot;;
        }
        s = so.str();
        return true;
    }
    return false;
}

bool sw::codec::base64::decode(const std::string &amp; s, void* so, size_t max_so, size_t *n_so_converted) const
{
    (*n_so_converted) = 0; // do not check error, the programmer has to treat access violations here
    if(max_so &lt; max_decode_size(s)) return false;
    const char* pi = (const char*) s.c_str();		// input iteration pointer
    const char*  pe = (const char*) pi+s.length();	// input iteration end() pointer
    unsigned char* po = (unsigned char*) so;		// output (iterated) pointer
    bool isend = false;
    while(pi&lt;pe) {
        if(decode4to3(pi, po)) {
            (*n_so_converted)+=3;
            // printf(&quot;regdec: n=%d, values={%02x,%02x,%02x}&quot;, *n_so_converted, po[0], po[1], po[2]);
            pi+=4; po+=3;
        } else {
            char c4[4];
            size_t c4l =0;
            memset(c4,0,4);
            while(c4l&lt;4 &amp;&amp; pi&lt;pe &amp;&amp; !isend) {
                switch(_dmap[(unsigned char)*pi]) {
                    case 0xff: return false;				// invalid char
                    case 0xfe: *pi++; break;				// ignored char (space char, newline...)
                    case 0xfd: isend = true; break;			// (id 0xff-1) in decode map is '='
                    case 0xfc: isend = true; break;			// 0xfc in decode map is '&#92;&#48;'
                    default: c4[c4l++] = *pi++;
                }
            } if(!isend) {
                if(decode4to3(c4, po)) {
                    (*n_so_converted)+=3; // printf(&quot;spedec: n=%d, values={%02x,%02x,%02x}&quot;, *n_so_converted, po[0], po[1], po[2]);
                    po+=3;
                } else {
                    return false;
                }
            } else {
                for(int i=c4l; i&lt;4; i++) c4[i] = '&#92;&#48;';
                decode4to3(c4, po);
                (*n_so_converted) += c4l-1;
                pi=pe;
            }
        }
    }
    return true;
}

/*
void makebase64tables()
{
	const std::string cmap = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/&quot;;
	unsigned char dmap[256];
	char emap[64];
	int i;

	for(i=0; i&lt;cmap.length(); i++)
            emap[i] = cmap[i];

	memset(dmap, 0xff, 256);

	for(i=0; i&lt;cmap.length(); i++)
		dmap[(unsigned char) emap[i]] = (unsigned char) i;

	printf(&quot;const char emap = { \n&quot;);
	for(i=0; i&lt;sizeof(emap); i++) {
            if(i%8==0) printf(&quot;\n&quot;);
            printf(&quot;0x%02x,&quot;, (unsigned) emap[i]);
	}
	printf(&quot;}; \n\n const unsigned char base64::_dmap[256] = {&quot;);
	for(i=0; i&lt;sizeof(dmap); i++) {
            if(i%8==0) printf(&quot;\n&quot;);
            if(i==0)
              printf(&quot;0xfc,&quot;);
            else if(i == '=')
                printf(&quot;0xfd,&quot;);
            else if(isspace(i) || i=='\n' || i=='\r')
                printf(&quot;0xfe,&quot;);
            else
                printf(&quot;0x%02x,&quot;, (unsigned) dmap[i]);
	}
	printf(&quot;\n};\n&quot;);
}

*/
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/cpp/base64-encoderdecoder-class/2010-06-25/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>ZIP file wrapper class</title>
		<link>http://www.atwillys.de/programming/php/zip-file-wrapper-class/2010-06-24/</link>
		<comments>http://www.atwillys.de/programming/php/zip-file-wrapper-class/2010-06-24/#comments</comments>
		<pubDate>Thu, 24 Jun 2010 14:10:33 +0000</pubDate>
		<dc:creator>stfwi</dc:creator>
				<category><![CDATA[PHP]]></category>
		<category><![CDATA[class]]></category>
		<category><![CDATA[zip]]></category>

		<guid isPermaLink="false">http://www.atwillys.de/?p=4</guid>
		<description><![CDATA[I was a little bit fed up with the situation that I have to organize too much around the ZipArchive class. So, here is a wrapper for this class, which applies to the usual tasks you have for zip files &#8230; <a href="http://www.atwillys.de/programming/php/zip-file-wrapper-class/2010-06-24/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<div class='en' style='' lang='en' dir='ltr'>
<p>I was a little bit fed up with the situation that I have to organize too much around the ZipArchive class. So, here is a wrapper for this class, which applies to the usual tasks you have for zip files (compress recursive, extract recursive, look what&#8217;s inside etc.). The class ZipFile does not return error, but instead throws exceptions, which is (so I think) more elaborated. Notice, that if you should put the ZipException in a separate file for autoloading. File contents of ZipException.class.php and ZipFile.class.php:</p>
</div>
<div class='en' style='' lang='en' dir='ltr'>
<h3>Class source code</h3>
</div>
<pre class="brush: php; title: ; notranslate">
&lt;?php
/**
 * Exception thrown by class ZipFile
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2007-2010
 * @license GPL
 * @version 1.0
 */
class ZipException extends Exception {
}
?&gt;

&lt;?php
/**
 * Wrapper class for ZipArchive with exceptions. Provides simple functions
 * to compress, list and extract files or folders (recursively).
 * @package de.atwillys.sw.php.swLib
 * @author Stefan Wilhelm
 * @copyright Stefan Wilhelm, 2007-2010
 * @license GPL
 * @version 1.0
 * @uses ZipException
 *
 */
class ZipFile {

    /**
     * The zip object
     * @var ZipArchive
     */
    private $zip = null;

    /**
     * Constructor
     * @return ZipFile
     */
    public final function __construct() {
        $this-&gt;zip = new ZipArchive();
    }

    /**
     * Destructor
     */
    public final function __destruct() {
        $this-&gt;close();
    }

    /**
     * Throws an ZipException according to the occurred ZipArchive
     * error code.
     * @static
     * @param int $zipArchiveErrorCode
     * @throws ZipException
     * @return void
     */
    private static final function throwZipException($zipArchiveErrorCode) {
        switch($zipArchiveErrorCode) {
            case ZIPARCHIVE::ER_EXISTS:
                throw new ZipException('Zip file already exists');
            case ZIPARCHIVE::ER_OPEN:
                throw new ZipException('Failed to open zip file');
            case ZIPARCHIVE::ER_READ:
                throw new ZipException('Could not read zip file');
            case ZIPARCHIVE::ER_INCONS:
                throw new ZipException('Zip file inconsistent');
            case ZIPARCHIVE::ER_INVAL:
                throw new ZipException('Zip file invalid');
            case ZIPARCHIVE::ER_MEMORY:
                throw new ZipException('Not enough memory to handle zip file');
            case ZIPARCHIVE::ER_NOENT:
                throw new ZipException('Zip file has no entries');
            case ZIPARCHIVE::ER_NOZIP:
                throw new ZipException('File is no zip file');
            case ZIPARCHIVE::ER_SEEK:
                throw new ZipException('Could seek the position in zip file');
            default:
                throw new ZipException('Unknown error occurred handling zip file');
        }
    }

    /**
     * Generates a new zip file from a file or a folder. Folders are
     * compressed recursively.
     * @param string $fileOrFolder
     * @param string $zipArchive
     * @return string
     */
    public static function compress($fileOrFolder, $zipArchive) {
        $zip = new self();
        $zip-&gt;create($zipArchive);
        try {
            $zip-&gt;add($fileOrFolder);
        } catch(Exception $e) {
            $zip-&gt;close();
            @unlink($zipArchive);
            throw $e;
        }
        $zip-&gt;close();
        return realpath($zipArchive);
    }

    /**
     * Extracts a file or folder to a specified location folder.
     * The folder must exist before extracting
     * @param string $zipArchive
     * @param string $folder
     */
    public static function extract($zipArchive, $folder) {
        if(!is_dir($folder)) {
            throw new ZipException('Directory to extract to does not exist');
        } else {
            $zip = new self();
            $zip-&gt;open($zipArchive);
            $e = $zip-&gt;zip-&gt;extractTo($folder);
            if(!$e) {
                $zip-&gt;close();
                throw new ZipException('Failed to extract files');
            }
            $zip-&gt;close();
        }
    }

    /**
     * Returns an assoc. array containing the list of all entries in the zip archive.
     * Each entry is an assoc. array that contains details about the file/folder.
     * @return array
     */
    public static function contents($zipArchive) {
        $zip = new self();
        $zip-&gt;open($zipArchive);
        $l = $zip-&gt;getContentList();
        $zip-&gt;close();
        return $l;
    }

    /**
     * Opens a zip file for manipulation and returns
     * the ZipFile object for the opened file.
     * @param string $file
     * @throws ZipException
     * @return void
     */
    public function open($file) {
        if(!is_file($file)) {
            throw new ZipException('Zip file to open does not exist');
        } else if(!is_readable($file)) {
            throw new ZipException('Zip file to open is not readable');
        } else {
            $e = $this-&gt;zip-&gt;open($file);
            if($e !== true) {
                self::throwZipException($e);
            }
        }
    }

    /**
     * Creates and opens a zip file for manipulation and returns
     * the ZipFile object for the created file.
     * @static
     * @param string $file
     * @throws ZipException
     * @return void
     */
    public function create($file) {
        if(is_file($file)) {
            throw new ZipException('Zip file to create already exists');
        } else {
            $e = $this-&gt;zip-&gt;open($file, ZipArchive::CREATE);
            if($e !== true) {
                self::throwZipException($e);
            }
        }
    }

    /**
     * Closes the zip file
     */
    public function close() {
        try {
            @$this-&gt;zip-&gt;close();
        } catch(Exception $e) {
            // ignore here
        }
    }

    /**
     * Adds a file or a directory (recursive) to the zip archive. The $basePath
     * indicates the root directory of the archive. If e.g. $pathToFileOrDir
     * is &quot;/tmp/folder1/folder2/file.txt&quot; and $basePath is &quot;/tmp/folder1&quot;, then
     * the file.txt will be in the archive as &quot;/folder2/file.txt&quot;.
     * @param string $pathToFileOrDir
     * @param string $basePath
     */
    public function add($pathToFileOrDir, $basePath=null) {
        $pathToFileOrDir = trim($pathToFileOrDir);
        $basePath = trim($basePath);
        Tracer::trace(&quot;add '$pathToFileOrDir', basepath '$basePath'&quot;);

        if(!is_file($pathToFileOrDir) &amp;&amp; !is_dir($pathToFileOrDir)) {
            throw new ZipException('File or directory to add does not exist in file system');
        } else {
            if(empty($basePath)) {
                // Assume that the file/directory is to be added to the zip root
                $basePath = dirname($pathToFileOrDir);
            }
            if(stripos($pathToFileOrDir, $basePath) === false) {
                throw new ZipException('The path to the file or directory must include base path');
            }

            // Create local path for zip
            if(is_file($pathToFileOrDir)) {
                $localPath = str_ireplace($basePath, '', $pathToFileOrDir);
                // Check if the directory structure already exist in the zip file
                if(dirname($localPath) != '') {
                    $e = $this-&gt;zip-&gt;addEmptyDir(dirname($localPath));
                    if($e !== true &amp;&amp; $e != ZipArchive::ER_EXISTS) {
                        self::throwZipException($e);
                    }
                }
                // Finally add the file
                $e = $this-&gt;zip-&gt;addFile($pathToFileOrDir, $localPath);
                if($e !== true) {
                    self::throwZipException($e);
                }
            } else if(is_dir($pathToFileOrDir)) {
                // Add a directory
                $content = FileSystem::find($pathToFileOrDir,'*');
                foreach($content as $file) {
                    $localPath = str_ireplace($basePath, '', $file);
                    if(is_dir($file)) {
                        $e = $this-&gt;zip-&gt;addEmptyDir($localPath);
                        if($e !== true &amp;&amp; $e != ZipArchive::ER_EXISTS) {
                            self::throwZipException($e);
                        }
                    } else {
                        $e = $this-&gt;zip-&gt;addFile($file, $localPath);
                        if($e !== true) {
                            self::throwZipException($e);
                        }
                    }
                }
            }
        }
    }

    /**
     * Removes a file or a folder in the zip file
     * @param string $fileOrFolderInZip
     */
    public function remove($fileOrFolderInZip) {
        $fileOrFolderInZip = trim($fileOrFolderInZip);
        if(strlen($fileOrFolderInZip) == 0) {
            throw new ZipException('No file or folder to delete given');
        } else if($fileOrFolderInZip == '/') {
            throw new ZipException('Cannot delete the root of zip file');
        } else {

            print &quot;$fileOrFolderInZip\n\n\n&quot;;
            if(!$this-&gt;zip-&gt;deleteName($fileOrFolderInZip)) {
                throw new ZipException('Failed to remove file/folder in zip archive');
            }
        }
    }

    /**
     * Returns an assoc. array containing the list of all entries in the zip archive.
     * Each entry is an assoc. array that contains details about the file/folder.
     * @return array
     */
    public function getContentList() {
        $stats = array();
        for($i=0; $i&lt;$this-&gt;zip-&gt;numFiles; $i++) {
            $stat = $this-&gt;zip-&gt;statIndex($i);
            if(!is_array($stat)) {
                throw new ZipException('Failed to receive the status or a contained file or folder');
            } else {
                $stats[$stat['name']] = $stat;
            }
        }
        return $stats;
    }

}
?&gt;
</pre>
<p>
<p><small>This Article is also available in <b>Deutsch</b>.</small></p>
]]></content:encoded>
			<wfw:commentRss>http://www.atwillys.de/programming/php/zip-file-wrapper-class/2010-06-24/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

