Using Content History in your Component

From Joomla! Documentation

In version 3.2, Joomla added the ability to track content history (also called versions) for all core components. This allows you to view and restore from prior versions of an article, banner, category, and other content types. This feature can easily be added to third-party components. This tutorial shows you how to do this. We will illustrate this by adding content history to an example component.

Important Note: This tutorial assumes that the component uses a JTable sub-class for its "CRUD" (create/update/delete) operations. If the component does not use JTable then the simplest thing to do is to re-work it to use JTable's store() and delete() methods. If you do that, you will be able to use the methods described in this tutorial.

Set up Working Environment[edit]

  1. Install a new instance of Joomla version 3.2 on your local workstation.
  2. Install the example component using the file in the Extension Manager: Install screen. You can get the ZIP archive here: <to be filled in when available>.

At this point, you should be able to see the Joompro Subscriptions component in the Components menu in the back end of Joomla. This is the component before we have added content history to it.

Add Rows to Content Types Table[edit]

The first thing we need to do is to add two new rows into the #__content_types table. This table stores information about the different tables for each content type. As of Joomla version 3.2, this table is used by the com_tags and com_contenthistory components to get information about each content type for which history is stored. The columns for #__content_types are as follows:

  • type_id: auto-increment id number.
  • type_title: descriptive title for this table.
  • type_alias: <component name>.<type name>. For example: "com_content.article" or "com_content.category".
  • table: JSON string that contains the name of the JTable class and other information about the table.
  • rules: Not used as of Joomla version 3.2.
  • field_mappings: Used by the com_tags component to map database columns from the component table to the ucm_content table.
  • router: Optional location of the component's router, if any.
  • content_history_options: JSON string used to store information for rendering the pop-up windows in the content history component. We will discuss this in detail later in this tutorial.

Our example component uses a table called #__joompro_subscriptions to store each subscription. In addition, it uses the standard Joomla categories. So we will add a row in #__content_types for the category table and one for the component table.

The row for the categories can be copied from the row for "Weblinks Category" (or any other category row). The only columns that need to be changed are type_title and type_alias. The other columns are the same as for com_weblinks.category.

The SQL statement for adding this row is as follows:

INSERT INTO `#__content_types` (`type_id`, `type_title`, `type_alias`, `table`, `rules`, `field_mappings`, `router`, `content_history_options`) VALUES (null, 'Subscription Category', 'com_joomprosubs.category', '{"special":{"dbtable":"#__categories","key":"id","type":"Category","prefix":"JTable","config":"array()"},"common": {"dbtable":"#__ucm_content","key":"ucm_id","type":"Corecontent","prefix":"JTable","config":"array()"}}', , '{"common":{"core_content_item_id":"id","core_title":"title","core_state":"published","core_alias":"alias","core_created_time":"created_time","core_modified_time":"modified_time","core_body":"description", "core_hits":"hits","core_publish_up":"null","core_publish_down":"null","core_access":"access", "core_params":"params", "core_featured":"null", "core_metadata":"metadata", "core_language":"language", "core_images":"null", "core_urls":"null", "core_version":"version", "core_ordering":"null", "core_metakey":"metakey", "core_metadesc":"metadesc", "core_catid":"parent_id", "core_xreference":"null", "asset_id":"asset_id"}, "special":{"parent_id":"parent_id","lft":"lft","rgt":"rgt","level":"level","path":"path","extension":"extension","note":"note"}}', 'WeblinksHelperRoute::getCategoryRoute', '{"formFile":"administrator\\/components\\/com_categories\\/models\\/forms\\/category.xml", "hideFields":["asset_id","checked_out","checked_out_time","version","lft","rgt","level","path","extension"], "ignoreChanges":["modified_user_id", "modified_time", "checked_out", "checked_out_time", "version", "hits", "path"],"convertToInt":["publish_up", "publish_down"], "displayLookup":[{"sourceColumn":"created_user_id","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},{"sourceColumn":"access","targetTable":"#__viewlevels","targetColumn":"id","displayColumn":"title"},{"sourceColumn":"modified_user_id","targetTable":"#__users","targetColumn":"id","displayColumn":"name"},{"sourceColumn":"parent_id","targetTable":"#__categories","targetColumn":"id","displayColumn":"title"}]}');

