<?php

/**
 * GxActiveRecord class file.
 *
 * @author Rodrigo Coelho <rodrigo@giix.org>
 * @link http://giix.org/
 * @copyright Copyright &copy; 2010-2011 Rodrigo Coelho
 * @license http://giix.org/license/ New BSD License
 */

/**
 * GxActiveRecord is the base class for the generated AR (base) models.
 *
 * @author Rodrigo Coelho <rodrigo@giix.org>
 */
abstract class GxActiveRecord extends CActiveRecord {

	/**
	 * @var string the separator used to separate the primary keys values in a
	 * composite pk table. Usually a character.
	 */
	public static $pkSeparator = '-';

	public static function model($className=__CLASS__) {
		return parent::model($className);
	}

	/**
	 * This method should be overridden to declare related pivot models for each MANY_MANY relationship.
	 * The pivot model is used by {@link saveWithRelated}.
	 * @return array List of pivot models for each MANY_MANY relationship. Defaults to empty array.
	 */
	public function pivotModels() {
		return array();
	}

	/**
	 * The active record label.
	 * The active record label is the user friendly name displayed in the views.
	 * Each active record class should override this method and explicitly specify the label.
	 * See the documentation when overriding: http://www.yiiframework.com/doc/guide/1.1/en/topics.i18n#plural-forms-format
	 * @param integer $n The number value. This is used to support plurals. Defaults to 1 (means singular).
	 * Notice that this number doesn't necessarily corresponds to the number (count) of items.
	 * @return string The label.
	 * @throws Exception If the method wasn't overriden.
	 * @see getRelationLabel
	 */
	public static function label($n = 1) {
		throw new Exception(Yii::t('giix', 'This method should be overriden by the Active Record class.'));
	}

	/**
	 * Returns the text label for the specified active record relation, attribute or class property.
	 * The labels are the user friendly names displayed in the views.
	 * If defined in the model, the label for its attribute, property or relation is returned.
	 * If not defined in the model (in {@link CModel::attributeLabels}),
	 * the label is generated using the related active record class label (via {@link GxActiveRecord::label}) (for FK attributes and relations)
	 * or using {@link CModel::generateAttributeLabel} (for other attributes and class properties).
	 * @param string $relationName The relation, attribute or class property name.
	 * This method supports chained relations in the form of "post.author.name".
	 * @param integer $n The number value. This is used to support plurals.
	 * In the default implementation, when this argument is null, if the relation is BELONGS_TO or HAS_ONE, the singular form is returned.
	 * If the relation is HAS_MANY or MANY_MANY, the plural form is returned.
	 * If this argument is null and the relation is not one of the types listed above, the singular form is returned.
	 * For most languages, 1 means singular and all other values mean plural.
	 * Defaults to null.
	 * Note: It is not supported when returning labels for attributes or class properties.
	 * @param boolean $useRelationLabel Whether to use the relation label for the FK attribute.
	 * When true, if the specified attribute name is a FK, the corresponding related AR label will be used.
	 * Defaults to true.
	 * Note: this will only work when there is no label defined in {@link CModel::attributeLabels} for this attribute.
	 * @return string The label.
	 * @throws InvalidArgumentException If an attribute name is found and is not the last item in the relationName parameter.
	 * @see label
	 */
	public function getRelationLabel($relationName, $n = null, $useRelationLabel = true) {
		// Exploding the chained relation names.
		$relNames = explode('.', $relationName);

		// Everything starts with this object.
		$relClassName = get_class($this);

		// The item index.
		$relIndex = 0;

		// Get the count of relation names;
		$countRelNames = count($relNames);

		// Walk through the chained relations.
		foreach ($relNames as $relName) {
			// Increments the item index.
			$relIndex++;

			// Get the related static class.
			$relStaticClass = self::model($relClassName);

			// If is is the last name and the label is explicitly defined, return it.
			if ($relIndex === $countRelNames) {
				$labels = $relStaticClass->attributeLabels();
				if (isset($labels[$relName]))
					return $labels[$relName];
			}

			// Get the relations for the current class.
			$relations = $relStaticClass->relations();

			// Check if there is (not) a relation with the current name.
			if (!isset($relations[$relName])) {
				// There is no relation with the current name. It is an attribute or a property.
				// It must be the last name.
				if ($relIndex === $countRelNames) {
					// Check if it is an attribute.
					$attributeNames = $relStaticClass->attributeNames();
					$isAttribute = in_array($relName, $attributeNames);
					// If it is an attribute and the attribute is a FK and $useRelationLabel is true, return the related AR label.
					if ($isAttribute && $useRelationLabel && (($relData = self::findRelation($relStaticClass, $relName)) !== null)) {
						// This will always be a BELONGS_TO, then singular.
						return self::model($relData[3])->label(1);
					} else {
						// There's no label for this attribute or property, generate one.
						return $relStaticClass->generateAttributeLabel($relName);
					}
				} else {
					// It is not the last item.
					throw new InvalidArgumentException(Yii::t('giix', 'The attribute "{attribute}" should be the last name.', array('{attribute}' => $relName)));
				}
			}

			// Change the current class name: walk to the next relation.
			$relClassName = $relations[$relName][1];
		}

		// Automatically apply the correct number if requested.
		if ($n === null) {
			// Get the type of the last relation from the last but one class.
			$relType = $relations[end($relNames)][0];

			switch ($relType) {
				case self::HAS_MANY:
				case self::MANY_MANY:
					$n = 2;
					break;
				case self::BELONGS_TO:
				case self::HAS_ONE:
				default :
					$n = 1;
			}
		}

		// Get and return the label from the related AR.
		return self::model($relClassName)->label($n);
	}

