Location: PHPKode > projects > Nella Framework > nella-framework-7e780c6/Nella/NetteAddons/Forms/Multipler.php
<?php
/**
 * This file is part of the Nella Framework (http://nellafw.org).
 *
 * Copyright (c) 2006, 2012 Patrik Votoček (http://patrik.votocek.cz)
 *
 * For the full copyright and license information, please view the file LICENSE.txt that was distributed with this source code.
 */

namespace Nella\NetteAddons\Forms;

/**
 * Form multipler container
 *
 * @author	Patrik Votoček
 */
class Multipler extends Container
{
	const CONTAINERS_KEYS_SEPARATOR = ';',
		CONTAINERS_KEYS_ID = '__subcontainers',
		ADD_CONTAINER_BUTTON_ID = '__addcontainer';

	/** @var callable */
	private $factory;
	/** @var int */
	private $i = 1;

	/**
	 * @param callable
	 * @param int
	 */
	public function __construct($factory, $createDefault = 1)
	{
		parent::__construct();

		$this->factory = callback($factory);

		$this->addHidden(self::CONTAINERS_KEYS_ID);

		$this->setIds($createDefault > 0 ? range(1, $createDefault) : NULL);

		$this->monitor('Nette\Application\UI\Presenter');
		$this->monitor('Nette\Forms\Form');
	}

	/**
	 * @param \Nette\ComponentModel\IContainer
	 */
	protected function attached($obj)
	{
		parent::attached($obj);

		if ($obj instanceof \Nette\Forms\Form) {
			if (!$obj instanceof \Nette\Application\UI\Form) {
				throw new \Nette\InvalidStateException('Form multipler support only Nette\Application\UI\Form');
			}
		}

		if (!$obj instanceof \Nette\Application\UI\Presenter) {
			return;
		}

		$this->init();
	}

	protected function init()
	{
		$data = $this->receiveHttpData();
		if (!empty($data)) {
			if (isset($data[self::CONTAINERS_KEYS_ID])) {
				$this->setIds($data[self::CONTAINERS_KEYS_ID]);
			}
		}

		foreach ($this->getIds() as $name) {
			$this->createOne($name, TRUE);
		}
	}

	/**
	 * @param string
	 * @return MultiplerContainer
	 */
	protected function createComponent($name)
	{
		$container = $this->addContainer($name);

		$this->factory->invoke($container);

		return $container;
	}

	/**
	 * Fill-in with values
	 *
	 * @param  array|Traversable  values used to fill the form
	 * @param  bool     erase other controls?
	 * @return Multipler
	 */
	public function setValues($values, $erase = FALSE)
	{
		foreach($values as $key => $value) {
			if (is_array($value)) { // TODO: better subcontainer detection
				$this->getComponent($key);
			}
		}

		return parent::setValues($values, $erase);
	}

	/**
	 * @param string|int
	 * @return MultiplerContainer
	 */
	public function createOne($name = NULL, $registered = FALSE)
	{
		if (empty($name)) {
			$name = $this->i;
		}

		if (!$registered && in_array($name, $this->getIds())) {
			throw new \Nette\InvalidArgumentException("Container with name '$name' already exists.");
		}

		if (!$registered) {
			$this->addId($name);
		}

		return $this[$name];
	}

	/**
	 * @param MultiplerContainer
	 * @param bool
	 * @throws \Nette\InvalidStateException
	 */
	public function remove(MultiplerContainer $container, $cleanUpGroups = FALSE)
	{
		$name = $container->getName();
		if (!in_array($name, $this->getIds())) {
			throw new \Nette\InvalidStateException("Sub-container with name '$name' does not exist");
		}

		if ($cleanUpGroups) {
			$this->removeGroups($container);
		}
		$this->removeComponent($container);
		$this->removeId($name);
	}

	/**
	 * Adds naming container to the form
	 *
	 * @param string    name
	 * @return MultiplerContainer
	 */
	public function addContainer($name)
	{
		$container = new MultiplerContainer;
		$container->currentGroup = $this->currentGroup;
		$this->addComponent($container, $name, self::CONTAINERS_KEYS_ID);
		return $container;
	}

	/**
	 * @param string
	 * @return \Nette\Forms\Controls\SubmitButton
	 */
	public function addAddContainerButton($caption = NULL)
	{
		$button = $this->addSubmit(self::ADD_CONTAINER_BUTTON_ID, $caption)->setValidationScope(FALSE);
		$button->onClick[] = function(\Nette\Forms\Controls\SubmitButton $button) {
			$button->getParent()->createOne();
		};

		return $button;
	}

