Fuente: http://docs.joomla.org/Adding_custom_fields_to_the_article_component

Introducción

Este plugin muestra cómo un desarrollador de componentes puede utilizar las características de Joomla 2.5 para añadir campos personalizados en el componente artículo (com_content), sin la necesidad de cambiar los archivos principales. Las capacidades que se implementan en este paquete de demostración incluyen:

  • Añadir campos adicionales al administrador de artículos desde el backend.
  • Añadir campos adicionales al editor de artículos en el front-end.
  • Almacenamiento de los valores de los campos personalizados en una tabla de la base de datos.
  • Conversión de los valores de los campo en una tabla HTML que se inyecta en el artículo mostrado.
  • Una capacidad que está presente pero no se demuestra, es la integración de los valores de los campos personalizados en otros componentes y vistas, tales como listas de artículos o blog de categorías.

Este ejemplo

Este ejemplo agrega valoraciones de comida a los artículos. Estas valoraciones consisten en la textura, temperatura y sabor. Cuando están presentes, se muestran como una tabla simple. Esta tabla se mostrará antes del contenido del artículo.

Plg_content_rating-1

Un ejemplo de cómo el plugin coloca la tabla al comienzo de un artículo. El ajuste de los valores de estos campos se puede realizar tanto desde el backend como desde el frontend.

Plg_content_rating-frontend

Acceso a los campos adicionales desde el frontend

Plg_content_rating-backend

… y el backend

Framework

Within Joomla, the article manager is basically a form driven component. Both in the backend as the frontend. When handling a form, the framework performs the following actions.

  1. Loads the form description. This is a XML structure.
  2. Loads the data from the user state if present, and if not from the database.
  3. Injects the data into the form
  4. Renders/displays the form for the user to work on.

When the user is finished and issues a SAVE, the framework will:

  1. Captures the submitted data
  2. Loads the form description.
  3. Validates the data against the form description.
  4. If validation fails, stores the data in the user state and starts from the beginning.
  5. Updates the database accordingly

The framework has placeholders for all these moments. This example plugin will hook into these placeholders, expanding the form and data with additional custom fields.

The hooks are:

onContentPrepareForm()
Called after loading the form description. It will add an extra form group containing the additional fields
onContentPrepareData()
Called after loading the article from the database. It will inject the additional fields and their value into the article.
onContentAfterSave()
Called after an article has been successfully stored in database. It will store the additional fields in their designated database table.
onContentAfterDelete()
Called after an article has been deleted (emptying of trash). It will delete the additional fields.
onContentPrepare()
Called before the article will be rendered/displayed. It will inject the rating values as a table at the beginning of the article, if values have been entered.

Database

The contents of the extra custom fields need to be stored in the database. Ideally would be if they have their own dedicated database table. This however, requires considerable extension overhead. With Joomla 2.5 is is possible to extend the user profile. The extra fields are stored in the table #__user_profiles. That table is constructed and used in such a manner that it can also hold the extra article fields. [I would like to spark the discussion to evolve #__user_profiles into a generic table for expanding core tables.]

This plugin example will use table #__user_profiles to store the extra fields. It will use the tables user_id as article_id

Extension files

The example plugin contains the following files. It has an extra directory rating. The primary reason is that it contains a XML file that is not a manifest. If it were located in the plugin top-level directory, the installer would find it and mistakenly identify it as an orphaned plugin. This would result in the form showing up when ‘Discovering’ lost extensions.

── plugins/content/rating
   ├── language
   │   └── en-GB
   │       ├── en-GB.plg_content_rating.ini      [language file]
   │       └── en-GB.plg_content_rating.sys.ini  [language file]
   ├── rating
   │   ├── rating.css  [CSS for the rendered rating table]
   │   └── rating.xml  [Form description]
   ├── rating.php   [Plugin hooks]
   └── rating.xml   [Manifest]

Manifest

A regular plugin manifest which takes an extra configuration argument, a CSS name that is tagged onto the rendered rating table.

<?xml version="1.0" encoding="utf-8"?>
   <extension version="2.5" type="plugin" group="content" method="upgrade">
      <name>plg_content_rating</name>
      <author>Joomla! Project</author>
      <creationDate>June 2012</creationDate>
      <copyright>(C) 2005 - 2012 Open Source Matters. All rights reserved.</copyright>
      <license>GNU General Public License version 2 or later; see LICENSE.txt</license>
      <authorEmail>admin@joomla.org</authorEmail>
      <authorUrl>www.joomla.org</authorUrl>
      <version>2.5.0</version>
      <description>PLG_CONTENT_RATING_XML_DESCRIPTION</description>
 
      <files>
         <folder>language</folder>
         <folder>rating</folder>
         <filename plugin="rating">rating.php</filename>
         <filename>index.html</filename>
      </files>
 
      <config>
         <fields name="params">
            <fieldset name="basic">
               <field
                  name="ratingclass_sfx"
                  type="text"
                  label="PLG_CONTENT_RATING_ITEM_FIELD_RATING_CLASS_LABEL"
                  description="PLG_CONTENT_RATING_ITEM_FIELD_RATING_CLASS_DESC"
               />
            </fieldset>
         </fields>
      </config>
   </extension>

