Your IP : 216.73.216.11


Current Path : /home/digilove/public_html/41423/
Upload File :
Current File : //home/digilove/public_html/41423/DataModel.tar

Relation.php000064400000020125152355061210007032 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel;

defined('_JEXEC') || die;

use FOF40\Container\Container;
use FOF40\Model\DataModel;

abstract class Relation
{
	/** @var   DataModel  The data model we are attached to */
	protected $parentModel;

	/** @var   string  The class name of the foreign key's model */
	protected $foreignModelClass;

	/** @var   string  The application name of the foreign model */
	protected $foreignModelComponent;

	/** @var   string  The bade name of the foreign model */
	protected $foreignModelName;

	/** @var   string   The local table key for this relation */
	protected $localKey;

	/** @var   string   The foreign table key for this relation */
	protected $foreignKey;

	/** @var   null  For many-to-many relations, the pivot (glue) table */
	protected $pivotTable;

	/** @var   null  For many-to-many relations, the pivot table's column storing the local key */
	protected $pivotLocalKey;

	/** @var   null  For many-to-many relations, the pivot table's column storing the foreign key */
	protected $pivotForeignKey;

	/** @var   Collection  The data loaded by this relation */
	protected $data;

	/** @var  array  Maps each local table key to an array of foreign table keys, used in many-to-many relations */
	protected $foreignKeyMap = [];

	/** @var  Container  The component container for this relation */
	protected $container;

	/**
	 * Public constructor. Initialises the relation.
	 *
	 * @param   DataModel  $parentModel       The data model we are attached to
	 * @param   string     $foreignModelName  The name of the foreign key's model in the format
	 *                                        "modelName@com_something"
	 * @param   string     $localKey          The local table key for this relation
	 * @param   string     $foreignKey        The foreign key for this relation
	 * @param   string     $pivotTable        For many-to-many relations, the pivot (glue) table
	 * @param   string     $pivotLocalKey     For many-to-many relations, the pivot table's column storing the local
	 *                                        key
	 * @param   string     $pivotForeignKey   For many-to-many relations, the pivot table's column storing the foreign
	 *                                        key
	 */
	public function __construct(DataModel $parentModel, $foreignModelName, $localKey = null, $foreignKey = null, $pivotTable = null, $pivotLocalKey = null, $pivotForeignKey = null)
	{
		$this->parentModel       = $parentModel;
		$this->foreignModelClass = $foreignModelName;
		$this->localKey          = $localKey;
		$this->foreignKey        = $foreignKey;
		$this->pivotTable        = $pivotTable;
		$this->pivotLocalKey     = $pivotLocalKey;
		$this->pivotForeignKey   = $pivotForeignKey;

		$this->container = $parentModel->getContainer();

		$class = $foreignModelName;

		if (strpos($class, '@') === false)
		{
			$this->foreignModelComponent = null;
			$this->foreignModelName      = $class;
		}
		else
		{
			$foreignParts                = explode('@', $class, 2);
			$this->foreignModelComponent = $foreignParts[1];
			$this->foreignModelName      = $foreignParts[0];
		}
	}

	/**
	 * Reset the relation data
	 *
	 * @return $this For chaining
	 */
	public function reset()
	{
		$this->data          = null;
		$this->foreignKeyMap = [];

		return $this;
	}

	/**
	 * Rebase the relation to a different model
	 *
	 * @param   DataModel  $model
	 *
	 * @return $this For chaining
	 */
	public function rebase(DataModel $model)
	{
		$this->parentModel = $model;

		return $this->reset();
	}

	/**
	 * Get the relation data.
	 *
	 * If you want to apply additional filtering to the foreign model, use the $callback. It can be any function,
	 * static method, public method or closure with an interface of function(DataModel $foreignModel). You are not
	 * supposed to return anything, just modify $foreignModel's state directly. For example, you may want to do:
	 * $foreignModel->setState('foo', 'bar')
	 *
	 * @param   callable    $callback  The callback to run on the remote model.
	 * @param   Collection  $dataCollection
	 *
	 * @return Collection|DataModel
	 */
	public function getData($callback = null, Collection $dataCollection = null)
	{
		if (is_null($this->data))
		{
			// Initialise
			$this->data = new Collection();

			// Get a model instance
			$foreignModel = $this->getForeignModel();
			$foreignModel->setIgnoreRequest(true);

			$filtered = $this->filterForeignModel($foreignModel, $dataCollection);

			if (!$filtered)
			{
				return $this->data;
			}

			// Apply the callback, if applicable
			if (!is_null($callback) && is_callable($callback))
			{
				call_user_func($callback, $foreignModel);
			}

			// Get the list of items from the foreign model and cache in $this->data
			$this->data = $foreignModel->get(true);
		}

		return $this->data;
	}

	/**
	 * Populates the internal $this->data collection from the contents of the provided collection. This is used by
	 * DataModel to push the eager loaded data into each item's relation.
	 *
	 * @param   Collection  $data    The relation data to push into this relation
	 * @param   mixed       $keyMap  Used by many-to-many relations to pass around the local to foreign key map
	 *
	 * @return void
	 */
	public function setDataFromCollection(Collection &$data, $keyMap = null)
	{
		$this->data = new Collection();

		if (!empty($data))
		{
			$localKeyValue = $this->parentModel->getFieldValue($this->localKey);

			/** @var DataModel $item */
			foreach ($data as $item)
			{
				if ($item->getFieldValue($this->foreignKey) == $localKeyValue)
				{
					$this->data->add($item);
				}
			}
		}
	}

	/**
	 * Returns the count subquery for DataModel's has() and whereHas() methods.
	 *
	 * @return \JDatabaseQuery
	 */
	abstract public function getCountSubquery();

	/**
	 * Returns a new item of the foreignModel type, pre-initialised to fulfil this relation
	 *
	 * @return DataModel
	 *
	 * @throws DataModel\Relation\Exception\NewNotSupported when it's not supported
	 */
	abstract public function getNew();

	/**
	 * Saves all related items. You can use it to touch items as well: every item being saved causes the modified_by and
	 * modified_on fields to be changed automatically, thanks to the DataModel's magic.
	 */
	public function saveAll()
	{
		if ($this->data instanceof Collection)
		{
			foreach ($this->data as $item)
			{
				if ($item instanceof DataModel)
				{
					$item->save();
				}
			}
		}
	}

	/**
	 * Returns the foreign key map of a many-to-many relation, used for eager loading many-to-many relations
	 *
	 * @return array
	 */
	public function &getForeignKeyMap()
	{
		return $this->foreignKeyMap;
	}

	/**
	 * Gets an object instance of the foreign model
	 *
	 * @param   array  $config  Optional configuration information for the Model
	 *
	 * @return DataModel
	 */
	public function &getForeignModel(array $config = [])
	{
		// If the model comes from this component go through our Factory
		if (is_null($this->foreignModelComponent))
		{
			/** @var DataModel $model */
			$model = $this->container->factory->model($this->foreignModelName, $config)->tmpInstance();

			return $model;
		}

		// The model comes from another component. Create a container and go through its factory.
		$foreignContainer = Container::getInstance($this->foreignModelComponent, ['tempInstance' => true]);
		/** @var DataModel $model */
		$model = $foreignContainer->factory->model($this->foreignModelName, $config)->tmpInstance();

		return $model;
	}

	/**
	 * Returns the name of the local key of the relation
	 *
	 * @return  string
	 */
	public function getLocalKey()
	{
		return $this->localKey;
	}

	/**
	 * Applies the relation filters to the foreign model when getData is called
	 *
	 * @param   DataModel   $foreignModel    The foreign model you're operating on
	 * @param   Collection  $dataCollection  If it's an eager loaded relation, the collection of loaded parent records
	 *
	 * @return boolean Return false to force an empty data collection
	 */
	abstract protected function filterForeignModel(DataModel $foreignModel, Collection $dataCollection = null);
}
RelationManager.php000064400000032762152355061210010337 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel;

defined('_JEXEC') || die;

use FOF40\Model\DataModel;

class RelationManager
{
	/** @var array The known relation types */
	protected static $relationTypes = [];

	/** @var DataModel The data model we are attached to */
	protected $parentModel;

	/** @var Relation[] The relations known to us */
	protected $relations = [];

	/** @var array A list of the names of eager loaded relations */
	protected $eager = [];

	/**
	 * Creates a new relation manager for the defined parent model
	 *
	 * @param   DataModel  $parentModel  The model we are attached to
	 */
	public function __construct(DataModel $parentModel)
	{
		// Set the parent model
		$this->parentModel = $parentModel;

		// Make sure the relation types are initialised
		static::getRelationTypes();

		// @todo Maybe set up a few relations automatically?
	}

	/**
	 * Populates the static map of relation type methods and relation handling classes
	 *
	 * @return array Key = method name, Value = relation handling class
	 */
	public static function getRelationTypes()
	{
		if (empty(static::$relationTypes))
		{
			$relationTypeDirectory = __DIR__ . '/Relation';
			$fs                    = new \DirectoryIterator($relationTypeDirectory);

			/** @var $file \DirectoryIterator */
			foreach ($fs as $file)
			{
				if ($file->isDir())
				{
					continue;
				}

				if ($file->getExtension() != 'php')
				{
					continue;
				}

				$baseName   = ucfirst($file->getBasename('.php'));
				$methodName = strtolower($baseName[0]) . substr($baseName, 1);
				$className  = '\\FOF40\\Model\\DataModel\\Relation\\' . $baseName;

				if (!class_exists($className, true))
				{
					continue;
				}

				static::$relationTypes[$methodName] = $className;
			}
		}

		return static::$relationTypes;
	}

	/**
	 * Implements deep cloning of the relation object
	 */
	function __clone()
	{
		$relations = [];

		/** @var Relation[] $relations */
		foreach ($this->relations as $key => $relation)
		{
			$relations[$key] = clone($relation);
			$relations[$key]->reset();
		}

		$this->relations = $relations;
	}

	/**
	 * Rebase a relation manager
	 *
	 * @param   DataModel  $parentModel
	 */
	public function rebase(DataModel $parentModel)
	{
		$this->parentModel = $parentModel;

		if (count($this->relations) > 0)
		{
			foreach ($this->relations as $relation)
			{
				/** @var Relation $relation */
				$relation->rebase($parentModel);
			}
		}
	}

	/**
	 * Populates the internal $this->data collection of a relation from the contents of the provided collection. This is
	 * used by DataModel to push the eager loaded data into each item's relation.
	 *
	 * @param   string      $name    Relation name
	 * @param   Collection  $data    The relation data to push into this relation
	 * @param   mixed       $keyMap  Used by many-to-many relations to pass around the local to foreign key map
	 *
	 * @return void
	 *
	 * @throws Relation\Exception\RelationNotFound
	 */
	public function setDataFromCollection($name, Collection &$data, $keyMap = null)
	{
		if (!isset($this->relations[$name]))
		{
			throw new DataModel\Relation\Exception\RelationNotFound("Relation '$name' not found");
		}

		$this->relations[$name]->setDataFromCollection($data, $keyMap);
	}

	/**
	 * Adds a relation to the relation manager
	 *
	 * @param   string  $name              The name of the relation as known to this relation manager, e.g. 'phone'
	 * @param   string  $type              The relation type, e.g. 'hasOne'
	 * @param   string  $foreignModelName  The name of the foreign key's model in the format "modelName@com_something"
	 * @param   string  $localKey          The local table key for this relation
	 * @param   string  $foreignKey        The foreign key for this relation
	 * @param   string  $pivotTable        For many-to-many relations, the pivot (glue) table
	 * @param   string  $pivotLocalKey     For many-to-many relations, the pivot table's column storing the local key
	 * @param   string  $pivotForeignKey   For many-to-many relations, the pivot table's column storing the foreign key
	 *
	 * @return DataModel The parent model, for chaining
	 *
	 * @throws Relation\Exception\RelationTypeNotFound when $type is not known
	 * @throws Relation\Exception\ForeignModelNotFound when $foreignModelClass doesn't exist
	 */
	public function addRelation($name, $type, $foreignModelName = null, $localKey = null, $foreignKey = null, $pivotTable = null, $pivotLocalKey = null, $pivotForeignKey = null)
	{
		if (!isset(static::$relationTypes[$type]))
		{
			throw new DataModel\Relation\Exception\RelationTypeNotFound("Relation type '$type' not found");
		}

		// Guess the foreign model class if necessary
		if (empty($foreignModelName))
		{
			$foreignModelName = ucfirst($name);
		}

		$className = static::$relationTypes[$type];

		/** @var Relation $relation */
		$relation = new $className($this->parentModel, $foreignModelName, $localKey, $foreignKey,
			$pivotTable, $pivotLocalKey, $pivotForeignKey);

		$this->relations[$name] = $relation;

		return $this->parentModel;
	}

	/**
	 * Removes a known relation
	 *
	 * @param   string  $name  The name of the relation to remove
	 *
	 * @return DataModel The parent model, for chaining
	 */
	public function removeRelation($name)
	{
		if (isset($this->relations[$name]))
		{
			unset ($this->relations[$name]);
		}

		return $this->parentModel;
	}

	/**
	 * Removes all known relations
	 */
	public function resetRelations()
	{
		$this->relations = [];
	}

	/**
	 * Resets the data of all relations in this manager. This doesn't remove relations, just their data so that they
	 * get loaded again.
	 *
	 * @param   array  $relationsToReset  The names of the relations to reset. Pass an empty array (default) to reset
	 *                                    all relations.
	 */
	public function resetRelationData(array $relationsToReset = [])
	{
		/** @var Relation $relation */
		foreach ($this->relations as $name => $relation)
		{
			if (!empty($relationsToReset) && !in_array($name, $relationsToReset))
			{
				continue;
			}

			$relation->reset();
		}
	}

