HEX
Server: Apache
System: Linux WWW 6.1.0-40-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.153-1 (2025-09-20) x86_64
User: web11 (1011)
PHP: 8.2.29
Disabled: NONE
Upload Files
File: /var/www/apklausos/application/controllers/admin/Update.php
<?php

/*
* LimeSurvey
* Copyright (C) 2007-2011 The LimeSurvey Project Team / Carsten Schmitz
* All rights reserved.
* License: GNU/GPL License v2 or later, see LICENSE.php
* LimeSurvey is free software. This version may have been modified pursuant
* to the GNU General Public License, and as distributed it includes or
* is derivative of works licensed under the GNU General Public License or
* other free or open source software licenses.
* See COPYRIGHT.php for copyright notices and details.
*
*
*/

/**
*
* @package       LimeSurvey
* @subpackage    Backend
*/

/**
*
* This controller performs updates, it is highly ajax oriented
* Methods are only called from JavaScript controller (which is called from the global_setting view). comfortupdate.js is the first registered script.
*
*
*
* Public methods are written in a chronological way:
*   - First, when the user click on the 'check for updates' button, the plugin buildComfortButtons.js call for getstablebutton() or getbothbuttons() method and inject the HTML inside the li#udapteButtonsContainer in the _checkButtons view
*   - Then, when the user click on one of those buttons, the comfortUpdateNextStep.js plugin will call for the getWelcome() method and inject the HTML inside div#updaterContainer in the _right_container view (all steps will be then injected here)
*   - Then, when the user click on the continue button, the comfortUpdateNextStep.js plugin will call for the step1() method and inject the  the HTML inside div#updaterContainer in the _right_container view
*   - etc. etc.
*
*
*
*  Some steps must be shown out of the chronological process: getNewKey and submitKey. They are at the end of the controller's interface.
*  Some steps must be 'checked again' after the user fixed some errors (such as file permissions).
*  Those steps are/can be diplayed by the plugin displayComfortStep.js. They are called from buttons like :
*
*  <a class='button' href='<?php App()->createUrl('admin/globalsettings', array('update'=>'methodToCall', 'neededVariable'=>$value));?>'>
*    <span class='ui-button-text'>button text</span>
*  </a>
*
* so they will call an url such as : globalsettings?update=methodToCall&neededVariable=value.
* So the globalsetting controller will render the view as usual, but : the _ajaxVariables view will parse those url datas to some hidden field.
* The comfortupdate.js check the value of the hidden field update, and if the update's one contain a step, it call displayComfortStep.js which will display the right step instead of the 'check update' buttons.
*
* Most steps are retrieving datas from the comfort update server thanks to the model UpdateForm's methods.
* The server return an answer object, with a property 'result' to tell if the process was succesfull or if it failed. This object contains in general all the necessary datas for the views.
*
*
* Handling errors :
* They are different types of possible errors :
* - Warning message (like : modified files, etc.) : they don't stop the process, they are parsed to the step view, and the view manage how to display them. They can be generated from the ComfortUpdate server ($answer_from_server->result == TRUE ; and something like $answer_from_server->error == message or anything else that the step view manage ), or in the LimeSurvey update controller/model
* - Error while processing a request on the server part : should never happen, but if something goes wrong in the server side (like generating an object from model), the server returns an error object ($answer_from_server->result == FALSE ; $answer_from_server->error == message )
*   Those errors stop the process, and are display in _error view. Very usefull to debug. They are parsed directly to $this->renderError
* - Error while checking needed datas in the LimeSurvey update controller : the controller always check if it has the needed datas (such as destintion_build, or zip_file), or the state of the key (outdated, etc). For the code to be dryer, the method parse an error string to $this->renderErrorString($error), which generate the error object, and then render the error view
*
* @package       LimeSurvey
* @subpackage    Backend
*/