	/**
	 * Returns the text label for the specified attribute.
	 * Also supported: relations and chained relations in the form of "post.author.name".
	 * This method just calls {@link getRelationLabel}.
	 * @param string $attribute The attribute name.
	 * @return string The attribute label.
	 * @see CActiveRecord::getAttributeLabel
	 * @see getRelationLabel
	 */
	public function getAttributeLabel($attribute) {
		return $this->getRelationLabel($attribute);
	}

	/**
	 * The specified column(s) is(are) the responsible for the
	 * string representation of the model instance.
	 * The column is used in the {@link __toString} default implementation.
	 * Every model must specify the attributes used to build their
	 * string representation by overriding this method.
	 * This method must be overriden in each model class
	 * that extends this class.
	 * @return string|array The name of the representing column for the table (string) or
	 * the names of the representing columns (array).
	 * @see __toString
	 */
	public static function representingColumn() {
		return null;
	}

	/**
	 * Returns a string representation of the model instance, based on
	 * {@link representingColumn}.
	 * If the representing column is not set, the primary key will be used.
	 * If there is no primary key, the first field will be used.
	 * When you overwrite this method, all model attributes used to build
	 * the string representation of the model must be specified in
	 * {@link representingColumn}.
	 * @return string The string representation for the model instance.
	 * @see representingColumn
	 */
	public function __toString() {
		$representingColumn = $this->representingColumn();

		if (($representingColumn === null) || ($representingColumn === array()))
			if ($this->getTableSchema()->primaryKey !== null) {
				$representingColumn = $this->getTableSchema()->primaryKey;
			} else {
				$columnNames = $this->getTableSchema()->getColumnNames();
				$representingColumn = $columnNames[0];
			}

		if (is_array($representingColumn)) {
			$part = '';
			foreach ($representingColumn as $representingColumn_item) {
				$part .= ( $this->$representingColumn_item === null ? '' : $this->$representingColumn_item) . '-';
			}
			return substr($part, 0, -1);
		} else {
			return $this->$representingColumn === null ? '' : (string) $this->$representingColumn;
		}
	}

	/**
	 * Finds all active records satisfying the specified condition, selecting only the requested
	 * attributes and, if specified, the primary keys.
	 * See {@link CActiveRecord::find} for detailed explanation about $condition and $params.
	 * #MethodTracker
	 * This method is based on {@link CActiveRecord::findAll}, from version 1.1.7 (r3135). Changes:
	 * <ul>
	 * <li>Selects only the specified attributes.</li>
	 * <li>Detects and selects the representing column.</li>
	 * <li>Detects and selects the PK attribute.</li>
	 * </ul>
	 * @param string|array $attributes The names of the attributes to be selected.
	 * Optional. If not specified, the {@link representingColumn} will be used.
	 * @param boolean $withPk Specifies if the primary keys will be selected.
	 * @param mixed $condition Query condition or criteria.
	 * @param array $params Parameters to be bound to an SQL statement.
	 * @return array List of active records satisfying the specified condition. An empty array is returned if none is found.
	 */
	public function findAllAttributes($attributes = null, $withPk = false, $condition='', $params=array()) {
		$criteria = $this->getCommandBuilder()->createCriteria($condition, $params);
		if ($attributes === null)
			$attributes = $this->representingColumn();
		if ($withPk) {
			$pks = self::model(get_class($this))->getTableSchema()->primaryKey;
			if (!is_array($pks))
				$pks = array($pks);
			if (!is_array($attributes))
				$attributes = array($attributes);
			$attributes = array_merge($pks, $attributes);
		}
		$criteria->select = $attributes;
		return parent::findAll($criteria);
	}