	/**
	 * Returns a list of all known relations' names
	 *
	 * @return array
	 */
	public function getRelationNames()
	{
		return array_keys($this->relations);
	}

	/**
	 * Gets the related items of a relation
	 *
	 * @param   string  $name  The name of the relation to return data for
	 *
	 * @return Relation
	 *
	 * @throws Relation\Exception\RelationNotFound
	 */
	public function &getRelation($name)
	{
		if (!isset($this->relations[$name]))
		{
			throw new DataModel\Relation\Exception\RelationNotFound("Relation '$name' not found");
		}

		return $this->relations[$name];
	}


	/**
	 * Get a new related item which satisfies relation $name and adds it to this relation's data list.
	 *
	 * @param   string  $name  The relation based on which a new item is returned
	 *
	 * @return DataModel
	 *
	 * @throws Relation\Exception\RelationNotFound
	 */
	public function getNew($name)
	{
		if (!isset($this->relations[$name]))
		{
			throw new DataModel\Relation\Exception\RelationNotFound("Relation '$name' not found");
		}

		return $this->relations[$name]->getNew();
	}

	/**
	 * Saves all related items belonging to the specified relation or, if $name is null, all known relations which
	 * support saving.
	 *
	 * @param   null|string  $name  The relation to save, or null to save all known relations
	 *
	 * @return DataModel The parent model, for chaining
	 *
	 * @throws Relation\Exception\RelationNotFound
	 */
	public function save($name = null)
	{
		if (is_null($name))
		{
			foreach ($this->relations as $relation)
			{
				try
				{
					$relation->saveAll();
				}
				catch (DataModel\Relation\Exception\SaveNotSupported $e)
				{
					// We don't care if a relation doesn't support saving
				}
			}
		}
		else
		{
			if (!isset($this->relations[$name]))
			{
				throw new DataModel\Relation\Exception\RelationNotFound("Relation '$name' not found");
			}

			$this->relations[$name]->saveAll();
		}

		return $this->parentModel;
	}

	/**
	 * Gets the related items of a relation
	 *
	 * @param   string                   $name            The name of the relation to return data for
	 * @param   callable                 $callback        A callback to customise the returned data
	 * @param   \FOF40\Utils\Collection  $dataCollection  Used when fetching the data of an eager loaded relation
	 *
	 * @return Collection|DataModel
	 *
	 * @throws Relation\Exception\RelationNotFound
	 * @see Relation::getData()
	 *
	 */
	public function getData($name, $callback = null, \FOF40\Utils\Collection $dataCollection = null)
	{
		if (!isset($this->relations[$name]))
		{
			throw new DataModel\Relation\Exception\RelationNotFound("Relation '$name' not found");
		}

		return $this->relations[$name]->getData($callback, $dataCollection);
	}

	/**
	 * Gets the foreign key map of a many-to-many relation
	 *
	 * @param   string  $name  The name of the relation to return data for
	 *
	 * @return array
	 *
	 * @throws Relation\Exception\RelationNotFound
	 */
	public function &getForeignKeyMap($name)
	{
		if (!isset($this->relations[$name]))
		{
			throw new DataModel\Relation\Exception\RelationNotFound("Relation '$name' not found");
		}

		return $this->relations[$name]->getForeignKeyMap();
	}

	/**
	 * Returns the count sub-query for a relation, used for relation filters (whereHas in the DataModel).
	 *
	 * @param   string  $name        The relation to get the sub-query for
	 * @param   string  $tableAlias  The alias to use for the local table
	 *
	 * @return \JDatabaseQuery
	 * @throws Relation\Exception\RelationNotFound
	 */
	public function getCountSubquery($name, $tableAlias = null)
	{
		if (!isset($this->relations[$name]))
		{
			throw new DataModel\Relation\Exception\RelationNotFound("Relation '$name' not found");
		}

		return $this->relations[$name]->getCountSubquery($tableAlias);
	}

	/**
	 * A magic method which allows us to define relations using shorthand notation, e.g. $manager->hasOne('phone')
	 * instead of $manager->addRelation('phone', 'hasOne')
	 *
	 * You can also use it to get data of a relation using shorthand notation, e.g. $manager->getPhone($callback)
	 * instead of $manager->getData('phone', $callback);
	 *
	 * @param   string  $name       The magic method to call
	 * @param   array   $arguments  The arguments to the magic method
	 *
	 * @return DataModel The parent model, for chaining
	 *
	 * @throws \InvalidArgumentException
	 * @throws DataModel\Relation\Exception\RelationTypeNotFound
	 */
	function __call($name, $arguments)
	{
		$numberOfArguments = count($arguments);

		if (isset(static::$relationTypes[$name]))
		{
			if ($numberOfArguments == 1)
			{
				return $this->addRelation($arguments[0], $name);
			}
			elseif ($numberOfArguments == 2)
			{
				return $this->addRelation($arguments[0], $name, $arguments[1]);
			}
			elseif ($numberOfArguments == 3)
			{
				return $this->addRelation($arguments[0], $name, $arguments[1], $arguments[2]);
			}
			elseif ($numberOfArguments == 4)
			{
				return $this->addRelation($arguments[0], $name, $arguments[1], $arguments[2], $arguments[3]);
			}
			elseif ($numberOfArguments == 5)
			{
				return $this->addRelation($arguments[0], $name, $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
			}
			elseif ($numberOfArguments == 6)
			{
				return $this->addRelation($arguments[0], $name, $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
			}
			elseif ($numberOfArguments >= 7)
			{
				return $this->addRelation($arguments[0], $name, $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5], $arguments[6]);
			}
			else
			{
				throw new \InvalidArgumentException("You can not create an unnamed '$name' relation");
			}
		}
		elseif (substr($name, 0, 3) == 'get')
		{
			$relationName = substr($name, 3);
			$relationName = strtolower($relationName[0]) . substr($relationName, 1);

			if ($numberOfArguments == 0)
			{
				return $this->getData($relationName);
			}
			elseif ($numberOfArguments == 1)
			{
				return $this->getData($relationName, $arguments[0]);
			}
			elseif ($numberOfArguments == 2)
			{
				return $this->getData($relationName, $arguments[0], $arguments[1]);
			}
			else
			{
				throw new \InvalidArgumentException("Invalid number of arguments getting data for the '$relationName' relation");
			}
		}

		// Throw an exception otherwise
		throw new DataModel\Relation\Exception\RelationTypeNotFound("Relation type '$name' not known to relation manager");
	}

	/**
	 * Is $name a magic-callable method?
	 *
	 * @param   string  $name  The name of a potential magic-callable method
	 *
	 * @return bool
	 */
	public function isMagicMethod($name)
	{
		if (isset(static::$relationTypes[$name]))
		{
			return true;
		}
		elseif (substr($name, 0, 3) == 'get')
		{
			$relationName = substr($name, 3);
			$relationName = strtolower($relationName[0]) . substr($relationName, 1);

			if (isset($this->relations[$relationName]))
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Is $name a magic property? Corollary: returns true if a relation of this name is known to the relation manager.
	 *
	 * @param   string  $name  The name of a potential magic property
	 *
	 * @return bool
	 */
	public function isMagicProperty($name)
	{
		return isset($this->relations[$name]);
	}

	/**
	 * Magic method to get the data of a relation using shorthand notation, e.g. $manager->phone instead of
	 * $manager->getData('phone')
	 *
	 * @param $name
	 *
	 * @return Collection
	 */
	function __get($name)
	{
		return $this->getData($name);
	}
}
Collection.php000064400000015506152355061210007357 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel;

defined('_JEXEC') || die;

use FOF40\Model\DataModel;
use FOF40\Utils\Collection as BaseCollection;

/**
 * A collection of data models. You can enumerate it like an array, use it everywhere a collection is expected (e.g. a
 * foreach loop) and even implements a countable interface. You can also batch-apply DataModel methods on it thanks to
 * its magic __call() method, hence the type-hinting below.
 *
 * @method void setFieldValue(string $name, mixed $value = '')
 * @method void archive()
 * @method void save(mixed $data, string $orderingFilter = '', bool $ignore = null)
 * @method void push(mixed $data, string $orderingFilter = '', bool $ignore = null, array $relations = null)
 * @method void bind(mixed $data, array $ignore = [])
 * @method void check()
 * @method void reorder(string $where = '')
 * @method void delete(mixed $id = null)
 * @method void trash(mixed $id)
 * @method void forceDelete(mixed $id = null)
 * @method void lock(int $userId = null)
 * @method void move(int $delta, string $where = '')
 * @method void publish()
 * @method void restore(mixed $id)
 * @method void touch(int $userId = null)
 * @method void unlock()
 * @method void unpublish()
 */
class Collection extends BaseCollection
{
	/**
	 * Find a model in the collection by key.
	 *
	 * @param   mixed  $key
	 * @param   mixed  $default
	 *
	 * @return DataModel
	 */
	public function find($key, $default = null)
	{
		if ($key instanceof DataModel)
		{
			$key = $key->getId();
		}

		return array_first($this->items, function ($itemKey, $model) use ($key) {
			/** @var DataModel $model */
			return $model->getId() == $key;

		}, $default);
	}

	/**
	 * Remove an item in the collection by key
	 *
	 * @param   mixed  $key
	 *
	 * @return void
	 */
	public function removeById($key)
	{
		if ($key instanceof DataModel)
		{
			$key = $key->getId();
		}

		$index = array_search($key, $this->modelKeys());

		if ($index !== false)
		{
			unset($this->items[$index]);
		}
	}

	/**
	 * Add an item to the collection.
	 *
	 * @param   mixed  $item
	 *
	 * @return Collection
	 */
	public function add($item)
	{
		$this->items[] = $item;

		return $this;
	}

	/**
	 * Determine if a key exists in the collection.
	 *
	 * @param   mixed  $key
	 *
	 * @return bool
	 */
	public function contains($key)
	{
		return !is_null($this->find($key));
	}

	/**
	 * Fetch a nested element of the collection.
	 *
	 * @param   string  $key
	 *
	 * @return Collection
	 */
	public function fetch(string $key): BaseCollection
	{
		return new static(array_fetch($this->toArray(), $key));
	}

	/**
	 * Get the max value of a given key.
	 *
	 * @param   string  $key
	 *
	 * @return mixed
	 */
	public function max($key)
	{
		return $this->reduce(function ($result, $item) use ($key) {
			return (is_null($result) || $item->{$key} > $result) ? $item->{$key} : $result;
		});
	}

	/**
	 * Get the min value of a given key.
	 *
	 * @param   string  $key
	 *
	 * @return mixed
	 */
	public function min($key)
	{
		return $this->reduce(function ($result, $item) use ($key) {
			return (is_null($result) || $item->{$key} < $result) ? $item->{$key} : $result;
		});
	}

	/**
	 * Get the array of primary keys
	 *
	 * @return array
	 */
	public function modelKeys()
	{
		return array_map(
			function ($m) {
				/** @var DataModel $m */
				return $m->getId();
			},
			$this->items);
	}

	/**
	 * Merge the collection with the given items.
	 *
	 * @param   BaseCollection|array  $collection
	 *
	 * @return BaseCollection
	 */
	public function merge($collection): BaseCollection
	{
		$dictionary = $this->getDictionary($this);

		foreach ($collection as $item)
		{
			$dictionary[$item->getId()] = $item;
		}

		return new static(array_values($dictionary));
	}

	/**
	 * Diff the collection with the given items.
	 *
	 * @param   BaseCollection|array  $collection
	 *
	 * @return  BaseCollection
	 */
	public function diff($collection): BaseCollection
	{
		$diff = new static;

		$dictionary = $this->getDictionary($collection);

		foreach ($this->items as $item)
		{
			/** @var DataModel $item */
			if (!isset($dictionary[$item->getId()]))
			{
				$diff->add($item);
			}
		}

		return $diff;
	}

	/**
	 * Intersect the collection with the given items.
	 *
	 * @param   BaseCollection|array  $collection
	 *
	 * @return  Collection
	 */
	public function intersect($collection): BaseCollection
	{
		$intersect = new static;

		$dictionary = $this->getDictionary($collection);

		foreach ($this->items as $item)
		{
			/** @var DataModel $item */
			if (isset($dictionary[$item->getId()]))
			{
				$intersect->add($item);
			}
		}

		return $intersect;
	}

	/**
	 * Return only unique items from the collection.
	 *
	 * @return BaseCollection
	 */
	public function unique(): BaseCollection
	{
		$dictionary = $this->getDictionary($this);

		return new static(array_values($dictionary));
	}

	/**
	 * Get a base Support collection instance from this collection.
	 *
	 * @return BaseCollection
	 */
	public function toBase()
	{
		return new BaseCollection($this->items);
	}

	/**
	 * Magic method which allows you to run a DataModel method to all items in the collection.
	 *
	 * For example, you can do $collection->save('foobar' => 1) to update the 'foobar' column to 1 across all items in
	 * the collection.
	 *
	 * IMPORTANT: The return value of the method call is not returned back to you!
	 *
	 * @param   string  $name       The method to call
	 * @param   array   $arguments  The arguments to the method
	 */
	public function __call($name, $arguments)
	{
		if (count($this) === 0)
		{
			return;
		}

		$class = get_class($this->first());

		if (method_exists($class, $name))
		{
			foreach ($this as $item)
			{
				switch (count($arguments))
				{
					case 0:
						$item->$name();
						break;

					case 1:
						$item->$name($arguments[0]);
						break;

					case 2:
						$item->$name($arguments[0], $arguments[1]);
						break;

					case 3:
						$item->$name($arguments[0], $arguments[1], $arguments[2]);
						break;

					case 4:
						$item->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3]);
						break;

					case 5:
						$item->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4]);
						break;

					case 6:
						$item->$name($arguments[0], $arguments[1], $arguments[2], $arguments[3], $arguments[4], $arguments[5]);
						break;

					default:
						call_user_func_array([$item, $name], $arguments);
						break;
				}
			}
		}
	}

	/**
	 * Get a dictionary keyed by primary keys.
	 *
	 * @param   BaseCollection  $collection
	 *
	 * @return array
	 */
	protected function getDictionary($collection)
	{
		$dictionary = [];

		foreach ($collection as $value)
		{
			$dictionary[$value->getId()] = $value;
		}

		return $dictionary;
	}
}
Relation/BelongsTo.php000064400000004617152355061210010736 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Relation;