language files

; Joomla! Project
; Copyright (C) 2005 - 2012 Open Source Matters. All rights reserved.
; License GNU General Public License version 2 or later; see LICENSE.txt, see LICENSE.php
 
PLG_CONTENT_RATING="Content - Rating"
PLG_CONTENT_RATING_XML_DESCRIPTION="[plg_content_rating] Example plugin on how to add custom fields (texture/temperature/taste) to articles (com_content). Adds ratings to an article, and display them as a table before the article content."
PLG_CONTENT_RATING_SLIDER_LABEL="Rating Options"
PLG_CONTENT_RATING_FIELD_TEXTURE_LABEL="Texture"
PLG_CONTENT_RATING_FIELD_TEXTURE_DESC="What does the sample feel like"
PLG_CONTENT_RATING_FIELD_TEMPERATURE_LABEL="Temperature"
PLG_CONTENT_RATING_FIELD_TEMPERATURE_DESC="What is the temperature of the sample"
PLG_CONTENT_RATING_FIELD_TASTE_LABEL="Taste"
PLG_CONTENT_RATING_FIELD_TASTE_DESC="How does the sample taste"
PLG_CONTENT_RATING_ITEM_FIELD_RATING_CLASS_LABEL="Rating Class"
PLG_CONTENT_RATING_ITEM_FIELD_RATING_CLASS_DESC="Optional CSS class to add to the rating. This allows CSS styling specific to the page."

CSS

The CSS file for the rendered table.

div.rating {
    display: block;
    float: left;
    padding-right: 10px;
}
div.rating table tr td {
    margin: 1px !important;
    padding: 2px !important;
}
div.rating table tr.row0  {
    background-color:#efefef;
}
div.rating table tr.row1 {
    background-color:#ffffff;
}

Form description

The form containing the custom fields.

<?xml version="1.0" encoding="utf-8"?>
<form>
	<fields name="rating">
		<fieldset name="rating" label="PLG_CONTENT_RATING_SLIDER_LABEL">
			<field
            name="texture"
            type="text"
            id="texture"
            description="PLG_CONTENT_RATING_FIELD_TEXTURE_DESC"
            label="PLG_CONTENT_RATING_FIELD_TEXTURE_LABEL"
            message="PLG_CONTENT_RATING_FIELD_TEXTURE_MESSAGE"
            size="30"
            />
            <field
            name="temperature"
            type="text"
            id="temperature"
            description="PLG_CONTENT_RATING_FIELD_TEMPERATURE_DESC"
            label="PLG_CONTENT_RATING_FIELD_TEMPERATURE_LABEL"
            message="PLG_CONTENT_RATING_FIELD_TEMPERATURE_MESSAGE"
            size="30"
            />
            <field
            name="taste"
            type="text"
            id="taste"
            description="PLG_CONTENT_RATING_FIELD_TASTE_DESC"
            label="PLG_CONTENT_RATING_FIELD_TASTE_LABEL"
            message="PLG_CONTENT_RATING_FIELD_TASTE_MESSAGE"
            size="30"
            />
		</fieldset>
	</fields>
</form>

Plugin hooks

The main file ‘rating.php’ contains the hooks which are called by the Joomla framework.

For all calls:

  • The $context parameter is a string identifying the calling placeholder.
  • The $data parameter is an object containing the form values
  • The $article parameter is an object (JTableContent) representing the article.

__construct

The constructor. It loads the language file.

public function __construct(& $subject, $config)
{
	parent::__construct($subject, $config);
	$this->loadLanguage();
}

onContentPrepareForm

onContentPrepareForm extends the article manager form in both the front-end as the back-end. It loads the form fragment as described in rating/rating.xml and merges that into the framework form.

public function onContentPrepareForm($form, $data)
{
	if (!($form instanceof JForm))
	{
		$this->_subject->setError('JERROR_NOT_A_FORM');
		return false;
	}
 
	// Add the extra fields to the form.
	JForm::addFormPath(dirname(__FILE__) . '/rating');
	$form->loadFile('rating', false);
	return true;
}

onContentPrepareData

onContentPrepareData() is executed after an article is loaded from the #__content table. It will gather and merge the values of the custom fields, which are located in the table #__user_profiles.

The field articleId in $data contains the article record number. That number is used to retrieve the values of the custom fields. If no record number is present, or it is zero, then a new record is being create. When this occurs, $data gets populated with the default values from the form description xml.