	/**
	 * Extracts and returns only the primary keys values from each model.
	 * @param GxActiveRecord|array $model A model or an array of models.
	 * @param boolean $forceString Whether pk values on composite pk tables
	 * should be compressed into a string. The values on the string will by
	 * separated by {@link pkSeparator}.
	 * @return string|array The pk value as a string (for single pk tables) or
	 * array (for composite pk tables) if one model was specified or
	 * an array of strings or arrays if multiple models were specified.
	 */
	public static function extractPkValue($model, $forceString = false) {
		if ($model === null)
			return null;
		if (!is_array($model)) {
			$pk = $model->getPrimaryKey();
			if ($forceString && is_array($pk))
				$pk = implode(self::$pkSeparator, $pk);
			return $pk;
		} else {
			$pks = array();
			foreach ($model as $model_item) {
				$pks[] = self::extractPkValue($model_item, $forceString);
			}
			return $pks;
		}
	}

	/**
	 * Fills the provided array of PK values with the composite PK column names.
	 * Warning: the order of the values in the array must match the order of
	 * the columns in the composite PK.
	 * The returned array has the format required by {@link CActiveRecord::findByPk}
	 * for composite keys.
	 * The method supports single PK also.
	 * @param mixed $pk The PK value or array of PK values.
	 * @return array The array of PK values, indexed by column name.
	 * @see CActiveRecord::findByPk
	 * @throws InvalidArgumentException If the count of values doesn't match the
	 * count of columns in the composite PK.
	 */
	public function fillPkColumnNames($pk) {
		// Get the table PK column names.
		$columnNames = $this->getTableSchema()->primaryKey;

		// Check if the count of values and columns match.
		$columnCount = count($columnNames);
		if (count($pk) !== $columnCount)
			throw new InvalidArgumentException(Yii::t('giix', 'The count of values in the argument "pk" ({countPk}) does not match the count of columns in the composite PK ({countColumns}).'), array(
				'{countPk}' => count($pk),
				'{countColumns}' => $columnCount,
			));

		// Build the array indexed by the column names.
		if ($columnCount === 1) {
			if (is_array($pk))
				$pk = $pk[0];
			return array($columnNames => $pk);
		} else {
			$result = array();
			for ($columnIndex = 0; $columnIndex < $columnCount; $columnIndex++) {
				$result[$columnNames[$columnIndex]] = $pk[$columnIndex];
			}
			return $result;
		}
	}