defined('_JEXEC') || die;

use FOF40\Model\DataModel;

/**
 * BelongsTo (reverse 1-to-1 or 1-to-many) relation: this model is a child which belongs to the foreign table
 *
 * For example, parentModel is Articles and foreignModel is Users. Each article belongs to one user. One user can have
 * one or more article.
 *
 * Example #2: parentModel is Phones and foreignModel is Users. Each phone belongs to one user. One user can have zero
 * or one phones.
 */
class BelongsTo extends HasOne
{
	/**
	 * Public constructor. Initialises the relation.
	 *
	 * @param   DataModel  $parentModel        The data model we are attached to
	 * @param   string     $foreignModelName   The name of the foreign key's model in the format "modelName@com_something"
	 * @param   string     $localKey           The local table key for this relation, default: parentModel's ID field name
	 * @param   string     $foreignKey         The foreign key for this relation, default: parentModel's ID field name
	 * @param   string     $pivotTable         IGNORED
	 * @param   string     $pivotLocalKey      IGNORED
	 * @param   string     $pivotForeignKey    IGNORED
	 */
	public function __construct(DataModel $parentModel, $foreignModelName, $localKey = null, $foreignKey = null, $pivotTable = null, $pivotLocalKey = null, $pivotForeignKey = null)
	{
		parent::__construct($parentModel, $foreignModelName, $localKey, $foreignKey, $pivotTable, $pivotLocalKey, $pivotForeignKey);

		if (empty($localKey))
		{
			/** @var DataModel $foreignModel */
			$foreignModel = $this->getForeignModel();
			$foreignModel->setIgnoreRequest(true);

			$this->localKey = $foreignModel->getIdFieldName();
		}

		if (empty($foreignKey))
		{
			if (!isset($foreignModel))
			{
				/** @var DataModel $foreignModel */
				$foreignModel = $this->getForeignModel();
				$foreignModel->setIgnoreRequest(true);
			}

			$this->foreignKey = $foreignModel->getIdFieldName();
		}
	}

	/**
	 * This is not supported by the belongsTo relation
	 *
	 * @throws DataModel\Relation\Exception\NewNotSupported when it's not supported
	 */
	public function getNew()
	{
		throw new DataModel\Relation\Exception\NewNotSupported("getNew() is not supported by the belongsTo relation type");
	}

}
Relation/BelongsToMany.php000064400000027161152355061210011562 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Relation;

defined('_JEXEC') || die;

use FOF40\Model\DataModel;
use FOF40\Model\DataModel\Relation;

/**
 * BelongsToMany (many-to-many) relation: one or more records of this model are related to one or more records in the
 * foreign model.
 *
 * For example, parentModel is Users and foreignModel is Groups. Each user can be assigned to many groups. Each group
 * can be assigned to many users.
 */
class BelongsToMany extends Relation
{
	/**
	 * Public constructor. Initialises the relation.
	 *
	 * @param   DataModel  $parentModel       The data model we are attached to
	 * @param   string     $foreignModelName  The name of the foreign key's model in the format
	 *                                        "modelName@com_something"
	 * @param   string     $localKey          The local table key for this relation, default: parentModel's ID field
	 *                                        name
	 * @param   string     $foreignKey        The foreign key for this relation, default: parentModel's ID field name
	 * @param   string     $pivotTable        For many-to-many relations, the pivot (glue) table
	 * @param   string     $pivotLocalKey     For many-to-many relations, the pivot table's column storing the local
	 *                                        key
	 * @param   string     $pivotForeignKey   For many-to-many relations, the pivot table's column storing the foreign
	 *                                        key
	 *
	 * @throws  DataModel\Relation\Exception\PivotTableNotFound
	 */
	public function __construct(DataModel $parentModel, $foreignModelName, $localKey = null, $foreignKey = null, $pivotTable = null, $pivotLocalKey = null, $pivotForeignKey = null)
	{
		parent::__construct($parentModel, $foreignModelName, $localKey, $foreignKey, $pivotTable, $pivotLocalKey, $pivotForeignKey);

		if (empty($localKey))
		{
			$this->localKey = $parentModel->getIdFieldName();
		}

		if (empty($pivotLocalKey))
		{
			$this->pivotLocalKey = $this->localKey;
		}

		if (empty($foreignKey))
		{
			/** @var DataModel $foreignModel */
			$foreignModel = $this->getForeignModel();
			$foreignModel->setIgnoreRequest(true);

			$this->foreignKey = $foreignModel->getIdFieldName();
		}

		if (empty($pivotForeignKey))
		{
			$this->pivotForeignKey = $this->foreignKey;
		}

		if (empty($pivotTable))
		{
			// Get the local model's name (e.g. "users")
			$localName = $parentModel->getName();
			$localName = strtolower($localName);

			// Get the foreign model's name (e.g. "groups")
			if (!isset($foreignModel))
			{
				/** @var DataModel $foreignModel */
				$foreignModel = $this->getForeignModel();
				$foreignModel->setIgnoreRequest(true);
			}

			$foreignName = $foreignModel->getName();
			$foreignName = strtolower($foreignName);

			// Get the local model's app name
			$parentModelBareComponent  = $parentModel->getContainer()->bareComponentName;
			$foreignModelBareComponent = $foreignModel->getContainer()->bareComponentName;

			// There are two possibilities for the table name: #__component_local_foreign or #__component_foreign_local.
			// There are also two possibilities for a component name (local or foreign model's)
			$db     = $parentModel->getDbo();
			$prefix = $db->getPrefix();

			$tableNames = [
				'#__' . strtolower($parentModelBareComponent) . '_' . $localName . '_' . $foreignName,
				'#__' . strtolower($parentModelBareComponent) . '_' . $foreignName . '_' . $localName,
				'#__' . strtolower($foreignModelBareComponent) . '_' . $localName . '_' . $foreignName,
				'#__' . strtolower($foreignModelBareComponent) . '_' . $foreignName . '_' . $localName,
			];

			$allTables = $db->getTableList();

			$this->pivotTable = null;

			foreach ($tableNames as $tableName)
			{
				$checkName = $prefix . substr($tableName, 3);

				if (in_array($checkName, $allTables))
				{
					$this->pivotTable = $tableName;
				}
			}

			if (empty($this->pivotTable))
			{
				throw new DataModel\Relation\Exception\PivotTableNotFound("Pivot table for many-to-many relation between '$localName and '$foreignName' not found'");
			}
		}
	}

	/**
	 * Populates the internal $this->data collection from the contents of the provided collection. This is used by
	 * DataModel to push the eager loaded data into each item's relation.
	 *
	 * @param   DataModel\Collection  $data    The relation data to push into this relation
	 * @param   mixed                 $keyMap  Passes around the local to foreign key map
	 *
	 * @return void
	 */
	public function setDataFromCollection(DataModel\Collection &$data, $keyMap = null)
	{
		$this->data = new DataModel\Collection();

		if (!is_array($keyMap))
		{
			return;
		}

		if (!empty($data))
		{
			// Get the local key value
			$localKeyValue = $this->parentModel->getFieldValue($this->localKey);

			// Make sure this local key exists in the (cached) pivot table
			if (!isset($keyMap[$localKeyValue]))
			{
				return;
			}

			/** @var DataModel $item */
			foreach ($data as $item)
			{
				// Only accept foreign items whose key is associated in the pivot table with our local key
				if (in_array($item->getFieldValue($this->foreignKey), $keyMap[$localKeyValue]))
				{
					$this->data->add($item);
				}
			}
		}
	}

	/**
	 * Returns the count subquery for DataModel's has() and whereHas() methods.
	 *
	 * @param   string  $tableAlias  The alias of the local table in the query. Leave blank to use the table's name.
	 *
	 * @return \JDatabaseQuery
	 */
	public function getCountSubquery($tableAlias = null)
	{
		/** @var DataModel $foreignModel */
		$foreignModel = $this->getForeignModel();
		$foreignModel->setIgnoreRequest(true);

		$db = $foreignModel->getDbo();

		if (empty($tableAlias))
		{
			$tableAlias = $this->parentModel->getTableName();
		}

		return $db->getQuery(true)
			->select('COUNT(*)')
			->from($db->qn($foreignModel->getTableName()) . ' AS ' . $db->qn('reltbl'))
			->innerJoin(
				$db->qn($this->pivotTable) . ' AS ' . $db->qn('pivotTable') . ' ON('
				. $db->qn('pivotTable') . '.' . $db->qn($this->pivotForeignKey) . ' = '
				. $db->qn('reltbl') . '.' . $db->qn($foreignModel->getFieldAlias($this->foreignKey))
				. ')'
			)
			->where(
				$db->qn('pivotTable') . '.' . $db->qn($this->pivotLocalKey) . ' ='
				. $db->qn($tableAlias) . '.'
				. $db->qn($this->parentModel->getFieldAlias($this->localKey))
			);
	}

	/**
	 * Saves all related items. For many-to-many relations there are two things we have to do:
	 * 1. Save all related items; and
	 * 2. Overwrite the pivot table data with the new associations
	 */
	public function saveAll()
	{
		// Save all related items
		parent::saveAll();

		$this->saveRelations();
	}

	/**
	 * Overwrite the pivot table data with the new associations
	 */
	public function saveRelations()
	{
		// Get all the new keys
		$newKeys = [];

		if ($this->data instanceof DataModel\Collection)
		{
			foreach ($this->data as $item)
			{
				if ($item instanceof DataModel)
				{
					$newKeys[] = $item->getId();
				}
				elseif (!is_object($item))
				{
					$newKeys[] = $item;
				}
			}
		}

		$newKeys = array_unique($newKeys);

		$db            = $this->parentModel->getDbo();
		$localKeyValue = $this->parentModel->getFieldValue($this->localKey);

		// Kill all existing relations in the pivot table
		$query = $db->getQuery(true)
			->delete($db->qn($this->pivotTable))
			->where($db->qn($this->pivotLocalKey) . ' = ' . $db->q($localKeyValue));
		$db->setQuery($query);
		$db->execute();

		// Write the new relations to the database
		$protoQuery = $db->getQuery(true)
			->insert($db->qn($this->pivotTable))
			->columns([$db->qn($this->pivotLocalKey), $db->qn($this->pivotForeignKey)]);

		$i     = 0;
		$query = null;

		foreach ($newKeys as $key)
		{
			$i++;

			if (is_null($query))
			{
				$query = clone $protoQuery;
			}

			$query->values($db->q($localKeyValue) . ', ' . $db->q($key));

			if (($i % 50) == 0)
			{
				$db->setQuery($query);
				$db->execute();
				$query = null;
			}
		}

		if (!is_null($query))
		{
			$db->setQuery($query);
			$db->execute();
		}
	}

	/**
	 * This is not supported by the belongsTo relation
	 *
	 * @throws DataModel\Relation\Exception\NewNotSupported when it's not supported
	 */
	public function getNew()
	{
		throw new DataModel\Relation\Exception\NewNotSupported("getNew() is not supported for many-to-may relations. Please add/remove items from the relation data and use push() to effect changes.");
	}

