<?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>Ardamis &#187; programming</title>
	<atom:link href="http://www.ardamis.com/tag/programming/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.ardamis.com</link>
	<description>Ardamis is a blog about web development and technology in general.</description>
	<lastBuildDate>Sat, 04 Feb 2012 15:26:27 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>A PHP MySQL snippet for looking up names</title>
		<link>http://www.ardamis.com/2011/10/16/a-php-mysql-snippet-for-looking-up-names/</link>
		<comments>http://www.ardamis.com/2011/10/16/a-php-mysql-snippet-for-looking-up-names/#comments</comments>
		<pubDate>Mon, 17 Oct 2011 00:26:35 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1775</guid>
		<description><![CDATA[A PHP snippet and MySQL query for an Ajax autocompleter lookup of people names by either "Firstname Lastname" or "Lastname, Firstname" from a single input field.]]></description>
			<content:encoded><![CDATA[<p>For a recent project, I needed to create a form that would perform a look up of people names in a MySQL database, but I wanted to use a single input field.  To make it easy on the users of the form, I wanted the input field to accept names in either &#8220;Firstname Lastname&#8221; or &#8220;Lastname, Firstname&#8221; format, and I wanted it to autocomplete matches as the users typed, including when they typed both names separated by a space or a comma followed by a space.</p>
<p>The Ajax lookup was quick work with <a href="http://jqueryui.com/demos/autocomplete/">jQuery UI&#8217;s Autocomplete widget</a>.  The harder part was figuring out the most simple table structure and an appropriate SQL query.</p>
<h2>A flawed beginning</h2>
<p>My people table contains a &#8220;first_name&#8221; column and a &#8220;last_name&#8221; column, nothing uncommon there.  To get the project out the door, I wrote a PHP function that ran two ALTER TABLE queries on the people table to create two additional columns for pre-formatted strings (column &#8220;firstlast&#8221;, to be formatted as &#8220;Firstname Lastname&#8221;, and column &#8220;lastfirst&#8221;, to be formatted as &#8220;Lastname, Firstname&#8221;), added indexes on these columns, and then walked through each record in the table, populating these new fields.  I then wrote a very straight forward SQL query to perform a lookup on both fields.  The PHP and query looked something like this:</p>
<pre class="brush: php; title: ; notranslate">
// The jQuery UI Autocomplete widget passes the user input as a value for the parameter &quot;name&quot;
$name= $_GET['name'];

// This SQL query uses argument swapping
$query = sprintf(&quot;SELECT * FROM people WHERE (`firstlast` LIKE '%1\$s' OR `lastfirst` LIKE '%1\$s') ORDER BY `lastfirst` ASC&quot;,
mysql_real_escape_string($name. &quot;%&quot;, $link));
</pre>
<p>This was effective, accurate, and pretty fast, but the addition of columns bothered me and I didn&#8217;t like that I needed to run a process to generate those pre-formatted fields each time a record was added to the table (or if a change was made to an existing record).  One possible alternative was to watch the input and match either lastname or firstname until the user entered a comma or a space, then explode the string on the comma or space and search more precisely.  Once a comma or a space was encountered, I felt pretty sure that I would be able to accurately determine which part of the input was the first name and which was the last name.  But this had that same inefficient, clunky bad-code-smell as the extra columns.  (Explode is one of those functions that I try to avoid using.)  Writing lots of extra PHP didn&#8217;t seem necessary or right.</p>
<p>I&#8217;m much more comfortable with PHP than with MySQL queries, but I realize that one can do some amazing things within the SQL query, and that it&#8217;s probably faster to use SQL to perform some functions.  So, I decided that I&#8217;d try to work up a query that solved my problem, rather than write more lines of PHP.</p>
<h2>CONCAT_WS to the rescue</h2>
<p>I Googled around for a bit and settled on using <code><a href="http://dev.mysql.com/doc/refman/5.5/en/string-functions.html#function_concat-ws">CONCAT_WS</a></code> to concatenate the first names and last names into a single string be matched, but found it a bit confusing to work with.  I kept trying to use it to create an alias, &#8220;lastfirst&#8221;, and then use the alias in the WHERE clause, which doesn&#8217;t work, or I was getting the literal column names back instead of the values.  Eventually, I hit upon the correct usage.</p>
<p>The PHP and query now looks like this:</p>
<pre class="brush: php; title: ; notranslate">
// The jQuery UI Autocomplete widget passes the user input as a value for the parameter &quot;name&quot;
$name= $_GET['name'];

// This SQL query uses argument swapping
$query = sprintf(&quot;SELECT *, CONCAT_WS(  ', ',  `last_name`,  `first_name` ) as lastfirst FROM people WHERE (CONCAT_WS(  ', ',  `last_name`,  `first_name` ) LIKE '%1\$s' OR CONCAT_WS(  ' ',  `first_name`,  `last_name` ) LIKE '%1\$s') ORDER BY lastfirst ASC&quot;,
mysql_real_escape_string($name. &quot;%&quot;, $link));
</pre>
<p>The first instance of CONCAT_WS isn&#8217;t needed for the lookup.  The first instance allows me to order the results alphabetically and provides me an array key of &#8220;lastfirst&#8221; with a value of the person&#8217;s name already formatted as &#8220;Lastname, Firstname&#8221;, so I don&#8217;t have to do it later with PHP.  The lookup comes from the two instances of CONCAT_WS in the WHERE clause.  I haven&#8217;t done any performance measuring here, but the results of the lookup get back to the user plenty fast enough, if not just as quickly as the method using dedicated columns.</p>
<p>The result of the query is output back to the page as JSON-formatted data for use in the jQuery Autocomplete.</p>
<p>The end result works exactly as I had hoped.  A user of the form is able to type a person&#8217;s name in whatever way is comfortable to them, as &#8220;Bob Smith&#8221; or &#8220;Smith, Bob&#8221;, and the matches are found either way.  The only thing it doesn&#8217;t do is output the matches back to the autocompleter in the same format that the user is using.  But I can live with that for now. </p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/10/16/a-php-mysql-snippet-for-looking-up-names/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A cache-friendly method for reducing WordPress comment spam</title>
		<link>http://www.ardamis.com/2011/08/27/a-cache-proof-method-for-reducing-comment-spam/</link>
		<comments>http://www.ardamis.com/2011/08/27/a-cache-proof-method-for-reducing-comment-spam/#comments</comments>
		<pubDate>Sat, 27 Aug 2011 08:34:53 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[application]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[comment spam]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[spam]]></category>
		<category><![CDATA[templates]]></category>
		<category><![CDATA[themes]]></category>
		<category><![CDATA[web app]]></category>
		<category><![CDATA[xhtml]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1673</guid>
		<description><![CDATA[A safe-for-cached-pages method of filtering out spam comments by requiring at least some time to have passed between the time the page is loaded and the form is submitted.]]></description>
			<content:encoded><![CDATA[<p>In the endless battle against WordPress comment spam, I&#8217;ve developed and then refined a few different methods for preventing spam from getting to the database to begin with.  My philosophy has always been that a human visitor and a spam bot behave differently (after all, we&#8217;re not dealing with <a href="http://en.wikipedia.org/wiki/Do_Androids_Dream_of_Electric_Sheep%3F#Androids">Nexus-6 model androids</a> here), and an effective spam-prevention method should be able to recognize the differences.  I also have a dislike for CAPTCHA methods that require a human visitor to <em>prove</em>, via an intentionally difficult test, that they aren&#8217;t a bot.  The ideal method, I feel, would be invisible to a human visitor, but still accurately identify comments submitted by bots.</p>
<h2>A history of spam fighting</h2>
<p>The most successful and simple method I found was a server-side system for <a href="http://www.ardamis.com/2007/12/15/using-timestamps-to-reduce-wordpress-comment-spam/">reducing comment spam by using a handshake method involving timestamps</a> on hidden form fields.  The general idea was that a bot would submit a comment more quickly than a human visitor, so if the comment was submitted too soon after the page was loaded, it was rejected.  A human caught in this trap would be able to click the Back button on the browser to resubmit.  This had proven to be very effective on ardamis.com, cutting the number of <a href="http://www.ardamis.com/2010/08/09/reducing-wordpress-spam-comments/">spam comments intercepted by Akismet per day to nearly zero</a>.  For a long time, the only problem was that it required modifying a core WordPress file, <strong>wp-comments-post.php</strong>.  Each time WordPress was updated, the core file was replaced.  If I didn&#8217;t then go back and make my modifications again, <a href="http://www.ardamis.com/2011/01/18/a-chart-illustrating-the-reduction-in-comment-spam-at-ardamis-com/">I would lose the spam protection</a> until I made the changes.  As it became easier to update WordPress (via the admin panel) and I updated it more frequently, editing the core file became more of a nuisance.</p>
<h2>A huge facepalm</h2>
<p>When Google began weighting page load times as part of its ranking algorithm, I implemented the <a href="http://wordpress.org/extend/plugins/wp-super-cache/" title="WP Super Cache">WP Super Cache</a> caching plugin on ardamis.com and configured it to use .htaccess and mod_rewrite to serve cache files.  Page load times certainly decreased, but the amount of spam detected by Akismet increased.  After a while, I realized that this was because the spam bots were submitting comments from static, cached pages, and the timestamps on those pages, which had been generated server-side with PHP, were already minutes old when the page was requested.  The form processing script, which normally rejects comments that are submitted too quickly to be written by a human visitor, happily accepted the timestamps.  Even worse, a second function of my anti-spam method also rejected comments that were submitted 10 minutes or more after the page was loaded.  Of course, most of the visitors were being served cached pages that were already more than 10 minutes old, so even legitimate comments were being rejected.  Using PHP to generate my timestamps obviously was not going to work if I wanted to keep serving cached pages.</p>
<h2>JavaScript to the rescue</h2>
<p>Generating real-time timestamps on cached pages requires JavaScript.  But instead of a reliable server clock setting the timestamp, the time is coming from the visitor&#8217;s system, which can&#8217;t be trusted to be accurate.  Merely changing the comment form to use JavaScript to generate the first timestamp wouldn&#8217;t work, because verifying a timestamp generated on the client-side against one generated with a server-side language would be disastrous.</p>
<p>Replacing the PHP-generated timestamps with JavaScript-generated timestamps would require substantial changes to the system.</p>
<p>Traditional client-side form validation using JavaScript happens when the form is submitted.  If the validation fails, the form is not submitted, and the visitor typically gets an alert with suggestions on how to make the form acceptable.  If the validation passes, the form submission continues without bothering the visitor.  To get our two timestamps, we can generate a first timestamp when the page loads and compare it to a second timestamp generated when the form is submitted.  If the visitor submits the form too quickly, we can display an alert showing the number of seconds remaining until the form can be successfully submitted.  This should hopefully be invisible to most visitors who choose to leave comments, but at the very least, far less irritating than a CAPTCHA system.</p>
<p>It took me two tries to get it right, but I&#8217;m going to discuss the less successful method first to point out its flaws.</p>
<h3>Method One (not good enough)</h3>
<p>Here&#8217;s how the original system flowed.</p>
<ol>
<li>Generate a first JS timestamp when the page is loaded.</li>
<li>Generate a second JS timestamp when the form is submitted.</li>
<li>Before the form is submitted, compare the two, and if enough time has passed, write a pre-determined passcode to a hidden INPUT element, then submit the form.</li>
<li>On the form processing page, use server-side logic to verify that the passcode is present and valid.</li>
</ol>
<p>The problem was that it seemed that certain bots could parse JavaScript enough to drop the pre-determined passcode into the hidden form field before submitting the form, circumventing the timestamps completely and defeating the system.  </p>
<p>It also failed to adhere to one of the basic tenants of form validation &#8211; that the input must be checked on both the client-side and the server-side.</p>
<h3>Method Two (better)</h3>
<p>Rather than having the server-side validation be merely a check to confirm that the passcode is present, method two goes back to comparing the timestamps a second time on the server side. Instead of a single hidden input, we now have two &#8211; one for each timestamp.  This is intended to prevent a bot from figuring out the ultimate validation mechanism by simply parsing the JavaScript.  Finally, the hidden fields are added to the form via jQuery, which makes it easier to implement and may act as another layer of obfuscation. </p>
<ol>
<li>Generate a first JS timestamp when the page is loaded and write it to a hidden form field.</li>
<li>Generate a second JS timestamp when the form is submitted and write it to a hidden form field.</li>
<li>Before the form is submitted, compare the two, and if enough time has passed, submit the form (client-side validation).</li>
<li>On the form processing page, use server-side logic to compare the timestamps a second time (server-side validation).</li>
</ol>
<p>The timestamp handshake works more like it did in the server-side-only method.  We still have to pass something from the comment form to the processing script, but it&#8217;s not too obvious from the HTML what is being done with it.</p>
<h2>The same downside plagues me</h2>
<p>Unfortunately, if we want to have any server-side validation at all, and we do, the core file <strong>wp-comments-post.php</strong> will still have to be modified.  In my experience, the system is not sufficiently effective using just client-side validation.  </p>
<h2>The code</h2>
<p>Two files must be modified to implement the validation.</p>
<p><strong>File 1: The theme&#8217;s comments.php file (older themes) or wp-includes\comment-template.php (newer themes)</strong></p>
<p>Your comment form lives somewhere.  My theme is based on Kubrick, the old default WordPress theme, and my comment form is in my theme folder, in a file named <strong>comments.php</strong>.  If your theme is newer and based on the current default theme, twentyeleven, the form is in <strong>wp-includes\comment-template.php</strong>.  If your theme isn&#8217;t based on either of these, all bets are off.  I know it&#8217;s confusing.  Sorry.</p>
<p>Add the JavaScript that creates and populates the timestamp fields.  Be sure to confirm that your comment form has an ID of <strong>commentform</strong>.  I&#8217;m using jQuery to help fire functions when the page loads.</p>
<pre class="brush: jscript; title: ; notranslate">
&lt;script type=&quot;text/javascript&quot; src=&quot;http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js&quot;&gt;&lt;/script&gt;
&lt;script type=&quot;text/javascript&quot;&gt;
$(document).ready(function(){
	ardGenTS1();
});

function ardGenTS1() {
	// prepare the form
	$('#commentform').append('&lt;input type=&quot;hidden&quot; name=&quot;ardTS1&quot; id=&quot;ardTS1&quot; value=&quot;1&quot; /&gt;');
	$('#commentform').append('&lt;input type=&quot;hidden&quot; name=&quot;ardTS2&quot; id=&quot;ardTS2&quot; value=&quot;1&quot; /&gt;');
	$('#commentform').attr('onsubmit', 'return validate()');
	// set a first timestamp when the page loads
	var ardTS1 = (new Date).getTime();
	document.getElementById(&quot;ardTS1&quot;).value = ardTS1;
}

function validate() {
	// read the first timestamp
	var ardTS1 = document.getElementById(&quot;ardTS1&quot;).value;
//	alert ('ardTS1: ' + ardTS1);
	// generate the second timestamp
	var ardTS2 = (new Date).getTime();
	document.getElementById(&quot;ardTS2&quot;).value = ardTS2;
//	alert ('ardTS2: ' + document.getElementById(&quot;ardTS2&quot;).value);
	// find the difference
	var diff = ardTS2 - ardTS1;
	var elapsed = Math.round(diff / 1000);
	var remaining = 10 - elapsed;
//	alert ('diff: ' + diff + '\n\nelapsed:' + elapsed);
	// check whether enough time has elapsed
	if (diff &gt; 10000) {
		// submit the form
		return true;
	}else{
		// display an alert if the form is submitted within 10 seconds
		alert(&quot;This site is protected by an anti-spam feature that requires 10 seconds to have elapsed between the page load and the form submission.\n\nPlease close this alert window.  The form may be resubmitted successfully in &quot; + remaining + &quot; seconds.&quot;);
		// prevent the form from being submitted
		return false;
	}
}
&lt;/script&gt;
</pre>
<p><strong>File 2: The wp-comments-post.php file</strong></p>
<p>The wp-comments-post.php file lives in the root of WordPress and handles the form processing.  We need to add a few lines that check the contents of our new validation input field.</p>
<p>Somewhere after line 53 or so (where <em>$comment_content</em> is defined), insert the following code.</p>
<pre class="brush: php; title: ; notranslate">
$ardTS1 = ( isset($_POST['ardTS1']) ) ? trim($_POST['ardTS1']) : 1;
$ardTS2 = ( isset($_POST['ardTS2']) ) ? trim($_POST['ardTS2']) : 2;
$ardTS = $ardTS2 - $ardTS1;

if ( $ardTS &lt; 10000 ) {
// If the difference of the timestamps is not more than 10 seconds, exit
    wp_die( __('&lt;strong&gt;ERROR&lt;/strong&gt;:  This site uses JavaScript validation to reduce comment spam.  Either your browser has JavaScript disabled, or the comment was not legitimately submitted.') );
}
</pre>
<p>That&#8217;s it.  Not so bad, right?</p>
<h2>Final thoughts</h2>
<p>One advantage to this method over the old PHP-only method is that even if the core file is replaced and the server-side validation is lost, the client-side validation continues to work, perhaps providing some measure of protection.</p>
<p>The method is safe for use with caching systems that create purely static, HTML pages.  I&#8217;ll follow up later and report on how effective it seems to be at stopping spam comments before they get to Akismet (and into the WordPress database).</p>
<p>Now, for a little extra protection, you can rename the <strong>wp-comments-post.php</strong> file and change the path in the comment form&#8217;s action attribute.  I&#8217;ve <a href="http://www.ardamis.com/2010/08/09/reducing-wordpress-spam-comments/">posted logs</a> showing that some bots just try to post spam directly to the <strong>wp-comments-post.php</strong> file, so renaming that file is an easy way to cut down on spam.  Just remember to come back and delete the <strong>wp-comments-post.php</strong> file each time you update WordPress.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/08/27/a-cache-proof-method-for-reducing-comment-spam/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>OOP PHP Notes</title>
		<link>http://www.ardamis.com/2011/07/30/oop-php-notes/</link>
		<comments>http://www.ardamis.com/2011/07/30/oop-php-notes/#comments</comments>
		<pubDate>Sat, 30 Jul 2011 18:56:43 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[application]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1565</guid>
		<description><![CDATA[My notes on learning Object Oriented Programming in PHP.]]></description>
			<content:encoded><![CDATA[<p><strong>Overview of classes and objects</strong></p>
<p>Objects are the building blocks of the application (ie: the workers in a factory)<br />
Classes can be thought of as blueprints for the objects.  Classes describe the objects, which are created in memory.<br />
So, the programmer writes the classes and the PHP interpreter creates the objects from the classes.</p>
<p>A class may contain both variables and functions.<br />
A variable inside a class is called a <em>property</em>.<br />
A function inside a class is called a <em>method</em>.</p>
<p><strong>Instantiation</strong></p>
<p>To create an object, you instantiate a class (you create an instance of the class as an object).<br />
For example, if we have a class named &#8216;person&#8217; and want to instantiate it as the variable $oliver:</p>
<pre class="brush: php; title: ; notranslate">$oliver = new person();</pre>
<p>The variable $oliver is referred to as the &#8216;handle&#8217;.</p>
<p><strong>Accessing properties and methods</strong></p>
<p>To access the properties and methods of a class, we use the object&#8217;s handle, followed by the arrow operator &#8220;-&gt;&#8221;.<br />
For example, if our class has a method &#8216;get_name&#8217;, we can echo that to the page with:</p>
<pre class="brush: php; title: ; notranslate">echo $oliver-&gt;get_name();</pre>
<p>Note that there are no single or double quotes used in instantiating a class or accessing properties and methods of a class.</p>
<p><strong>Constructors</strong></p>
<p>A class may have a special method called a constructor.  The constructor method is called automatically when the object is instantiated.<br />
The constructor method begins with two underscores and the word &#8216;construct&#8217;:</p>
<pre class="brush: php; title: ; notranslate">function __construct($variable) { }</pre>
<p>One can pass values to the constructor method by providing arguments after the class name.<br />
For example, to pass the name &#8220;John Doe&#8221; to the constructor method in the &#8216;person&#8217; class:</p>
<pre class="brush: php; title: ; notranslate">$john = new person(&quot;John Doe&quot;);</pre>
<p><span style="color:#F00">!</span> If a constructor exists and expects arguments, you must instantiate the class with the arguments expected by the constructor.</p>
<p><strong>Access modifiers and visibility declarations</strong></p>
<p>Properties must, and methods may, have one of three access modifiers (visibility declarations): public, protected, and private.<br />
Public: can be accessed from outside the class, eg: $myclass->secret_variable;<br />
Protected: can be accessed within the class and by classes derived from the class<br />
Private: can be accessed only within the class</p>
<p>Declaring a property with var makes the property public.</p>
<p>Methods declared without an explicit access modifier are considered public.</p>
<p><span style="color:#F00">!</span> If you call a protected method from outside the class, any PHP output before the call is still processed, but you get an error message when the interpreter gets to that call:</p>
<pre class="brush: plain; title: ; notranslate">Fatal error: Call to protected method...</pre>
<p><strong>Inheritance</strong></p>
<p>Inheritance allows a child class to be created from a parent class, whereby the child has all of the public and protected properties and methods of the parent.</p>
<p>A child class <em>extends</em> a parent class:</p>
<pre class="brush: php; title: ; notranslate">class employee extends person {
}</pre>
<p>A child class can redefine/override/replace a method in the parent class by reusing the method name.</p>
<p><span style="color:#F00">!</span>  A child class&#8217;s method&#8217;s access modifier can not be more restrictive than that of the parent class.  For example, if the parent class has a public set_name() method and the child class&#8217;s set_name() method is protected, the class itself will generate a fatal error, and no prior PHP output will be rendered. (In the error below, <em>employee</em> is the child class to <em>person</em>):</p>
<pre class="brush: plain; title: ; notranslate">Fatal error: Access level to employee::set_name() must be public (as in class person) in E:\xampp\htdocs\tester\oop\class_lib.php on line 38</pre>
<p>To differentiate between a method in a parent class vs the method as redefined in a child class, one must specifically name the class that contains the method you want to call using the scope resolution operator (::):</p>
<pre class="brush: php; title: ; notranslate">person::set_name($new_name);</pre>
<p>The scope resolution operator allows access to static, constant, and overridden properties or methods of a class, generally, a parent class. This would be done inside the child class, after redefining a parent&#8217;s method of the same name.</p>
<p>It&#8217;s also possible to use &#8216;parent&#8217; to refer to the child&#8217;s parent class:</p>
<pre class="brush: php; title: ; notranslate">parent::set_name($new_name);</pre>
<p>(I&#8217;m still a bit vague on this and am looking for examples of situations in which this would be used.)</p>
<p><strong>Classes inside classes</strong></p>
<p>Just as it&#8217;s possible to instantiate a class and use the object in a view file, it&#8217;s possible to instantiate an object and call its methods from inside another class.</p>
<p><strong>Static properties and methods</strong></p>
<p>Declaring class properties or methods as static makes them accessible without needing an instantiation of the class. A property declared as static can not be accessed with an instantiated class object (though a static method can).</p>
<p><strong>Resources</strong><br />
<a href="http://us2.php.net/manual/en/language.oop5.php" title="http://us2.php.net/manual/en/language.oop5.php">http://us2.php.net/manual/en/language.oop5.php</a><br />
<a href="http://net.tutsplus.com/tutorials/php/oop-in-php/" title="http://net.tutsplus.com/tutorials/php/oop-in-php/">http://net.tutsplus.com/tutorials/php/oop-in-php/</a><br />
<a href="http://www.phpfreaks.com/tutorial/oo-php-part-1-oop-in-full-effect" title="http://www.phpfreaks.com/tutorial/oo-php-part-1-oop-in-full-effect">http://www.phpfreaks.com/tutorial/oo-php-part-1-oop-in-full-effect</a><br />
<a href="http://www.killerphp.com/tutorials/object-oriented-php/" title="http://www.killerphp.com/tutorials/object-oriented-php/">http://www.killerphp.com/tutorials/object-oriented-php/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/07/30/oop-php-notes/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Jealous of The Social Network</title>
		<link>http://www.ardamis.com/2011/06/12/jealous-of-the-social-network/</link>
		<comments>http://www.ardamis.com/2011/06/12/jealous-of-the-social-network/#comments</comments>
		<pubDate>Mon, 13 Jun 2011 02:46:50 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[application]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[downloads]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[web app]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[xhtml]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1467</guid>
		<description><![CDATA[A short list of my programming accomplishments.]]></description>
			<content:encoded><![CDATA[<p>So I finally watched <a href="http://en.wikipedia.org/wiki/The_Social_Network"><em>The Social Network</em></a> over the weekend, and it&#8217;s made me feel jealous and a bit guilty.</p>
<p>In a meager effort to console myself for so far failing to be a billionaire, I&#8217;m assembling the short list of web-application type things I&#8217;ve built here.</p>
<ol>
<li>A dice roller: <a href="http://alephstudios.com/rollforit/">rollforit</a>. Enter a name, create a room, invite your friends, and start rolling dice.  For people who want to play pen and paper, table-top RPG dice games with their distant friends.</li>
<li>A URL shortener: <a href="http://minifi.de/">Minifi.de</a>.  Minifi.de comes with an API and a bookmarklet.  It really works, too!  The <a href="http://minifi.de/technical.php">technical explanation</a> has more details.</li>
<li>A social networking site: <a href="http://alephstudios.com/snapbase/">Snapbase</a>.  Snapbase is a social site that shows you what&#8217;s going on in your city or anywhere in the world as pictures are uploaded by your friends and neighbors.  The application extracts location information from the EXIF data embedded in images and displays recent images taken near your present location.</li>
<li>A <a href="http://alephstudios.com/helpdesk/">trouble-ticketing system</a> for an IT help desk or technical support center.  It&#8217;s really pretty extensive, with asset management, user accounts, salted encrypted passwords, and all sorts of nifty things.  I really must write a full description of it at some point, but until then, the <a href="http://alephstudios.com/helpdesk/documentation/">documentation</a> is the next best thing.</li>
<li>An <a href="http://alephstudios.com/ias/">account-based invoice tracking and access system</a> for grouping invoices according to clients, then sharing invoice history with those clients and allowing them to easily pay outstanding invoices via Paypal.</li>
<li>An <a href="http://alephstudios.com/ias/">account-based invoice access system</a> where clients can view paid and unpaid invoices, and even easily pay an outstanding invoice via Paypal. I actually use this almost every day.</li>
<li>A simple method for <a href="http://www.ardamis.com/2008/06/11/protecting-a-download-using-a-unique-url/">protecting a download using a unique URL</a> that can be emailed to authorized users. The URL can be set to expire after a certain amount of time or any number of downloads.</li>
<li>An update to the above download protection script to <a href="http://www.ardamis.com/2009/06/26/protecting-multiple-downloads-using-unique-urls/">protect multiple downloads</a>, generate batches of keys, leave notes about who received the key, the ability to specify per-key the allowable number of downloads and age, and some basic reporting.</li>
<li>An HTML auction template generator called <a href="http://simpleauctionwizard.com/">Simple Auction Wizard</a>.  It helps you create HTML auction templates for eBay, and uses SWFUpload and tinyMCE.</li>
</ol>
<p>I have another project in the works that promises to be more financially viable, but the most clever thing on that list is Snapbase.  It&#8217;s in something akin to alpha right now; barely usable.  I really wish I had the time to pursue it.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/06/12/jealous-of-the-social-network/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>How to fix the &#8220;PHP Fatal error:  Call to undefined function  get_header()&#8221; error in WordPress</title>
		<link>http://www.ardamis.com/2011/06/02/fix-for-php-fatal-error-get_header-in-wordpress/</link>
		<comments>http://www.ardamis.com/2011/06/02/fix-for-php-fatal-error-get_header-in-wordpress/#comments</comments>
		<pubDate>Thu, 02 Jun 2011 17:07:13 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[500 error]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[security]]></category>
		<category><![CDATA[themes]]></category>
		<category><![CDATA[troubleshooting]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1430</guid>
		<description><![CDATA[Fix the "PHP Fatal error: Call to undefined function get_header()" error in WordPress.]]></description>
			<content:encoded><![CDATA[<p>While making changes to my WordPress theme, I noticed that the error_log file in my theme folder contained dozens of PHP Fatal error lines:</p>
<pre class="brush: plain; title: ; notranslate">
...
[01-Jun-2011 14:25:15] PHP Fatal error:  Call to undefined function  get_header() in /home/accountname/public_html/ardamis.com/wp-content/themes/ars/index.php on line 7
[01-Jun-2011 20:58:23] PHP Fatal error:  Call to undefined function  get_header() in /home/accountname/public_html/ardamis.com/wp-content/themes/ars/index.php on line 7
...
</pre>
<p>The first seven lines of my theme&#8217;s index.php file:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php ini_set('display_errors', 0); ?&gt;
&lt;?php
/**
 * @package WordPress
 * @subpackage Ars_Theme
*/
get_header(); ?&gt;
</pre>
<p>I realized that the error was being generated each time that my theme&#8217;s index.php file was called directly, and that the error was caused by the theme&#8217;s inability to locate the WordPress <strong>get_header</strong> function (which is completely normal).  Thankfully, the descriptive error wasn&#8217;t being output to the browser, but was only being logged to the error_log file, due to the inclusion of the <strong>ini_set(&#8216;display_errors&#8217;, 0);</strong> line.  I had learned this the hard way a few months ago when I found that calling the theme&#8217;s index.php file directly would generate an error message, output to the browser, that would reveal my hosting account username as part of the absolute path to the file throwing the error.</p>
<p>I decided the best way to handle this would be to check to see if the file could find the <strong>get_header</strong> function, and if it could not, simply redirect the visitor to the site&#8217;s home page.  The code I used to do this:</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php ini_set('display_errors', 0); ?&gt;
&lt;?php
/**
* @package WordPress
* @subpackage Ars_Theme
*/
if (function_exists('get_header')) {
	get_header();
}else{
    /* Redirect browser */
    header(&quot;Location: http://&quot; . $_SERVER['HTTP_HOST'] . &quot;&quot;);
    /* Make sure that code below does not get executed when we redirect. */
    exit;
}; ?&gt;
</pre>
<p>So there you have it.  No more fatal errors due to <strong>get_header</strong> when loading the WordPress theme&#8217;s index.php file directly.  And if something else in the file should throw an error, <strong>ini_set(&#8216;display_errors&#8217;, 0);</strong> means it still won&#8217;t be sent to the browser.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/06/02/fix-for-php-fatal-error-get_header-in-wordpress/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Online tools for detecting malware on web sites</title>
		<link>http://www.ardamis.com/2011/05/07/detecting-malware-on-web-sites/</link>
		<comments>http://www.ardamis.com/2011/05/07/detecting-malware-on-web-sites/#comments</comments>
		<pubDate>Sat, 07 May 2011 18:48:22 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[htaccess]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[security]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1275</guid>
		<description><![CDATA[Online tools for detecting infections on web sites and for identifying vulnerabilites.]]></description>
			<content:encoded><![CDATA[<p>Just a few notes to myself about monitoring web sites for infections/malware and potential vulnerabilities.</p>
<h2>Tools for detecting infections on web sites</h2>
<h3>Google Webmaster Tools</h3>
<p>Your first stop should be here, as I&#8217;ve personally witnessed alerts show up in Webmaster Tools, even when all the following tools gave the site a passing grade.  If your site is registered here, and Google finds weird pages on your site, an alert will appear.  You can also have the messages forwarded to your email account on file, by choosing the Forward option under the All Messages area of the Home page.</p>
<div id="attachment_1382" class="wp-caption aligncenter" style="width: 784px"><a href="http://www.ardamis.com/wp-content/uploads/2011/05/google-webmaster-tools-hack-alert.png"><img src="http://www.ardamis.com/wp-content/uploads/2011/05/google-webmaster-tools-hack-alert.png" alt="" title="google-webmaster-tools-hack-alert" width="774" height="435" class="size-full wp-image-1382" /></a><p class="wp-caption-text">Google Webmaster Tools Hack Alert</p></div>
<h3>Google Safe Browsing</h3>
<p>The Google Safe Browsing report for ardamis.com: <a href="http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=ardamis.com">http://safebrowsing.clients.google.com/safebrowsing/diagnostic?site=ardamis.com</a></p>
<h3>Norton Safe Web</h3>
<p><a href="https://safeweb.norton.com/">https://safeweb.norton.com/</a></p>
<p>The Norton Safe Web report for ardamis.com: <a href="https://safeweb.norton.com/report/show?url=ardamis.com">https://safeweb.norton.com/report/show?url=ardamis.com</a></p>
<h2>Tools for analyzing a site for vulnerabilities</h2>
<h3>Sucuri Site Check</h3>
<p><a href="http://sitecheck.sucuri.net/scanner/">http://sitecheck.sucuri.net/scanner/</a></p>
<p>The Sucuri report for ardamis.com: <a href="http://sitecheck.sucuri.net/scanner/?scan=www.ardamis.com">http://sitecheck.sucuri.net/scanner/?scan=www.ardamis.com</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/05/07/detecting-malware-on-web-sites/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Programmatically re-enabling Word COM add-ins</title>
		<link>http://www.ardamis.com/2011/05/05/programmatically-re-enabling-word-com-add-ins/</link>
		<comments>http://www.ardamis.com/2011/05/05/programmatically-re-enabling-word-com-add-ins/#comments</comments>
		<pubDate>Thu, 05 May 2011 21:14:09 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Nonsense]]></category>
		<category><![CDATA[application]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[Office]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[templates]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=1354</guid>
		<description><![CDATA[A Word macro to re-enable all disabled COM add-ins, then force desired LoadBehaviors on specific add-ins, effectively putting Word back into a certain state.]]></description>
			<content:encoded><![CDATA[<p>Nearly a year ago, I wrote a post on how to <a href="http://www.ardamis.com/2010/06/20/detect-and-fix-word-add-in-problems-with-a-macro-and-batch-file/">detect and fix Word add-in problems with a macro and batch file</a>, in a Windows XP and Office 2007 environment.</p>
<p>This was sufficiently effective, but it was also overly complicated, requiring four separate components:</p>
<ol>
<li>an autoexec Word 2007 macro that runs each time Word is opened</li>
<li>a batch file that runs the registry merge file and writes an entry to a log file</li>
<li>the registry merge file that contains the correct LoadBehavior settings for the add-ins</li>
<li>a text file that acts as a log</li>
</ol>
<p>This month, I decided to rewrite the macro to handle the registry changes and write to the log file.  It was also a good opportunity to dig a bit deeper into VBA, and I also wanted to confirm that it would work in a more modern environment of Windows 7 and Office 2010 (that code is near the bottom of the post). The new system has only two components:</p>
<ol>
<li>an autoexec Word 2007 macro that runs each time Word is opened</li>
<li>a text file that acts as a log</li>
</ol>
<h2>Background</h2>
<p>First, a bit of background.</p>
<p>Many of the problems with Word 2007 are due to Word&#8217;s handling of add-ins. When something unexpected happens in Word, and Word attributes the problem to an add-in, Word will react by flagging it and prompting the user for a decision the next time Word opens. Depending on the severity of the problem and the user&#8217;s response, the add-in can be either &#8216;hard-disabled&#8217; or &#8216;soft-disabled&#8217;.</p>
<p>Microsoft explains the differences between Hard Disabled vs Soft Disabled in a MSDN article at: <a href="http://msdn.microsoft.com/en-us/library/ms268871(VS.80).aspx">http://msdn.microsoft.com/en-us/library/ms268871(VS.80).aspx</a>.</p>
<p>I&#8217;ve explained a bit about the process by which Word disables add-ins at the end of this post, and I&#8217;ve written a shorter post about <a href="http://www.ardamis.com/2010/02/26/fixing-word-2007-add-in-issues/">the basics behind the registry keys responsible for disabling add-ins</a>.</p>
<h2>Handling disabled add-ins programmatically</h2>
<p>A Word macro can access the condition of an add-in via an Application.COMAddIns object, and it can read and write to the registry.  This allows us to tell when an add-in has been disabled and re-enabled it.</p>
<p>My macro has some admittedly hackish parts that need to be cleaned up, there is the matter of unsetting variables to be addressed, and it could certainly be made more elegant, but it works.  Note that a file named <strong>addinslog.txt</strong> must exist in the %TEMP% directory in order for the macro to write the log file.  This is what the Word 2007 macro looks like, using the COM add-in installed with Adobe Acrobat 8 Standard as the required add-in&#8230;</p>
<pre class="brush: plain; title: ; notranslate">
Option Explicit

' Set up a function to search for a key and return true or false
Public Function KeyExists(key)
    Dim objShell
    On Error Resume Next
    Set objShell = CreateObject(&quot;WScript.Shell&quot;)
        objShell.RegRead (key)
    Set objShell = Nothing
    If Err = 0 Then KeyExists = True
End Function

Sub AutoExec()
'
' FixMissingAddins Macro
' Display a message box with any critical but not 'Connected' COM add-ins, then fix them programatically
'
' Oliver Baty
' June, 2010 - April, 2011
'
' Information on the Application.COMAddIns array
' http://msdn.microsoft.com/en-us/library/aa831759(v=office.10).aspx
'
' Running macros automatically
' http://support.microsoft.com/kb/286310
'
' Using Windows Scripting Shell (WshShell) to read from and write to the local registry
' http://technet.microsoft.com/en-us/library/ee156602.aspx

' Declare the WshShell variable (this is used to edit the registry)
    Dim WshShell

' Declare the fso and logFile variables (these are used to write to a txt file)
    Dim fso
    Dim logFile

' Create an instance of the WScript Shell object
    Set WshShell = CreateObject(&quot;WScript.Shell&quot;)

' Declare some other variables
   Dim MyAddin As COMAddIn
   Dim stringOfAddins As String
   Dim listOfDisconnectedAddins As String
   Dim requiredAddIn As Variant
   Dim msg As String

' Notes on deleting registry keys and values in VB
' http://www.vbforums.com/showthread.php?t=425483
' http://www.tek-tips.com/viewthread.cfm?qid=674375
' http://www.robvanderwoude.com/vbstech_registry_wshshell.php

' Create a string containing the names of all 'Connected' COM add-ins named &quot;stringOfAddins&quot;
   For Each MyAddin In Application.COMAddIns
      If MyAddin.Connect = True Then
          stringOfAddins = stringOfAddins &amp; MyAddin.ProgID &amp; &quot; - &quot;
      End If
   Next

' Create an array to hold the names of the critical (required) add-ins named &quot;requiredAddIns&quot;
' Example: change to &quot;Dim requiredAddIns(0 To 4)&quot; if the macro is checking 5 total add-ins)
   Dim requiredAddIns(0 To 0) As String

' Add each required AddIn to the array
   requiredAddIns(0) = &quot;PDFMaker.OfficeAddin&quot;
'   requiredAddIns(1) = &quot;&quot;
'   requiredAddIns(2) = &quot;&quot;
'   requiredAddIns(3) = &quot;&quot;
'   requiredAddIns(4) = &quot;&quot;

' Cycle through the array of required add-ins, and see if they exist in the connected add-ins list
   For Each requiredAddIn In requiredAddIns
      If InStr(stringOfAddins, requiredAddIn) Then
        ' The required add-in is in the string of connected add-ins
         msg = msg
      Else
        ' The required add-in is not in the string of connected add-ins, so add the add-in name to a string named &quot;listOfDisconnectedAddins&quot;
         msg = msg &amp; requiredAddIn &amp; vbCrLf
         listOfDisconnectedAddins = requiredAddIn &amp; &quot; &quot; &amp; listOfDisconnectedAddins
         listOfDisconnectedAddins = Trim(listOfDisconnectedAddins)
      End If
   Next

' If the msg variable is not blank (it contains at least one add-in's name) handle it, otherwise, do nothing
   If msg = &quot;&quot; Then
        ' There are no critical, unconnected add-ins (yay!)
        ' The script can now exit
   Else
        ' There are critical add-ins that are not connected, so handle this
        MsgBox &quot;The following critical Word Add-In(s) are disabled: &quot; &amp; vbCrLf &amp; vbCrLf &amp; msg &amp; vbCrLf &amp; vbCrLf &amp; &quot;To correct this problem, please save any documents you are working on, then close Word and reopen Word.&quot;

            ' I find it extremely hackish to check for each possible key and delete it if found... need to research how to delete the tree
            ' One potential obstacle to this method is that I've seen a DocumentRecovery subkey under Resiliency (only once, while editing this macro), that I haven't researched yet

            ' Note: Since the WSH Shell has no Enumeration functionality, you cannot
            '       use the WSH Shell object to delete an entire &quot;tree&quot; unless you
            '       know the exact name of every subkey.
            '       If you don't, use the WMI StdRegProv instead.
            ' http://www.robvanderwoude.com/vbstech_registry_wshshell.php

            ' More info on WMI StdRegProv at:
            ' http://msdn.microsoft.com/en-us/library/aa393664(v=vs.85).aspx

        ' This is hackish, but it effectively deletes a registry key, if it exists
        If KeyExists(&quot;HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Word\Resiliency\DisabledItems\&quot;) Then
            WshShell.RegDelete &quot;HKCU\Software\Microsoft\Office\12.0\Word\Resiliency\DisabledItems\&quot;
        ElseIf KeyExists(&quot;HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Word\Resiliency\StartupItems\&quot;) Then
            WshShell.RegDelete &quot;HKCU\Software\Microsoft\Office\12.0\Word\Resiliency\StartupItems\&quot;
        ElseIf KeyExists(&quot;HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Word\Resiliency\&quot;) Then
            WshShell.RegDelete &quot;HKCU\Software\Microsoft\Office\12.0\Word\Resiliency\&quot;
        End If

        ' To be completely thorough, we can also set the desired LoadBehavior for certain add-ins
        ' This can be done selectively, and only if the LoadBehavior was incorrect, but the quick and dirty way would be to just force the values

        WshShell.RegWrite &quot;HKLM\SOFTWARE\Microsoft\Office\Word\Addins\PDFMaker.OfficeAddin\LoadBehavior&quot;, 3, &quot;REG_DWORD&quot;

        ' Release the WshShell object
        Set WshShell = Nothing

        ' Declare a few variables for the log file
        Dim user, machine, datetime, output

        Set WshShell = CreateObject(&quot;WScript.Shell&quot;)
        user = WshShell.ExpandEnvironmentStrings(&quot;%USERNAME%&quot;)
        machine = WshShell.ExpandEnvironmentStrings(&quot;%COMPUTERNAME%&quot;)
        temp = WshShell.ExpandEnvironmentStrings(&quot;%TEMP%&quot;)
        ' Convert the slashes in Now to hyphens to prevent a fatal error
        datetime = Replace(Now, &quot;/&quot;, &quot;-&quot;)
        ' Create the string that will be written to the log file
        output = datetime + &quot;, &quot; + user + &quot;, &quot; + machine + &quot;, &quot; + listOfDisconnectedAddins

        ' Write the event to a log file
        logfile = temp + &quot;\addinslog.txt&quot;
        ' http://msdn.microsoft.com/en-us/library/2z9ffy99(v=vs.85).aspx
        ' http://www.devguru.com/technologies/vbscript/quickref/filesystemobject_opentextfile.html
        Set fso = CreateObject(&quot;Scripting.FileSystemObject&quot;)
        Set logFile = fso.OpenTextFile(logfile, 8, True)
        logFile.WriteLine (output)
        logFile.Close
        Set logFile = Nothing
        Set fso = Nothing

        ' Should we clear the variables?

        ' Release the WshShell object
        Set WshShell = Nothing
   End If

   ' Ardamis.com - We're in your macros, fixing your COM add-ins.
End Sub
</pre>
<p>While working on this, I found that there were some gaps in my understanding of the sequence of events that occur when Word 2007 disables a COM add-in.  Please comment if you find that any of this is inaccurate or incomplete.</p>
<h2>What happens when Word launches</h2>
<p>A critical key to the whole business of Word add-ins is HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Word\Resiliency</p>
<p>When Word launches, it looks for data under the Resiliency key and a subkey: HKEY_CURRENT_USER\Software\Microsoft\Office\12.0\Word\Resiliency\StartupItems</p>
<p>If the StartupItems subkey contains a REG_BINARY value that corresponds to an add-in, Word throws the familiar warning:</p>
<p><em>Microsoft Office Word</em><br />
<em>Word experienced a serious problem with the ‘[addin name]’ add-in. If you have seen this message multiple times, you should disable this add-in and check to see if an update is available. Do you want to disable this add-in?</em><br />
<em>[Yes] [No]</em></p>
<p>Choosing No at the prompt removes the Resiliency key and allows Word to continue to launch, leaving the LoadBehavior for that add-in unchanged.</p>
<p>Choosing No also writes an Error event to the Application Event Viewer log:</p>
<pre class="brush: plain; title: ; notranslate">
Event Type:	Error
Event Source:	Microsoft Office 12
Event Category:	None
Event ID:	2000
Date:		5/23/2011
Time:		3:15:29 PM
User:		N/A
Computer:	[WORKSTATION_NAME]
Description:
Accepted Safe Mode action : Microsoft Office Word.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.
</pre>
<p>Choosing Yes at the prompt removes the StartupItems subkey and creates a new DisabledItems subkey. This DisabledItems subkey will contain a different REG_BINARY value, the data of which contains information about the disabled add-in.</p>
<p>Choosing Yes also writes an Error event to the Application Event Viewer log:</p>
<pre class="brush: plain; title: ; notranslate">
Event Type:	Error
Event Source:	Microsoft Office 12
Event Category:	None
Event ID:	2001
Date:		5/23/2011
Time:		3:12:36 PM
User:		N/A
Computer:	[WORKSTATION_NAME]
Description:
Rejected Safe Mode action : Microsoft Office Word.

For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.
</pre>
<p>At this point, the add-in is &#8216;hard-disabled&#8217;, but not &#8216;soft-disabled&#8217;.</p>
<p>Word then continues to launch, but without loading the add-in.  </p>
<p>To see which add-ins have been hard-disabled, click on the Office Button | Word Options | Add-Ins, and scroll down to &#8220;Disabled Application Add-ins&#8221;.</p>
<p>To see which add-ins have been soft-disabled, click on the Office Button | Word Options | Add-Ins. Select &#8220;COM Add-Ins&#8221; in the Manage menu and click Go.</p>
<p>Word is somewhat tricky in this regard, as the add-in will not have a checkmark, but the LoadBehavior registry value will be unchanged.  At any other time, the presence of a checkmark is an indication of the LoadBehavior, but when an add-in has been hard-disabled, the box will always be unchecked.</p>
<h2>What users can do at this point</h2>
<p>Going through Word Options and enabling the hard-disabled COM add-in will remove the Resiliency key.  This may not make the add-in immediately available in Word, however.</p>
<p>To immediately load the add-in and gain its functionality, you can check the box.  Otherwise, close and reopen Word, which will cause Word to launch with the add-in&#8217;s specified LoadBehavior.</p>
<p>In case you were curious about the keyboard shortcuts used to enable the first disabled add-in in the list of disabled add-ins (maybe you wanted to do something with SendKeys, for example), they are:<br />
<strong>Alt+F, I, A, A, Tab, Tab, Tab, D, Enter, G, Space, Alt+E, C, Alt+F4</strong>.</p>
<p>In summary, deleting the Resiliency key after the &#8220;serious problem&#8221; prompt, then closing and reopening Word, returns Word to a normal operating state.</p>
<p>What I intend to accomplish with the macro is to re-enable the hard-disabled add-in, return any LoadBehavior values back to the desired settings, then prompt the user to save their work and close and reopen Word.</p>
<p>This should return Word to a working state.</p>
<h2>Word 2010 on 64-bit Windows 7</h2>
<p>As a bonus, here&#8217;s the same macro, with some minor adjustments to run in Word 2010 on Windows 7 64-bit, with Adobe Acrobat 9 Pro&#8217;s COM add-in acting as one of the required add-ins.  The OneNote add-in is not enabled in Word by default, and the macro below does not attempt to enable it, but does consider it a required add-in.  This is done to demonstrate the pop-up window.  Note that a file named <strong>addinslog.txt</strong> must exist in the %TEMP% directory in order for the macro to write the log file.</p>
<pre class="brush: plain; title: ; notranslate">
Option Explicit

' Set up a function to search for a key and return true or false
Public Function KeyExists(key)
    Dim objShell
    On Error Resume Next
    Set objShell = CreateObject(&quot;WScript.Shell&quot;)
        objShell.RegRead (key)
    Set objShell = Nothing
    If Err = 0 Then KeyExists = True
End Function

Sub AutoExec()
'
' FixMissingAddins Macro
' Display a message box with any critical but not 'Connected' COM add-ins, then fix them programatically
'
' Oliver Baty
' June, 2010 - April, 2011
'
' Information on the Application.COMAddIns array
' http://msdn.microsoft.com/en-us/library/aa831759(v=office.10).aspx
'
' Running macros automatically
' http://support.microsoft.com/kb/286310
'
' Using Windows Scripting Shell (WshShell) to read from and write to the local registry
' http://technet.microsoft.com/en-us/library/ee156602.aspx

' Declare the WshShell variable (this is used to edit the registry)
    Dim WshShell

' Declare the fso and logFile variables (these are used to write to a txt file)
    Dim fso
    Dim logfile

' Create an instance of the WScript Shell object
    Set WshShell = CreateObject(&quot;WScript.Shell&quot;)

' Declare some other variables
   Dim MyAddin As COMAddIn
   Dim stringOfAddins As String
   Dim listOfDisconnectedAddins As String
   Dim requiredAddIn As Variant
   Dim msg As String

' Notes on deleting registry keys and values in VB
' http://www.vbforums.com/showthread.php?t=425483
' http://www.tek-tips.com/viewthread.cfm?qid=674375
' http://www.robvanderwoude.com/vbstech_registry_wshshell.php

' Create a string containing the names of all 'Connected' COM add-ins named &quot;stringOfAddins&quot;
   For Each MyAddin In Application.COMAddIns
      If MyAddin.Connect = True Then
          stringOfAddins = stringOfAddins &amp; MyAddin.ProgID &amp; &quot; - &quot;
      End If
   Next

' Create an array to hold the names of the critical (required) add-ins named &quot;requiredAddIns&quot;
' Example: change to &quot;Dim requiredAddIns(0 To 4)&quot; if the macro is checking 5 total add-ins)
   Dim requiredAddIns(0 To 1) As String

' Add each required AddIn to the array
   requiredAddIns(0) = &quot;PDFMaker.OfficeAddin&quot;
   requiredAddIns(1) = &quot;OneNote.WordAddinTakeNotesService&quot;
'   requiredAddIns(2) = &quot;&quot;
'   requiredAddIns(3) = &quot;&quot;
'   requiredAddIns(4) = &quot;&quot;

' Cycle through the array of required add-ins, and see if they exist in the connected add-ins list
   For Each requiredAddIn In requiredAddIns
      If InStr(stringOfAddins, requiredAddIn) Then
        ' The required add-in is in the string of connected add-ins
         msg = msg
      Else
        ' The required add-in is not in the string of connected add-ins, so add the add-in name to a string named &quot;listOfDisconnectedAddins&quot;
         msg = msg &amp; requiredAddIn &amp; vbCrLf
         listOfDisconnectedAddins = requiredAddIn &amp; &quot; &quot; &amp; listOfDisconnectedAddins
         listOfDisconnectedAddins = Trim(listOfDisconnectedAddins)
      End If
   Next

' If the msg variable is not blank (it contains at least one add-in's name) handle it, otherwise, do nothing
   If msg = &quot;&quot; Then
        ' There are no critical, unconnected add-ins (yay!)
        ' The script can now exit
   Else
        ' There are critical add-ins that are not connected, so handle this
        MsgBox &quot;The following critical Word Add-In(s) are disabled: &quot; &amp; vbCrLf &amp; vbCrLf &amp; msg &amp; vbCrLf &amp; vbCrLf &amp; &quot;To correct this problem, please save any documents you are working on, then close Word and reopen Word.&quot;

            ' I find it extremely hackish to check for each possible key and delete it if found... need to research how to delete the tree
            ' One potential obstacle to this method is that I've seen a DocumentRecovery subkey under Resiliency (only once, while editing this macro), that I haven't researched yet

            ' Note: Since the WSH Shell has no Enumeration functionality, you cannot
            '       use the WSH Shell object to delete an entire &quot;tree&quot; unless you
            '       know the exact name of every subkey.
            '       If you don't, use the WMI StdRegProv instead.
            ' http://www.robvanderwoude.com/vbstech_registry_wshshell.php

            ' More info on WMI StdRegProv at:
            ' http://msdn.microsoft.com/en-us/library/aa393664(v=vs.85).aspx

        ' This is hackish, but it effectively deletes a registry key, if it exists
        If KeyExists(&quot;HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Word\Resiliency\DisabledItems\&quot;) Then
            WshShell.RegDelete &quot;HKCU\Software\Microsoft\Office\14.0\Word\Resiliency\DisabledItems\&quot;
        ElseIf KeyExists(&quot;HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Word\Resiliency\StartupItems\&quot;) Then
            WshShell.RegDelete &quot;HKCU\Software\Microsoft\Office\14.0\Word\Resiliency\StartupItems\&quot;
        ElseIf KeyExists(&quot;HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Word\Resiliency\&quot;) Then
            WshShell.RegDelete &quot;HKCU\Software\Microsoft\Office\14.0\Word\Resiliency\&quot;
        End If

        ' To be completely thorough, we can also set the desired LoadBehavior for certain add-ins
        ' This can be done selectively, and only if the LoadBehavior was incorrect, but the quick and dirty way would be to just force the values

        WshShell.RegWrite &quot;HKCU\Software\Microsoft\Office\Word\Addins\PDFMaker.OfficeAddin\LoadBehavior&quot;, 3, &quot;REG_DWORD&quot;

        ' Release the WshShell object
        Set WshShell = Nothing

        ' Declare a few variables for the log file
        Dim user, machine, temp, datetime, output

        Set WshShell = CreateObject(&quot;WScript.Shell&quot;)
        user = WshShell.ExpandEnvironmentStrings(&quot;%USERNAME%&quot;)
        machine = WshShell.ExpandEnvironmentStrings(&quot;%COMPUTERNAME%&quot;)
        temp = WshShell.ExpandEnvironmentStrings(&quot;%TEMP%&quot;)
        ' Convert the slashes in Now to hyphens to prevent a fatal error
        datetime = Replace(Now, &quot;/&quot;, &quot;-&quot;)
        ' Create the string that will be written to the log file
        output = datetime + &quot;, &quot; + user + &quot;, &quot; + machine + &quot;, &quot; + listOfDisconnectedAddins

        ' Write the event to a log file
        logfile = temp + &quot;\addinslog.txt&quot;
        ' http://msdn.microsoft.com/en-us/library/2z9ffy99(v=vs.85).aspx
        ' http://www.devguru.com/technologies/vbscript/quickref/filesystemobject_opentextfile.html
        Set fso = CreateObject(&quot;Scripting.FileSystemObject&quot;)
        Set logfile = fso.OpenTextFile(logfile, 8, True)
        logfile.WriteLine (output)
        logfile.Close
        Set logfile = Nothing
        Set fso = Nothing

        ' Should we clear the variables?

        ' Release the WshShell object
        Set WshShell = Nothing
   End If

   ' Ardamis.com - We're in your macros, fixing your COM add-ins.
End Sub
</pre>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/05/05/programmatically-re-enabling-word-com-add-ins/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Online tools for testing web page performance</title>
		<link>http://www.ardamis.com/2011/04/10/online-tools-for-testing-web-page-performance/</link>
		<comments>http://www.ardamis.com/2011/04/10/online-tools-for-testing-web-page-performance/#comments</comments>
		<pubDate>Mon, 11 Apr 2011 02:49:53 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[blogging]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[database]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=816</guid>
		<description><![CDATA[A collection of online tools for measuring the performance of web pages, including time to first byte.]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve written a few tutorials lately on how to reduce page load times.  While I use Google&#8217;s Page Speed Firefox/Firebug plugin for evaluating pages for load times, there are times when I want a second opinion, or want to point a client to a tool.  This post is a collection of links to online tools for testing web page performance.</p>
<h2>Page Speed Online</h2>
<p><a href="http://pagespeed.googlelabs.com/">http://pagespeed.googlelabs.com/</a></p>
<p>Google&#8217;s wonderful Page Speed tool, once only available as a Firefox browser Add-on, finally arrives as an online tool.  Achieving a high score (ardamis.com is a 96/100) should be on every web developer&#8217;s list of things to do before the culmination of a project.</p>
<p>Enter a URL and Page Speed Online will run performance tests based on a set of best practices known to reduce page load times.</p>
<p>Optimizing caching &#8211; keeping your application&#8217;s data and logic off the network altogether<br />
Minimizing round-trip times &#8211; reducing the number of serial request-response cycles<br />
Minimizing request overhead &#8211; reducing upload size<br />
Minimizing payload size &#8211; reducing the size of responses, downloads, and cached pages<br />
Optimizing browser rendering &#8211; improving the browser&#8217;s layout of a page</p>
<h2>WebPagetest</h2>
<p><a href="http://www.webpagetest.org/">http://www.webpagetest.org/</a></p>
<p>WebPagetest is an excellent application for users who want the same sort of detailed reporting that one gets with Page Speed.</p>
<p>Load time speed test on first view (cold cache) and repeat view (hot cache), first byte and start render<br />
Optimization checklist<br />
Enable keep-alive, HTML compression, image compression, cache static content, combine JavaScript and CSS, and use of CDN<br />
Waterfall<br />
Response headers for each request</p>
<h2>Load Impact</h2>
<p><a href="http://loadimpact.com/pageanalyzer.php">http://loadimpact.com/pageanalyzer.php</a></p>
<p>Load Impact is an online load testing service that lets you load- and stress test your website over the Internet.  The page analyzer analyzes your web page performance by emulating how a web browser would load your page and all resources referenced in it. The page and its referenced resources are loaded and important performance metrics are measured and displayed in a load-bar diagram along with other per-resource attributes such as URL, size, compression ratio and HTTP status code.</p>
<h2>ByteCheck</h2>
<p><a href="http://www.bytecheck.com/">http://www.bytecheck.com/</a></p>
<p>ByteCheck is a super minimal site that return your page&#8217;s all-important time to first byte (TTFB). Time to first byte is the time it takes for a browser to start receiving information after it has started to make the request to the server, and is responsible for a visitor&#8217;s first impression that a page is fast- or slow-loading.</p>
<h2>Web Page Analyzer</h2>
<p><a href="http://websiteoptimization.com/services/analyze/">http://websiteoptimization.com/services/analyze/</a></p>
<p>My opinion is that the Web Page Analyzer report is good for beginners without much technical knowledge of things like gzip compression and Expires headers.  It&#8217;s a bit dated, and is primarily concerned with basics like how many images a page contains.  It tells you how fast you can expect your page to load for dial-up visitors, which strikes me as quaint and not particularly useful.</p>
<p>Total HTTP requests<br />
Total size<br />
Total size per object type (CSS, JavaScript, images, etc.)<br />
Analysis of number of files and file size as compared to recommended limits.</p>
<h2>The Performance Grader</h2>
<p><a href="http://www.joomlaperformance.com/component/option,com_performance/Itemid,52/">http://www.joomlaperformance.com/component/option,com_performance/Itemid,52/</a></p>
<p>This is another simplistic analysis of a site, like Web Page Analyzer, that returns its analysis in the form of pass/fail grades on about 14 different tests.  I expect that it would be useful for developers who want to show a client a third-party&#8217;s analysis of their work, if the third-party is not terribly technically savvy.  </p>
<p>One unique thing about this tool, though, is that it totals up the size of all images referenced in CSS files (even those that the current page isn&#8217;t using).</p>
<p>HTML Size<br />
Total Size<br />
Total Requests<br />
Generation Time<br />
Number of Hosts<br />
Number of Images<br />
Size of Images<br />
Number of CSS Files<br />
Size of CSS Files<br />
Number of Script Files<br />
Size of Script Files<br />
HTML Encoding<br />
Valid HTML<br />
Frames</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2011/04/10/online-tools-for-testing-web-page-performance/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Web development tools</title>
		<link>http://www.ardamis.com/2010/08/15/web-development-tools/</link>
		<comments>http://www.ardamis.com/2010/08/15/web-development-tools/#comments</comments>
		<pubDate>Mon, 16 Aug 2010 03:39:17 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[htaccess]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[Microsoft]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[script.aculo.us]]></category>
		<category><![CDATA[templates]]></category>
		<category><![CDATA[themes]]></category>
		<category><![CDATA[web app]]></category>
		<category><![CDATA[xhtml]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=842</guid>
		<description><![CDATA[A collection of web development tools for building better sites more easily.]]></description>
			<content:encoded><![CDATA[<p>A collection of web development tools for building better sites more easily.</p>
<h3>Frameworks and scripts</h3>
<p><a href="http://html5boilerplate.com/">HTML5 Boilerplate</a> is the professional badass&#8217;s base HTML/CSS/JS template for a fast, robust and future-proof site.</p>
<p><a href="http://scriptsrc.net/">scriptsrc.net</a> is a collection of script tags of the latest versions of a range of JavaScript libraries.</p>
<p><a href="http://www.modernizr.com/">Modernizr</a> adds classes to the &lt;html&gt; element which allow you to target specific browser functionality in your stylesheet. You don&#8217;t actually need to write any Javascript to use it.</p>
<h3>Images</h3>
<p><a href="http://placehold.it/">placehold.it</a> is a quick and simple image placeholder service.</p>
<h3>Text manipulation</h3>
<p><a href="http://www.textfixer.com/tools/">TextFixer</a> is a collection of online text tools. Remove line breaks from text, alphabetize text, capitalize the first letter of sentences, remove whitespaces, and uppercase text or lowercase text.</p>
<h3>XHTML</h3>
<p><a href="http://willpeavy.com/minifier/">HTML Minifier</a> will minify HTML (or XHTML), and any CSS or JS included in your markup.</p>
<h3>CSS</h3>
<p><a href="http://css3generator.com/">CSS3 Generator</a> is an awesome code generator for CSS3 snippets, and shows the minimum browser versions required to display the effects.</p>
<p><a href="http://procssor.com/">proCSSor</a> is an advanced CSS prettifier with tons of formatting options.</p>
<h3>JavaScript</h3>
<p><a href="http://jsbeautifier.org/">Online javascript beautifier</a> will reformat and reindent bookmarklets, ugly javascript, unpack scripts packed by the popular <a href="http://dean.edwards.name/packer/">Dean Edward&#8217;s packer,</a> as well as deobfuscate scripts processed by javascriptobfuscator.com.  The source code for the latest version is always available on <a href="http://github.com/einars/js-beautify">github</a>, and you can download the beautifier for local use (<a href="http://github.com/einars/js-beautify/zipball/master">zip</a>, <a href="http://github.com/einars/js-beautify/tarball/master">tar.gz</a>) as well.</p>
<h3>Fonts and Typography</h3>
<p>Fontshop.com has written <a href="http://www.fontshop.com/education/">A Field Guide to Typography</a> to get you excited about fonts and typography.</p>
<p><a href="http://www.typetester.org/">Typetester</a> is an online app for comparing different fonts for the screen, you can test up to three fonts at a time and choose the one you like. Its primary role is to make web designer’s life easier.</p>
<p>A quick chart of the <a href="http://www.ampsoft.net/webdesign-l/WindowsMacFonts.html">fonts common to all versions of Windows and the Mac equivalents</a>, or a more extensive <a href="http://media.24ways.org/2007/17/fontmatrix.html">matrix of fonts bundled with Mac and Windows operating systems, Microsoft Office and Adobe Creative Suite</a>.</p>
<p><a href="http://html-ipsum.com/">&lt;html&gt;ipsum</a> has Lorem ipsum already wrapped in HTML tags.  Pre-made paragraphs, lists, etc&#8230;</p>
<p>More resources at: <a href="http://www.smashingmagazine.com/2009/01/27/css-typographic-tools-and-techniques/">50 Useful Design Tools For Beautiful Web Typography</a> and <a href="http://speckyboy.com/2009/01/12/21-typography-and-font-web-apps-you-cant-live-without/">21 Typography and Font Web Apps You Can&#8217;t Live Without</a>.</p>
<h3>Colors</h3>
<p><a href="http://colorschemedesigner.com/">Color Scheme Designer</a>.</p>
<h3>Markup</h3>
<p>Google Webmaster Tools&#8217; <a href="http://www.google.com/webmasters/tools/richsnippets">Rich Snippets Testing Tool</a>.</p>
<p>Use the Rich Snippets Testing Tool to check that Google can correctly parse your structured data markup and display it in search results.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2010/08/15/web-development-tools/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>How to compress .php, .css and .js files without mod_gzip or mod_deflate</title>
		<link>http://www.ardamis.com/2010/07/11/compress-files-without-mod_gzip-or-mod_deflate/</link>
		<comments>http://www.ardamis.com/2010/07/11/compress-files-without-mod_gzip-or-mod_deflate/#comments</comments>
		<pubDate>Mon, 12 Jul 2010 04:42:27 +0000</pubDate>
		<dc:creator>ardamis</dc:creator>
				<category><![CDATA[Tutorials]]></category>
		<category><![CDATA[Web Site Dev]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[Apache]]></category>
		<category><![CDATA[coding]]></category>
		<category><![CDATA[css]]></category>
		<category><![CDATA[downloads]]></category>
		<category><![CDATA[firefox]]></category>
		<category><![CDATA[godaddy]]></category>
		<category><![CDATA[htaccess]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[programming]]></category>
		<category><![CDATA[themes]]></category>
		<category><![CDATA[web app]]></category>

		<guid isPermaLink="false">http://www.ardamis.com/?p=753</guid>
		<description><![CDATA[How to speed up page load times by compressing .php, .css and .js files on Apache web hosts that do not have mod_gzip or mod_deflate enabled.]]></description>
			<content:encoded><![CDATA[<p>File compression is possible on Apache web hosts that do not have mod_gzip or mod_deflate enabled, and it&#8217;s easier than you might think.</p>
<p>A great explanation of why compression helps the web deliver a better user experience is at <a href="http://betterexplained.com/articles/how-to-optimize-your-site-with-gzip-compression/">betterexplained.com</a>.</p>
<p>Two authoritative articles on the subject are Google&#8217;s <a href="http://code.google.com/speed/page-speed/docs/payload.html#GzipCompression">Performance Best Practices documentation | Enable compression</a> and Yahoo&#8217;s <a href="http://developer.yahoo.com/performance/rules.html#gzip">Best Practices for Speeding Up Your Web Site | Gzip Components</a>.</p>
<h2>Compressing PHP files</h2>
<p>If your Apache server does not have mod_gzip or mod_deflate enabled (Godaddy and JustHost shared hosting, for example), you can use PHP to compress pages on-the-fly.  This is still preferable to sending uncompressed files to the browser, so don&#8217;t worry about the additional work the server has to do to compress the files at each request.  </p>
<h3>Option 1: PHP.INI using zlib.output_compression</h3>
<p>The zlib extension can be used to transparently compress PHP pages on-the-fly if the browser sends an &#8220;Accept-Encoding: gzip&#8221; or &#8220;deflate&#8221; header.  Compression with zlib.output_compression seems to be disabled on most hosts by default, but can be enabled with a custom php.ini file:</p>
<pre class="brush: plain; title: ; notranslate">
[PHP]

zlib.output_compression = On
</pre>
<p>Credit: <a href="http://php.net/manual/en/zlib.configuration.php">http://php.net/manual/en/zlib.configuration.php</a></p>
<p>Check with your host for instructions on how to implement this, and whether you need a php.ini file in each directory.</p>
<h3>Option 2: PHP using ob_gzhandler</h3>
<p>If your host does not allow custom php.ini files, you can add the following line of code to the top of your PHP pages, above the DOCTYPE declaration or first line of output:</p>
<pre class="brush: plain; title: ; notranslate">
&lt;?php if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip')) ob_start(&quot;ob_gzhandler&quot;); else ob_start(); ?&gt;
</pre>
<p>Credit: <a href="http://help.godaddy.com/article/4485">GoDaddy.com</a></p>
<p>For WordPress sites, this code would be added to the top of the theme&#8217;s header.php file.</p>
<p>According to php.net, <a href="http://php.net/manual/en/function.ob-gzhandler.php">using zlib.output_compression is preferred over ob_gzhandler()</a>.</p>
<p>For WordPress or other CMS sites, an advantage of zlib.output_compression over the ob_gzhandler method is that all of the .php pages served will be compressed, not just those that contain the global include (eg.: header.php, etc.).</p>
<p>Running both ob_gzhandler and zlib.output_compression at the same time will throw a warning, similar to:</p>
<p><em>Warning: ob_start() [ref.outcontrol]: output handler &#8216;ob_gzhandler&#8217; conflicts with &#8216;zlib output compression&#8217; in /home/path/public_html/ardamis.com/wp-content/themes/mytheme/header.php on line 7</em></p>
<h2>Compressing CSS and JavaScript files</h2>
<p>Because the on-the-fly methods above only work for PHP pages, you&#8217;ll need something else to compress CSS and JavaScript files.  Furthermore, these files typically don&#8217;t change, so there isn&#8217;t a need to compress them at each request.  A better method is to serve pre-compressed versions of these files.  I&#8217;ll describe a few different ways to do this, but in both cases, you&#8217;ll need to add some lines to your .htaccess file to send user agents the gzipped versions if they support the encoding.  This requires that Apache&#8217;s mod_rewrite be enabled (and I think it&#8217;s almost universally enabled).</p>
<h3>Option 1: Compress locally and upload</h3>
<p>CSS and JavaScript files can be gzipped on the workstation, then uploaded along with the uncompressed files.  Use a utility like <a href="http://www.7-zip.org/">7-Zip</a> (quite possibly the best compression software around, and it&#8217;s free) to compress the CSS and JavaScript files using the gzip format (with extension *.gz), then upload them to your server.</p>
<p>For Windows users, here is a handy command to compress all the .css and .js files in the current directory and all sub directories (adjust the path to the 7-Zip executable, Zz.exe, as necessary):</p>
<pre class="brush: plain; title: ; notranslate">
for /R %i in (*.css *.js) do &quot;C:\Program Files (x86)\7-Zip\7z.exe&quot; a -tgzip &quot;%i.gz&quot; &quot;%i&quot; -mx9
</pre>
<p>Note that the above command is to be run from the command line.  The batch file equivalent would be:</p>
<pre class="brush: plain; title: ; notranslate">
for /R %%i in (*.css *.js) do &quot;C:\Program Files (x86)\7-Zip\7z.exe&quot; a -tgzip &quot;%%i.gz&quot; &quot;%%i&quot; -mx9
</pre>
<h3>Option 2: Compress on the server</h3>
<p>If you have shell access, you can run a command to create a gzip copy of each CSS and JavaScript file on your site (or, if you are developing on Linux, you can run it locally):</p>
<pre class="brush: plain; title: ; notranslate">
find . -regex &quot;.*\(css\|js\)$&quot; -exec bash -c 'echo Compressing &quot;{}&quot; &amp;&amp; gzip -c --best &quot;{}&quot; &gt; &quot;{}.gz&quot;' \;
</pre>
<p>This may be a bit too technical for many people, but is also much more convenient.  It is particularly useful when you need to compress a large number of files (as in the case of a WordPress installation with multiple plugins).  Remember to run it every time you automatically update WordPress, your theme, or any plugins, so as to replace the gzip&#8217;d versions of any updated CSS and JavaScript files.</p>
<h3>The .htaccess (for both options)</h3>
<p>Add the following lines to your .htaccess file to identify the user agents that can accept the gzip encoded versions of these files.</p>
<pre class="brush: plain; title: ; notranslate">
&lt;files *.js.gz&gt;
  AddType &quot;text/javascript&quot; .gz
  AddEncoding gzip .gz
&lt;/files&gt;
&lt;files *.css.gz&gt;
  AddType &quot;text/css&quot; .gz
  AddEncoding gzip .gz
&lt;/files&gt;
RewriteEngine on
#Check to see if browser can accept gzip files.
ReWriteCond %{HTTP:accept-encoding} gzip
RewriteCond %{HTTP_USER_AGENT} !Safari
#make sure there's no trailing .gz on the url
ReWriteCond %{REQUEST_FILENAME} !^.+\.gz$
#check to see if a .gz version of the file exists.
RewriteCond %{REQUEST_FILENAME}.gz -f
#All conditions met so add .gz to URL filename (invisibly)
RewriteRule ^(.+) $1.gz [QSA,L]
</pre>
<p>Credit: <a href="http://www.opensourcetutor.com/2009/06/01/how-to-compress-css-javascript-an-alternative-to-mod_deflate-or-mod_gzip/">opensourcetutor.com</a></p>
<p>I&#8217;m not sure it&#8217;s still necessary to exclude Safari.</p>
<p>For added benefit, minify the CSS and JavaScript files before gzipping them.  Google&#8217;s excellent <a href="http://code.google.com/speed/page-speed/">Page Speed</a> Firefox/Firebug Add-on makes this very easy.  Yahoo&#8217;s <a href="http://developer.yahoo.com/yui/compressor/">YUI Compressor</a> is also quite good.</p>
<h2>Verify that your content is being compressed</h2>
<p>Use the nifty Web Page Content Compression Verification tool at <a href="http://www.whatsmyip.org/http_compression/">http://www.whatsmyip.org/http_compression/</a> to confirm that your server is sending the compressed files.</p>
<h2>Speed up page load times for returning visitors</h2>
<p>Compression is only part of the story.  In order to further speed page load times for your returning visitors, you will want to <a href="http://www.ardamis.com/2010/07/17/sending-headers-to-leverage-browser-caching/">send the correct headers to leverage browser caching</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ardamis.com/2010/07/11/compress-files-without-mod_gzip-or-mod_deflate/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
	</channel>
</rss>