	/**
	 * Saves the current record and its MANY_MANY relations.
	 * This method will save the active record and update
	 * the necessary pivot tables for the MANY_MANY relations.
	 * The pivot table is the table that maps the relationship between two
	 * other tables in a MANY_MANY relation.
	 * This method won't save data on other active record models.
	 * @param array $relatedData The relation data in the format returned by {@link GxController::getRelatedData}.
	 * @param boolean $runValidation Whether to perform validation before saving the record.
	 * If the validation fails, the record will not be saved to database. This applies to all (including related) models.
	 * This does not apply for related models when in batch mode. This does not apply for deletes.
	 * @param array $attributes List of attributes that need to be saved. Defaults to null,
	 * meaning all attributes that are loaded from DB will be saved. This applies only to the main model.
	 * @param array $options Additional options. Valid options are:
	 * <ul>
	 * <li>'withTransaction', boolean: Whether to use a transaction.</li>
	 * <li>'batch', boolean: Whether to try to do the deletes and inserts in batch.
	 * While batches may be faster, using active record instances provides better control, validation, event support etc.
	 * Batch is only supported for deletes.</li>
	 * </ul>
	 * @return boolean Whether the saving succeeds.
	 * @see pivotModels
	 */
	public function saveWithRelated($relatedData, $runValidation = true, $attributes = null, $options = array()) {
		// Merge the specified options with the default options.
		$options = array_merge(
						// The default options.
						array(
							'withTransaction' => true,
							'batch' => true,
						)
						,
						// The specified options.
						$options
		);

		try {
			// Start the transaction if required.
			if ($options['withTransaction'] && ($this->getDbConnection()->getCurrentTransaction() === null)) {
				$transacted = true;
				$transaction = $this->getDbConnection()->beginTransaction();
			} else
				$transacted = false;

			// Save the main model.
			if (!$this->save($runValidation, $attributes)) {
				if ($transacted)
					$transaction->rollback();
				return false;
			}

			// If there is related data, call saveRelated.
			if (!empty($relatedData)) {
				if (!$this->saveRelated($relatedData, $runValidation, $options['batch'])) {
					if ($transacted)
						$transaction->rollback();
					return false;
				}
			}

			// If transacted, commit the transaction.
			if ($transacted)
				$transaction->commit();
		} catch (Exception $ex) {
			// If there is an exception, roll back the transaction...
			if ($transacted)
				$transaction->rollback();
			// ... and rethrow the exception.
			throw $ex;
		}
		return true;
	}

	/**
	 * Saves the MANY_MANY relations of this record.
	 * Internally used by {@link saveWithRelated} and {@link saveMultiple}.
	 * See {@link saveWithRelated} and {@link saveMultiple} for details.
	 * @param array $relatedData The relation data in the format returned by {@link GxController::getRelatedData}.
	 * @param boolean $runValidation Whether to perform validation before saving the record.
	 * @param boolean $batch Whether to try to do the deletes and inserts in batch.
	 * While batches may be faster, using active record instances provides better control, validation, event support etc.
	 * Batch is only supported for deletes.
	 * @return boolean Whether the saving succeeds.
	 * @see saveWithRelated
	 * @see saveMultiple
	 * @throws CDbException If this record is new.
	 * @throws Exception If this active record has composite PK.
	 */
	protected function saveRelated($relatedData, $runValidation = true, $batch = true) {
		if (empty($relatedData))
			return true;

		// This active record can't be new for the method to work correctly.
		if ($this->getIsNewRecord())
			throw new CDbException(Yii::t('giix', 'Cannot save the related records to the database because the main record is new.'));

		// Save each related data.
		foreach ($relatedData as $relationName => $relationData) {
			// The pivot model class name.
			$pivotClassNames = $this->pivotModels();
			$pivotClassName = $pivotClassNames[$relationName];
			$pivotModelStatic = GxActiveRecord::model($pivotClassName);
			// Get the foreign key names for the models.
			$activeRelation = $this->getActiveRelation($relationName);
			$relatedClassName = $activeRelation->className;
			if (preg_match('/(.+)\((.+),\s*(.+)\)/', $activeRelation->foreignKey, $matches)) {
				// By convention, the first fk is for this model, the second is for the related model.
				$thisFkName = $matches[2];
				$relatedFkName = $matches[3];
			}
			// Get the primary key value of the main model.
			$thisPkValue = $this->getPrimaryKey();
			if (is_array($thisPkValue))
				throw new Exception(Yii::t('giix', 'Composite primary keys are not supported.'));
			// Get the current related models of this relation and map the current related primary keys.
			$currentRelation = $pivotModelStatic->findAll(new CDbCriteria(array(
								'select' => $relatedFkName,
								'condition' => "$thisFkName = :thisfkvalue",
								'params' => array(':thisfkvalue' => $thisPkValue),
							)));
			$currentMap = array();
			foreach ($currentRelation as $currentRelModel) {
				$currentMap[] = $currentRelModel->$relatedFkName;
			}
			// Compare the current map to the new data and identify what is to be kept, deleted or inserted.
			$newMap = $relationData;
			$deleteMap = array();
			$insertMap = array();
			if ($newMap !== null) {
				// Identify the relations to be deleted.
				foreach ($currentMap as $currentItem) {
					if (!in_array($currentItem, $newMap))
						$deleteMap[] = $currentItem;
				}
				// Identify the relations to be inserted.
				foreach ($newMap as $newItem) {
					if (!in_array($newItem, $currentMap))
						$insertMap[] = $newItem;
				}
			} else // If the new data is empty, everything must be deleted.
				$deleteMap = $currentMap;
			// If nothing changed, we simply continue the loop.
			if (empty($deleteMap) && empty($insertMap))
				continue;
			// Now act inserting and deleting the related data: first prepare the data.
			// Inject the foreign key names of both models and the primary key value of the main model in the maps.
			foreach ($deleteMap as &$deleteMapPkValue)
				$deleteMapPkValue = array_merge(array($relatedFkName => $deleteMapPkValue), array($thisFkName => $thisPkValue));
			unset($deleteMapPkValue); // Clear reference;
			foreach ($insertMap as &$insertMapPkValue)
				$insertMapPkValue = array_merge(array($relatedFkName => $insertMapPkValue), array($thisFkName => $thisPkValue));
			unset($insertMapPkValue); // Clear reference;
			// Now act inserting and deleting the related data: then execute the changes.
			// Delete the data.
			if (!empty($deleteMap)) {
				if ($batch) {
					// Delete in batch mode.
					if ($pivotModelStatic->deleteByPk($deleteMap) !== count($deleteMap)) {
						return false;
					}
				} else {
					// Delete one active record at a time.
					foreach ($deleteMap as $value) {
						$pivotModel = GxActiveRecord::model($pivotClassName)->findByPk($value);
						if (!$pivotModel->delete()) {
							return false;
						}
					}
				}
			}
			// Insert the new data.
			foreach ($insertMap as $value) {
				$pivotModel = new $pivotClassName();
				$pivotModel->setAttributes($value);
				if (!$pivotModel->save($runValidation)) {
					return false;
				}
			}
		} // This is the end of the loop "save each related data".

		return true;
	}