	/**
	 * Applies the relation filters to the foreign model when getData is called
	 *
	 * @param   DataModel             $foreignModel    The foreign model you're operating on
	 * @param   DataModel\Collection  $dataCollection  If it's an eager loaded relation, the collection of loaded
	 *                                                 parent records
	 *
	 * @return boolean Return false to force an empty data collection
	 */
	protected function filterForeignModel(DataModel $foreignModel, DataModel\Collection $dataCollection = null)
	{
		$db = $this->parentModel->getDbo();

		// Decide how to proceed, based on eager or lazy loading
		if (is_object($dataCollection))
		{
			// Eager loaded relation
			if (!empty($dataCollection))
			{
				// Get a list of local keys from the collection
				$values = [];

				/** @var $item DataModel */
				foreach ($dataCollection as $item)
				{
					$v = $item->getFieldValue($this->localKey, null);

					if (!is_null($v))
					{
						$values[] = $v;
					}
				}

				// Keep only unique values
				$values = array_unique($values);
				$values = array_map(function ($x) use (&$db) {
					return $db->q($x);
				}, $values);

				// Get the foreign keys from the glue table
				$query = $db->getQuery(true)
					->select([$db->qn($this->pivotLocalKey), $db->qn($this->pivotForeignKey)])
					->from($db->qn($this->pivotTable))
					->where($db->qn($this->pivotLocalKey) . ' IN(' . implode(',', $values) . ')');
				$db->setQuery($query);
				$foreignKeysUnmapped = $db->loadRowList();

				$this->foreignKeyMap = [];
				$foreignKeys         = [];

				foreach ($foreignKeysUnmapped as $unmapped)
				{
					$local   = $unmapped[0];
					$foreign = $unmapped[1];

					if (!isset($this->foreignKeyMap[$local]))
					{
						$this->foreignKeyMap[$local] = [];
					}

					$this->foreignKeyMap[$local][] = $foreign;

					$foreignKeys[] = $foreign;
				}

				// Keep only unique values. However, the array keys are all screwed up. See below.
				$foreignKeys = array_unique($foreignKeys);

				// This looks stupid, but it's required to reset the array keys. Without it where() below fails.
				$foreignKeys = array_merge($foreignKeys);

				// Apply the filter
				if (!empty($foreignKeys))
				{
					$foreignModel->where($this->foreignKey, 'in', $foreignKeys);
				}
				else
				{
					return false;
				}
			}
			else
			{
				return false;
			}
		}
		else
		{
			// Lazy loaded relation; get the single local key
			$localKey = $this->parentModel->getFieldValue($this->localKey, null);

			if (is_null($localKey) || ($localKey === ''))
			{
				return false;
			}

			$query = $db->getQuery(true)
				->select($db->qn($this->pivotForeignKey))
				->from($db->qn($this->pivotTable))
				->where($db->qn($this->pivotLocalKey) . ' = ' . $db->q($localKey));
			$db->setQuery($query);
			$foreignKeys = $db->loadColumn();

			$this->foreignKeyMap[$localKey] = $foreignKeys;

			// If there are no foreign keys (no foreign items assigned to our item) we return false which then causes
			// the relation to return null, marking the lack of data.
			if (empty($foreignKeys))
			{
				return false;
			}

			$foreignModel->where($this->foreignKey, 'in', $this->foreignKeyMap[$localKey]);
		}

		return true;
	}
}
Relation/HasMany.php000064400000011631152355061210010374 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Relation;

defined('_JEXEC') || die;

use FOF40\Model\DataModel;
use FOF40\Model\DataModel\Relation;

/**
 * HasMany (1-to-many) relation: this model is a parent which has zero or more children in the foreign table
 *
 * For example, parentModel is Users and foreignModel is Articles. Each user has zero or more articles.
 */
class HasMany extends Relation
{
	/**
	 * Public constructor. Initialises the relation.
	 *
	 * @param   DataModel  $parentModel       The data model we are attached to
	 * @param   string     $foreignModelName  The name of the foreign key's model in the format
	 *                                        "modelName@com_something"
	 * @param   string     $localKey          The local table key for this relation, default: parentModel's ID field
	 *                                        name
	 * @param   string     $foreignKey        The foreign key for this relation, default: parentModel's ID field name
	 * @param   string     $pivotTable        IGNORED
	 * @param   string     $pivotLocalKey     IGNORED
	 * @param   string     $pivotForeignKey   IGNORED
	 */
	public function __construct(DataModel $parentModel, $foreignModelName, $localKey = null, $foreignKey = null, $pivotTable = null, $pivotLocalKey = null, $pivotForeignKey = null)
	{
		parent::__construct($parentModel, $foreignModelName, $localKey, $foreignKey, $pivotTable, $pivotLocalKey, $pivotForeignKey);

		if (empty($this->localKey))
		{
			$this->localKey = $parentModel->getIdFieldName();
		}

		if (empty($this->foreignKey))
		{
			$this->foreignKey = $this->localKey;
		}
	}

	/**
	 * Returns the count subquery for DataModel's has() and whereHas() methods.
	 *
	 * @param   string  $tableAlias  The alias of the local table in the query. Leave blank to use the table's name.
	 *
	 * @return \JDatabaseQuery
	 */
	public function getCountSubquery($tableAlias = null)
	{
		// Get a model instance
		$foreignModel = $this->getForeignModel();
		$foreignModel->setIgnoreRequest(true);

		$db = $foreignModel->getDbo();

		if (empty($tableAlias))
		{
			$tableAlias = $this->parentModel->getTableName();
		}

		return $db->getQuery(true)
			->select('COUNT(*)')
			->from($db->qn($foreignModel->getTableName(), 'reltbl'))
			->where($db->qn('reltbl') . '.' . $db->qn($foreignModel->getFieldAlias($this->foreignKey)) . ' = '
				. $db->qn($tableAlias) . '.'
				. $db->qn($this->parentModel->getFieldAlias($this->localKey)));
	}

	/**
	 * Returns a new item of the foreignModel type, pre-initialised to fulfil this relation
	 *
	 * @return DataModel
	 *
	 * @throws DataModel\Relation\Exception\NewNotSupported when it's not supported
	 */
	public function getNew()
	{
		// Get a model instance
		$foreignModel = $this->getForeignModel();
		$foreignModel->setIgnoreRequest(true);

		// Prime the model
		$foreignModel->setFieldValue($this->foreignKey, $this->parentModel->getFieldValue($this->localKey));

		// Make sure we do have a data list
		if (!($this->data instanceof DataModel\Collection))
		{
			$this->getData();
		}

		// Add the model to the data list
		$this->data->add($foreignModel);

		return $this->data->last();
	}

	/**
	 * Applies the relation filters to the foreign model when getData is called
	 *
	 * @param   DataModel             $foreignModel    The foreign model you're operating on
	 * @param   DataModel\Collection  $dataCollection  If it's an eager loaded relation, the collection of loaded
	 *                                                 parent records
	 *
	 * @return boolean Return false to force an empty data collection
	 */
	protected function filterForeignModel(DataModel $foreignModel, DataModel\Collection $dataCollection = null)
	{
		// Decide how to proceed, based on eager or lazy loading
		if (is_object($dataCollection))
		{
			// Eager loaded relation
			if (!empty($dataCollection))
			{
				// Get a list of local keys from the collection
				$values = [];

				/** @var $item DataModel */
				foreach ($dataCollection as $item)
				{
					$v = $item->getFieldValue($this->localKey, null);

					if (!is_null($v))
					{
						$values[] = $v;
					}
				}

				// Keep only unique values. This double step is required to re-index the array and avoid issues with
				// Joomla Registry class. See issue #681
				$values = array_values(array_unique($values));

				// Apply the filter
				if (!empty($values))
				{
					$foreignModel->where($this->foreignKey, 'in', $values);
				}
				else
				{
					return false;
				}
			}
			else
			{
				return false;
			}
		}
		else
		{
			// Lazy loaded relation; get the single local key
			$localKey = $this->parentModel->getFieldValue($this->localKey, null);

			if (is_null($localKey) || ($localKey === ''))
			{
				return false;
			}

			$foreignModel->where($this->foreignKey, '==', $localKey);
		}

		return true;
	}
} 
Relation/HasOne.php000064400000002551152355061210010212 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Relation;

defined('_JEXEC') || die;

use FOF40\Model\DataModel;
use FOF40\Model\DataModel\Collection;

/**
 * HasOne (straight 1-to-1) relation: this model is a parent which has exactly one child in the foreign table
 *
 * For example, parentModel is Users and foreignModel is Phones. Each uses has exactly one Phone.
 */
class HasOne extends HasMany
{
	/**
	 * Get the relation data.
	 *
	 * If you want to apply additional filtering to the foreign model, use the $callback. It can be any function,
	 * static method, public method or closure with an interface of function(DataModel $foreignModel). You are not
	 * supposed to return anything, just modify $foreignModel's state directly. For example, you may want to do:
	 * $foreignModel->setState('foo', 'bar')
	 *
	 * @param callable   $callback The callback to run on the remote model.
	 * @param Collection $dataCollection
	 *
	 * @return Collection|DataModel
	 */
	public function getData($callback = null, Collection $dataCollection = null)
	{
		if (is_null($dataCollection))
		{
			return parent::getData($callback, $dataCollection)->first();
		}
		else
		{
			return parent::getData($callback, $dataCollection);
		}
	}
}
Relation/Exception/RelationTypeNotFound.php000064400000000454152355061210015067 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Relation\Exception;

defined('_JEXEC') || die;

class RelationTypeNotFound extends \Exception {}
Relation/Exception/ForeignModelNotFound.php000064400000000454152355061210015022 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Relation\Exception;

defined('_JEXEC') || die;

class ForeignModelNotFound extends \Exception {}
Relation/Exception/SaveNotSupported.php000064400000000450152355061210014254 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Relation\Exception;

defined('_JEXEC') || die;

class SaveNotSupported extends \Exception {}
Relation/Exception/PivotTableNotFound.php000064400000000452152355061210014517 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Relation\Exception;

defined('_JEXEC') || die;

class PivotTableNotFound extends \Exception {}
Relation/Exception/NewNotSupported.php000064400000000450152355061210014107 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Relation\Exception;

defined('_JEXEC') || die;

class NewNotSupported extends \Exception
{
}
Relation/Exception/RelationNotFound.php000064400000000450152355061210014221 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Relation\Exception;

defined('_JEXEC') || die;

class RelationNotFound extends \Exception {}
Filter/Relation.php000064400000001350152355061210010256 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Filter;

defined('_JEXEC') || die;

class Relation extends Number
{
	/** @var \JDatabaseQuery The COUNT subquery to filter by */
	protected $subQuery;

	public function __construct($db, $relationName, $subQuery)
	{
		$field = (object)array(
			'name'	=> $relationName,
			'type'	=> 'relation',
		);

		parent::__construct($db, $field);

		$this->subQuery = $subQuery;
	}

	public function callback($value)
	{
		return call_user_func($value, $this->subQuery);
	}

	public function getFieldName()
	{
		return '(' . $this->subQuery . ')';
	}
}
Filter/Boolean.php000064400000000760152355061210010064 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Filter;

defined('_JEXEC') || die;

class Boolean extends Number
{
	/**
	 * Is it a null or otherwise empty value?
	 *
	 * @param   mixed  $value  The value to test for emptiness
	 *
	 * @return  bool
	 */
	public function isEmpty($value)
	{
		return is_null($value) || ($value === '');
	}
} 
Filter/Number.php000064400000015721152355061210007740 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Filter;

defined('_JEXEC') || die;

class Number extends AbstractFilter
{
	/**
	 * The partial match is mapped to an exact match
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function partial($value)
	{
		return $this->exact($value);
	}

	/**
	 * Perform a between limits match. When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The highest value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function between($from, $to, $include = true)
	{
		$from = (float) $from;
		$to   = (float) $to;

		if ($this->isEmpty($from) || $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$from = $this->sanitiseValue($from);
		$to   = $this->sanitiseValue($to);

		$sql = '((' . $this->getFieldName() . ' >' . $extra . ' ' . $from . ') AND ';

		return $sql . ('(' . $this->getFieldName() . ' <' . $extra . ' ' . $to . '))');
	}

	/**
	 * Perform an outside limits match. When $include is true
	 * the condition tested is:
	 * (VALUE <= $from) || (VALUE >= $to)
	 * When $include is false the condition tested is:
	 * (VALUE < $from) || (VALUE > $to)
	 *
	 * @param   mixed    $from     The lowest value of the excluded range
	 * @param   mixed    $to       The highest value of the excluded range
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function outside($from, $to, $include = false)
	{
		$from = (float) $from;
		$to   = (float) $to;

		if ($this->isEmpty($from) || $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$from = $this->sanitiseValue($from);
		$to   = $this->sanitiseValue($to);

		$sql = '((' . $this->getFieldName() . ' <' . $extra . ' ' . $from . ') OR ';

		return $sql . ('(' . $this->getFieldName() . ' >' . $extra . ' ' . $to . '))');
	}

	/**
	 * Perform an interval match. It's similar to a 'between' match, but the
	 * from and to values are calculated based on $value and $interval:
	 * $value - $interval < VALUE < $value + $interval
	 *
	 * @param   integer|float  $value     The center value of the search space
	 * @param   integer|float  $interval  The width of the search space
	 * @param   boolean        $include   Should I include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause
	 */
	public function interval($value, $interval, $include = true)
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		// Convert them to float, just to be sure
		$value    = (float) $value;
		$interval = (float) $interval;

		$from = $value - $interval;
		$to   = $value + $interval;

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$from = $this->sanitiseValue($from);
		$to   = $this->sanitiseValue($to);

		$sql = '((' . $this->getFieldName() . ' >' . $extra . ' ' . $from . ') AND ';

		return $sql . ('(' . $this->getFieldName() . ' <' . $extra . ' ' . $to . '))');
	}

	/**
	 * Perform a range limits match. When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The highest value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function range($from, $to, $include = true)
	{
		if ($this->isEmpty($from) && $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = [];

		if ($from)
		{
			$sql[] = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $from . ')';
		}
		if ($to)
		{
			$sql[] = '(' . $this->getFieldName() . ' <' . $extra . ' ' . $to . ')';
		}

		return '(' . implode(' AND ', $sql) . ')';
	}

	/**
	 * Perform an interval match. It's similar to a 'between' match, but the
	 * from and to values are calculated based on $value and $interval:
	 * $value - $interval < VALUE < $value + $interval
	 *
	 * @param   integer|float  $value     The starting value of the search space
	 * @param   integer|float  $interval  The interval period of the search space
	 * @param   boolean        $include   Should I include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause
	 */
	public function modulo($value, $interval, $include = true)
	{
		if ($this->isEmpty($value) || $this->isEmpty($interval))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $value . ' AND ';

		return $sql . ('(' . $this->getFieldName() . ' - ' . $value . ') % ' . $interval . ' = 0)');
	}

	/**
	 * Overrides the parent to handle floats in locales where the decimal separator is a comma instead of a dot
	 *
	 * @param   mixed   $value
	 * @param   string  $operator
	 *
	 * @return  string
	 */
	public function search($value, $operator = '=')
	{
		$value = $this->sanitiseValue($value);

		return parent::search($value, $operator);
	}

	/**
	 * Sanitises float values. Really ugly and desperate workaround. Read below.
	 *
	 * Some locales, such as el-GR, use a comma as the decimal separator. This means that $x = 1.23; echo (string) $x;
	 * will yield 1,23 (with a comma!) instead of 1.23 (with a dot!). This affects the way the SQL WHERE clauses are
	 * generated. All database servers expect a dot as the decimal separator. If they see a decimal with a comma as the
	 * separator they throw a SQL error.
	 *
	 * This method will try to replace commas with dots. I tried working around this with locale switching and the %F
	 * (capital F) format option in sprintf to no avail. I'm pretty sure I was doing something wrong, but I ran out of
	 * time trying to find an academically correct solution. The current implementation of sanitiseValue is a silly
	 * hack around the problem. If you have a proper –and better performing– solution please send in a PR and I'll put
	 * it to the test.
	 *
	 * @param   mixed  $value  A string representing a number, integer, float or array of them.
	 *
	 * @return  mixed  The sanitised value, or null if the input wasn't numeric.
	 */
	public function sanitiseValue($value)
	{
		if (!is_numeric($value) && !is_string($value) && !is_array($value))
		{
			$value = null;
		}

		if (!is_array($value))
		{
			return str_replace(',', '.', (string) $value);
		}

		return array_map([$this, 'sanitiseValue'], $value);
	}
}
Filter/AbstractFilter.php000064400000023074152355061210011421 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Filter;