It is important that when onContentPrepareData() returns, $data contains the custom fields. This is due to how the framework uses them as place holders. When a user saves a newly created article, and an error situation occurs (like some values are missing) the form data is temporarily stored in the user state. If the place holders are not present, their values will not be preserved. The user will notice this because the custom fields will get erased.

public function onContentPrepareData($context, $data)
{
	if (is_object($data))
	{
		$articleId = isset($data->id) ? $data->id : 0;
		if ($articleId > 0)
		{
			// Load the data from the database.
			$db = JFactory::getDbo();
			$query = $db->getQuery(true);
			$query->select('profile_key, profile_value');
			$query->from('#__user_profiles');
			$query->where('user_id = ' . $db->Quote($articleId));
			$query->where('profile_key LIKE ' . $db->Quote('rating.%'));
			$query->order('ordering');
			$db->setQuery($query);
			$results = $db->loadRowList();
 
			// Check for a database error.
			if ($db->getErrorNum())
			{
				$this->_subject->setError($db->getErrorMsg());
				return false;
			}
 
			// Merge the data.
			$data->rating = array();
 
			foreach ($results as $v)
			{
				$k = str_replace('rating.', '', $v[0]);
				$data->rating[$k] = json_decode($v[1], true);
				if ($data->rating[$k] === null)
				{
					$data->rating[$k] = $v[1];
				}
			}
		} else {
			// load the form
			JForm::addFormPath(dirname(__FILE__) . '/rating');
			$form = new JForm('com_content.article');
			$form->loadFile('rating', false);
 
			// Merge the default values
			$data->rating = array();
			foreach ($form->getFieldset('rating') as $field) {
				$data->rating[] = array($field->fieldname, $field->value);
			}
		}
	}
 
	return true;
}

onContentAfterSave

onContentAfterSave() is called after the article is stored into the database #__content table. It will extract the values of the custom fields and store them into a separate table.

public function onContentAfterSave($context, &$article, $isNew)
{
	$articleId = $article->id;
	if ($articleId && isset($article->rating) && (count($article->rating)))
	{
		try
		{
			$db = JFactory::getDbo();
 
			$query = $db->getQuery(true);
			$query->delete('#__user_profiles');
			$query->where('user_id = ' . $db->Quote($articleId));
			$query->where('profile_key LIKE ' . $db->Quote('rating.%'));
			$db->setQuery($query);
			if (!$db->query()) {
				throw new Exception($db->getErrorMsg());
			}
 
			$query->clear();
			$query->insert('#__user_profiles');
			$order = 1;
			foreach ($article->rating as $k => $v)
			{
				$query->values($articleId.', '.$db->quote('rating.'.$k).', '.$db->quote(json_encode($v)).', '.$order++);
			}
			$db->setQuery($query);
 
			if (!$db->query()) {
				throw new Exception($db->getErrorMsg());
			}
		}
		catch (JException $e)
		{
			$this->_subject->setError($e->getMessage());
			return false;
		}
	}
 
	return true;
}

onContentAfterDelete

onContentAfterDelete() is called after the article is deleted from the database #__content table. It will delete the linked custom fields which are stored in a separate table.

public function onContentAfterDelete($context, $article)
{
	$articleId = $article->id;
	if ($articleId)
	{
		try
		{
			$db = JFactory::getDbo();
 
			$query = $db->getQuery(true);
			$query->delete();
			$query->from('#__user_profiles');
			$query->where('user_id = ' . $db->Quote($articleId));
			$query->where('profile_key LIKE ' . $db->Quote('rating.%'));
			$db->setQuery($query);
 
			if (!$db->query())
			{
				throw new Exception($db->getErrorMsg());
			}
		}
		catch (JException $e)
		{
			$this->_subject->setError($e->getMessage());
			return false;
		}
	}
 
	return true;
}

onContentPrepare

onContentPrepare() is called when the article is being prepared for display. This is the moment where HTML can be injected into what will be displayed.

$params are the article params, $this->params are the plugin configuration parameters as described in the manifest.

public function onContentPrepare($context, &$article, &$params, $page = 0)
{
	if (!isset($article->rating) || !count($article->rating))
	return;
 
	// add extra css for table
	$doc = JFactory::getDocument();
	$doc->addStyleSheet(JURI::base(true).'/plugins/content/rating/rating/rating.css');
 
	// construct a result table on the fly   
	jimport('joomla.html.grid');
	$table = new JGrid();
 
	// Create columns
	$table->addColumn('attr')
	->addColumn('value');   
 
	// populate
	$rownr = 0;
	foreach ($article->rating as $attr => $value) {
		$table->addRow(array('class' => 'row'.($rownr % 2)));
		$table->setRowCell('attr', $attr);
		$table->setRowCell('value', $value);
		$rownr++;
	}
 
	// wrap table in a classed <div>
	$suffix = $this->params->get('ratingclass_sfx', 'rating');
	$html = '<div class="'.$suffix.'">'.(string)$table.'</div>';
 
	$article->text = $html.$article->text;
}