Joomla’s Cache Is Deleted on Article Update: How to Fix

Note: This is an extremely advanced post that requires some serious knowledge with Joomla as well as more than average programming skills. If you feel that it’s a bit over your head then you should ask some Joomla experts to do it for you (like, ahem, us) since caching is a very delicate feature in Joomla.

A client of ours were (or should it be “was” instead of “were” despite the fact that we’re talking about a company? English grammar can be very tricky!) encountering very high load issues on their server only during working hours. They were using Joomla’s core (and not K2) for content management. After a very long investigation of the issue (at first we thought the whole issue was related to an unoptimized assets table, but we quickly ruled this out out as the assets table is not updated on article update), we narrowed down the problem to one word: caching – or lack of caching thereof.

Let us explain… They were using Conservative Caching on their website, and during working hours, their editors were frequently creating/updating articles on the website, and every time they created/updated an article the whole cache was cleared!

But why was the cache cleared?

That is the million dollar question, and the million dollar answer is that the method cleanCache in the ContentModelArticle class (which is defined in the file article.php file located under the administrator/components/com_content/models folder) is called when an article is inserted or updated. Here’s the function, in case you’re wondering how it looks like:

protected function cleanCache($group = null, $client_id = 0)
{
	parent::cleanCache('com_content');
	parent::cleanCache('mod_articles_archive');
	parent::cleanCache('mod_articles_categories');
	parent::cleanCache('mod_articles_category');
	parent::cleanCache('mod_articles_latest');
	parent::cleanCache('mod_articles_news');
	parent::cleanCache('mod_articles_popular');
}

As you can see, the function deletes all the content cache (the cache generated by the main content component and all the content modules). This function is invoked on every update and every insert. Obviously, on a large site relying on caching to sustain performance, that function can cause some serious load issues on the server that can potentially crash the site.

But why does the function clear all the content cache?

We have no idea. We think that this code is there because it was the easiest way to make sure that the site displays the most up-to-date content with as little code (and work [wink wink]) as possible. Unfortunately, the easiest way is far from being the optimal way. The optimal way consists of only deleting that cache pertinent to the updated article (and not the whole cache). We really wonder why nobody has ever complained about this before, since what this function does is not acceptable for any large Joomla website.

So how did we solve the problem?

Solving this problem was hard – like really really hard! We modified the function cleanCache to only delete the cache of the homepage, the updated article, and all the category blog pages for the category that this particular article belongs to. In the process of modifying the function, we created an auxiliary method called myMakeID that returns the ID of the cache to be deleted (the myMakeID method is a variation of the makeId static method on the JCache class which is defined in the cache.php file which is located under the libraries/joomla/cache folder). Here is the updated cleanCache method, as well as the auxiliary myMakeID method:

protected function cleanCache($group = null, $client_id = 0)
{
	//let's first get the Item ID of the category (which should be always in the POST)
	$catId = (int)$_POST['jform']['catid'];
	
	if (isset($_GET['id']))
		$id = (int)$_GET['id'];
	else
		$id = 0;
	
	$db = JFactory::getDbo();
	
	//now let's get the Item ID for that category
	$sql = "SELECT id FROM #__menu WHERE `link` ='index.php?option=com_content&view=category&layout=blog&id=".$catId."'";
	$db->setQuery($sql);
	$itemId = $db->loadResult();
	
	//now let's get the Item ID for the homepage
	$sql = "SELECT id FROM #__menu WHERE `home` =1";
	$db->setQuery($sql);
	$homeItemId = $db->loadResult();
	
	
	$conf         = JFactory::getConfig();
        $platformDirs = FOFPlatform::getInstance()->getPlatformBaseDirs();

	$options = array(
		'defaultgroup' => ($group) ? $group : (isset($this->option) ? $this->option : JFactory::getApplication()->input->get('option')),
		'cachebase'    => ($client_id) ? $platformDirs['admin'] . '/cache' : $conf->get('cache_path', $platformDirs['public'] . '/cache'));

	$cache = JCache::getInstance('callback', $options);
	
	//first delete the cache for this particular article
	if (!empty($id) && !empty($catId)){
		$makeID = $this->myMakeID($id, $catId, $itemId);
		$cache->remove($makeID, 'com_content');
		$cache->remove($makeID, 'mobile-com_content');
	}
	if (!empty($catId)){
		$makeID = $this->myMakeID($catId, NULL, $itemId, 'ContentViewCategory', 'category', NULL, 'blog');
		$cache->remove($makeID, 'com_content');
		$cache->remove($makeID, 'mobile-com_content');
	}
	if (!empty($homeItemId)){
		$makeID = $this->myMakeID(NULL, NULL, $homeItemId, 'ContentViewFeatured', 'featured', NULL, NULL);
		$cache->remove($makeID, 'com_content');
		$cache->remove($makeID, 'mobile-com_content');
	}
	
}

protected function myMakeID($id, $catId, $itemId, $classView = 'ContentViewArticle', $view='article', $format = 'html', $layout = NULL)
{
	$safeuriaddon = new stdClass;
	if (!is_null($catId))
		$safeuriaddon->catid= (int)$catId;
	else
		$safeuriaddon->catid= NULL;
	if (!is_null($id))
		$safeuriaddon->id= (int)$id;
	else
		$safeuriaddon->id= NULL;
		
	$safeuriaddon->cid= NULL;
	$safeuriaddon->year= NULL;
	$safeuriaddon->month= NULL;
	$safeuriaddon->limit= NULL;
	$safeuriaddon->limitstart= NULL;
	$safeuriaddon->showall= NULL;
	$safeuriaddon->return= NULL;
	$safeuriaddon->filter= NULL;
	$safeuriaddon->filter_order= NULL;
	$safeuriaddon->filter_order_Dir= NULL;
	$safeuriaddon->{'filter-search'} = NULL;
	$safeuriaddon->print= NULL;
	$safeuriaddon->lang= NULL;
	$safeuriaddon->Itemid= (int)$itemId;
	$safeuriaddon->format= $format;
	$safeuriaddon->option= 'com_content';
	$safeuriaddon->view= $view;
	$safeuriaddon->layout= $layout;
	$safeuriaddon->tpl= NULL;
	return md5(serialize(array(md5(serialize($safeuriaddon)), $classView, 'display')));
}

Adding the above code to the aforementioned article.php model file will solve the problem, but keep in mind that this is a core modification (which means future updates to the site may wipe out your changes). If you’re not at ease doing this modification yourself then please contact us. We can do it for you in as little time as possible, for a very affordable fee, and with never-seen-before (even on TV during the 1990’s) enthusiasm mixed with a healthy dose of humor!

No comments yet.

Leave a comment