defined('_JEXEC') || die;

use FOF40\Model\DataModel\Filter\Exception\InvalidFieldObject;
use FOF40\Model\DataModel\Filter\Exception\NoDatabaseObject;

abstract class AbstractFilter
{
	/**
	 * The null value for this type
	 *
	 * @var  mixed
	 */
	public $null_value;

	protected $db;

	/**
	 * The column name of the table field
	 *
	 * @var string
	 */
	protected $name = '';

	/**
	 * The column type of the table field
	 *
	 * @var string
	 */
	protected $type = '';

	/**
	 * Should I allow filtering against the number 0?
	 *
	 * @var bool
	 */
	protected $filterZero = true;

	/**
	 * Prefix each table name with this table alias. For example, field bar normally creates a WHERE clause:
	 * `bar` = '1'
	 * If tableAlias is set to "foo" then the WHERE clause it generates becomes
	 * `foo`.`bar` = '1'
	 *
	 * @var  null
	 */
	protected $tableAlias;

	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db     The database object
	 * @param   object            $field  The field information as taken from the db
	 */
	public function __construct($db, $field)
	{
		$this->db = $db;

		if (!is_object($field) || !isset($field->name) || !isset($field->type))
		{
			throw new InvalidFieldObject;
		}

		$this->name = $field->name;
		$this->type = $field->type;

		if (isset ($field->filterZero))
		{
			$this->filterZero = $field->filterZero;
		}

		if (isset ($field->tableAlias))
		{
			$this->tableAlias = $field->tableAlias;
		}
	}

	/**
	 * Creates a field Object based on the field column type
	 *
	 * @param   object  $field   The field information
	 * @param   array   $config  The field configuration (like the db object to use)
	 *
	 * @return  AbstractFilter  The Filter object
	 *
	 * @throws  \InvalidArgumentException
	 */
	public static function getField($field, $config = [])
	{
		if (!is_object($field) || !isset($field->name) || !isset($field->type))
		{
			throw new InvalidFieldObject;
		}

		$type = $field->type;

		$classType = self::getFieldType($type);

		$className = '\\FOF40\\Model\\DataModel\\Filter\\' . ucfirst($classType);

		if (($classType !== false) && class_exists($className, true))
		{
			if (!isset($config['dbo']))
			{
				throw new NoDatabaseObject($className);
			}

			$db = $config['dbo'];

			return new $className($db, $field);
		}

		return null;
	}

	/**
	 * Get the class name based on the field Type
	 *
	 * @param   string  $type  The type of the field
	 *
	 * @return  string  the class name suffix
	 */
	public static function getFieldType($type)
	{
		// Remove parentheses, indicating field options / size (they don't matter in type detection)
		if (!empty($type))
		{
			[$type,] = explode('(', $type);
		}

		$detectedType = null;

		switch (trim($type))
		{
			case 'varchar':
			case 'text':
			case 'smalltext':
			case 'longtext':
			case 'char':
			case 'mediumtext':
			case 'character varying':
			case 'nvarchar':
			case 'nchar':
				$detectedType = 'Text';
				break;

			case 'date':
			case 'datetime':
			case 'time':
			case 'year':
			case 'timestamp':
			case 'timestamp without time zone':
			case 'timestamp with time zone':
				$detectedType = 'Date';
				break;

			case 'tinyint':
			case 'smallint':
				$detectedType = 'Boolean';
				break;
		}

		// Sometimes we have character types followed by a space and some cruft. Let's handle them.
		if (is_null($detectedType) && !empty($type))
		{
			[$type,] = explode(' ', $type);

			switch (trim($type))
			{
				case 'varchar':
				case 'text':
				case 'smalltext':
				case 'longtext':
				case 'char':
				case 'mediumtext':
				case 'nvarchar':
				case 'nchar':
					$detectedType = 'Text';
					break;

				case 'date':
				case 'datetime':
				case 'time':
				case 'year':
				case 'timestamp':
					$detectedType = 'Date';
					break;

				case 'tinyint':
				case 'smallint':
					$detectedType = 'Boolean';
					break;

				default:
					$detectedType = 'Number';
					break;
			}
		}

		// If all else fails assume it's a Number and hope for the best
		if (empty($detectedType))
		{
			$detectedType = 'Number';
		}

		return $detectedType;
	}

	/**
	 * Is it a null or otherwise empty value?
	 *
	 * @param   mixed  $value  The value to test for emptiness
	 *
	 * @return  boolean
	 */
	public function isEmpty($value)
	{
		return (($value === $this->null_value) || empty($value))
			&& !($this->filterZero && ($value === "0"));
	}

	/**
	 * Returns the default search method for a field. This always returns 'exact'
	 * and you are supposed to override it in specialised classes. The possible
	 * values are exact, partial, between and outside, unless something
	 * different is returned by getSearchMethods().
	 *
	 * @return  string
	 * @see  self::getSearchMethods()
	 *
	 */
	public function getDefaultSearchMethod()
	{
		return 'exact';
	}

	/**
	 * Return the search methods available for this field class,
	 *
	 * @return  array
	 */
	public function getSearchMethods()
	{
		$ignore = [
			'isEmpty', 'getField', 'getFieldType', '__construct', 'getDefaultSearchMethod', 'getSearchMethods',
			'getFieldName',
		];

		$class   = new \ReflectionClass(__CLASS__);
		$methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC);

		$tmp = [];

		foreach ($methods as $method)
		{
			$tmp[] = $method->name;
		}

		$methods = $tmp;

		if ($methods = array_diff($methods, $ignore))
		{
			return $methods;
		}

		return [];
	}

	/**
	 * Perform an exact match (equality matching)
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function exact($value)
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		if (is_array($value))
		{
			$db    = $this->db;
			$value = array_map([$db, 'quote'], $value);

			return '(' . $this->getFieldName() . ' IN (' . implode(',', $value) . '))';
		}
		else
		{
			return $this->search($value);
		}
	}

	/**
	 * Perform a partial match (usually: search in string)
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function partial($value);

	/**
	 * Perform a between limits match (usually: search for a value between
	 * two numbers or a date between two preset dates). When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The highest value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function between($from, $to, $include = true);

	/**
	 * Perform an outside limits match (usually: search for a value outside an
	 * area or a date outside a preset period). When $include is true
	 * the condition tested is:
	 * (VALUE <= $from) || (VALUE >= $to)
	 * When $include is false the condition tested is:
	 * (VALUE < $from) || (VALUE > $to)
	 *
	 * @param   mixed    $from     The lowest value of the excluded range
	 * @param   mixed    $to       The highest value of the excluded range
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function outside($from, $to, $include = false);

	/**
	 * Perform an interval search (usually: a date interval check)
	 *
	 * @param   string               $from      The value to search
	 * @param   string|array|object  $interval  The interval
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function interval($from, $interval);

	/**
	 * Perform a between limits match (usually: search for a value between
	 * two numbers or a date between two preset dates). When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The higherst value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	abstract public function range($from, $to, $include = true);

	/**
	 * Perform an modulo search
	 *
	 * @param   integer|float  $from      The starting value of the search space
	 * @param   integer|float  $interval  The interval period of the search space
	 * @param   boolean        $include   Should I include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause
	 */
	abstract public function modulo($from, $interval, $include = true);

	/**
	 * Return the SQL where clause for a search
	 *
	 * @param   mixed   $value     The value to search for
	 * @param   string  $operator  The operator to use
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function search($value, $operator = '=')
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		$prefix = '';

		if (substr($operator, 0, 1) == '!')
		{
			$prefix   = 'NOT ';
			$operator = substr($operator, 1);
		}

		return $prefix . '(' . $this->getFieldName() . ' ' . $operator . ' ' . $this->db->quote($value) . ')';
	}

	/**
	 * Get the field name
	 *
	 * @return  string    The field name
	 */
	public function getFieldName()
	{
		$name = $this->db->qn($this->name);

		if (!empty($this->tableAlias))
		{
			$name = $this->db->qn($this->tableAlias) . '.' . $name;
		}

		return $name;
	}
}
Filter/Date.php000064400000011677152355061210007373 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Filter;

defined('_JEXEC') || die;

class Date extends Text
{
	/**
	 * Returns the default search method for this field.
	 *
	 * @return  string
	 */
	public function getDefaultSearchMethod()
	{
		return 'exact';
	}

	/**
	 * Perform a between limits match. When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The highest value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function between($from, $to, $include = true)
	{
		if ($this->isEmpty($from) || $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '((' . $this->getFieldName() . ' >' . $extra . ' ' . $this->db->q($from) . ') AND ';

		return $sql . ('(' . $this->getFieldName() . ' <' . $extra . ' ' . $this->db->q($to) . '))');
	}

	/**
	 * Perform an outside limits match. When $include is true
	 * the condition tested is:
	 * (VALUE <= $from) || (VALUE >= $to)
	 * When $include is false the condition tested is:
	 * (VALUE < $from) || (VALUE > $to)
	 *
	 * @param   mixed    $from     The lowest value of the excluded range
	 * @param   mixed    $to       The highest value of the excluded range
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function outside($from, $to, $include = false)
	{
		if ($this->isEmpty($from) || $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '((' . $this->getFieldName() . ' <' . $extra . ' ' . $this->db->q($from) . ') AND ';

		return $sql . ('(' . $this->getFieldName() . ' >' . $extra . ' ' . $this->db->q($to) . '))');
	}

	/**
	 * Interval date search
	 *
	 * @param   string               $value     The value to search
	 * @param   string|array|object  $interval  The interval. Can be (+1 MONTH or array('value' => 1, 'unit' =>
	 *                                          'MONTH', 'sign' => '+'))
	 * @param   boolean              $include   If the borders should be included
	 *
	 * @return  string  the sql string
	 */
	public function interval($value, $interval, $include = true)
	{
		if ($this->isEmpty($value) || $this->isEmpty($interval))
		{
			return '';
		}

		$interval = $this->getInterval($interval);

		// Sanity check on $interval array
		if (!isset($interval['sign']) || !isset($interval['value']) || !isset($interval['unit']))
		{
			return '';
		}

		$function = $interval['sign'] == '+' ? 'DATE_ADD' : 'DATE_SUB';

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $function;

		return $sql . ('(' . $this->getFieldName() . ', INTERVAL ' . $interval['value'] . ' ' . $interval['unit'] . '))');
	}

	/**
	 * Perform a between limits match. When $include is true
	 * the condition tested is:
	 * $from <= VALUE <= $to
	 * When $include is false the condition tested is:
	 * $from < VALUE < $to
	 *
	 * @param   mixed    $from     The lowest value to compare to
	 * @param   mixed    $to       The highest value to compare to
	 * @param   boolean  $include  Should we include the boundaries in the search?
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function range($from, $to, $include = true)
	{
		if ($this->isEmpty($from) && $this->isEmpty($to))
		{
			return '';
		}

		$extra = '';

		if ($include)
		{
			$extra = '=';
		}

		$sql = [];

		if ($from)
		{
			$sql[] = '(' . $this->getFieldName() . ' >' . $extra . ' ' . $this->db->q($from) . ')';
		}
		if ($to)
		{
			$sql[] = '(' . $this->getFieldName() . ' <' . $extra . ' ' . $this->db->q($to) . ')';
		}

		return '(' . implode(' AND ', $sql) . ')';
	}

	/**
	 * Parses an interval –which may be given as a string, array or object– into
	 * a standardised hash array that can then be used bu the interval() method.
	 *
	 * @param   string|array|object  $interval  The interval expression to parse
	 *
	 * @return  array  The parsed, hash array form of the interval
	 */
	protected function getInterval($interval)
	{
		if (is_string($interval))
		{
			if (strlen($interval) > 2)
			{
				$interval = explode(" ", $interval);
				$sign     = ($interval[0] == '-') ? '-' : '+';
				$value    = (int) substr($interval[0], 1);

				$interval = [
					'unit'  => $interval[1],
					'value' => $value,
					'sign'  => $sign,
				];
			}
			else
			{
				$interval = [
					'unit'  => 'MONTH',
					'value' => 1,
					'sign'  => '+',
				];
			}
		}
		else
		{
			$interval = (array) $interval;
		}

		return $interval;
	}

}
Filter/Text.php000064400000006211152355061210007426 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Filter;