For the actual subscriptions, we only need the following information in the content types table:

  • type_title: Subscriptions
  • type_alias: com_joomprosubs.subscription
  • table: {"special":{"dbtable":"#__joompro_subscriptions","key":"id","type":"Subscription","prefix":"JoomprosubsTable"}}. This creates a JSON object as follows:
dbtable = #__joompro_subscriptions
key = id
type = Subscription
prefix = JoomprosubsTable

The type_alias column is used by the com_contenthistory component to find the row for each component in the #__content_types table. The table column gives the com_contenthistory component the information it needs to work with the JTable class for each component. The None of the other columns need to be set for now. Note that we will come back to the content_history_options column later in the tutorial to improve the appearance of the pop-up window.

The SQL for adding this second row to the #__content_types table is as follows:

INSERT INTO `#__content_types` (`type_id`, `type_title`, `type_alias`, `table`, `rules`, `field_mappings`, `router`, `content_history_options`) VALUES (null, 'Subscriptions', 'com_joomprosubs.subscription', '{"special":{"dbtable":"#__joompro_subscriptions","key":"id","type":"Subscription","prefix":"JoomprosubsTable"}}', , , , );

These lines should be added to the file administrator/components/com_joomprosubs/sql/install.mysql.utf8.sql so that these rows will be created when the component is installed. You should also run these commands on your database to add the two rows to your #__content_types table before going to the next section. Remember to change the placeholder prefix "#__" to the actual prefix for your database before running the SQL commands on your local database.

Tip: When creating the values for the JSON strings, one trick is to copy from an existing row and edit the strings.

Add Component Level Options[edit]

The content history component uses two options as follows.

  • save_history: If Yes, history is saved. Otherwise, no history is saved.
  • history_limit: If > 0, limits the number of different history versions saved in the database. For example, if this is set to 10, then when the 11th history version is saved, the oldest is deleted automatically.

If our example component, we will add the following lines to the file administrator/components/com_joomprosubs/config.xml.

<fieldset name="component">
		class="btn-group btn-group-yesno"

With this code, we will now be able to add and save these options. Go into the Options for the component and set save_history to Yes and history_limit to 10.

Update Content History During Table Save and Delete[edit]

Next we need to tell our component to call the content history methods during the save and delete operations. We can do this by adding this line of code to the __construct() method of the JoomprosubsTableSubscription class.

JObserverMapper::addObserverClassToClass('JTableObserverContenthistory', 'JoomprosubsTableSubscription', array('typeAlias' => 'com_joomprosubs.subscription'));

This code registers the content history table as an observer class for our component table. When a row is saved or deleted from our table, the appropriate methods are called automatically for the content history table.

At this point, we should be able to see content history working for the Joomprosubs categories. Test this by adding a new category for the component and then clicking on the Versions button in the toolbar. It should work exactly the same as categories for the core Joomla components.

Add Versions Button to Tool Bar[edit]

At this point, categories are working completely and we are saving history versions in the #__ucm_history table for our component. However, we need to be able to access these changes on our edit screen. To do this, we need to add the Versions button to the edit screen's toolbar. We do this by adding this code to the file administrator/components/com_joomprosubs/views/subscription/view.html.php, just before the code that adds the "cancel" button, as shown here.

else {
  if ($this->state->params->get('save_history', 1) && $user->authorise('core.edit')) {
    JToolbarHelper::versions('com_joomprosubs.subscription', $this->item->id);
  JToolBarHelper::cancel('subscription.cancel', 'JTOOLBAR_CLOSE');

This code checks that we have the save_history option set and that we have edit permission for this component. If so, we show the Versions button using the JToolbarHelper::versions() method. This method has two arguments: the type_alias and the primary key of the component item row in the database.

With this code added, we can now go into the Joomprosubs component, add and save a new subscription, and now see the Versions button. If we click on it, we will see the Item Version History modal window open with our saved version.

There are still two things missing, however. One is that we don't have a way to add the Version Note to each version. The second is that we don't currently have meaningful labels for the versions when we use the Preview or Compare features. We will address these in the next two sections of this tutorial.

Add Version Note Field to Form[edit]

Add Labels to Pop-Up Windows[edit]

Update Extension SQL Files[edit]