// Backward compatibility function for updates to 5.3 from any previous version
// Psalm does not like that the classes are in the same file
if (class_exists('SurveyCommonAction')) {
// phpcs:disable
    class DynamicSurveyCommonAction extends SurveyCommonAction
    {
    }
} else {
    // try to include the old one
    /** @psalm-suppress UndefinedClass */ 
    class DynamicSurveyCommonAction extends Survey_Common_Action
    {
    }
}

class Update extends DynamicSurveyCommonAction
{
// phpcs:enable
    /**
     * Backward compatibility function for updates to 5.3 from any previous version
     *
     * @inheritDoc
     */
    protected function renderWrappedTemplate($sAction = '', $aViewUrls = array(), $aData = array(), $sRenderFile = false)
    {
        if (method_exists(get_parent_class($this), 'renderWrappedTemplate')) {
            parent::renderWrappedTemplate($sAction, $aViewUrls, $aData, $sRenderFile);
        } else {
            parent::_renderWrappedTemplate($sAction, $aViewUrls, $aData, $sRenderFile);
        }
    }

    /**
     * First function to be called, when coming to admin/update
     *
     */
    public function index()
    {
        if (!Permission::model()->hasGlobalPermission('superadmin')) {
            App()->setFlashMessage(gT('You are not allowed to enter this page'), 'error');
            $this->getController()->redirect(App()->getController()->createUrl("/admin"));
        }

        if (App()->getConfig('demoMode')) {
            App()->setFlashMessage(gT('This function cannot be executed because demo mode is active.'), 'error');
            $this->getController()->redirect(App()->getController()->createUrl("/admin"));
        }
        $buttons = 1;
        $updateModel = new UpdateForm();
        $serverAnswer = $updateModel->getUpdateInfo($buttons);
        $aData['serverAnswer'] = $serverAnswer;

        $aData['topbar']['title'] = gT('ComfortUpdate');
        $aData['topbar']['rightButtons'] = Yii::app()->getController()->renderPartial(
            '/admin/update/partials/topbarBtns/rightSideButtons',
            [],
            true
        );

        App()->getClientScript()->registerScriptFile(App()->getConfig('adminscripts') . 'comfortupdate/comfortupdate.js');
        App()->getClientScript()->registerScriptFile(App()->getConfig('adminscripts') . 'comfortupdate/buildComfortButtons.js');
        App()->getClientScript()->registerScriptFile(App()->getConfig('adminscripts') . 'comfortupdate/displayComfortStep.js');

        $this->renderWrappedTemplate('update', '_updateContainer', $aData);
    }

    public function managekey()
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            $buttons = 1;
            $updateModel = new UpdateForm();
            $serverAnswer = $updateModel->getUpdateInfo($buttons);
            $aData['serverAnswer'] = $serverAnswer;
            $updateKey = $aData['updateKey'] = getGlobalSetting('update_key');

            if (!$updateKey) {
                $pageTitle = gT('Subscribe to ComfortUpdate');
            } else {
                $pageTitle = gT('ComfortUpdate');
            }

            $aData['topbar']['title'] = $pageTitle;
            $aData['topbar']['rightButtons'] = Yii::app()->getController()->renderPartial(
                '/admin/update/partials/topbarBtns_subscribe/rightSideButtons',
                [
                    'saveAndClose' => !$updateKey
                ],
                true
            );

