<?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>Colin Mollenhour&#039;s Technical Blog</title>
	<atom:link href="http://colin.mollenhour.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://colin.mollenhour.com</link>
	<description>Just another WordPress weblog</description>
	<lastBuildDate>Sat, 26 Jan 2013 03:55:51 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>View and Filter Orders Grid with Product SKU</title>
		<link>http://colin.mollenhour.com/2012/03/03/view-and-filter-orders-grid-with-product-sku/</link>
		<comments>http://colin.mollenhour.com/2012/03/03/view-and-filter-orders-grid-with-product-sku/#comments</comments>
		<pubDate>Sat, 03 Mar 2012 08:29:49 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=165</guid>
		<description><![CDATA[One of the most bewildering missing features I have come across in Magento is the inability to look up orders by ordered products. There are occasions where this feature is sorely needed. For example, you may discover a product needs to be recalled and need a list of every order in the past month containing [...]]]></description>
				<content:encoded><![CDATA[<p>One of the most bewildering missing features I have come across in Magento is the inability to look up orders by ordered products. There are occasions where this feature is sorely needed. For example, you may discover a product needs to be recalled and need a list of every order in the past month containing that product. I discovered I wasn&#8217;t the only soul <a href="http://www.magentocommerce.com/boards/viewthread/178474/" target="_blank">begging</a> for this feature. There is <a href="http://webkul.com/blog/magento-product-name-and-product-sku-in-sales-order-grid/" target="_blank">one solution</a> posted which worked but had a few problems. To be precise, it breaks the pager, it replaces a core file and filtering doesn&#8217;t work. So, I&#8217;ve fixed the major problems and packaged and released it as a free module on github!</p>
<p>This module adds a column to the orders grid which displays either the product name and ordered quantities or the product SKU and ordered quantities which is configurable under Sales > Orders Grid Products Column. The one not selected to be displayed will be added to the title attribute so it can be seen with a mouse hover. The column is filterable by either the product name or SKU depending on the configuration.</p>
<h3><a href="https://github.com/colinmollenhour/Cm_OrderProducts" target="_blank">Enjoy!</a></h3>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2012/03/03/view-and-filter-orders-grid-with-product-sku/feed/</wfw:commentRss>
		<slash:comments>14</slash:comments>
		</item>
		<item>
		<title>Resolving Magento module dependencies without using the &#8220;depends&#8221; element</title>
		<link>http://colin.mollenhour.com/2011/10/13/resolving-magento-module-dependencies-without-using-the-depends-element/</link>
		<comments>http://colin.mollenhour.com/2011/10/13/resolving-magento-module-dependencies-without-using-the-depends-element/#comments</comments>
		<pubDate>Thu, 13 Oct 2011 21:08:06 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=160</guid>
		<description><![CDATA[You might someday (or perhaps you already have) come across a case where you want one module to be loaded after another module, but don&#8217;t necessarily want it to depend on the other module. Reasons might be that one config value should override another (such as a model/block/controller rewrite), or one layout should be applied [...]]]></description>
				<content:encoded><![CDATA[<p>You might someday (or perhaps you already have) come across a case where you want one module to be loaded after another module, but don&#8217;t necessarily want it to depend on the other module. Reasons might be that one config value should override another (such as a model/block/controller rewrite), or one layout should be applied after another (such as to remove a block added by the former). There might not <em>actually</em> be a dependence, or perhaps adding a dependence would create a circular dependency. There are two possible solutions for this:</p>
<ol>
<li>Merge the two (or more) module .xml files manually into one .xml file with the proper XML element order.</li>
<li>Name the .xml files (or symlinks a.la. <a href="http://code.google.com/p/module-manager/" title="Module Manager" target="_blank">modman</a>) such that they sort alphabetically in the proper order.</li>
</ol>
<p>The element order within a single file will be stable (SimpleXML), and Magento loads the files alphabetically except that those beginning with Mage_ come first. Problem solved.</p>
<p>Example:</p>
<pre>
Before:
  app/etc/modules/My_Module.xml
  app/etc/modules/Their_Module.xml
After:
  app/etc/modules/50_Their_Module.xml
  app/etc/modules/60_My_Module.xml
</pre>
<p>Now &#8220;My_Module&#8221; is loaded after &#8220;Their_Module&#8221; so I can override their config and/or layout without modifying their files and without My_Module depending on Their_Module!</p>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2011/10/13/resolving-magento-module-dependencies-without-using-the-depends-element/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Benchmarking Zend_Cache backends for Magento</title>
		<link>http://colin.mollenhour.com/2011/10/03/benchmarking-zend_cache-backends-for-magento/</link>
		<comments>http://colin.mollenhour.com/2011/10/03/benchmarking-zend_cache-backends-for-magento/#comments</comments>
		<pubDate>Mon, 03 Oct 2011 20:37:59 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[zend framework]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=144</guid>
		<description><![CDATA[The Zend_Cache module from the Zend Framework is a nice piece of work. It has a slew of programmer-friendly frontends and a respectable set of backends with a well-designed interface. I love the a-la-carte approach, but I am only really interested in the Zend_Cache_Core frontend and the backends that support tagging since that is what is required by Magento. This begs the question, which backend should you use? While I have my own opinion on that matter (ahem, Redis. -post coming soon-ish), I wanted a reliable way to test Zend_Cache backend performances so I wrote a benchmark! This benchmark was both forked from and inspired by the benchmark found in <a href="http://magebase.com/magento-tutorials/improving-the-file-cache-backend/">Vinai Kopp's Symlink Cache</a>. It uses Magento's core/cache model rather than Zend_Cache_Core directly so a Magento (or <a href="https://github.com/colinmollenhour/magento-lite">Magento-lite</a>) installation and bash are the only requirements.

The purpose of this post is not to provide a bunch of cache backend benchmarks, but rather to simply introduce my benchmark code in the hopes that others conduct their own tests and hopefully publish their findings.]]></description>
				<content:encoded><![CDATA[<p>The Zend_Cache module from the Zend Framework is a nice piece of work. It has a slew of programmer-friendly frontends and a respectable set of backends with a well-designed interface. I love the a-la-carte approach, but I am only really interested in the Zend_Cache_Core frontend and the backends that support tagging since that is what is required by Magento. This begs the question, which backend should you use? While I have my own opinion on that matter (ahem, Redis. -post coming soon-ish), I wanted a reliable way to test Zend_Cache backend performances so I wrote a benchmark! This benchmark was both forked from and inspired by the benchmark found in <a href="http://magebase.com/magento-tutorials/improving-the-file-cache-backend/">Vinai Kopp&#8217;s Symlink Cache</a>. It uses Magento&#8217;s core/cache model rather than Zend_Cache_Core directly so a Magento (or <a href="https://github.com/colinmollenhour/magento-lite">Magento-lite</a>) installation and bash are the only requirements.</p>
<p>The purpose of this post is not to provide a bunch of cache backend benchmarks, but rather to simply introduce my benchmark code in the hopes that others conduct their own tests and hopefully publish their findings. A link to this post is appreciated. Also, if there are any criticisms of the benchmark I&#8217;d love to see a pull request. :)</p>
<p><span id="more-144"></span></p>
<p>The benchmark suite is fully-featured:</p>
<ul>
<li>Repeatable tests. Dataset is written to static files so the <strong>exact</strong> same test can be repeated, even with entirely different backends.</li>
<li>Test datasets can easily be zipped up and copied to different environments or shared for others to use.</li>
<li>Can relatively easily test multiple pre-generated datasets to compare different scenarios on the same hardware.</li>
<li>Uses true multi-process benchmarking, each process with a different set of random operations.</li>
<li>Flexible dataset generation via options to init command. Cache record data size, number of tags, expiration, popularity and volatility are all randomized.</li>
</ul>
<p><br/><br />
Currently the benchmarks are run via the command line so testing the APC backend or any others that only work via a cgi or apache module environment will not work. This could be remedied easily enough with the use of CuRL and some php copy/paste if you had the desire to test on your actual web server.</p>
<p>Here is an example run using the Redis backend using my dev environment, a Lubuntu VirtualBox guest:</p>
<pre>
Cache Backend: Zend_Cache_Backend_Redis
Loading 'default' test data...
Loaded 10000 cache records in 29.1080 seconds. Data size is 5008.9K
Analyzing current cache contents...
Counted 10023 cache IDs and 2005 cache tags in 0.2062 seconds
Benchmarking getIdsMatchingTags...
Average: 0.00036 seconds (36.82 ids per tag)
Benchmarking 4 concurrent clients, each with 100000 operations...
4 concurrent clients completed in 62 seconds

         |   reads|  writes|  cleans
------------------------------------
Client  1| 1811.83|  184.66|    6.81
Client  2| 1799.84|  165.29|    6.91
Client  3| 1818.90|  165.17|    6.79
Client  0| 1790.91|  153.56|    7.40
------------------------------------
ops/sec  | 7221.48|  668.68|   27.91
</pre>
<p>The important numbers to look at are the summed ops/sec. Given the three variables: dataset, hardware and backend, it is easy to change just one of these without affecting the others so this benchmark can be used to test any one of the three variables reliably. The three metrics observed are reads, writes and cleans. The first two are pretty self-explanatory. The third is a clean operation on a single tag using <code>Zend_Cache::CLEANING_MODE_MATCHING_ANY_TAG</code> which is the only mode Magento ever uses other than <code>Zend_Cache::CLEANING_MODE_ALL</code> for manual cache refreshes. Individual read/write operations are very fast so given the large number of operations in a test I did not feel the need to examine min, max, average, or standard deviations.</p>
<p>The test uses (hopefully) sane defaults for dataset generation parameters, but there is plenty of flexibility. I advise you to examine your production environment (number of cache keys, number of cache tags, number of concurrent clients) to tweak the test to more closely match your own environment. Here is the output of the <code>--help</code> cli parameter:</p>
<pre>
$ php shell/cache-benchmark.php --help
This script will either initialize a new benchmark dataset or run a benchmark.

Usage:  php -f shell/cache-benchmark.php [command] [options]

Commands:
  init [options]        Initialize a new dataset.
  load --name &lt;string&gt;  Load an existing dataset.
  clean                 Flush the cache backend.
  tags                  Benchmark getIdsMatchingTags method.
  ops [options]         Execute a pre-generated set of operations on the existing cache.

'init' options:
  --name &lt;string&gt;       A unique name for this dataset (default to "default")
  --keys &lt;num&gt;          Number of cache keys (default to 10000)
  --tags &lt;num&gt;          Number of cache tags (default to 2000)
  --min-tags &lt;num&gt;      The min number of tags to use for each record (default 0)
  --max-tags &lt;num&gt;      The max number of tags to use for each record (default 15)
  --min-rec-size &lt;num&gt;  The smallest size for a record (default 1)
  --max-rec-size &lt;num&gt;  The largest size for a record (default 1024)
  --clients &lt;num&gt;       The number of clients for multi-threaded testing (defaults to 4)
  --seed &lt;num&gt;          The random number generator seed (default random)

'ops' options:
  --name &lt;string&gt;       The dataset to use (from the --name option from init command)
  --client &lt;num&gt;        Client number (0-n where n is --clients option from init command)
  -q|--quiet            Be less verbose.
</pre>
<p>To handle multi-process benchmarking the test is actually launched from a shell script which backgrounds each client and sums the results using awk so unless you are doing single-process benchmarks you never need to invoke the &#8216;ops&#8217; command yourself.</p>
<h2>Give me the code already!</h2>
<p>The code is hosted at <a href="https://github.com/colinmollenhour/magento-cache-benchmark">github.com/colinmollenhour/magento-cache-benchmark</a>. If you use modman you can install it like so:</p>
<pre>
modman clone cachebench git://github.com/colinmollenhour/magento-cache-benchmark.git
</pre>
<p>Or, you may also <a href="https://github.com/colinmollenhour/magento-cache-benchmark/zipball/v1.0">download it directly</a> and just extract cache-benchmark.php to the &#8220;shell&#8221; folder in your Magento installation.</p>
<h2>Run a test!</h2>
<p>Assuming you&#8217;ve cloned/downloaded the code already, here is how you run your first test:</p>
<pre>
php shell/cache-benchmark.php init
bash var/cachebench/default/run.sh
</pre>
<p>Could it get any easier?</p>
<p>PS. I included a &#8220;Null&#8221; backend which is just a black hole for the purpose of getting a general idea of your PHP overhead.</p>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2011/10/03/benchmarking-zend_cache-backends-for-magento/feed/</wfw:commentRss>
		<slash:comments>41</slash:comments>
		</item>
		<item>
		<title>Cross-SCM (git and subversion) projects with modman</title>
		<link>http://colin.mollenhour.com/2011/03/03/cross-scm-git-and-subversion-projects-with-modman/</link>
		<comments>http://colin.mollenhour.com/2011/03/03/cross-scm-git-and-subversion-projects-with-modman/#comments</comments>
		<pubDate>Thu, 03 Mar 2011 05:10:39 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=139</guid>
		<description><![CDATA[I recently had a need to include some code from two subversion repositories in a git-based project. A bit of Googling brought me to the disappointing conclusion that there is no easy maintenance-free way of doing this with git.. Modman to the rescue! With a simple one-liner added to my modman file I can accomplish [...]]]></description>
				<content:encoded><![CDATA[<p>I recently had a need to include some code from two subversion repositories in a git-based project. A bit of Googling brought me to the disappointing conclusion that there is no easy maintenance-free way of doing this with git.. <a href="http://code.google.com/p/module-manager/">Modman to the rescue!</a> With a simple one-liner added to my modman file I can accomplish what is essentially an svn:externals declaration in my git-based project, or a subversion repository as a git submodule! Behold:</p>
<p>
EDIT: Updated to use multi-line continuations for readability.</p>
<pre class="brush: plain;">
# Substitute for svn:externals
@shell \
  SRC=https://example.com/source/magento/common; \
  DEST=modules; \
  if [ -d $DEST/.svn ]; then \
    svn update $DEST; \
  else \
    svn checkout $SRC $DEST; \
  fi

# Import subversion-based modules
@import    modules/Foo
@import    modules/Bar
</pre>
<p>
Now, when I clone the module (git is supported natively as of 1.1.0) or any time I run an update, deploy or repair, the subversion modules will be checked out or updated as well. So, if you ever need to mix and match SCM&#8217;s, go right ahead. I still recommend the use of git submodules for all-git projects and svn:externals for all-subversion projects, of course.
</p>
<p>
I&#8217;ll consider adding a builtin function that tidies up the above and handles things like switching urls..</p>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2011/03/03/cross-scm-git-and-subversion-projects-with-modman/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>The Right Way to Optimize Apache&#8217;s .htaccess Files</title>
		<link>http://colin.mollenhour.com/2010/06/30/the-right-way-to-optimize-apaches-htaccess-files/</link>
		<comments>http://colin.mollenhour.com/2010/06/30/the-right-way-to-optimize-apaches-htaccess-files/#comments</comments>
		<pubDate>Wed, 30 Jun 2010 23:47:09 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[Magento]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=127</guid>
		<description><![CDATA[While researching Magento performance optimizations you have probably already read about how to optimize Apache&#8217;s configuration by moving your configuration directives into the configuration files and out of the .htaccess files. Of course you need root to do this, but assuming you can, there are still very very wrong ways to do this that will [...]]]></description>
				<content:encoded><![CDATA[<p>
While researching Magento performance optimizations you have probably already read about how to optimize Apache&#8217;s configuration by moving your configuration directives into the configuration files and out of the .htaccess files. Of course you need root to do this, but assuming you can, there are still very very wrong ways to do this that will result in no real performance gains, leave gaping security holes, and consume more time than necessary. Read on, a handy script for solving these problems and before and after performance benchmarks to prove the gains are included.
</p>
<p><span id="more-127"></span></p>
<h2>Mistake #1</h2>
<p>If you move your .htaccess contents into the apache config files that is great, but if you forget to disable .htaccess files using <code>AllowOverride None</code> then you&#8217;ve entirely defeated the purpose! The reason this technique can result in performance gains is that it allows the server to ignore the .htaccess files which means fewer directory traversals and fewer file reads with each request. One page load probably results in 20-100 HTTP requests to your Apache server. If you have deeply nested files (all of those cached product images and skin images come to mind) then this results in a lot of disk access. So, you must disable the .htaccess files completely with <code>AllowOverride None</code>! Which brings me to the second major mistake that people can make&#8230;
</p>
<h2>Mistake #2</h2>
<p>
Now that you&#8217;ve disabled .htaccess, guess what? Those .htaccess files had a purpose.. For example, to deny access to your <code>app/etc/local.xml</code> config file!! Great way to expose your database password, let&#8217;s just hope the user is a localhost-only user and you don&#8217;t re-use passwords&#8230; Also, in <code>downloader/.htaccess</code> for example, compression is disabled so that the Magento Connect console can circumvent Apache&#8217;s buffering to display the console output as it occurs. There are others as well. Point is, now you have to move all of these .htaccess files into your apache config. Ugh.. Which brings me to my last point&#8230;
</p>
<h2>Mistake #3</h2>
<p>
It doesn&#8217;t have to be difficult! I&#8217;ve written a bash script (buildhtaccess.sh, see below) which provides you an easy way to benefit from this tweak. In essence the following script will find all of your .htaccess files and compile them into one file which can then be included in your Apache config with only a few lines. If you modify an .htaccess file or a new one is installed by an extension, re-run the script and reload the apache config.
</p>
<h2>Tutorial</h2>
<p>First, download and run the script to generate your .htaccess-combined file.</p>
<pre class="brush: plain;">
$ cd &lt;webroot&gt;
$ wget http://gist.github.com/raw/459311/buildhtaccess.sh
$ bash buildhtaccess.sh
</pre>
</p>
<p>Next, edit your apache config file. Find the <code>&lt;VirtualHost&gt;</code> directive and at the end of it add the following:</p>
<pre class="brush: plain;">
  # Include combined Magento .htaccess files
  &lt;Directory /your_webroot_here&gt;
    AllowOverride None
  &lt;/Directory&gt;
  Include /your_webroot_here/.htaccess-combined
</pre>
</p>
<p>
Lastly, run a graceful reload of the Apache config. A full restart is not necessary. Now you&#8217;re done.
</p>
<h2>Code</h2>
<p>And here is the code for generating the .htaccess-combined file:<br />
<script src="http://gist.github.com/459311.js?file=buildhtaccess.sh"></script>
</p>
<h2>Benchmarks</h2>
<p>For my benchmark I ran &#8220;ab&#8221; (apache bench) since it is quick and easy but admittedly not the best for benchmarking overall site performance. In this case apache bench is going to under state the value of this tweak since one page load in a browser will result in repeated hits on the webserver, all of which will be affected by this optimization. So, I benchmarked on /errors/503.php so that the database is not factored but PHP code is still run. Specifically, I used <code>$ ab -n 1000 -c 20 .../errors/503.php</code>.<br/><br />
Before: <b>~3000 requests per second (3018 max)</b><br/><br />
After: <b>~4600 requests per second (5030 max)</b><br/></p>
<p>That&#8217;s a pretty nice improvement. Unfortunately it is only substantial with fast requests such as the error pages and static files but it should definitely reduce disk contention on a busy server and increase scalability, but probably not do a whole lot for user-experience on a low-contention server.
</p>
<h5>I&#8217;d love to see your results, please post them in the comments!</h5>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2010/06/30/the-right-way-to-optimize-apaches-htaccess-files/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Module Manager Update, now on Google Code!</title>
		<link>http://colin.mollenhour.com/2010/01/31/module-manager-update/</link>
		<comments>http://colin.mollenhour.com/2010/01/31/module-manager-update/#comments</comments>
		<pubDate>Sun, 31 Jan 2010 04:15:40 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[subversion]]></category>
		<category><![CDATA[version control]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=109</guid>
		<description><![CDATA[I recently updated Module Manager (first post) to support nested modules and renamed the .modman definitions file to simply modman for easier editing in IDEs. To top it off I am officially releasing it to the public under the Apache License 2.0 so that you may start using it in your own projects. Additionally, Module [...]]]></description>
				<content:encoded><![CDATA[<p>
I recently updated Module Manager (<a href="http://colin.mollenhour.com/2009/10/17/module-manager-for-when-svnexternals-just-doesnt-cut-it/">first post</a>) to support nested modules and renamed the <code>.modman</code> definitions file to simply <code>modman</code> for easier editing in IDEs. To top it off I am officially releasing it to the public under the Apache License 2.0 so that you may start using it in your own projects.</p>
<p>
Additionally, <span style="font-size:120%; font-weight:bold;"><a href="http://code.google.com/p/module-manager/">Module Manager</a></span> now has it&#8217;s own home on Google Code so check it out! (pun intended)
</p>
<p>
Here is an example of the new <b>nested modules</b> feature:
</p>
<pre class="brush: plain;">
# My template files
skin               skin/frontend/my/default
design             app/design/frontend/my/default

# Import Colin_HotDealz module
@import            modules/Colin_HotDealz
</pre>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2010/01/31/module-manager-update/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>Module Manager &#8211; for when svn:externals just doesn&#8217;t cut it.</title>
		<link>http://colin.mollenhour.com/2009/10/17/module-manager-for-when-svnexternals-just-doesnt-cut-it/</link>
		<comments>http://colin.mollenhour.com/2009/10/17/module-manager-for-when-svnexternals-just-doesnt-cut-it/#comments</comments>
		<pubDate>Sat, 17 Oct 2009 05:38:16 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[subversion]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=96</guid>
		<description><![CDATA[Update 1/30/2010: Added nested modules, renamed .modman file to modman, released under Apache License 2.0, and hosted on Google Code. This project was inspired by my recent Magento development work so I&#8217;m not sure how much application it has outside of that, but if you&#8217;ve ever wanted to use svn:externals with individual files (you can&#8217;t [...]]]></description>
				<content:encoded><![CDATA[<hr/>
<p><span style="font-size:120%; font-weight:bold"><a href="http://colin.mollenhour.com/2010/01/31/module-manager-update/">Update 1/30/2010:</a></span><br />
Added nested modules, renamed .modman file to modman, released under Apache License 2.0, and hosted on Google Code.</p>
<hr/>
<p>This project was inspired by my recent Magento development work so I&#8217;m not sure how much application it has outside of that, but if you&#8217;ve ever wanted to use svn:externals with individual files (you can&#8217;t btw) then this is the next best thing (and therefore the best thing). Perhaps some other VCS supports this functionality natively , if you know of it please clue me in. I&#8217;ve used git and bazaar but still like the simplicity of subversion for small, non-distributed projects.</p>
<p>Magento&#8217;s source code is split among several different groups of files: code, design, locale, skin, libs, etc (as in config), etc.. (as in etcetera)<br />
You really cannot avoid ever having to mix a file in here or there which means svn:externals won&#8217;t cut it. Besides, svn:externals won&#8217;t let you make one commit from all of your externals so to commit your changes you have to do it once for each folder which makes your commit history nearly useless. Enter &#8220;modman&#8221;, my Magento module manager. While it could be used for deployment it is primarily geared towards developers. Here is how it works:</p>
<p><span id="more-96"></span></p>
<p>Create a branch and put your source code in it. Now create a &#8220;.modman&#8221; file within the root of the branch. In this file you define (much like svn:externals) a path within your repository and the path that it should map to. Here is an example (with a fictitious module named &#8220;HotDealz&#8221;:</p>
<pre class="brush: plain;">
code   app/code/local/Colin/HotDealz 
template   app/design/frontend/default/default/template/hotdealz
hotdealz.xml   app/design/frontend/default/default/layout/hotdealz.xml
images   skin/frontend/default/default/images/hotdealz
HotDealz.csv   app/locale/en_US/Colin_HotDealz.csz
</pre>
<p>As you can see, very similar to svn:externals only with individual file support which is handy for things like locale files that can&#8217;t go in subdirectories.</p>
<h2>How It Works</h2>
<p>Usage is similar to regular command line usage of subversion. The &#8220;modman&#8221; script provides a wrapper around svn which checks the code out in a .modman directory at the root of your project then reads the .modman file and creates symbolic links as necessary so that your source code is actually all contained within one directory and your webserver accesses the files where they are supposed to be. No svn:externals definitions are used. Obviously this requires a few things:</p>
<ol>
<li>bash</li>
<li>a webserver that is configured to FollowSymLinks</li>
</ol>
<p><br/></p>
<p>
If you do web development on Windows, you may be thinking, &#8220;What about Windows support!?&#8221;. Well, you have a few options:
</p>
<ul>
<li>Do your development inside a VirtualBox instance of Linux and share the drive to Windows for editing on your IDE (recommended).</li>
<li>Try to configure cygwin to work (untested, not recommended).</li>
</ul>
<p><br/></p>
<p>
I chose the former long before I wrote this script since the servers for my projects are all Linux-based and doing development on WAMP when your project runs on L*MP is silly. You can actually still use TortoiseSVN by simply opening the .modman directory from explorer so really after the initial checkout you do not need to continue to use the script except for when adding new definitions to the .modman file.
</p>
<h2>Sample Usage</h2>
<p>Here are some typical commands used with modman:</p>
<pre class="brush: plain;">
$ modman --help
$ modman init
$ modman hotdealz checkout &quot;svn url here&quot;
$ modman hotdealz status (can be run from any directory within the project root)
$ modman hotdealz commit
$ modman hotdealz update
$ modman hotdealz add Colin_HotDealz.xml app/etc/modules/HotDealz.xml
etc...
</pre>
<p>Most commonly used svn commands are passed through to svn for convenience but for advanced work you can just cd to the .modman directory or use TortoiseSVN or your IDE&#8217;s integrated subversion tools (I use Netbeans).</p>
<h3>Documentation</h3>
<pre>
----------------------
Magento Module Manager
----------------------
Usage: modman &lt;module&gt; &lt;action&gt; [svn args]
Supported global actions (no module specified):
  init           initialize the current directory as the modman root (no module name)
  update-all     update all modules that are currently checked out

Supported actions: (all svn commands support additional arguments)
  checkout       checkout a new modman compatible module
  export         export a modman compatible module (does real copy instead of symlinks)
  update [...]   update module
  add &lt;svn_path&gt; &lt;real_path&gt;   add a file/dir to the .modman file and working copy
  delete &lt;svn_path&gt;   remove a file/dir from the .modman file and working copy
  status         show status of entire module
  diff [...]     run svn diff on module
  commit [...]   commit changes to module
  info           show the module's working copy path and run svn info on working copy
  list           list the definitions in the .modman file

The repository should contain a file called &quot;.modman&quot; which defines which files
go where relative to the directory where modman was initialized.

If additional arguments are given to commands such as commit, paths
should be relative to the svn root.

---- Start example .modman file ----
# Comments are supported, begin a line with a hash
code                   app/code/local/My/Module/
design                 app/design/frontend/default/default/mymodule/
locale/My_Module.xml   app/locale/en_US/My_Module.xml
My_Module.xml          app/etc/modules/My_Module.xml
---- End example .modman file ----

Author:
Colin Mollenhour

http://colin.mollenhour.com/

colin@mollenhour.com
</pre>
<h3><a href="http://gist.github.com/212246">View Source</a></h3>
<h3><a href="http://gist.github.com/raw/212246/1b301e9aeb9bf83730008ccdb6ecb18651f073f5/modman">Download</a></h3>
<p>Leave a comment if you use it!</p>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2009/10/17/module-manager-for-when-svnexternals-just-doesnt-cut-it/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Yet Another &#8220;Hiding Out-Of-Stock Products&#8221; Update</title>
		<link>http://colin.mollenhour.com/2009/09/28/yet-another-hiding-out-of-stock-products-update/</link>
		<comments>http://colin.mollenhour.com/2009/09/28/yet-another-hiding-out-of-stock-products-update/#comments</comments>
		<pubDate>Mon, 28 Sep 2009 21:14:07 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=84</guid>
		<description><![CDATA[Update: Bug fix It turns out that when layered navigation indexes are refreshed the website_id is 0 which causes the filtered results to be empty which makes layered navigation disappear. The fix is to simply place an if statement around the filter dependent on the website id no being 0. The Magento Connect module has [...]]]></description>
				<content:encoded><![CDATA[<h3>Update: Bug fix</h3>
<p>It turns out that when layered navigation indexes are refreshed the <code>website_id</code> is 0 which causes the filtered results to be empty which makes layered navigation disappear. The fix is to simply place an if statement around the filter dependent on the website id no being 0. The Magento Connect module has been updated with this fix (0.3.1).</p>
<hr/>
<p><del datetime="2009-10-20T04:55:42+00:00">Ok, I think this will finally be the last update to this module&#8230;</del> I&#8217;ve learned a lot about Magento since the first version of this module, and think I finally did it right this time. This update adds support for advanced search (thanks saho for the bug report) and also every other aspect of the store I could find.<br />
<span id="more-84"></span><br />
I ended up doing a search like so:</p>
<pre class="brush: plain;">
app/code/core/Mage $ grep -rl addVisibleIn.\*FilterToCollection .
./Checkout/Block/Cart/Crosssell.php
./Tag/Block/Product/Result.php
./Tag/Block/Customer/View.php
./Adminhtml/Block/Sales/Order/Create/Search/Grid.php
./Adminhtml/Model/Sales/Order/Random.php
./Bundle/Block/Catalog/Product/List/Partof.php
./Bundle/Model/Observer.php
./Reports/Block/Product/Abstract.php
./Wishlist/Block/Share/Email/Items.php
./Wishlist/Block/Share/Wishlist.php
./Wishlist/Block/Customer/Wishlist.php
./Wishlist/Block/Customer/Sidebar.php
./Wishlist/Model/Mysql4/Wishlist.php
./CatalogSearch/Model/Layer.php
./CatalogSearch/Model/Advanced.php
./Rss/Block/Catalog/New.php
./Rss/Block/Catalog/Special.php
./CatalogIndex/Model/Indexer.php
./Sales/Model/Order.php
./Catalog/Block/Seo/Sitemap/Product.php
./Catalog/Block/Product/Compare/List.php
./Catalog/Block/Product/New.php
./Catalog/Block/Product/List/Related.php
./Catalog/Block/Product/List/Crosssell.php
./Catalog/Block/Product/List/Upsell.php
./Catalog/Block/Product/Bestsellers.php
./Catalog/Model/Layer.php
./Catalog/Model/Product/Visibility.php
./Catalog/Helper/Product/Compare.php
</pre>
<p>and determined (obviously) that it would be best to simply override the <code>addVisibleIn*FilterToCollection</code> functions rather than to try to override all of the above. I actually discovered the following code which turned out to not be what I wanted, but I think that is due to it not being updated when Varien added the stock_status table:</p>
<pre class="brush: php;">
Mage::getSingleton('cataloginventory/stock')-&gt;addInStockFilterToCollection($collection);
</pre>
<p>However, I liked the idea of having such a function available that actually works as intended (the above does not work on configurable products) so I did just that.</p>
<p>First, the function that can be used on any product collection to filter out of stock products:<br/><br />
<code>app/code/local/Lucky/InStockOnly/Model/Stock.php</p>
<pre class="brush: php;">
&lt;?php

class Lucky_InStockOnly_Model_Stock {

  /**
   * Add a statusInStock requirement for visibility
   */
  public function addInStockFilterToCollection($collection)
  {
    if($websiteId = Mage::app()-&gt;getWebsite()-&gt;getWebsiteId()) {
      $collection-&gt;joinField(
        'stock_status',
        'cataloginventory/stock_status',
        'stock_status',
        'product_id=entity_id', array(
          'stock_status' =&gt; Mage_CatalogInventory_Model_Stock_Status::STATUS_IN_STOCK,
          'website_id' =&gt; $websiteId
        )
      );
    }
  }

}
</pre>
<p>Next we add some functions that override the addVisibleIn*FilterToCollection functions that are used heavily throughout Magento:<br/><br />
<code>app/code/local/Lucky/InStockOnly/Model/Visibility.php</code></p>
<pre class="brush: php;">
&lt;?php

class Lucky_InStockOnly_Model_Visibility extends Mage_Catalog_Model_Product_Visibility {

  public function addVisibleInCatalogFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection)
  {
    parent::addVisibleInCatalogFilterToCollection($collection);
    Mage::getSingleton('cataloginventory/instockonly')-&gt;addInStockFilterToCollection($collection);
    return $this;
  }

  public function addVisibleInSearchFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection)
  {
    parent::addVisibleInSearchFilterToCollection($collection);
    Mage::getSingleton('cataloginventory/instockonly')-&gt;addInStockFilterToCollection($collection);
    return $this;
  }

  public function addVisibleInSiteFilterToCollection(Mage_Eav_Model_Entity_Collection_Abstract $collection)
  {
    parent::addVisibleInSiteFilterToCollection($collection);
    Mage::getSingleton('cataloginventory/instockonly')-&gt;addInStockFilterToCollection($collection);
    return $this;
  }

}
</pre>
<p>Now the model rewrites:<br/><br />
<code>app/code/local/Lucky/InStockOnly/etc/config.xml</code></p>
<pre class="brush: xml;">
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;config&gt;

&lt;global&gt;
  &lt;models&gt;

    &lt;catalog&gt;
      &lt;rewrite&gt;
        &lt;product_visibility&gt;Lucky_InStockOnly_Model_Visibility&lt;/product_visibility&gt;
      &lt;/rewrite&gt;
    &lt;/catalog&gt;
    &lt;cataloginventory&gt;
      &lt;rewrite&gt;
        &lt;instockonly&gt;Lucky_InStockOnly_Model_Stock&lt;/instockonly&gt;
      &lt;/rewrite&gt;
    &lt;/cataloginventory&gt;

  &lt;/models&gt;
&lt;/global&gt;

&lt;/config&gt;
</pre>
<p>And of course we need to enable the module (same as before):</p>
<p><code>app/etc/modules/Lucky_InStockOnly.xml</code></p>
<pre class="brush: xml;">
&lt;config&gt;
  &lt;modules&gt;
    &lt;Lucky_InStockOnly&gt;
      &lt;active&gt;true&lt;/active&gt;
      &lt;codePool&gt;local&lt;/codePool&gt;
    &lt;/Lucky_InStockOnly&gt;
  &lt;/modules&gt;
&lt;/config&gt;
</pre>
<h4>Known Bugs</h4>
<p>If you have attributes that are used to create configurable products and are also used in layered navigation, out of stock products will still show up when layered navigation is in use for an attribute where sibling products are in stock. I don't think there is a good fix for this other than having an observer disable products entirely when they go out of stock, but this has some side-effects which might negatively affect some people.</p>
<h4>Download</h4>
<p>This version has been packaged as a proper Magento Connect extension and is available here: <a href="http://www.magentocommerce.com/extension/2028">In Stock Only</a></p>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2009/09/28/yet-another-hiding-out-of-stock-products-update/feed/</wfw:commentRss>
		<slash:comments>27</slash:comments>
		</item>
		<item>
		<title>Hiding &#8220;Out of Stock&#8221; items in Layered Navigation</title>
		<link>http://colin.mollenhour.com/2009/07/14/hiding-out-of-stock-items-in-layered-navigation/</link>
		<comments>http://colin.mollenhour.com/2009/07/14/hiding-out-of-stock-items-in-layered-navigation/#comments</comments>
		<pubDate>Tue, 14 Jul 2009 17:33:43 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[Programming]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=75</guid>
		<description><![CDATA[Updated 9/28/09 See the latest post in this series: Yet Another “Hiding Out-Of-Stock Products” Update, for an update that fixes advanced search, tags, up-sells, cross-sells, etc.. Updated 7/23/09 Updated once again to add support for filtering search results. (Added CatalogSearch/Model/Layer.php, moved Model/Layer.php to Catalog/Model/Layer.php, updated config.xml) My previous post about &#8220;Hiding Out of Stock Items [...]]]></description>
				<content:encoded><![CDATA[<h3>Updated 9/28/09</h3>
<p>See the latest post in this series: <a href="http://colin.mollenhour.com/2009/09/yet-another-hiding-out-of-stock-products-update/">Yet Another “Hiding Out-Of-Stock Products” Update</a>, for an update that fixes advanced search, tags, up-sells, cross-sells, etc..</p>
<h3>Updated 7/23/09</h3>
<p>Updated once again to add support for filtering search results. (Added CatalogSearch/Model/Layer.php, moved Model/Layer.php to Catalog/Model/Layer.php, updated config.xml)</p>
<p>My previous post about <a href="http://colin.mollenhour.com/2009/06/hiding-out-of-stock-items-in-magento/">&#8220;Hiding Out of Stock Items in Magento&#8221;</a> had one pretty major downfall, in that it did not handle the product counts in the layered navigation. In response to a <a href="http://www.magentocommerce.com/boards/viewreply/156045/">forum post regarding this</a> I looked into fixing my original solution and have come up with a more concise and complete approach which I present now as a replacement to my original InStockOnly module.</p>
<p><span id="more-75"></span></p>
<p>Rather than observe an event we are going to simply overload the <code>Mage_Catalog_Model_Layer->prepareProductCollection</code> method which, as it turns out, serves as the basis for all of the catalog product collections including the product lists and the layered navigation product counts!</p>
<p>My module is called Lucky_InStockOnly for lack of a better name.</p>
<p>Here is the code for <code>app/code/local/Lucky/InStockOnly/Catalog/Model/Layer.php</code>:</p>
<pre class="brush: php;">
&lt;?php

class Lucky_InStockOnly_Catalog_Model_Layer extends Mage_Catalog_Model_Layer {

  /**
   * Add a statusInStock requirement for visibility
   */
  public function prepareProductCollection($collection){
    parent::prepareProductCollection($collection);
    $collection-&gt;joinField(
      'stock_status',
      'cataloginventory/stock_status',
      'stock_status',
      'product_id=entity_id', array(
        'stock_status' =&gt; Mage_CatalogInventory_Model_Stock_Status::STATUS_IN_STOCK,
        'website_id' =&gt; Mage::app()-&gt;getWebsite()-&gt;getWebsiteId(),
      )
    );
    return $this;
  }

}
</pre>
<p>We do the same thing for CatalogSearch so that search results also hide out of stock items. It turns out the API is consistent here and the code is identical except for the class names.</p>
<p><code>app/code/local/Lucky/InStockOnly/CatalogSearch/Model/Layer.php</code></p>
<pre class="brush: php;">
&lt;?php

class Lucky_InStockOnly_CatalogSearch_Model_Layer extends Mage_CatalogSearch_Model_Layer {

  /**
   * Add a statusInStock requirement for visibility
   */
  public function prepareProductCollection($collection){
    parent::prepareProductCollection($collection);
    $collection-&gt;joinField(
      'stock_status',
      'cataloginventory/stock_status',
      'stock_status',
      'product_id=entity_id', array(
        'stock_status' =&gt; Mage_CatalogInventory_Model_Stock_Status::STATUS_IN_STOCK,
        'website_id' =&gt; Mage::app()-&gt;getWebsite()-&gt;getWebsiteId(),
      )
    );
    return $this;
  }

}
</pre>
<p>Now we simply setup our module config file to overload the catalog classes of the same names:</p>
<p><code>app/code/local/Lucky/InStockOnly/etc/config.xml</code></p>
<pre class="brush: xml;">
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;config&gt;

&lt;global&gt;
  &lt;models&gt;

    &lt;catalog&gt;
      &lt;rewrite&gt;
        &lt;layer&gt;Lucky_InStockOnly_Catalog_Model_Layer&lt;/layer&gt;
      &lt;/rewrite&gt;
    &lt;/catalog&gt;
    &lt;catalogsearch&gt;
      &lt;rewrite&gt;
        &lt;layer&gt;Lucky_InStockOnly_CatalogSearch_Model_Layer&lt;/layer&gt;
      &lt;/rewrite&gt;
    &lt;/catalogsearch&gt;


  &lt;/models&gt;
&lt;/global&gt;

&lt;/config&gt;

</pre>
<p>And of course we need to enable the module (same as first version):</p>
<p><code>app/etc/modules/Lucky_InStockOnly.xml</code></p>
<pre class="brush: xml;">
&lt;config&gt;
  &lt;modules&gt;
    &lt;Lucky_InStockOnly&gt;
      &lt;active&gt;true&lt;/active&gt;
      &lt;codePool&gt;local&lt;/codePool&gt;
    &lt;/Lucky_InStockOnly&gt;
  &lt;/modules&gt;
&lt;/config&gt;
</pre>
<p>You will need to refresh your cache and possibly rebuild the catalog and layered navigation indicies, then you are done! All out of stock products should be hidden, and the counts in the layered navigation should correctly indicate the number of items that are in-stock.</p>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2009/07/14/hiding-out-of-stock-items-in-layered-navigation/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>Adding Minimal Price To Any Product Collection in Magento</title>
		<link>http://colin.mollenhour.com/2009/06/04/adding-minimal-price-to-any-product-collection-in-magento/</link>
		<comments>http://colin.mollenhour.com/2009/06/04/adding-minimal-price-to-any-product-collection-in-magento/#comments</comments>
		<pubDate>Thu, 04 Jun 2009 07:05:08 +0000</pubDate>
		<dc:creator>colin</dc:creator>
				<category><![CDATA[Magento]]></category>
		<category><![CDATA[php]]></category>

		<guid isPermaLink="false">http://colin.mollenhour.com/?p=65</guid>
		<description><![CDATA[There are many &#8220;Bestseller&#8220;, &#8220;Most Viewed&#8221;, and other front-page modules available for Magento. Those that use the Reports module to maintain good performance have the slight drawback that they do not inherit the catalog/product_collection resource model and it&#8217;s handy addMinimalPrices() function. This function adds the fields necessary for the getPriceHtml($product, TRUE) function to work, which [...]]]></description>
				<content:encoded><![CDATA[<p>There are many &#8220;<a href="http://inchoo.net/ecommerce/magento/bestseller-products-in-magento/">Bestseller</a>&#8220;, &#8220;Most Viewed&#8221;, and other front-page modules available for Magento. Those that use the Reports module to maintain good performance have the slight drawback that they do not inherit the <code>catalog/product_collection</code> resource model and it&#8217;s handy <code>addMinimalPrices()</code> function. This function adds the fields necessary for the <code>getPriceHtml($product, TRUE)</code> function to work, which prints out the &#8220;As low as&#8221; text for products that have tiered pricing. This is a must-have feature if you use tiered pricing, so here is how you can make <code>getPriceHTML($product, TRUE)</code> work with any collection of products:
</p>
<pre class="brush: php;">
$productIds = array_keys($_products-&gt;getItems());
$minimalPriceModel = Mage::getResourceModel('catalogindex/price');
$minimalPriceModel-&gt;setStoreId(Mage::app()-&gt;getStore()-&gt;getId());
$minimalPriceModel-&gt;setCustomerGroupId(
  Mage::getSingleton('customer/session')-&gt;getCustomerGroupId());
$minimalPrices = $minimalPriceModel-&gt;getMinimalPrices($productIds);
foreach ($minimalPrices as $row) {
  $item = $_products-&gt;getItemById($row['entity_id']);
  if ($item) {
    $item-&gt;setData('minimal_price', $row['value']);
    $item-&gt;setData('minimal_tax_class_id', $row['tax_class_id']);
  }
}
</pre>
<p>There you have it!</p>
]]></content:encoded>
			<wfw:commentRss>http://colin.mollenhour.com/2009/06/04/adding-minimal-price-to-any-product-collection-in-magento/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>