defined('_JEXEC') || die;

class Text extends AbstractFilter
{
	/**
	 * Constructor
	 *
	 * @param   \JDatabaseDriver  $db     The database object
	 * @param   object            $field  The field information as taken from the db
	 */
	public function __construct($db, $field)
	{
		parent::__construct($db, $field);

		$this->null_value = '';
	}

	/**
	 * Returns the default search method for this field.
	 *
	 * @return  string
	 */
	public function getDefaultSearchMethod()
	{
		return 'partial';
	}

	/**
	 * Perform a partial match (search in string)
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function partial($value)
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		return '(' . $this->getFieldName() . ' LIKE ' . $this->db->quote('%' . $value . '%') . ')';
	}

	/**
	 * Perform an exact match (match string)
	 *
	 * @param   mixed  $value  The value to compare to
	 *
	 * @return  string  The SQL where clause for this search
	 */
	public function exact($value)
	{
		if ($this->isEmpty($value))
		{
			return '';
		}

		if (is_array($value) || is_object($value))
		{
			$value = (array) $value;

			$db    = $this->db;
			$value = array_map([$db, 'quote'], $value);

			return '(' . $this->getFieldName() . ' IN (' . implode(',', $value) . '))';
		}

		return '(' . $this->getFieldName() . ' LIKE ' . $this->db->quote($value) . ')';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $from     Ignored
	 * @param   mixed    $to       Ignored
	 * @param   boolean  $include  Ignored
	 *
	 * @return  string  Empty string
	 */
	public function between($from, $to, $include = true)
	{
		return '';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $from     Ignored
	 * @param   mixed    $to       Ignored
	 * @param   boolean  $include  Ignored
	 *
	 * @return  string  Empty string
	 */
	public function outside($from, $to, $include = false)
	{
		return '';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $value     Ignored
	 * @param   mixed    $interval  Ignored
	 * @param   boolean  $include   Ignored
	 *
	 * @return  string  Empty string
	 */
	public function interval($value, $interval, $include = true)
	{
		return '';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $from     Ignored
	 * @param   mixed    $to       Ignored
	 * @param   boolean  $include  Ignored
	 *
	 * @return  string  Empty string
	 */
	public function range($from, $to, $include = false)
	{
		return '';
	}

	/**
	 * Dummy method; this search makes no sense for text fields
	 *
	 * @param   mixed    $from      Ignored
	 * @param   mixed    $interval  Ignored
	 * @param   boolean  $include   Ignored
	 *
	 * @return  string  Empty string
	 */
	public function modulo($from, $interval, $include = false)
	{
		return '';
	}
} 
Filter/Exception/NoDatabaseObject.php000064400000001106152355061210013566 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Filter\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class NoDatabaseObject extends \InvalidArgumentException
{
	public function __construct( $fieldType, $code = 500, Exception $previous = null )
	{
		$message = Text::sprintf('LIB_FOF40_MODEL_ERR_FILTER_NODBOBJECT', $fieldType);

		parent::__construct( $message, $code, $previous );
	}

}
Filter/Exception/InvalidFieldObject.php000064400000001133152355061210014117 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Filter\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class InvalidFieldObject extends \InvalidArgumentException
{
	public function __construct( $message = "", $code = 500, Exception $previous = null )
	{
		if (empty($message))
		{
			$message = Text::_('LIB_FOF40_MODEL_ERR_FILTER_INVALIDFIELD');
		}

		parent::__construct( $message, $code, $previous );
	}

}
Behaviour/Enabled.php000064400000003342152355061210010535 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use JDatabaseQuery;

/**
 * FOF model behavior class to filter access to items based on the enabled status
 *
 * @since    2.1
 */
class Enabled extends Observer
{
	/**
	 * This event runs before we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   DataModel      &$model  The model which calls this event
	 * @param   JDatabaseQuery &$query  The query we are manipulating
	 *
	 * @return  void
	 */
	public function onBeforeBuildQuery(DataModel &$model, JDatabaseQuery &$query)
	{
		// Make sure the field actually exists
		if (!$model->hasField('enabled'))
		{
			return;
		}

		$fieldName = $model->getFieldAlias('enabled');
		$db        = $model->getDbo();

		$model->whereRaw($db->qn($fieldName) . ' = ' . $db->q(1));
	}

	/**
	 * The event runs after DataModel has retrieved a single item from the database. It is used to apply automatic
	 * filters.
	 *
	 * @param   DataModel &$model  The model which was called
	 * @param   mixed     &$keys   The keys used to locate the record which was loaded
	 *
	 * @return  void
	 */
	public function onAfterLoad(DataModel &$model, &$keys)
	{
		// Make sure we have a DataModel
		if (!($model instanceof DataModel))
		{
			return;
		}

		// Make sure the field actually exists
		if (!$model->hasField('enabled'))
		{
			return;
		}

		// Filter by enabled status
		if (!$model->getFieldValue('enabled', 0))
		{
			$model->reset(true);
		}
	}
}
Behaviour/Access.php000064400000003447152355061210010412 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use JDatabaseQuery;

/**
 * FOF model behavior class to filter access to items based on the viewing access levels.
 *
 * @since    2.1
 */
class Access extends Observer
{
	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   DataModel      &$model The model which calls this event
	 * @param   JDatabaseQuery &$query The query we are manipulating
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(DataModel &$model, JDatabaseQuery &$query)
	{
		// Make sure the field actually exists
		if (!$model->hasField('access'))
		{
			return;
		}

		$model->applyAccessFiltering(null);
	}

	/**
	 * The event runs after DataModel has retrieved a single item from the database. It is used to apply automatic
	 * filters.
	 *
	 * @param   DataModel &$model  The model which was called
	 * @param   mixed     &$keys   The keys used to locate the record which was loaded
	 *
	 * @return  void
	 */
	public function onAfterLoad(DataModel &$model, &$keys)
	{
		// Make sure we have a DataModel
		if (!($model instanceof DataModel))
		{
			return;
		}

		// Make sure the field actually exists
		if (!$model->hasField('access'))
		{
			return;
		}

		// Get the user
		$user = $model->getContainer()->platform->getUser();
		$recordAccessLevel = $model->getFieldValue('access', null);

		// Filter by authorised access levels
		if (!in_array($recordAccessLevel, $user->getAuthorisedViewLevels()))
		{
			$model->reset(true);
		}
	}
}
Behaviour/PageParametersToState.php000064400000003324152355061210013407 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Factory as JoomlaFactory;
use Joomla\Registry\Registry;

/**
 * FOF model behavior class to populate the state with the front-end page parameters
 *
 * @since    2.1
 */
class PageParametersToState extends Observer
{
	public function onAfterConstruct(DataModel &$model)
	{
		// This only applies to the front-end
		if (!$model->getContainer()->platform->isFrontend())
		{
			return;
		}

		// Get the page parameters
		/** @var SiteApplication $app */
		$app = JoomlaFactory::getApplication();
		/** @var Registry $params */
		$params = $app->getParams();

		// Extract the page parameter keys
		$asArray = [];

		if (is_object($params) && method_exists($params, 'toArray'))
		{
			$asArray = $params->toArray();
		}

		if (empty($asArray))
		{
			// There are no keys; no point in going on.
			return;
		}

		$keys = array_keys($asArray);
		unset($asArray);

		// Loop all page parameter keys
		foreach ($keys as $key)
		{
			// This is the current model state
			$currentState = $model->getState($key);

			// This is the explicitly requested state in the input
			$explicitInput = $model->input->get($key, null, 'raw');

			// If the current state is empty and there's no explicit input we'll use the page parameters instead
			if (!is_null($currentState))
			{
				return;
			}

			if (!is_null($explicitInput))
			{
				return;
			}

			$model->setState($key, $params->get($key));
		}
	}
}
Behaviour/Filters.php000064400000006621152355061210010616 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use JDatabaseQuery;
use Joomla\Registry\Registry;

class Filters extends Observer
{
	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   DataModel  &$model  The model which calls this event
	 * @param   JDatabaseQuery      &$query  The query we are manipulating
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(DataModel &$model, JDatabaseQuery &$query)
	{
		$tableKey = $model->getIdFieldName();
		$db = $model->getDbo();

		$fields     = $model->getTableFields();
		$blacklist  = $model->getBlacklistFilters();
		$filterZero = $model->getBehaviorParam('filterZero', null);
		$tableAlias = $model->getBehaviorParam('tableAlias', null);

		foreach ($fields as $fieldname => $fieldmeta)
		{
			if (in_array($fieldname, $blacklist))
			{
				continue;
			}

			$fieldInfo = (object)array(
				'name'	=> $fieldname,
				'type'	=> $fieldmeta->Type,
				'filterZero' => $filterZero,
				'tableAlias' => $tableAlias,
			);

			$filterName = $fieldInfo->name;
			$filterState = $model->getState($filterName, null);

			// Special primary key handling: if ignore request is set we'll also look for an 'id' state variable if a
			// state variable by the same name as the key doesn't exist. If ignore request is not set in the model we
			// do not filter by 'id' since this interferes with going from an edit page to a browse page (the list is
			// filtered by id without user controls to unset it).
			if ($fieldInfo->name == $tableKey)
			{
				$filterState = $model->getState($filterName, null);

				if (!$model->getIgnoreRequest())
				{
					continue;
				}

				if (empty($filterState))
				{
					$filterState = $model->getState('id', null);
				}
			}

			$field = DataModel\Filter\AbstractFilter::getField($fieldInfo, array('dbo' => $db));

			if (!is_object($field) || !($field instanceof DataModel\Filter\AbstractFilter))
			{
				continue;
			}

			if ((is_array($filterState) && (
						array_key_exists('value', $filterState) ||
						array_key_exists('from', $filterState) ||
						array_key_exists('to', $filterState)
					)) || is_object($filterState))
			{
				$options = new Registry($filterState);
			}
			else
			{
				$options = new Registry();
				$options->set('value', $filterState);
			}

			$methods = $field->getSearchMethods();
			$method = $options->get('method', $field->getDefaultSearchMethod());

			if (!in_array($method, $methods))
			{
				$method = 'exact';
			}

			switch ($method)
			{
				case 'between':
				case 'outside':
				case 'range' :
					$sql = $field->$method($options->get('from', null), $options->get('to', null), $options->get('include', false));
					break;

				case 'interval':
				case 'modulo':
					$sql = $field->$method($options->get('value', null), $options->get('interval'));
					break;

				case 'search':
					$sql = $field->$method($options->get('value', null), $options->get('operator', '='));
					break;

				case 'exact':
				case 'partial':
				default:
					$sql = $field->$method($options->get('value', null));
					break;
			}

			if ($sql)
			{
				$query->where($sql);
			}
		}
	}
}
Behaviour/EmptyNonZero.php000064400000001636152355061210011620 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use JDatabaseQuery;

/**
 * FOF model behavior class to let the Filters behaviour know that zero value is a valid filter value
 *
 * @since    2.1
 */
class EmptyNonZero extends Observer
{
	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   DataModel      &$model  The model which calls this event
	 * @param   JDatabaseQuery &$query  The query we are manipulating
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(DataModel &$model, JDatabaseQuery &$query)
	{
		$model->setBehaviorParam('filterZero', 1);
	}
}
Behaviour/ContentHistory.php000064400000004244152355061210012201 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use ContenthistoryHelper;
use FOF40\Event\Observer;
use FOF40\Model\DataModel;

/**
 * FOF model behavior class to add Joomla! content history support
 *
 * @since    2.1
 */
class ContentHistory extends Observer
{
	/** @var  ContentHistoryHelper */
	protected $historyHelper;

	/**
	 * The event which runs after storing (saving) data to the database
	 *
	 * @param   DataModel  &$model  The model which calls this event
	 *
	 * @return  boolean  True to allow saving without an error
	 */
	public function onAfterSave(DataModel &$model)
	{
		$model->checkContentType();

		$componentParams = $model->getContainer()->params;

		if ($componentParams->get('save_history', 0))
		{
			if (!$this->historyHelper)
			{
				$this->historyHelper = new ContentHistoryHelper($model->getContentType());
			}

			$this->historyHelper->store($model);
		}

		return true;
	}

	/**
	 * The event which runs before deleting a record
	 *
	 * @param   DataModel &$model  The model which calls this event
	 * @param   integer    $oid    The PK value of the record to delete
	 *
	 * @return  boolean  True to allow the deletion
	 */
	public function onBeforeDelete(DataModel &$model, $oid)
	{
		$componentParams = $model->getContainer()->params;

		if ($componentParams->get('save_history', 0))
		{
			if (!$this->historyHelper)
			{
				$this->historyHelper = new ContentHistoryHelper($model->getContentType());
			}

			$this->historyHelper->deleteHistory($model);
		}

		return true;
	}

	/**
	 * This event runs after publishing a record in a model
	 *
	 * @param   DataModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterPublish(DataModel &$model)
	{
		$model->updateUcmContent();
	}

	/**
	 * This event runs after unpublishing a record in a model
	 *
	 * @param   DataModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterUnpublish(DataModel &$model)
	{
		$model->updateUcmContent();
	}
}
Behaviour/Own.php000064400000004012152355061210007741 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use JDatabaseQuery;

/**
 * FOF model behavior class to filter access to items owned by the currently logged in user only
 *
 * @since    2.1
 */
class Own extends Observer
{
	/**
	 * This event runs after we have built the query used to fetch a record
	 * list in a model. It is used to apply automatic query filters.
	 *
	 * @param   DataModel      &$model  The model which calls this event
	 * @param   JDatabaseQuery &$query  The query we are manipulating
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(DataModel &$model, JDatabaseQuery &$query)
	{
		// Make sure the field actually exists
		if (!$model->hasField('created_by'))
		{
			return;
		}

		// Get the current user's id
		$user_id = $model->getContainer()->platform->getUser()->id;

		// And filter the query output by the user id
		$db = $model->getContainer()->platform->getDbo();

		$query->where($db->qn($model->getFieldAlias('created_by')) . ' = ' . $db->q($user_id));
	}

	/**
	 * The event runs after DataModel has retrieved a single item from the database. It is used to apply automatic
	 * filters.
	 *
	 * @param   DataModel &$model  The model which was called
	 * @param   mixed     &$keys   The keys used to locate the record which was loaded
	 *
	 * @return  void
	 */
	public function onAfterLoad(DataModel &$model, &$keys)
	{
		// Make sure we have a DataModel
		if (!($model instanceof DataModel))
		{
			return;
		}

		// Make sure the field actually exists
		if (!$model->hasField('created_by'))
		{
			return;
		}

		// Get the user
		$user_id    = $model->getContainer()->platform->getUser()->id;
		$recordUser = $model->getFieldValue('created_by', null);

		// Filter by authorised access levels
		if ($recordUser != $user_id)
		{
			$model->reset(true);
		}
	}
}
Behaviour/Created.php000064400000004050152355061210010547 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;

/**
 * FOF model behavior class to updated the created_by and created_on fields on newly created records.
 *
 * This behaviour is added to DataModel by default. If you want to remove it you need to do
 * $this->behavioursDispatcher->removeBehaviour('Created');
 *
 * @since  3.0
 */
class Created extends Observer
{
	/**
	 * Add the created_on and created_by fields in the fieldsSkipChecks list of the model. We expect them to be empty
	 * so that we can fill them in through this behaviour.
	 *
	 * @param   DataModel  $model
	 */
	public function onBeforeCheck(DataModel &$model)
	{
		$model->addSkipCheckField('created_on');
		$model->addSkipCheckField('created_by');
	}

	/**
	 * @param   DataModel  $model
	 * @param   \stdClass  $dataObject
	 */
	public function onBeforeCreate(DataModel &$model, &$dataObject)
	{
		// Handle the created_on field
		if ($model->hasField('created_on'))
		{
			$nullDate   = $model->isNullableField('created_on') ? null : $model->getDbo()->getNullDate();
			$created_on = $model->getFieldValue('created_on');

			if (empty($created_on) || ($created_on == $nullDate))
			{
				$model->setFieldValue('created_on', $model->getContainer()->platform->getDate()->toSql(false, $model->getDbo()));

				$createdOnField              = $model->getFieldAlias('created_on');
				$dataObject->$createdOnField = $model->getFieldValue('created_on');
			}
		}

		// Handle the created_by field
		if ($model->hasField('created_by'))
		{
			$created_by = $model->getFieldValue('created_by');

			if (empty($created_by))
			{
				$model->setFieldValue('created_by', $model->getContainer()->platform->getUser()->id);

				$createdByField              = $model->getFieldAlias('created_by');
				$dataObject->$createdByField = $model->getFieldValue('created_by');
			}
		}
	}
}
Behaviour/Modified.php000064400000003623152355061210010725 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use JDatabaseQuery;

/**
 * FOF model behavior class to updated the modified_by and modified_on fields on newly created records.
 *
 * This behaviour is added to DataModel by default. If you want to remove it you need to do
 * $this->behavioursDispatcher->removeBehaviour('Modified');
 *
 * @since  3.0
 */
class Modified extends Observer
{
	/**
	 * Add the modified_on and modified_by fields in the fieldsSkipChecks list of the model. We expect them to be empty
	 * so that we can fill them in through this behaviour.
	 *
	 * @param   DataModel  $model
	 */
	public function onBeforeCheck(DataModel &$model)
	{
		$model->addSkipCheckField('modified_on');
		$model->addSkipCheckField('modified_by');
	}

	/**
	 * @param   DataModel  $model
	 * @param   \stdClass  $dataObject
	 */
	public function onBeforeUpdate(DataModel &$model, &$dataObject)
	{
		// Make sure we're not modifying a locked record
		$userId = $model->getContainer()->platform->getUser()->id;
		$isLocked = $model->isLocked($userId);

		if ($isLocked)
		{
			return;
		}

		// Handle the modified_on field
		if ($model->hasField('modified_on'))
		{
			$model->setFieldValue('modified_on', $model->getContainer()->platform->getDate()->toSql(false, $model->getDbo()));

			$modifiedOnField = $model->getFieldAlias('modified_on');
			$dataObject->$modifiedOnField = $model->getFieldValue('modified_on');
		}

		// Handle the modified_by field
		if ($model->hasField('modified_by'))
		{
			$model->setFieldValue('modified_by', $userId);

			$modifiedByField = $model->getFieldAlias('modified_by');
			$dataObject->$modifiedByField = $model->getFieldValue('modified_by');
		}
	}
}
Behaviour/RelationFilters.php000064400000004360152355061210012312 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use JDatabaseQuery;
use Joomla\Registry\Registry;

class RelationFilters extends Observer
{
	/**
	 * This event runs after we have built the query used to fetch a record list in a model. It is used to apply
	 * automatic query filters based on model relations.
	 *
	 * @param   DataModel  &$model  The model which calls this event
	 * @param   JDatabaseQuery      &$query  The query we are manipulating
	 *
	 * @return  void
	 */
	public function onAfterBuildQuery(DataModel &$model, JDatabaseQuery &$query)
	{
		$relationFilters = $model->getRelationFilters();

		foreach ($relationFilters as $filterState)
		{
			$relationName = $filterState['relation'];

			$tableAlias = $model->getBehaviorParam('tableAlias', null);
			$subQuery = $model->getRelations()->getCountSubquery($relationName, $tableAlias);

			// Callback method needs different handling
			if (isset($filterState['method']) && ($filterState['method'] == 'callback'))
			{
				call_user_func_array($filterState['value'], array(&$subQuery));
				$filterState['method'] = 'search';
				$filterState['operator'] = '>=';
				$filterState['value'] = '1';
			}

			$options = new Registry($filterState);

			$filter = new DataModel\Filter\Relation($model->getDbo(), $relationName, $subQuery);
			$methods = $filter->getSearchMethods();
			$method = $options->get('method', $filter->getDefaultSearchMethod());

			if (!in_array($method, $methods))
			{
				$method = 'exact';
			}

			switch ($method)
			{
				case 'between':
				case 'outside':
					$sql = $filter->$method($options->get('from', null), $options->get('to'));
					break;

				case 'interval':
					$sql = $filter->$method($options->get('value', null), $options->get('interval'));
					break;

				case 'search':
					$sql = $filter->$method($options->get('value', null), $options->get('operator', '='));
					break;

				default:
					$sql = $filter->$method($options->get('value', null));
					break;
			}

			if ($sql)
			{
				$query->where($sql);
			}
		}
	}
} 
Behaviour/Tags.php000064400000010100152355061210010067 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observable;
use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use Joomla\CMS\Helper\TagsHelper;

/**
 * FOF model behavior class to add Joomla! Tags support
 *
 * @since    2.1
 */
class Tags extends Observer
{
	/** @var TagsHelper */
	protected $tagsHelper;

	public function __construct(Observable &$subject)
	{
		parent::__construct($subject);

		$this->tagsHelper = new TagsHelper();
	}

	/**
	 * This event runs after unpublishing a record in a model
	 *
	 * @param   DataModel  &$model       The model which calls this event
	 * @param   \stdClass  &$dataObject  The data to bind to the form
	 *
	 * @return  void
	 */
	public function onBeforeCreate(DataModel &$model, &$dataObject)
	{
		$tagField = $model->getBehaviorParam('tagFieldName', 'tags');

		unset($dataObject->$tagField);
	}

	/**
	 * This event runs after unpublishing a record in a model
	 *
	 * @param   DataModel  &$model       The model which calls this event
	 * @param   \stdClass  &$dataObject  The data to bind to the form
	 *
	 * @return  void
	 */
	public function onBeforeUpdate(DataModel &$model, &$dataObject)
	{
		$tagField = $model->getBehaviorParam('tagFieldName', 'tags');

		unset($dataObject->$tagField);
	}

	/**
	 * The event which runs after binding data to the table
	 *
	 * @param   DataModel    &$model  The model which calls this event
	 *
	 * @return  void
	 *
	 * @throws  \Exception  Error message if failed to store tags
	 */
	public function onAfterSave(DataModel &$model)
	{
		$tagField = $model->getBehaviorParam('tagFieldName', 'tags');

		// Avoid to update on other method (e.g. publish, ...)
		if (!in_array($model->getContainer()->input->getCmd('task'), ['apply', 'save', 'savenew']))
		{
			return;
		}

		$oldTags = $this->tagsHelper->getTagIds($model->getId(), $model->getContentType());
		$newTags = $model->$tagField ? implode(',', $model->$tagField) : null;

		// If no changes, we stop here
		if ($oldTags == $newTags)
		{
			return;
		}

		// Check if the content type exists, and create it if it does not
		$model->checkContentType();

		$this->tagsHelper->typeAlias = $model->getContentType();

		if (!$this->tagsHelper->postStoreProcess($model, $model->$tagField))
		{
			throw new \Exception('Error storing tags');
		}
	}

	/**
	 * The event which runs after deleting a record
	 *
	 * @param   DataModel &$model  The model which calls this event
	 * @param   integer    $oid    The PK value of the record which was deleted
	 *
	 * @return  void
	 *
	 * @throws  \Exception  Error message if failed to detele tags
	 */
	public function onAfterDelete(DataModel &$model, $oid)
	{
		$this->tagsHelper->typeAlias = $model->getContentType();

		if (!$this->tagsHelper->deleteTagData($model, $oid))
		{
			throw new \Exception('Error deleting Tags');
		}
	}

	/**
	 * This event runs after unpublishing a record in a model
	 *
	 * @param   DataModel  &$model  The model which calls this event
	 * @param   mixed       $data   An associative array or object to bind to the DataModel instance.
	 *
	 * @return  void
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function onAfterBind(DataModel &$model, &$data)
	{
		$tagField = $model->getBehaviorParam('tagFieldName', 'tags');

		if ($model->$tagField)
		{
			return;
		}

		$type = $model->getContentType();

		$model->addKnownField($tagField);
		$model->$tagField = $this->tagsHelper->getTagIds($model->getId(), $type);
	}

	/**
	 * This event runs after publishing a record in a model
	 *
	 * @param   DataModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterPublish(DataModel &$model)
	{
		$model->updateUcmContent();
	}

	/**
	 * This event runs after unpublishing a record in a model
	 *
	 * @param   DataModel  &$model  The model which calls this event
	 *
	 * @return  void
	 */
	public function onAfterUnpublish(DataModel &$model)
	{
		$model->updateUcmContent();
	}
}
Behaviour/Language.php000064400000011131152355061210010721 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use JDatabaseQuery;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Factory as JoomlaFactory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\Registry\Registry;

/**
 * FOF model behavior class to filter front-end access to items
 * based on the language.
 *
 * @since    2.1
 */
class Language extends Observer
{
    /** @var  \PlgSystemLanguageFilter */
    protected $lang_filter_plugin;

	/**
	 * This event runs before we have built the query used to fetch a record
	 * list in a model. It is used to blacklist the language filter
	 *
	 * @param   DataModel       &$model  The model which calls this event
	 * @param   JDatabaseQuery  &$query  The model which calls this event
	 *
	 * @return  void
	 * @noinspection PhpUnusedParameterInspection
	 */
	public function onBeforeBuildQuery(DataModel &$model, JDatabaseQuery &$query)
	{
		if ($model->getContainer()->platform->isFrontend())
		{
			$model->blacklistFilters('language');
		}

		// Make sure the field actually exists AND we're not in CLI
		if (!$model->hasField('language') || $model->getContainer()->platform->isCli())
		{
			return;
		}

		/** @var SiteApplication $app */
		$app = JoomlaFactory::getApplication();
		$hasLanguageFilter = method_exists($app, 'getLanguageFilter');

		if ($hasLanguageFilter)
		{
			$hasLanguageFilter = $app->getLanguageFilter();
		}

		if (!$hasLanguageFilter)
		{
			return;
		}

        // Ask Joomla for the plugin only if we don't already have it. Useful for tests
        if(!$this->lang_filter_plugin)
        {
            $this->lang_filter_plugin = PluginHelper::getPlugin('system', 'languagefilter');
        }

		$lang_filter_params = new Registry($this->lang_filter_plugin->params);

		$languages = array('*');

		if ($lang_filter_params->get('remove_default_prefix'))
		{
			// Get default site language
            $platform    = $model->getContainer()->platform;
			$lg          = $platform->getLanguage();
			$languages[] = $lg->getTag();
		}
		else
		{
            // We have to use JoomlaInput since the language fragment is not set in the $_REQUEST, thus we won't have it in our model
            // TODO Double check the previous assumption
			$languages[] = JoomlaFactory::getApplication()->input->getCmd('language', '*');
		}

		// Filter out double languages
		$languages = array_unique($languages);

		// And filter the query output by these languages
		$db = $model->getDbo();
		$languages = array_map(array($db, 'quote'), $languages);
		$fieldName = $model->getFieldAlias('language');

		$model->whereRaw($db->qn($fieldName) . ' IN(' . implode(', ', $languages) . ')');
	}

	/**
	 * The event runs after DataModel has retrieved a single item from the database. It is used to apply automatic
	 * filters.
	 *
	 * @param   DataModel &$model  The model which was called
	 * @param   mixed     &$keys   The keys used to locate the record which was loaded
	 *
	 * @return  void
	 */
	public function onAfterLoad(DataModel &$model, &$keys)
	{
		// Make sure we have a DataModel
		if (!($model instanceof DataModel))
		{
			return;
		}

		// Make sure the field actually exists AND we're not in CLI
		if (!$model->hasField('language') || $model->getContainer()->platform->isCli())
		{
			return;
		}

		// Make sure it is a multilingual site and get a list of languages
		/** @var SiteApplication $app */
		$app = JoomlaFactory::getApplication();
		$hasLanguageFilter = method_exists($app, 'getLanguageFilter');

		if ($hasLanguageFilter)
		{
			$hasLanguageFilter = $app->getLanguageFilter();
		}

		if (!$hasLanguageFilter)
		{
			return;
		}

        // Ask Joomla for the plugin only if we don't already have it. Useful for tests
        if(!$this->lang_filter_plugin)
        {
            $this->lang_filter_plugin = PluginHelper::getPlugin('system', 'languagefilter');
        }

		$lang_filter_params = new Registry($this->lang_filter_plugin->params);

		$languages = array('*');

		if ($lang_filter_params->get('remove_default_prefix'))
		{
			// Get default site language
			$lg = $model->getContainer()->platform->getLanguage();
			$languages[] = $lg->getTag();
		}
		else
		{
			$languages[] = JoomlaFactory::getApplication()->input->getCmd('language', '*');
		}

		// Filter out double languages
		$languages = array_unique($languages);

		// Filter by language
		if (!in_array($model->getFieldValue('language'), $languages))
		{
			$model->reset();
		}
	}
}
Behaviour/Assets.php000064400000011362152355061210010446 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Behaviour;

defined('_JEXEC') || die;

use FOF40\Event\Observer;
use FOF40\Model\DataModel;
use Joomla\CMS\Access\Rules;
use Joomla\CMS\Factory;
use Joomla\CMS\Table\Asset;

/**
 * FOF model behavior class to add Joomla! ACL assets support
 *
 * @since    2.1
 */
class Assets extends Observer
{
	public function onAfterSave(DataModel &$model)
	{
		if (!$model->hasField('asset_id') || !$model->isAssetsTracked())
		{
			return true;
		}

		$assetFieldAlias = $model->getFieldAlias('asset_id');
		$currentAssetId  = $model->getFieldValue('asset_id');

		unset($model->$assetFieldAlias);

		// Create the object used for inserting/updating data to the database
		$fields = $model->getTableFields();

		// Let's remove the asset_id field, since we unset the property above and we would get a PHP notice
		if (isset($fields[$assetFieldAlias]))
		{
			unset($fields[$assetFieldAlias]);
		}

		// Asset Tracking
		$parentId = $model->getAssetParentId();
		$name     = $model->getAssetName();
		$title    = $model->getAssetTitle();

		$asset = new Asset(Factory::getDbo());
		$asset->loadByName($name);

		// Re-inject the asset id.
		$this->$assetFieldAlias = $asset->id;

		// Check for an error.
		$error = $asset->getError();

		// Since we are using \Joomla\CMS\Table\Table, there is no way to mock it and test for failures :(
		// @codeCoverageIgnoreStart
		if (!empty($error))
		{
			throw new \Exception($error);
		}
		// @codeCoverageIgnoreEnd

		// Specify how a new or moved node asset is inserted into the tree.
		// Since we're unsetting the table field before, this statement is always true...
		if (empty($model->$assetFieldAlias) || $asset->parent_id !== $parentId)
		{
			$asset->setLocation($parentId, 'last-child');
		}

		// Prepare the asset to be stored.
		$asset->parent_id = $parentId;
		$asset->name      = $name;
		$asset->title     = $title;

		if ($model->getRules() instanceof Rules)
		{
			$asset->rules = (string) $model->getRules();
		}

		// Since we are using \Joomla\CMS\Table\Table, there is no way to mock it and test for failures :(
		// @codeCoverageIgnoreStart
		if (!$asset->check() || !$asset->store())
		{
			throw new \Exception($asset->getError());
		}
		// @codeCoverageIgnoreEnd

		// Create an asset_id or heal one that is corrupted.
		if (empty($model->$assetFieldAlias) || (($currentAssetId != $model->$assetFieldAlias) && !empty($model->$assetFieldAlias)))
		{
			// Update the asset_id field in this table.
			$model->$assetFieldAlias = (int) $asset->id;

			$k = $model->getKeyName();

			$db = $model->getDbo();

			$query = $db->getQuery(true)
				->update($db->qn($model->getTableName()))
				->set($db->qn($assetFieldAlias) . ' = ' . (int) $model->$assetFieldAlias)
				->where($db->qn($k) . ' = ' . (int) $model->$k);

			$db->setQuery($query)->execute();
		}

		return true;
	}

	public function onAfterBind(DataModel &$model, &$src)
	{
		if (!$model->isAssetsTracked())
		{
			return true;
		}

		$rawRules = [];

		if (is_array($src) && array_key_exists('rules', $src) && is_array($src['rules']))
		{
			$rawRules = $src['rules'];
		}
		elseif (is_object($src) && isset($src->rules) && is_array($src->rules))
		{
			$rawRules = $src->rules;
		}

		if (empty($rawRules))
		{
			return true;
		}

		// Bind the rules.
		if (isset($rawRules) && is_array($rawRules))
		{
			// We have to manually remove any empty value, since they will be converted to int,
			// and "Inherited" values will become "Denied". Joomla is doing this manually, too.
			$rules = [];

			foreach ($rawRules as $action => $ids)
			{
				// Build the rules array.
				$rules[$action] = [];

				foreach ($ids as $id => $p)
				{
					if ($p !== '')
					{
						$rules[$action][$id] = $p == '1' || $p == 'true';
					}
				}
			}

			$model->setRules($rules);
		}

		return true;
	}

	public function onBeforeDelete(DataModel &$model, $oid)
	{
		if (!$model->isAssetsTracked())
		{
			return true;
		}

		$k = $model->getKeyName();

		// If the table is not loaded, let's try to load it with the id
		if (!$model->$k)
		{
			$model->load($oid);
		}

		// If I have an invalid assetName I have to stop
		$name = $model->getAssetName();

		// Do NOT touch \Joomla\CMS\Table\Table here -- we are loading the core asset table which is a \Joomla\CMS\Table\Table, not a FOF Table
		$asset = new Asset(Factory::getDbo());

		if ($asset->loadByName($name))
		{
			// Since we are using \Joomla\CMS\Table\Table, there is no way to mock it and test for failures :(
			// @codeCoverageIgnoreStart
			if (!$asset->delete())
			{
				throw new \Exception($asset->getError());
			}
			// @codeCoverageIgnoreEnd
		}

		return true;
	}
}
Exception/SpecialColumnMissing.php000064400000000450152355061210013302 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

class SpecialColumnMissing extends BaseException
{

}
Exception/TreeInvalidLftRgt.php000064400000000724152355061210012547 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;

abstract class TreeInvalidLftRgt extends \RuntimeException
{
	public function __construct( $message = '', $code = 500, Exception $previous = null )
	{
		parent::__construct( $message, $code, $previous );
	}

}
Exception/TreeIncompatibleTable.php000064400000001111152355061210013403 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class TreeIncompatibleTable extends \UnexpectedValueException
{
	public function __construct( $tableName, $code = 500, Exception $previous = null )
	{
		$message = Text::sprintf('LIB_FOF40_MODEL_ERR_TREE_INCOMPATIBLETABLE', $tableName);

		parent::__construct( $message, $code, $previous );
	}

}
Exception/TreeUnsupportedMethod.php000064400000001076152355061210013530 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class TreeUnsupportedMethod extends \LogicException
{
	public function __construct( $method = '', $code = 500, Exception $previous = null )
	{
		$message = Text::sprintf('LIB_FOF40_MODEL_ERR_TREE_UNSUPPORTEDMETHOD', $method);

		parent::__construct( $message, $code, $previous );
	}

}
Exception/TreeMethodOnlyAllowedInRoot.php000064400000001077152355061210014565 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class TreeMethodOnlyAllowedInRoot extends \RuntimeException
{
	public function __construct( $method = '', $code = 500, Exception $previous = null )
	{
		$message = Text::sprintf('LIB_FOF40_MODEL_ERR_TREE_ONLYINROOT', $method);

		parent::__construct( $message, $code, $previous );
	}

}
Exception/NoTableColumns.php000064400000000442152355061210012100 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

class NoTableColumns extends BaseException
{

}
Exception/BaseException.php000064400000000445152355061210011747 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

class BaseException extends \RuntimeException
{

}
Exception/TreeInvalidLftRgtParent.php000064400000001127152355061210013717 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class TreeInvalidLftRgtParent extends TreeInvalidLftRgt
{
	public function __construct( $message = '', $code = 500, Exception $previous = null )
	{
		if (empty($message))
		{
			$message = Text::_('LIB_FOF40_MODEL_ERR_TREE_INVALIDLFTRGT_PARENT');
		}

		parent::__construct( $message, $code, $previous );
	}

}
Exception/CannotLockNotLoadedRecord.php000064400000001125152355061210014176 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class CannotLockNotLoadedRecord extends BaseException
{
	public function __construct( $message = '', $code = 500, Exception $previous = null )
	{
		if (empty($message))
		{
			$message = Text::_('LIB_FOF40_MODEL_ERR_CANNOTLOCKNOTLOADEDRECORD');
		}

		parent::__construct( $message, $code, $previous );
	}

}
Exception/TreeRootNotFound.php000064400000001076152355061210012437 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class TreeRootNotFound extends \RuntimeException
{
	public function __construct($tableName, $lft, $code = 500, Exception $previous = null)
	{
		$message = Text::sprintf('LIB_FOF40_MODEL_ERR_TREE_ROOTNOTFOUND', $tableName, $lft);

		parent::__construct($message, $code, $previous);
	}

}
Exception/InvalidSearchMethod.php000064400000000447152355061210013075 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

class InvalidSearchMethod extends BaseException
{

}
Exception/TreeInvalidLftRgtCurrent.php000064400000001131152355061210014103 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class TreeInvalidLftRgtCurrent extends TreeInvalidLftRgt
{
	public function __construct( $message = '', $code = 500, Exception $previous = null )
	{
		if (empty($message))
		{
			$message = Text::_('LIB_FOF40_MODEL_ERR_TREE_INVALIDLFTRGT_CURRENT');
		}

		parent::__construct( $message, $code, $previous );
	}

}
Exception/NoAssetKey.php000064400000001103152355061210011233 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class NoAssetKey extends \UnexpectedValueException
{
	public function __construct( $message = '', $code = 500, Exception $previous = null )
	{
		if (empty($message))
		{
			$message = Text::_('LIB_FOF40_MODEL_ERR_NOASSETKEY');
		}

		parent::__construct( $message, $code, $previous );
	}

}
Exception/TreeInvalidLftRgtSibling.php000064400000001131152355061210014050 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class TreeInvalidLftRgtSibling extends TreeInvalidLftRgt
{
	public function __construct( $message = '', $code = 500, Exception $previous = null )
	{
		if (empty($message))
		{
			$message = Text::_('LIB_FOF40_MODEL_ERR_TREE_INVALIDLFTRGT_SIBLING');
		}

		parent::__construct( $message, $code, $previous );
	}

}
Exception/TreeUnexpectedPrimaryKey.php000064400000001130152355061210014147 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class TreeUnexpectedPrimaryKey extends \UnexpectedValueException
{
	public function __construct( $message = '', $code = 500, Exception $previous = null )
	{
		if (empty($message))
		{
			$message = Text::_('LIB_FOF40_MODEL_ERR_TREE_UNEXPECTEDPK');
		}

		parent::__construct( $message, $code, $previous );
	}

}
Exception/NoContentType.php000064400000001070152355061210011762 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class NoContentType extends \UnexpectedValueException
{
	public function __construct( $className, $code = 500, Exception $previous = null )
	{
		$message = Text::sprintf('LIB_FOF40_MODEL_ERR_NOCONTENTTYPE', $className);

		parent::__construct( $message, $code, $previous );
	}

}
Exception/NoItemsFound.php000064400000001052152355061210011563 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class NoItemsFound extends BaseException
{
	public function __construct( $className, $code = 404, Exception $previous = null )
	{
		$message = Text::sprintf('LIB_FOF40_MODEL_ERR_NOITEMSFOUND', $className);

		parent::__construct( $message, $code, $previous );
	}

}
Exception/TreeInvalidLftRgtOther.php000064400000001125152355061210013545 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class TreeInvalidLftRgtOther extends TreeInvalidLftRgt
{
	public function __construct( $message = '', $code = 500, Exception $previous = null )
	{
		if (empty($message))
		{
			$message = Text::_('LIB_FOF40_MODEL_ERR_TREE_INVALIDLFTRGT_OTHER');
		}

		parent::__construct( $message, $code, $previous );
	}

}
Exception/RecordNotLoaded.php000064400000001076152355061210012227 0ustar00<?php
/**
 * @package   FOF
 * @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU General Public License version 3, or later
 */

namespace  FOF40\Model\DataModel\Exception;

defined('_JEXEC') || die;

use Exception;
use Joomla\CMS\Language\Text;

class RecordNotLoaded extends BaseException
{
	public function __construct( $message = "", $code = 404, Exception $previous = null )
	{
		if (empty($message))
		{
			$message = Text::_('LIB_FOF40_MODEL_ERR_COULDNOTLOAD');
		}

		parent::__construct( $message, $code, $previous );
	}

}