            if (!$updateKey) {
                $this->renderWrappedTemplate('update/manage/', 'subscribe', $aData);
            } else {
                $aData['updateKeyInfos'] = $updateModel->checkUpdateKeyonServer($updateKey);
                $this->renderWrappedTemplate('update/manage/', 'manage_key', $aData);
            }
        }
    }

    public function manageSubmitkey()
    {
        $updateModel = new UpdateForm();
        $aData['updateKey'] = $updateKey = SettingGlobal::model()->findByPk('update_key');

        if (Permission::model()->hasGlobalPermission('superadmin')) {
            if (App()->request->getPost('keyid')) {
                // We trim it, just in case user added a space...
                $submittedUpdateKey = trim(App()->request->getPost('keyid', ''));

                $updateModel = new UpdateForm();
                $check = $updateModel->checkUpdateKeyonServer($submittedUpdateKey);
                if ($check->result) {
                    // If the key is validated by server, we update the local database with this key
                    $updateKey = $updateModel->setUpdateKey($submittedUpdateKey);
                    App()->session['flashmessage'] = gT("Your key has been updated and validated! You can now use ComfortUpdate.");
                    // then, we render the what returned the server (views and key infos or error )
                    App()->getController()->redirect(App()->getController()->createUrl('admin/update/sa/managekey'));
                } else {
                    switch ($check->error) {
                        case 'out_of_updates':
                            $title = gT("Your update key has exceeded the maximum number of updates!");
                            $message = gT("Please buy/enter a new one!");
                            break;

                        case 'expired':
                            $title = gT("Your update key has expired!");
                            $message = gT("Please buy/enter a new one!");
                            break;

                        case 'not_found':
                            $title = gT("Unknown update key!");
                            $message = gT("Your key is unknown to the update server.");
                            break;

                        case 'key_null':
                            $title = gT("Key can't be empty!");
                            $message = "";
                            break;

                        case 'no_server_answer':
                            $title = gT('No server answer!');
                            $message = gT("It seems that the ComfortUpdate server is not responding. Please try again in few minutes or contact the LimeSurvey team.");
                            break;
                    }

                    App()->setFlashMessage('<strong>' . gT($title) . '</strong> ' . gT($message), 'error');
                    App()->getController()->redirect(App()->getController()->createUrl('admin/update/sa/managekey'));
                }
            }
        }
    }

    public function deleteKey()
    {
        $this->requirePostRequest();

        if (Permission::model()->hasGlobalPermission('superadmin')) {
            SettingGlobal::model()->deleteByPk('update_key');
            App()->setFlashMessage('Your update key has been removed');
            App()->getController()->redirect(App()->getController()->createUrl('admin/update/sa/managekey'));
        }
    }

    /**
     * This function return the update buttons for stable branch
     * @return string html the button code
     */
    public function getstablebutton()
    {
        echo $this->getButtons("1");
    }

    /**
     * This function return the update buttons for all versions
     * @return string the buttons code
     */
    public function getbothbuttons()
    {
        echo $this->getButtons("1");
    }

    /**
     * This function has a special rendering, because the ComfortUpdate server can choose what it's going to show :
     * the welcome message or the subscribe message or the updater update, etc.
     * The same system is used for the static views (update key, etc.)
     *
     * @return string|null the welcome message
     */
    public function getwelcome()
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            // We get the update key in the database. If it's empty, getWelcomeMessage will return subscription
            $updateKey = getGlobalSetting("update_key");
            $updateModel = new UpdateForm();
            $destinationBuild = $_REQUEST['destinationBuild'];
                $welcome = (array) $updateModel->getWelcomeMessage($updateKey, $destinationBuild);
                $welcome['destinationBuild'] = $destinationBuild;
            $welcome = (object) $welcome;

                return $this->renderWelcome($welcome);
        }
    }

    /**
     * returns the "Checking basic requirements" step
     * @return string the welcome message
     */
    public function checkLocalErrors()
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            // We use request rather than post, because this step can be called by url by displayComfortStep.js
            if (isset($_REQUEST['destinationBuild'])) {
                $destinationBuild = $_REQUEST['destinationBuild'];
                $access_token     = $_REQUEST['access_token'];

                $updateModel = new UpdateForm();
                $localChecks = $updateModel->getLocalChecks($destinationBuild);
                $aData['localChecks'] = $localChecks;
                $aData['changelog'] = null;
                $aData['destinationBuild'] = $destinationBuild;
                $aData['access_token'] = $access_token;

                return $this->controller->renderPartial('update/updater/steps/_check_local_errors', $aData, false, false);
            }
            return $this->renderErrorString("unknown_destination_build");
        }
    }

    /**
     * Display change log
     * @return string
     */
    public function changeLog()
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            // We use request rather than post, because this step can be called by url by displayComfortStep.js
            if (isset($_REQUEST['destinationBuild'])) {
                $destinationBuild = $_REQUEST['destinationBuild'];
                $access_token     = $_REQUEST['access_token'];

                // We get the change log from the ComfortUpdate server
                $updateModel = new UpdateForm();
                $changelog = $updateModel->getChangeLog($destinationBuild);

                if ($changelog->result) {
                    $aData['errors'] = false;
                    $aData['changelogs'] = $changelog;
                    $aData['html_from_server'] = $changelog->html;
                    $aData['destinationBuild'] = $destinationBuild;
                    $aData['access_token'] = $access_token;
                } else {
                    return $this->renderError($changelog);
                }
                return $this->controller->renderPartial('update/updater/steps/_change_log', $aData, false, false);
            }
            return $this->renderErrorString("unknown_destination_build");
        }
    }

    /**
     * diaplay the result of the changed files check
     *
     * @return string  HTML
     */
    public function fileSystem()
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            App()->session['update_changed_files'] = null;
            if (isset($_REQUEST['destinationBuild'])) {
                $tobuild = $_REQUEST['destinationBuild'];
                $access_token = $_REQUEST['access_token'];
                $frombuild = App()->getConfig("buildnumber");

                $updateModel = new UpdateForm();
                $changedFiles = $updateModel->getChangedFiles($tobuild);

                if ($changedFiles->result) {
                    $aData = $updateModel->getFileStatus($changedFiles->files);
                    App()->session['update_changed_files'] = json_decode(json_encode($changedFiles->files), true);

                    $aData['html_from_server'] = $changedFiles->html ?? '';
                    $aData['destinationBuild'] = $tobuild;
                    $aData['updateinfo'] = $changedFiles->files;
                    $aData['access_token'] = $access_token;

                    return $this->controller->renderPartial('update/updater/steps/_fileSystem', $aData, false, false);
                }
                return $this->renderError($changedFiles);
            }
            return $this->renderErrorString("unknown_destination_build");
        }
    }

    /**
     * backup files
     * @return string
     */
    public function backup()
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            if (App()->request->getPost('destinationBuild')) {
                $destinationBuild = App()->request->getPost('destinationBuild');
                $access_token     = $_REQUEST['access_token'];
                $updateModel = new UpdateForm();
                $changedFiles = App()->session['update_changed_files'];
                if ($changedFiles) {
                    $backupInfos = $updateModel->backupFiles($changedFiles);

                    if ($backupInfos->result) {
                        $dbBackupInfos = $updateModel->backupDb($destinationBuild);
                        // If dbBackup fails, it will just provide a warning message : backup manually

                        $aData['dbBackupInfos'] = $dbBackupInfos;
                        $aData['basefilename'] = $backupInfos->basefilename;
                        $aData['tempdir'] = $backupInfos->tempdir;
                        $aData['destinationBuild'] = $destinationBuild;
                        $aData['access_token'] = $access_token;
                        return $this->controller->renderPartial('update/updater/steps/_backup', $aData, false, false);
                    } else {
                        $error = $backupInfos->error;
                    }
                } else {
                    $error = "no_updates_infos";
                }
            } else {
                $error = "unknown_destination_build";
            }
            return $this->renderErrorString($error);
        }
    }

    /**
     * Display step4
     * @return string
     */
    public function step4()
    {
        $event = new CExceptionEvent($this, new Exception());  // Dummy line to preload CExceptionEvent class.
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            if (App()->request->getPost('destinationBuild')) {
                $destinationBuild = App()->request->getPost('destinationBuild');
                $access_token     = $_REQUEST['access_token'];

                $changedFiles = App()->session['update_changed_files'];
                if ($changedFiles) {
                    // this is the last step - Download the zip file, unpack it and replace files accordingly

                    $updateModel = new UpdateForm();
                    $remove = $updateModel->removeDeletedFiles((array)$changedFiles);
                    if (!$remove->result) {
                        return $this->renderErrorString($remove->error, $remove->message);
                    };
                    $file = $updateModel->downloadUpdateFile($access_token, $destinationBuild);
                    if ($file->result) {
                        $unzip = $updateModel->unzipUpdateFile();
                        if ($unzip->result) {
                            // Should never bug (version.php is checked before))
                            $updateModel->updateVersion($destinationBuild);
                            $updateModel->removeTmpFile('update.zip');
                            $updateModel->removeTmpFile('comfort_updater_cookie.txt');

                            App()->session['update_result'] = null;
                            App()->session['security_update'] = null;
                            App()->session['update_changed_files'] = null;

                            $today = new DateTime("now");
                            App()->session['next_update_check'] = $today->add(new DateInterval('PT6H'));

                            return $this->controller->renderPartial('update/updater/steps/_final', array('destinationBuild' => $destinationBuild), false, false);
                        } else {
                            $error = $unzip->error;
                        }
                    } else {
                        $error = $file->error;
                    }
                } else {
                    $error = "no_updates_infos";
                }
            } else {
                $error = "unknown_destination_build";
            }
            return $this->renderErrorString($error);
        }
    }

    /**
     * This function update the updater
     * It is called from the view _updater_update.
     * The view _updater_update is called by the ComfortUpdate server during the getWelcome step if the updater version is not the minimal required one.
     * @return string the welcome message
     */
    public function updateUpdater()
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            if (App()->request->getPost('destinationBuild')) {
                $destinationBuild = App()->request->getPost('destinationBuild');
                $updateModel = new UpdateForm();

                $localChecks = $updateModel->getLocalChecksForUpdater();

                if ($localChecks->result) {
                    $file = $updateModel->downloadUpdateUpdaterFile($destinationBuild);

                    if ($file->result) {
                        $unzip = $updateModel->unzipUpdateUpdaterFile();
                        if ($unzip->result) {
                            $updateModel->removeTmpFile('update_updater.zip');
                            $updateModel->removeTmpFile('comfort_updater_cookie.txt');
                            SettingGlobal::setSetting('updateavailable', '0');
                            SettingGlobal::setSetting('updatebuild', '');
                            SettingGlobal::setSetting('updaterversions', '');
                            App()->session['update_result'] = null;
                            App()->session['next_update_check'] = null;
                            return $this->controller->renderPartial('update/updater/steps/_updater_updated', array('destinationBuild' => $destinationBuild), false, false);
                        } else {
                            $error = $unzip->error;
                        }
                    } else {
                        $error = $file->error;
                    }
                } else {
                    return $this->controller->renderPartial('update/updater/welcome/_error_files_update_updater', array('localChecks' => $localChecks), false, false);
                }
            }
            return $this->renderErrorString($error);
        }
    }

    /**
     * This return the subscribe message
     * @return string the welcome message
     */
    public function getnewkey()
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            // We want to call the server to display the subscribe message
            // So if needed, we can display a specific html message (like we do for update to LTS with a free key)
            // To force server to render the subscribe message, we call for the last 2.06+ release (which need at least a free key)
            $updateModel = new UpdateForm();
            $welcome = $updateModel->getWelcomeMessage(null, '160129'); //$updateKey
            echo $this->renderWelcome($welcome);
        }
    }

    /**
     * This function create or update the LS update key
     * @return string
     */
    public function submitkey()
    {

        if (Permission::model()->hasGlobalPermission('superadmin')) {
            if (App()->request->getPost('keyid')) {
                // We trim it, just in case user added a space...
                $submittedUpdateKey = trim(App()->request->getPost('keyid', ''));

                $updateModel = new UpdateForm();
                $check = $updateModel->checkUpdateKeyonServer($submittedUpdateKey);
                if ($check->result) {
                    // If the key is validated by server, we update the local database with this key
                    $updateKey = $updateModel->setUpdateKey($submittedUpdateKey);
                    $check = new stdClass();
                    $check->result = true;
                    $check->view = "key_updated";
                }
                // then, we render the what returned the server (views and key infos or error )
                echo $this->renderWelcome($check);
            } else {
                return $this->renderErrorString("key_null");
            }
        }
    }



    /**
     * Update database
     */
    public function db($continue = null)
    {
        App()->loadHelper("update/update");
        if (isset($continue) && $continue == "yes") {
            $aViewUrls['output'] = CheckForDBUpgrades($continue);
            $aData['display']['header'] = false;
        } else {
            $aData['display']['header'] = true;
            $aViewUrls['output'] = CheckForDBUpgrades();
        }

        $aData['updatedbaction'] = true;

        $this->renderWrappedTemplate('update', $aViewUrls, $aData);
    }

    /**
     * For updates from the old updater.
     */
    public function step4b()
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            if (!isset(App()->session['installlstep4b'])) {
                die();
            }
            $aData = App()->session['installlstep4b'];
            unset(App()->session['installlstep4b']);
            $this->renderWrappedTemplate('update/updater/steps', '_old_step4b', $aData);
        }
    }

    /**
     * This function change the notification state : big alert notification 1, or small one 0
     * It's called via ajax from view adminmenu
     */
    public function notificationstate($state = '0')
    {
        App()->session['notificationstate'] = $state;
        return '1';
    }
    /**
     * this function render the update buttons
     * @param string $crosscheck
     */
    private function getButtons($crosscheck)
    {
        if (Permission::model()->hasGlobalPermission('superadmin')) {
            $updateModel = new UpdateForm();
            $serverAnswer = $updateModel->getUpdateInfo($crosscheck);

            // TODO : if no update available, set session about  it...

            if ($serverAnswer->result) {
                unset($serverAnswer->result);
                return $this->controller->renderPartial('//admin/update/check_updates/update_buttons/_updatesavailable', array('updateInfos' => $serverAnswer), false, false);
            }
            // Error : we build the error title and messages
            return $this->controller->renderPartial('//admin/update/check_updates/update_buttons/_updatesavailable_error', array('serverAnswer' => $serverAnswer), false, false);
        }
    }

    /**
     * This method render the welcome/subscribe/key_updated message
     * @param obj $serverAnswer the answer return by the server
     */
    private function renderWelcome($serverAnswer)
    {
        if ($serverAnswer->result) {
            // Available views (in /admin/update/welcome/ )
            $views = array('welcome', 'subscribe', 'key_updated', 'updater_update');
            if (in_array($serverAnswer->view, $views)) {
                $sValidityDate = '';
                if (isset($serverAnswer->key_infos->validuntil)) {
                    $sValidityDate = convertToGlobalSettingFormat($serverAnswer->key_infos->validuntil);
                }
                return $this->controller->renderPartial('//admin/update/updater/welcome/_' . $serverAnswer->view, array('serverAnswer' => $serverAnswer, 'sValidityDate' => $sValidityDate), false, false);
            } else {
                $serverAnswer->result = false;
                $serverAnswer->error = "unknown_view";
            }
        }
        echo $this->renderError($serverAnswer);
    }


    /**
     * This method renders the error view
     * @param object $errorObject
     * @return string
     */
    private function renderError($errorObject)
    {
        echo $this->controller->renderPartial('//admin/update/updater/_error', array('errorObject' => $errorObject), false, false);
    }

    /**
     * This method convert a string to an error object, and then render the error view
     * @param string $error the error message
     * @return string
     */
    private function renderErrorString($error, $message = null)
    {
            $errorObject = new stdClass();
            $errorObject->result = false;
            $errorObject->error = $error;
            $errorObject->message = $message;
            return $this->renderError($errorObject);
    }

    /**
     * This function convert the huge updateinfos array to a base64 string, so it can be parsed to the view to be inserted in an hidden input element.
     *
     * @param array $updateinfos the udpadte infos array returned by the update server
     * @return string
     */
    private function parseToView($updateinfos)
    {
        $data = json_encode($updateinfos);
        return base64_encode($data);
    }
}