	/**
	 * Saves multiple active records.
	 * This method can detect automatically all new active records
	 * having a BELONGS_TO relation (to HAS_ONE or to HAS_MANY) and
	 * fill in the data for their FK if it is null.
	 * The order of the active records in the $models array parameter is
	 * important to make it work. The models that need to be saved first
	 * should come first in the array.
	 * @param GxActiveRecord|array $models A model or an array of models.
	 * The array should follow the format:
	 * <pre>
	 * array(
	 *   array(
	 *     'model' => $theModelInstance,
	 *     'modelOptions' => array( ... ),
	 *   ),
	 *   array(
	 *     'model' => $anotherModelInstance,
	 *     'modelOptions' => array( ... ),
	 *   ),
	 * )
	 * </pre>
	 * The following modelOptions are available:
	 * <ul>
	 * <li>'runValidation', boolean: see {@link CActiveRecord::save} for details. Defauls to true.</li>
	 * <li>'attributes', array: see {@link CActiveRecord::save} for details. Defauls to null.</li>
	 * <li>'relatedData', array: see {@link saveWithRelated} for details. Defauls to null.</li>
	 * <li>'batch', boolean: see {@link saveWithRelated} for details. Applies only to the related data. Defauls to true.</li>
	 * <li></li>
	 * </ul>
	 * @param boolean $runValidation Whether to perform validation before saving all the records.
	 * If the validation fails, the record will not be saved to database.
	 * Optional. If true, forces the validation on all records. If false,
	 * disables the validation on all records. If null, the options for
	 * each record will be followed. Defaults to true.
	 * @param array $options Additional options. Valid options are:
	 * <ul>
	 * <li>'withTransaction', boolean: Whether to use a transaction.
	 * Defaults to true.</li>
	 * <li>'detectRelations', boolean: detect automatically all new active records
	 * having a BELONGS_TO relation (to HAS_ONE or to HAS_MANY) and
	 * fill in the data for its FK if it is null.
	 * Defaults to false.</li>
	 * </ul>
	 * @return boolean Whether the saving succeeds.
	 * @throws Exception If "detectRelations" is true and the related model is not found.
	 * @see CActiveRecord::save
	 * @see saveWithRelated
	 */
	public static function saveMultiple($models, $runValidation = true, $options = array()) {
		// Merge the specified options with the default options.
		$options = array_merge(
						// The default options.
						array(
							'withTransaction' => true,
							'detectRelations' => false,
						)
						,
						// The specified options.
						$options
		);
		// Define the default model options.
		$defaultModelOptions = array(
			'runValidation' => true,
			'attributes' => null,
			'relatedData' => null,
			'batch' => true,
		);

		// If $models is a single record, make it an array.
		if (!is_array($models))
			$models = array($models);

		// The saved models array.
		$savedModels = array();

		try {
			// Start the transaction if required.
			if ($options['withTransaction'] && ($this->getDbConnection()->getCurrentTransaction() === null)) {
				$transacted = true;
				$transaction = $this->getDbConnection()->beginTransaction();
			} else
				$transacted = false;

			foreach ($models as $modelItem) {
				// Get the model instance.
				$model = $modelItem['model'];
				// Merge the options.
				if (isset($modelItem['modelOptions']) && ($modelItem['modelOptions'] !== array()))
					$modelOptions = array_merge($defaultModelOptions, $modelItem['modelOptions']);
				else
					$modelOptions = $defaultModelOptions;
				// If set, the global "runValidation" value overrides the model setting.
				if ($runValidation !== null)
					$modelOptions['runValidation'] = $runValidation;

				// Detect automatically the new active record and fill in the data for its FK.
				if ($options['detectRelations']) {
					// Find if the model is new...
					if ($model->getIsNewRecord()) {
						// ... if the model has a BELONGS_TO relation...
						foreach ($model->relations() as $relationName => $relationData) {
							if ($relationData[0] === GxActiveRecord::BELONGS_TO) {
								// ...and if its FK is null.
								$fkName = $relationData[2];
								if ($model->$fkName === null) {
									// The FK is null. We need to fill it in.
									// We take the related model class name.
									$relatedClassName = $relationData[1];
									// And look for it in the array of the already saved models.
									if (isset($savedModels[$relatedClassName])) {
										// We assume that this is the related model and
										// we assume that the relation is to the PK.
										$model->$fkName = $savedModels[$relatedClassName]->getPrimaryKey();
									} else {
										// Related model not found.
										// We can't continue without filling up the FK!
										throw new Exception(Yii::t('giix', 'Related model not found. Cannot continue without filling up the FK.'));
									}
								}
							}
						}
					}
				} // This is the end of 'detectRelations' loop.
				// Save the model
				if (!$this->save($modelOptions['runValidation'], $modelOptions['attributes'])) {
					if ($transacted)
						$transaction->rollback();
					return false;
				}

				// If there is related data, use saveRelated.
				if (!empty($modelOptions['relatedData'])) {
					if (!$model->saveRelated($modelOptions['relatedData'], $modelOptions['runValidation'], $modelOptions['batch'])) {
						if ($transacted)
							$transaction->rollback();
						return false;
					}
				}

				// Add the model to the saved models array.
				// Only the last model of each class is recorded.
				if ($options['detectRelations'])
					$savedModels[get_class($model)] = $model;
			}

			// If transacted, commit the transaction.
			if ($transacted)
				$transaction->commit();
		} catch (Exception $ex) {
			// If there is an exception, roll back the transaction...
			if ($transacted)
				$transaction->rollback();
			// ... and rethrow the exception.
			throw $ex;
		}
		return true;
	}

