diff --git a/library/Icinga/Web/Wizard.php b/library/Icinga/Web/Wizard.php new file mode 100644 index 000000000..dae2ffa63 --- /dev/null +++ b/library/Icinga/Web/Wizard.php @@ -0,0 +1,479 @@ +init(); + } + + /** + * Run additional initialization routines + * + * Should be implemented by subclasses to add pages to the wizard. + */ + protected function init() + { + + } + + /** + * Return the pages being part of this wizard + * + * @return array + */ + public function getPages() + { + return $this->pages; + } + + /** + * Return the page with the given name + * + * @param string $name The name of the page to return + * + * @return null|Form The page or null in case there is no page with the given name + */ + public function getPage($name) + { + foreach ($this->getPages() as $page) { + if ($name === $page->getName()) { + return $page; + } + } + } + + /** + * Add a new page to this wizard + * + * @param Form $page The page to add to the wizard + * + * @return self + */ + public function addPage(Form $page) + { + $this->pages[] = $page; + return $this; + } + + /** + * Add multiple pages to this wizard + * + * @param array $pages The pages to add to the wizard + * + * @return self + */ + public function addPages(array $pages) + { + foreach ($pages as $page) { + $this->addPage($page); + } + + return $this; + } + + /** + * Assert that this wizard has any pages + * + * @throws LogicException In case this wizard has no pages + */ + protected function assertHasPages() + { + $pages = $this->getPages(); + if (empty($pages)) { + throw new LogicException('Although Chuck Norris can advance a wizard without any pages, you can\'t.'); + } + } + + /** + * Return the current page of this wizard + * + * @return Form + * + * @throws LogicException In case the name of the current page currently being set is invalid + */ + public function getCurrentPage() + { + if ($this->currentPage === null) { + $this->assertHasPages(); + $pages = $this->getPages(); + $this->currentPage = $this->getSession()->get('current_page', $pages[0]->getName()); + } + + if (($page = $this->getPage($this->currentPage)) === null) { + throw new LogicException(sprintf('No page found with name "%s"', $this->currentPage)); + } + + return $page; + } + + /** + * Set the current page of this wizard + * + * @param Form $page The page to set as current page + * + * @return self + */ + public function setCurrentPage(Form $page) + { + $this->currentPage = $page->getName(); + $this->getSession()->set('current_page', $this->currentPage); + return $this; + } + + /** + * Setup the given page that is either going to be displayed or validated + * + * Implement this method in a subclass to populate default values and/or other data required to process the form. + * + * @param Form $page The page to setup + * @param Request $request The current request + */ + public function setupPage(Form $page, Request $request) + { + + } + + /** + * Process the given request using this wizard + * + * Validate the request data using the current page, update the wizard's + * position and redirect to the page's redirect url upon success. + * + * @param Request $request The request to be processed + * + * @return Request The request supposed to be processed + */ + public function handleRequest(Request $request = null) + { + $page = $this->getCurrentPage(); + + if ($request === null) { + $request = $page->getRequest(); + } + + $this->setupPage($page, $request); + $requestData = $page->getRequestData($request); + if ($page->wasSent($requestData)) { + if (($requestedPage = $this->getRequestedPage($requestData)) !== null) { + $isValid = false; + $direction = $this->getDirection($request); + if ($direction === static::FORWARD && $page->isValid($requestData)) { + $isValid = true; + if ($this->isLastPage($page)) { + $this->setIsFinished(); + } + } elseif ($direction === static::BACKWARD) { + $isValid = true; + } + + if ($isValid) { + $pageData = & $this->getPageData(); + $pageData[$page->getName()] = $requestData; + $this->setCurrentPage($this->getNewPage($requestedPage)); + $page->getResponse()->redirectAndExit($page->getRedirectUrl()); + } + } else { + $page->isValidPartial($requestData); + } + } elseif (($pageData = $this->getPageData($page->getName())) !== null) { + $page->populate($pageData); + } + + return $request; + } + + /** + * Return the name of the requested page + * + * @param array $requestData The request's data + * + * @return null|string The name of the requested page or null in case no page has been requested + */ + protected function getRequestedPage(array $requestData) + { + if (isset($requestData[static::BTN_NEXT])) { + return $requestData[static::BTN_NEXT]; + } elseif (isset($requestData[static::BTN_PREV])) { + return $requestData[static::BTN_PREV]; + } + } + + /** + * Return the direction of this wizard using the given request + * + * @param Request $request The request to use + * + * @return int The direction @see Wizard::FORWARD @see Wizard::BACKWARD @see Wizard::NO_CHANGE + */ + protected function getDirection(Request $request = null) + { + $currentPage = $this->getCurrentPage(); + + if ($request === null) { + $request = $currentPage->getRequest(); + } + + $requestData = $currentPage->getRequestData($request); + if (isset($requestData[static::BTN_NEXT])) { + return static::FORWARD; + } elseif (isset($requestData[static::BTN_PREV])) { + return static::BACKWARD; + } + + return static::NO_CHANGE; + } + + /** + * Return the new page to set as current page + * + * @param string $requestedPage The name of the requested page + * + * @return Form The new page + * + * @throws InvalidArgumentException In case the requested page does not exist or is not permitted yet + */ + protected function getNewPage($requestedPage) + { + if (($page = $this->getPage($requestedPage)) !== null) { + $permitted = true; + + $pages = $this->getPages(); + if (($index = array_search($page, $pages, true)) > 0) { + $permitted = $this->hasPageData($pages[$index - 1]->getName()); + } + + if ($permitted) { + return $page; + } + } + + throw new InvalidArgumentException( + sprintf('"%s" is either an unknown page or one you are not permitted to view', $requestedPage) + ); + } + + /** + * Return whether the given page is this wizard's last page + * + * @param Form $page The page to check + * + * @return bool + */ + protected function isLastPage(Form $page) + { + return $page->getName() === end($this->getPages())->getName(); + } + + /** + * Set whether this wizard has been completed + * + * @param bool $state Whether this wizard has been completed + * + * @return self + */ + public function setIsFinished($state = true) + { + $this->getSession()->set('isFinished', $state); + return $this; + } + + /** + * Return whether this wizard has been completed + * + * @return bool + */ + public function isFinished() + { + return $this->getSession()->get('isFinished', false); + } + + /** + * Return the overall page data or one for a particular page + * + * Note that this method returns by reference so in order to update the + * returned array set this method's return value also by reference. + * + * @param string $pageName The page for which to return the data + * + * @return array + */ + public function & getPageData($pageName = null) + { + $session = $this->getSession(); + + if (false === isset($session->page_data)) { + $session->page_data = array(); + } + + $pageData = & $session->getByRef('page_data'); + if ($pageName !== null) { + $data = null; + if (isset($pageData[$pageName])) { + $data = & $pageData[$pageName]; + } + + return $data; + } + + return $pageData; + } + + /** + * Return whether there is any data for the given page + * + * @param string $pageName The name of the page to check + * + * @return bool + */ + public function hasPageData($pageName) + { + return $this->getPageData($pageName) !== null; + } + + /** + * Return a session to be used by this wizard + * + * @return SessionNamespace + */ + public function getSession() + { + return Session::getSession()->getNamespace(get_class($this)); + } + + /** + * Add buttons to the given page based on its position in the page-chain + * + * @param Form $page The page to add the buttons to + */ + protected function addButtons(Form $page) + { + $pages = $this->getPages(); + $index = array_search($page, $pages, true); + if ($index === 0) { + $page->addElement( + 'button', + static::BTN_NEXT, + array( + 'type' => 'submit', + 'value' => $pages[1]->getName(), + 'label' => t('Next') + ) + ); + } elseif ($index < count($pages) - 1) { + $page->addElement( + 'button', + static::BTN_PREV, + array( + 'type' => 'submit', + 'value' => $pages[$index - 1]->getName(), + 'label' => t('Back') + ) + ); + $page->addElement( + 'button', + static::BTN_NEXT, + array( + 'type' => 'submit', + 'value' => $pages[$index + 1]->getName(), + 'label' => t('Next') + ) + ); + } else { + $page->addElement( + 'button', + static::BTN_PREV, + array( + 'type' => 'submit', + 'value' => $pages[$index - 1]->getName(), + 'label' => t('Back') + ) + ); + $page->addElement( + 'button', + static::BTN_NEXT, + array( + 'type' => 'submit', + 'value' => $page->getName(), + 'label' => t('Finish') + ) + ); + } + } + + /** + * Return the current page of this wizard with appropriate buttons being added + * + * @return Form + */ + public function getForm() + { + $form = $this->getCurrentPage(); + $form->create(); // Make sure that buttons are displayed at the very bottom + $this->addButtons($form); + return $form; + } + + /** + * Return the current page of this wizard rendered as HTML + * + * @return string + */ + public function __toString() + { + return $this->getForm()->render(); + } +}