/**
  * Edit page
  *
  * @param array $page
  * @param array $formData
  *      integer layout required
  *      string title optional|required for custom pages
  *      string description optional
  *      string slug optional|required for system pages
  *      string meta_description optional
  *      string meta_keywords optional
  *      string meta_robots optional
  *      integer user_menu optional
  *      integer user_menu_order optional
  *      integer menu optional
  *      integer site_map optional
  *      integer xml_map optional
  *      string xml_map_update optional
  *      float xml_map_priority optional
  *      integer footer_menu optional
  *      integer footer_menu_order optional
  *      integer active optional
  *      string redirect_url optional
  *      string page_direction optional
  *      integer page optional
  *      array visibility_settings optional
  * @param boolean $isSystemPage
  * @param array $parent
  * @return boolean|string
  */
 public function editPage(array $page, array $formData, $isSystemPage, array $parent = [])
 {
     try {
         $this->adapter->getDriver()->getConnection()->beginTransaction();
         // update page info
         $result = $this->getPageModel()->updateNode($page['id'], $this->getBasicPageFields($formData), false);
         if (true !== $result) {
             $this->adapter->getDriver()->getConnection()->rollback();
             return $result;
         }
         // generate a new page slug automatically
         if (!$isSystemPage && empty($formData['slug'])) {
             $update = $this->update()->table('page_structure')->set(['slug' => $this->generatePageSlug($page['id'], $formData['title'])])->where(['id' => $page['id']]);
             $statement = $this->prepareStatementForSqlObject($update);
             $statement->execute();
         }
         // move page
         if ($parent) {
             $nearKey = !empty($formData['page']) ? $formData['page'] : null;
             $pageDirection = !empty($formData['page_direction']) ? $formData['page_direction'] : null;
             $result = $this->getPageModel()->movePage($page, $parent, $this->getCurrentLanguage(), $nearKey, $pageDirection);
             if (true !== $result) {
                 $this->adapter->getDriver()->getConnection()->rollback();
                 return $result;
             }
         }
         // clear all old visibility settings
         $delete = $this->delete()->from('page_visibility')->where(['page_id' => $page['id']]);
         $statement = $this->prepareStatementForSqlObject($delete);
         $result = $statement->execute();
         // add new visibility settings
         if (!empty($formData['visibility_settings'])) {
             foreach ($formData['visibility_settings'] as $aclRoleId) {
                 $insert = $this->insert()->into('page_visibility')->values(['page_id' => $page['id'], 'hidden' => $aclRoleId]);
                 $statement = $this->prepareStatementForSqlObject($insert);
                 $statement->execute();
             }
         }
         // change public widgets position
         if ($page['layout'] != $formData['layout']) {
             if (false !== ($layout = $this->getPageLayout($formData['layout']))) {
                 $this->changePublicWidgetsPosition($page['id'], $formData['layout'], $layout['default_position']);
             }
         }
         // clear cache
         $this->clearLanguageSensitivePageCaches();
         $this->adapter->getDriver()->getConnection()->commit();
     } catch (Exception $e) {
         $this->adapter->getDriver()->getConnection()->rollback();
         ApplicationErrorLogger::log($e);
         return $e->getMessage();
     }
     // fire the edit page event
     PageEvent::fireEditPageEvent($page['id']);
     return true;
 }