Просмотр исходного кода

#73 - upgrade script, maintenance mode, email notifications, delete deprecated files, update files hash

Alex Trofimov 12 лет назад
Родитель
Сommit
0820e28213

+ 12 - 0
inc/classes/BxDolAccount.php

@@ -59,6 +59,18 @@ class BxDolAccount extends BxDol
         return $GLOBALS['bxDolClasses'][$sClass];
     }
 
+    /**
+     * Get studio operator account singleton instance on the class
+     */
+    public static function getInstanceStudioOperator()
+    {
+        $oQuery = BxDolAccountQuery::getInstance();
+        if (!($iId = $oQuery->getStudioOperatorId()))
+            return false;
+
+        return self::getInstance($iId);
+    }
+
     /**
      * Get account id
      */

+ 8 - 0
inc/classes/BxDolAccountQuery.php

@@ -82,6 +82,14 @@ class BxDolAccountQuery extends BxDolDb implements iBxDolSingleton
         return (int)$this->_getFieldByField('id', 'id', $iId);
     }
 
+    /**
+     * get first studio operator id
+     */
+    public function getStudioOperatorId()
+    {
+        return (int)$this->_getFieldByField('id', 'role', 3);
+    }
+
     /**
      * Get account email by id
      * @param  string  $s search account by this id

+ 6 - 2
inc/classes/BxDolCronUpgradeCheck.php

@@ -17,8 +17,12 @@ class BxDolCronUpgradeCheck extends BxDolCron
             return;
 
         $o = bx_instance('BxDolUpgrader');
-        if (!$o->prepare())
-            echo $o->getError() . "\n"; // TODO: email report ?
+        if (!$o->prepare()) {            
+            sendMailTemplateSystem('t_UpgradeFailed', array (
+                'error_msg' => $o->getError(),
+            ));
+            setParam('sys_autoupdate_system', ''); // disable auto-update if it is failed
+        }
     }
 }
 

+ 4 - 3
inc/classes/BxDolInstallerHasher.php

@@ -7,6 +7,8 @@
  * @{
  */
 