	/**
	 * Finds the relation of the specified column.
	 * @param string|GxActiveRecord $modelClass The model class name or a model instance.
	 * @param string|CDbColumnSchema $column The column.
	 * @return array The relation. The array will have 3 values:
	 * 0: the relation name,
	 * 1: the relation type (will always be GxActiveRecord::BELONGS_TO),
	 * 2: the foreign key (will always be the specified column),
	 * 3: the related active record class name.
	 * Or null if no matching relation was found.
	 */
	public static function findRelation($modelClass, $column) {
		if (is_string($modelClass))
			$staticModelClass = self::model($modelClass);
		else
			$staticModelClass = self::model(get_class($modelClass));

		if (is_string($column))
			$column = $staticModelClass->getTableSchema()->getColumn($column);

		if (!$column->isForeignKey)
			return null;

		$relations = $staticModelClass->relations();
		// Find the relation for this attribute.
		foreach ($relations as $relationName => $relation) {
			// For attributes on this model, relation must be BELONGS_TO.
			if (($relation[0] === GxActiveRecord::BELONGS_TO) && ($relation[2] === $column->name)) {
				return array(
					$relationName, // the relation name
					$relation[0], // the relation type
					$relation[2], // the foreign key
					$relation[1] // the related active record class name
				);
			}
		}
		// None found.
		return null;
	}

}