	/**
	 * Returns the values submitted by the form.
	 *
	 * @param  bool  return values as an array?
	 * @return Nette\ArrayHash|array
	 */
	public function getValues($asArray = FALSE)
	{
		$values = parent::getValues($asArray);
		unset($values[self::CONTAINERS_KEYS_ID]);
		return $values;
	}

	/**
	 * @param bool
	 * @return array|string
	 */
	protected function getFullName($asChain = FALSE)
	{
		$chain = array();
		$parent = $this;

		while (!$parent instanceof Form) {
			$chain[] = $parent->getName();
			$parent = $parent->getParent();
		}
		$chain[] = $parent->getName();

		$chain = array_reverse($chain);

		if ($asChain) {
			return $chain;
		}

		return implode('-', $chain);
	}

	/**
	 * Internal: receives submitted HTTP data.
	 *
	 * @return array
	 */
	protected function receiveHttpData()
	{
		$form = $this->getForm();
		$presenter = $form->getPresenter();

		if (!$form->isSubmitted()) {
			return;
		}

		$isPost = $form->getMethod() === \Nette\Forms\Form::POST;
		$request = $presenter->getRequest();
		if ($request->isMethod('forward') || $request->isMethod('post') !== $isPost) {
			return;
		}

		if ($isPost) {
			$data =  \Nette\Utils\Arrays::mergeTree($request->getPost(), $request->getFiles());
		} else {
			$data = $request->getParameters();
		}

		// get data only for this container
		$chain = array_slice($this->getFullName(TRUE), 1);
		while ($chain ) {
			$data = &$data[array_pop($chain)];
		}

		return $data;
	}

	/**
	 * @param array
	 * @return string
	 */
	private function idsToString(array $ids)
	{
		if (empty($ids)) {
			return "";
		}

		return implode(self::CONTAINERS_KEYS_SEPARATOR, $ids);
	}

	/**
	 * @param string
	 * @return array
	 */
	private function idsToArray($ids)
	{
		if (empty($ids) || $ids === self::CONTAINERS_KEYS_SEPARATOR) {
			return array();
		}

		return explode(self::CONTAINERS_KEYS_SEPARATOR, $ids);
	}

	/**
	 * @return array
	 */
	private function getIds()
	{
		return $this->idsToArray($this[self::CONTAINERS_KEYS_ID]->getValue());
	}

	/**
	 * @param string|array|NULL
	 */
	private function setIds($ids)
	{
		if (empty($ids)) {
			$ids = NULL;
		} elseif (!is_string($ids)) {
			$ids = $this->idsToString($ids);
		}

		$this[self::CONTAINERS_KEYS_ID]->setValue($ids);

		if (!empty($ids)) {
			$this->i = max($this->idsToArray($ids)) + 1;
		} else {
			$this->i = 1;
		}
	}

	/**
	 * @param string
	 */
	private function addId($id)
	{
		$ids = $this->getIds();
		$ids[] = $id;
		$this->setIds($ids);
	}

	/**
	 * @param string
	 */
	private function removeId($id)
	{
		$ids = $this->getIds();
		$key = array_search($id, $ids);
		unset($ids[$key]);
		$this->setIds($ids);
	}

	/** ------------------------------------------------ 3rd -------------------------------------------------- */

	/**
	 * @author	Filip Procházka <hide@address.com>
	 * @author	Jan Tvrdík
	 *
	 * @param MultiplerContainer
	 * @throws \Nette\InvalidArgumentException
	 */
	public function removeGroups(MultiplerContainer $container)
	{
		// get components
		$components = $container->getComponents(TRUE);

		// reflection is required to hack form groups
		$groupRefl = \Nette\Reflection\ClassType::from('Nette\Forms\ControlGroup');
		$controlsProperty = $groupRefl->getProperty('controls');
		$controlsProperty->setAccessible(TRUE);

		// walk groups and clean then from removed components
		$affected = array();
		foreach ($this->getForm()->getGroups() as $group) {
			$groupControls = $controlsProperty->getValue($group);

			foreach ($components as $control) {
				if ($groupControls->contains($control)) {
					$groupControls->detach($control);

					if (!in_array($group, $affected, TRUE)) {
						$affected[] = $group;
					}
				}
			}
		}

		// remove affected & empty groups
		if ($affected) {
			foreach ($this->getForm()->getComponents(FALSE, 'Nette\Forms\Container') as $container) {
				if ($index = array_search($container->currentGroup, $affected, TRUE)) {
					unset($affected[$index]);
				}
			}

			foreach ($affected as $group) {
				if (!$group->getControls() && in_array($group, $this->getForm()->getGroups(), TRUE)) {
					$this->getForm()->removeGroup($group);
				}
			}
		}
	}
}
Return current item: Nella Framework