+define('BX_SYSTEM_MODULE_ID', 0);
+
 bx_import('BxDolInstallerUtils');
 
 /**
@@ -15,7 +17,6 @@ bx_import('BxDolInstallerUtils');
  */
 class BxDolInstallerHasher extends BxDolInstallerUtils
 {
-    protected $_iSystemModuleId = 0;
     protected $_aSystemFiles = array(
 
         // slash at the end is necessary for directories
@@ -70,7 +71,7 @@ class BxDolInstallerHasher extends BxDolInstallerUtils
         $oDb = bx_instance('BxDolStudioInstallerQuery');
 
         foreach($aFiles as $aFile)
-            $oDb->insertModuleTrack($this->_iSystemModuleId, $aFile);
+            $oDb->insertModuleTrack(BX_SYSTEM_MODULE_ID, $aFile);
 
         return true;
     }
@@ -78,7 +79,7 @@ class BxDolInstallerHasher extends BxDolInstallerUtils
     public function checkSystemFilesHash (&$fChangedFilesPercent) 
     {
         $aFiles = $this->getSystemFilesHash ();
-        $aFilesChanged = $this->hashCheck($aFiles, $this->_iSystemModuleId);
+        $aFilesChanged = $this->hashCheck($aFiles, BX_SYSTEM_MODULE_ID);
         if (null !== $fChangedFilesPercent && 0 != count($aFiles))
             $fChangedFilesPercent = count($aFilesChanged) / count($aFiles);
         return $aFilesChanged;

+ 30 - 3
inc/utils.inc.php

@@ -30,6 +30,8 @@ define('BX_EMAIL_SYSTEM', 0); ///< system email without unsubscribe link, like f
 define('BX_EMAIL_NOTIFY', 1); ///< notification message, with unsubscribe link
 define('BX_EMAIL_MASS', 2); ///< mass email, one mesage send to manu users, with unsubscribe link
 
+define('BX_MAINTENANCE_FILE', '.bx_maintenance'); ///< file name to use as mantenance mode indicator
+
 /**
  * The following two functions are needed to convert title to uri and back.
  * It usefull when titles are used in URLs, like in Categories and Tags.
@@ -254,6 +256,8 @@ function htmlspecialchars_adv( $string )
  */
 function sendMailTemplate($sTemplateName, $iAccountId = 0, $iProfileId = 0, $aReplaceVars = array(), $iEmailType = BX_EMAIL_NOTIFY)
 {
+    bx_import('BxDolLanguages');
+
     bx_import('BxDolAccount');
     $oAccount = BxDolAccount::getInstance($iAccountId);
 
@@ -266,13 +270,32 @@ function sendMailTemplate($sTemplateName, $iAccountId = 0, $iProfileId = 0, $aRe
     if (!$oAccount || !$oProfile || !$oEmailTemplates)
         return false;
 
-    $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, $aReplaceVars, $iAccountId, $iProfileId);
+    $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, $aReplaceVars, $iAccountId, (int)$iProfileId);
     if (!$aTemplate)
         return false;
 
     return sendMail($oAccount->getEmail(), $aTemplate['Subject'], $aTemplate['Body'], 0, array(), $iEmailType);
 }
 
+/**
+ * Send system email 
+ */
+function sendMailTemplateSystem($sTemplateName, $aReplaceVars = array(), $iEmailType = BX_EMAIL_SYSTEM)
+{
+    bx_import('BxDolLanguages');
+
+    bx_import('BxDolEmailTemplates');
+    $oEmailTemplates = BxDolEmailTemplates::getInstance();
+
+    if (!$oEmailTemplates)
+        return false;
+
+    $aTemplate = $oEmailTemplates->parseTemplate($sTemplateName, $aReplaceVars);
+    if (!$aTemplate)
+        return false;
+
+    return sendMail(getParam('site_email'), $aTemplate['Subject'], $aTemplate['Body'], 0, array(), $iEmailType);
+}
 /**
  * Send email function
  *
@@ -1068,10 +1091,14 @@ function bx_trigger_error ($sMsg, $iNumLevelsBack = 0)
 /**
  * Get Dolphin system DB version, for files version @see BX_DOL_VERSION, these versions must match
  */
-function bx_get_ver ()
+function bx_get_ver ($bInvalidateCache = false)
 {
     bx_import('BxDolDb');
     $oDb = BxDolDb::getInstance();
+
+    if ($bInvalidateCache)
+        $oDb->cleanMemory('sys_version');
+    
     $sQuery = $oDb->prepare("SELECT `version` FROM `sys_modules` WHERE `name` = 'system'");
     return $oDb->fromMemory('sys_version', 'getOne', $sQuery);
 }
@@ -1085,7 +1112,7 @@ function bx_get_ver ()
  */
 function bx_check_maintenance_mode ($bShowHttpError = false)
 {
-    $bMaintetance = file_exists(BX_DIRECTORY_PATH_ROOT . '.bx_maintenance') && !defined('BX_DOL_UPGRADING');
+    $bMaintetance = file_exists(BX_DIRECTORY_PATH_ROOT . BX_MAINTENANCE_FILE) && !defined('BX_DOL_UPGRADING');
 
     if ($bMaintetance && $bShowHttpError)
         bx_show_service_unavailable_error_and_exit ('Site is temporarily unavailable due to scheduled maintenance, please try again in a minute.', 600);

+ 2 - 0
install/sql/system.sql

@@ -149,6 +149,8 @@ INSERT INTO `sys_email_templates` (`Module`, `NameSystem`, `Name`, `Subject`, `B
 ('system', '_sys_et_txt_name_profile_change_status_active', 't_ChangeStatusActive', '_sys_et_txt_subject_profile_change_status_active', '_sys_et_txt_body_profile_change_status_active'),
 ('system', '_sys_et_txt_name_profile_change_status_suspended', 't_ChangeStatusSuspended', '_sys_et_txt_subject_profile_change_status_suspended', '_sys_et_txt_body_profile_change_status_suspended'),
 ('system', '_sys_et_txt_name_profile_change_status_pending', 't_ChangeStatusPending', '_sys_et_txt_subject_profile_change_status_pending', '_sys_et_txt_body_profile_change_status_pending');
+('system', '_sys_et_txt_upgrade_failed', 't_UpgradeFailed', '_sys_et_txt_subject_upgrade_failed', '_sys_et_txt_body_upgrade_failed'),
+('system', '_sys_et_txt_upgrade_success', 't_UpgradeSuccess', '_sys_et_txt_subject_upgrade_success', '_sys_et_txt_body_upgrade_success');
 
 -- --------------------------------------------------------
 

+ 10 - 1
modules/boonex/english/data/langs/system.xml

@@ -1482,6 +1482,13 @@ If it is not enabled then please consider implement this optimization, since it
 <bx_include_auto:_email_footer.html />]]></string>
 	<string name="_sys_et_txt_body_pruning"><![CDATA[<bx_include_auto:_email_header.html />
 <p>{pruning_output}</p>
+<bx_include_auto:_email_footer.html />]]></string>
+	<string name="_sys_et_txt_body_upgrade_failed"><![CDATA[<bx_include_auto:_email_header.html />
+<p>Site upgrade failed due to the following error:</p>
+<p>{error_msg}</p>
+<bx_include_auto:_email_footer.html />]]></string>
+	<string name="_sys_et_txt_body_upgrade_success"><![CDATA[<bx_include_auto:_email_header.html />
+<p>Site was successfully upgraded to {new_version}.</p>
 <bx_include_auto:_email_footer.html />]]></string>
 	<string name="_sys_et_txt_name_system_admin_email"><![CDATA[Message sending from the Admin Panel]]></string>
 	<string name="_sys_et_txt_name_system_comment_replied"><![CDATA[The letter is sent to members whose comment was replied]]></string>
@@ -1506,7 +1513,9 @@ If it is not enabled then please consider implement this optimization, since it
 	<string name="_sys_et_txt_subject_profile_change_status_active"><![CDATA[Profile Activated]]></string>
 	<string name="_sys_et_txt_subject_profile_change_status_pending"><![CDATA[Profile Pending Review]]></string>
 	<string name="_sys_et_txt_subject_profile_change_status_suspended"><![CDATA[Profile was Suspended]]></string>
-	<string name="_sys_et_txt_subject_pruning"><![CDATA[{site_title} pruning result]]></string>
+	<string name="_sys_et_txt_subject_pruning"><![CDATA[{site_name} pruning result]]></string>
+	<string name="_sys_et_txt_subject_upgrade_failed"><![CDATA[{site_name} upgrade failed]]></string>
+	<string name="_sys_et_txt_subject_upgrade_success"><![CDATA[{site_name} upgrade success]]></string>
 	<string name="_sys_et_txt_unsubscribe"><![CDATA[Unsubscribe]]></string>
 	<string name="_sys_form_account"><![CDATA[Account]]></string>
 	<string name="_sys_form_account_input_captcha"><![CDATA[Security Image]]></string>

+ 25 - 11
upgrade/BxDolUpgradeCron.php

@@ -20,23 +20,37 @@ class BxDolUpgradeCron extends BxDolCron
 {
     public function processing()
     {
-        // TODO: lock site (set maintenance mode)
-
         $oController = new BxDolUpgradeController();
+        if ($oController->setMaintenanceMode(true)) {
+        
+            $aFolders = $oController->getAllUpgrades();
+            $j = count($aFolders);
+
+            for ($i = 0; $i < $j; ++$i) {
+                $sFolder = $oController->getAvailableUpgrade();
+                if (!$sFolder)
+                    continue;
 
-        $aFolders = $oController->getAllUpgrades();
-        $j = count($aFolders);
+                if (!$oController->runUpgrade($sFolder))
+                    break;
 
-        for ($i = 0; $i < $j; ++$i) {
-            $sFolder = $oController->getAvailableUpgrade();
-            if (!$sFolder)
-                continue;
+                $oController->writeLog();
 
-            if (!$oController->runUpgrade($sFolder))
-                echo $oController->getErrorMsg() . "\n"; // TODO: email report ?
+                sendMailTemplateSystem('t_UpgradeSuccess', array (
+                    'new_version' => bx_get_ver(true),
+                ));
+            }
+
+            $oController->setMaintenanceMode(false);
         }
 
-        // TODO: unlock site (remove maintenance mode)
+        if ($sErrorMsg = $oController->getErrorMsg()) {
+            $oController->writeLog();
+            sendMailTemplateSystem('t_UpgradeFailed', array (
+                'error_msg' => $sErrorMsg,
+            ));
+            setParam('sys_autoupdate_system', ''); // disable auto-update if it is failed
+        }
     }
 }
 

+ 89 - 47
upgrade/classes/BxDolUpgradeController.php

@@ -13,6 +13,7 @@ class BxDolUpgradeController
     protected $oUtil;
     protected $aLogMsgs;
     protected $sError;
+    protected $sConclusion;
 
     public function __construct()
     {
@@ -30,6 +31,48 @@ class BxDolUpgradeController
         return $this->aLogMsgs;
     }
 
+    public function getConclusion()
+    {
+        return $this->sConclusion;
+    }
+
+    public function writeLog()
+    {
+        if (empty($this->aLogMsgs))
+            return true;
+
+        $s = "\n\n\n--------- " . date('c') . "\n";
+        $s .= implode("\n", $this->aLogMsgs);
+
+        if ($this->sConclusion)
+            $s .= "\nCONCLUSION:" . $this->sConclusion;
+
+        if ($this->sError)
+            $s .= "\nERROR:" . $this->sError;
+
+        return file_put_contents(BX_DIRECTORY_PATH_ROOT . 'logs/upgrade.log', $s, FILE_APPEND);
+    }
+
+    public function setMaintenanceMode($bSetMaintenanceMode)
+    {
+        if ($bSetMaintenanceMode) {
+
+            if (false !== file_put_contents(BX_DIRECTORY_PATH_ROOT . BX_MAINTENANCE_FILE, time()))
+                return true;
+
+            $this->sError = 'Enabling site maintetance mode failed';
+            return false;
+            
+        } else {
+
+            if (unlink(BX_DIRECTORY_PATH_ROOT . BX_MAINTENANCE_FILE))
+                return true;
+
+            $this->sError = 'Disabling site maintetance mode failed';
+            return false;
+        }
+    }
+
     public function getAllUpgrades ()
     {
         $aTemplateFolders = array ();
@@ -58,34 +101,33 @@ class BxDolUpgradeController
         $this->aLogMsgs = array();
         $this->sError = false;
 
-        // set current folder
-        $this->oUtil->setFolder($sFolder);
-
-        // precheck
-        $mixedResult = $this->oUtil->executeCheck ();
-        if (true !== $mixedResult) {
-            $this->sError = $mixedResult;
+        if (bx_get_ver() != BX_DOL_VERSION) {
+            $this->sError = 'Database and files versions are different';
             return false;
-        } else {
-            $this->aLogMsgs[] = "$sFolder upgrade can be applied";
         }
 
-        $mixedResult = $this->oUtil->checkPermissions ();
-        if (true !== $mixedResult) {
-            $this->sError = $mixedResult;
-            return false;
-        }
+        // set current folder
+        $this->oUtil->setFolder($sFolder);
 
-        $mixedResult = $this->oUtil->copyFiles ();
-        if (true !== $mixedResult) {
-            $this->sError = $mixedResult;
-            return false;
+        // run oprations with files
+        $aFilesOperations = array (
+            'executeCheck' => "$sFolder upgrade will be applied",
+            'checkPermissions' => "Files permissions are ok and can be overwritten",
+            'copyFiles' => "Files copying successfully completed",
+            'filesDelete' => "Deprecated files were successfully deleted or there is no files to delete",
+            'updateFilesHash' => "System files hash was successfully updated",
+        );
+    
+        foreach ($aFilesOperations as $sFunc => $sSuccessMsg) {
+            $mixedResult = $this->oUtil->$sFunc ();
+            if (true !== $mixedResult) {
+                $this->sError = $mixedResult;
+                return false;
+            } else {
+                $this->aLogMsgs[] = $sSuccessMsg;
+            }
         }
 
-        // TODO: delete deprecated files
-
-        // TODO: update files hash
-
         // run system SQL upgrade
         $mixedResult = $this->oUtil->isExecuteSQLAvail ();
         if (true === $mixedResult) {
@@ -96,7 +138,7 @@ class BxDolUpgradeController
                 $this->sError = $mixedResult;
                 return false;
             } else {
-                $this->aLogMsgs[] = "System SQL script was successfully executed.";
+                $this->aLogMsgs[] = "System SQL script was successfully executed";
             }
 
         } elseif (false === $mixedResult) {
@@ -108,18 +150,18 @@ class BxDolUpgradeController
 
         // get list of available language files updates
         if (false === ($aLangs = $this->oUtil->readLangs ())) {
-            $this->sError = 'Error reading the directory with language updates.';
+            $this->sError = 'Error reading the directory with language updates';
             return false;
         } else {
-            $sTemplateMessage = 'The following languages will be affected for system: <br />';
+            $sTemplateMessage = "The following languages will be affected for system:\n";
             if (!$aLangs) {
-                $sTemplateMessage .= " - No languages will be affected.";
+                $sTemplateMessage .= " - No languages will be affected\n";
             } else {
                 foreach ($aLangs as $sLang) {
-                    $sTemplateMessage .= ' - ' . $sLang . '<br />';
+                    $sTemplateMessage .= ' - ' . $sLang . "\n";
                 }
             }
-            $this->aLogMsgs[] = $sTemplateMessage;
+            $this->aLogMsgs[] = trim($sTemplateMessage);
         }
 
         // run system langs upgrade
@@ -130,7 +172,7 @@ class BxDolUpgradeController
                 $this->sError = $mixedResult;
                 return false;
             } else {
-                $this->aLogMsgs[] = "System language strings were successfully added.";
+                $this->aLogMsgs[] = "System language strings were successfully added";
             }
 
         }
@@ -144,7 +186,7 @@ class BxDolUpgradeController
                 $this->sError = $mixedResult;
                 return false;
             } else {
-                $this->aLogMsgs[] = "System after update custom script was successfully executed.";
+                $this->aLogMsgs[] = "System after update custom script was successfully executed";
             }
 
         } elseif (false === $mixedResult) {
@@ -155,21 +197,21 @@ class BxDolUpgradeController
         }
 
         // get list of modules updates
-        if (false === ($aModules = $this->oUtil->readModules ())) {
-            // skip modules update
-            return true;
-        } else {
-            $sTemplateMessage = 'The following modules will be updated: <br />';
+        if (false !== ($aModules = $this->oUtil->readModules ())) {
+            $sTemplateMessage = "The following modules will be updated:\n";
             if (!$aModules) {
-                $sTemplateMessage .= " - No modules will be updated.";
+                $sTemplateMessage .= " - No modules will be updated\n";
             } else {
                 foreach ($aModules as $sModule) {
-                    $sTemplateMessage .= ' - ' . $sModule . '<br />';
+                    $sTemplateMessage .= ' - ' . $sModule . "\n";
                 }
             }
-            $this->aLogMsgs[] = $sTemplateMessage;
+            $this->aLogMsgs[] = trim($sTemplateMessage);
         }
 
+        if (!$aModules)
+            $aModules = array();
+
         foreach ($aModules as $sModule) {
 
             // run module SQL upgrade
@@ -181,7 +223,7 @@ class BxDolUpgradeController
                     $this->sError = $mixedResult;
                     return false;
                 } else {
-                    $this->aLogMsgs[] = "<b>$sModule</b> module SQL script was successfully executed.";
+                    $this->aLogMsgs[] = "'$sModule' module SQL script was successfully executed";
                 }
 
             } elseif (false === $mixedResult) {
@@ -193,18 +235,18 @@ class BxDolUpgradeController
 
             // get list of available language files updates
             if (false === ($aLangs = $this->oUtil->readLangs ($sModule))) {
-                $this->sError = 'Error reading the directory with language updates.';
+                $this->sError = 'Error reading the directory with language updates';
                 return false;
             } else {
-                $sTemplateMessage = "The following languages will be affected for <b>$sModule</b> module: <br />";
+                $sTemplateMessage = "The following languages will be affected for '$sModule' module:\n";
                 if (!$aLangs) {
-                    $sTemplateMessage .= " - No languages will be affected.";
+                    $sTemplateMessage .= " - No languages will be affected\n";
                 } else {
                     foreach ($aLangs as $sLang) {
-                        $sTemplateMessage .= ' - ' . $sLang . '<br />';
+                        $sTemplateMessage .= ' - ' . $sLang . "\n";
                     }
                 }
-                $this->aLogMsgs[] = $sTemplateMessage;
+                $this->aLogMsgs[] = trim($sTemplateMessage);
             }
 
             // run module langs upgrade
@@ -215,7 +257,7 @@ class BxDolUpgradeController
                     $this->sError = $mixedResult;
                     return false;
                 } else {
-                    $this->aLogMsgs[] = "<b>$sModule</b> module language strings were successfully added.";
+                    $this->aLogMsgs[] = "'$sModule' module language strings were successfully added";
                 }
 
             }
@@ -229,7 +271,7 @@ class BxDolUpgradeController
                     $this->sError = $mixedResult;
                     return false;
                 } else {
-                    $this->aLogMsgs[] = "<b>$sModule</b> module after update custom script was successfully executed.";
+                    $this->aLogMsgs[] = "'$sModule' module after update custom script was successfully executed";
                 }
 
             } elseif (false === $mixedResult) {
@@ -248,7 +290,7 @@ class BxDolUpgradeController
         // run module custom script upgrade
         $sResult = $this->oUtil->executeConclusion ();
         if ($sResult)
-            $this->aLogMsgs[] = $sResult;
+            $this->sConclusion = $sResult;
 
         return true;
     }

+ 20 - 0
upgrade/classes/BxDolUpgradeDb.php

@@ -370,6 +370,26 @@ EOJ;
         return mysql_real_escape_string($s);
     }
 
+    function prepare ($sQuery)
+    {
+        $aArgs = func_get_args();
+        $sQuery = array_shift($aArgs);
+        $iPos = 0;
+        foreach ($aArgs as $mixedArg) {
+            if (is_null($mixedArg))
+                $s = 'NULL';
+            elseif (is_numeric($mixedArg))
+                $s = $mixedArg;
+            else
+                $s = "'" . mysql_real_escape_string($mixedArg) . "'";
+
+            $i = bx_mb_strpos($sQuery, '?', $iPos);
+            $sQuery = bx_mb_substr_replace($sQuery, $s, $i, 1);
+            $iPos = $i + get_mb_len($s);
+        }
+        return $sQuery;
+    }
+
     function executeSQL($sPath, $aReplace = array (), $isBreakOnError = true)
     {
         if(!file_exists($sPath) || !($rHandler = fopen($sPath, "r")))

+ 42 - 3
upgrade/classes/BxDolUpgradeUtil.php

@@ -9,10 +9,10 @@
 
 class BxDolUpgradeUtil
 {
-    var $oDb;
-    var $sFolder;
+    protected $oDb;
+    protected $sFolder;
 
-    function BxDolUpgradeUtil($oDb)
+    public function __construct($oDb)
     {
         $this->oDb = $oDb;
     }
@@ -318,6 +318,45 @@ class BxDolUpgradeUtil
 
         return true;
     }
+
+    function filesDelete ()
+    {
+        $sFile = BX_UPGRADE_DIR_UPGRADES . $this->sFolder . '/files_delete.php';
+        if (!file_exists($sFile))
+            return true; // just skip it if file isn't found
+
+        include ($sFile);
+        if (!$aFilesDelete && !is_array($aFilesDelete))
+            return true;
+
+        foreach ($aFilesDelete as $sFile)
+            @unlink(BX_DIRECTORY_PATH_ROOT . $sFile);
+
+        return true;
+    }
+
+    function updateFilesHash ()
+    {
+        require_once(BX_DIRECTORY_PATH_CLASSES . 'BxDolIO.php');
+        require_once(BX_DIRECTORY_PATH_CLASSES . 'BxDolInstallerUtils.php');
+        require_once(BX_DIRECTORY_PATH_CLASSES . 'BxDolInstallerHasher.php');
+
+        $o = new BxDolInstallerHasher();
+
+        $aFiles = $o->getSystemFilesHash ();
+        if (!$aFiles)
+            return 'Can\'t get files list to update hash';
+
+        $sQuery = $this->oDb->prepare("DELETE FROM `sys_modules_file_tracks` WHERE `module_id` = ?", BX_SYSTEM_MODULE_ID);
+        $this->oDb->query($sQuery);
+
+        foreach($aFiles as $aFile) {
+            $sQuery = $this->oDb->prepare("INSERT IGNORE INTO `sys_modules_file_tracks`(`module_id`, `file`, `hash`) VALUES(?, ?, ?)", BX_SYSTEM_MODULE_ID, $aFile['file'], $aFile['hash']);
+            $this->oDb->query($sQuery);
+        }
+
+        return true;
+    }
 }
 
 /** @} */