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/QuickTranslationController.php
<?php

use Anper\Iuliia\Iuliia;

class QuickTranslationController extends LSBaseController
{
    /**
     * Here we have to use the correct layout (NOT main.php)
     *
     * @param string $view
     * @return bool
     */
    protected function beforeRender($view)
    {
        $this->layout = 'layout_questioneditor';
        LimeExpressionManager::SetSurveyId($this->aData['surveyid']);
        LimeExpressionManager::StartProcessingPage(false, true);

        return parent::beforeRender($view);
    }

    /**
     *
     *
     * @param $surveyid
     * @return void
     * @throws CHttpException
     */
    public function actionIndex($surveyid)
    {
        /* existing + read (survey) already checked in SurveyCommonAction : existing use model : then if surveyid is not valid : return a 404 */
        /* survey : read OK, not survey:tranlations:read … */
        if (!Permission::model()->hasSurveyPermission($surveyid, 'translations', 'read')) {
            throw new CHttpException(401, "401 Unauthorized");
        }

        $oSurvey = Survey::model()->findByPk($surveyid);

        //------------------------ Initial and get helper classes  --------------
        //KCFINDER SETTINGS
        Yii::app()->session['FileManagerContext'] = "edit:survey:{$oSurvey->sid}";
        Yii::app()->loadHelper('admin.htmleditor');
        initKcfinder();

        App()->getClientScript()->registerScriptFile(App()->getConfig('adminscripts') . 'translation.js');
        Yii::app()->loadHelper("database");
        Yii::app()->loadHelper("admin.htmleditor");
        Yii::app()->loadHelper("surveytranslator");
        //-----------------------------------------------------------------------

        //this GET-Param is the language to which it should be translated (e.g. 'de')
        $languageToTranslate = Yii::app()->getRequest()->getParam('lang');

        if (!empty($languageToTranslate) && !in_array($languageToTranslate, $oSurvey->getAllLanguages())) {
            Yii::app()->setFlashMessage(gT("Invalid language"), 'warning');
            $languageToTranslate = null;
        }
        $action = Yii::app()->getRequest()->getParam('action');
        $actionvalue = Yii::app()->getRequest()->getPost('actionvalue');

        if ($action == "ajaxtranslategoogleapi") {
            echo $this->translateGoogleApi();
            return;
        }

        $baselang = $oSurvey->language;
        $additionalLanguages = $oSurvey->additionalLanguages;

        //set it directly to the first additional language (if any exists), if no language was selected by user
        if (empty($languageToTranslate) && count($additionalLanguages) > 0) {
            $languageToTranslate = $additionalLanguages[0];
        }

        $survey_title = $oSurvey->defaultlanguage->surveyls_title;
        $supportedLanguages = getLanguageData(false, Yii::app()->session['adminlang']);

        $baselangdesc = $supportedLanguages[$baselang]['description'];

        $aData = array(
            "surveyid" => $surveyid,
            "survey_title" => $survey_title,
            "tolang" => $languageToTranslate,
        );
        $quickTranslation = new \LimeSurvey\Models\Services\QuickTranslation($oSurvey);

        if (!empty($languageToTranslate)) {
            // Only save if the administration user has the correct permission
            //todo: this is only necessary on save ...
            if ($actionvalue == "translateSave" && Permission::model()->hasSurveyPermission($surveyid, 'translations', 'update')) {
                $this->translateSave($languageToTranslate, $quickTranslation);
                Yii::app()->setFlashMessage(gT("Saved"), 'success');
            }

            $tolangdesc = $supportedLanguages[$languageToTranslate]['description'];
            // display tabs with fields to translate, as well as input fields for translated values

            //todo: this view information has to be passed to the view
            $views = $this->displayUntranslatedFields($quickTranslation, $languageToTranslate, $baselang, $baselangdesc, $tolangdesc);
        }

        $aData['sidemenu']['state'] = false;
        $aData['title_bar']['title'] = $oSurvey->currentLanguageSettings->surveyls_title . " (" . gT("ID") . ":" . $surveyid . ")";
        if (Permission::model()->hasSurveyPermission($surveyid, 'translations', 'update')) {
            $aData['surveybar']['savebutton']['form'] = 'translateform';
            $aData['topBar']['showSaveButton'] = true;
            //buttons in topbar
            $topbarData = TopbarConfiguration::getSurveyTopbarData($oSurvey->sid);
            $topbarData = array_merge($topbarData, $aData['topBar']);
            $aData['topbar']['middleButtons'] = $this->renderPartial(
                '/surveyAdministration/partial/topbar/surveyTopbarLeft_view',
                $topbarData,
                true
            );
            $aData['topbar']['rightButtons'] = $this->renderPartial(
                '/surveyAdministration/partial/topbar/surveyTopbarRight_view',
                $topbarData,
                true
            );
        }

        $aData['display']['menu_bars'] = false;
        $this->aData = $aData;
        $this->render('index', [
            'survey' => $oSurvey,
            'languageToTranslate' => $languageToTranslate,
            'additionalLanguages' => $additionalLanguages,
            'viewData' => $views
        ]);
    }

    /**
     * @param $survey Survey the survey object
     * @param $tolang
     * @param $baselang
     * @param $quickTranslation \LimeSurvey\Models\Services\QuickTranslation the quicktranslation object
     * @return void
     */
    private function translateSave($tolang, $quickTranslation)
    {
        $tab_names = $quickTranslation->getTabNames();
        $tab_names_full = $tab_names;

        //todo: this part could also into QuickTranslation
        foreach ($tab_names as $type) {
            $amTypeOptions = $quickTranslation->setupTranslateFields($type);
            $type2 = $amTypeOptions["associated"];

            if (!empty($type2)) {
                $tab_names_full[] = $type2;
            }
        }

        foreach ($tab_names_full as $type) {
            $size = (int) Yii::app()->getRequest()->getPost("{$type}_size"); //todo: what is size here?
            // start a loop in order to update each record
            $i = 0;
            while ($i <= $size) {
                // define each variable
                if (Yii::app()->getRequest()->getPost("{$type}_newvalue_{$i}")) {
                    $old = Yii::app()->getRequest()->getPost("{$type}_oldvalue_{$i}");
                    $new = Yii::app()->getRequest()->getPost("{$type}_newvalue_{$i}");

                    // check if the new value is different from old, and then update database
                    if ($new != $old) {
                        $qidOrGid = Yii::app()->getRequest()->getPost("{$type}_id1_{$i}");
                        $answerCode = Yii::app()->getRequest()->getPost("{$type}_id2_{$i}");
                        $iScaleID = Yii::app()->getRequest()->getPost("{$type}_scaleid_{$i}");
                        $quickTranslation->updateTranslations($type, $tolang, $new, $qidOrGid, $answerCode, $iScaleID);
                    }
                }
                $i++;
            } // end while
        } // end foreach
    }

    /**
     * Collecting database data and ckeditor data for the views.
     *
     * @param $quickTranslation \LimeSurvey\Models\Services\QuickTranslation the quick translation object
     * @param $tolang string language to translate to
     * @param $baselang  string the base language
     * @param $baselangdesc string the base language description
     * @param $tolangdesc string the language to translate description
     *
     * @return array
     * @throws CException
     */
    private function displayUntranslatedFields($quickTranslation, $tolang, $baselang, $baselangdesc, $tolangdesc)
    {
        // Define aData
        $survey = $quickTranslation->getSurvey();
        $tabsViewData['surveyid'] = $survey->sid;
        $tabsViewData['tab_names'] = $quickTranslation->getTabNames();
        $tabsViewData['tolang'] = $tolang;
        $tabsViewData['baselang'] = $baselang;
        $tabsViewData['baselangdesc'] = $baselangdesc;
        $tabsViewData['tolangdesc'] = $tolangdesc;

        //This is for the tab navbar
        $tabsViewData['amTypeOptions'] = $quickTranslation->getAllTranslateFields();

        //this array will contain all necessary data for the views
        /*
         * structure will be like
         *
         * $tabsViewData['tabname'] = [
         *      'data1' => ''
         *      ....
         *      'tabFieldData = []
         * ]
         */
        $tabsViewData['singleTabs'] = [];

        //iterate through all tabs and define content of each tab
        foreach ($tabsViewData['tab_names'] as $tabName) {
            $singleTabData = [];
            $translateFieldOptions = $quickTranslation->setupTranslateFields($tabName);
            $translationsBase = $quickTranslation->getTranslations($tabName, $baselang);
            $translationsTo = $quickTranslation->getTranslations($tabName, $tolang);
            $associatedName = $translateFieldOptions["associated"];
            $associated = false;
            if (!empty($associatedName)) {
                $associated = true;
                //get type options again
                $associatedData = $quickTranslation->setupTranslateFields($associatedName);
            }
            $singleTabData['type'] = $tabName;
            //always set first tab active
            $singleTabData['activeTab'] = $tabName === 'title';

            //iterates through active record results depending on the tab
            $countResultBase = count($translationsBase);
            for ($j = 0; $j < $countResultBase; $j++) {
                $singleTabFieldsData = [];
                $translationBase = $translationsBase[$j];
                $translationTo = $translationsTo[$j];

                $translationAttributesBase = [];
                $translationAttributesTo = [];
                $class = get_class($translationBase);
                if ($class === 'QuestionGroup') {
                    $translationAttributesBase = $translationBase->questiongroupl10ns[$baselang]->getAttributes();
                    $translationAttributesTo = array_key_exists($tolang, $translationTo->questiongroupl10ns)
                        ? $translationTo->questiongroupl10ns[$tolang]->getAttributes()
                        : $translationAttributesBase;
                } elseif ($class === 'Question' || $class === 'Subquestion') {
                    $translationAttributesBase = $translationBase->questionl10ns[$baselang]->getAttributes();
                    if (!empty($translationBase['parent_qid'])) {
                        $translationAttributesBase['parent'] = $translationBase->parent->getAttributes();
                    }
                    $translationAttributesTo = array_key_exists($tolang, $translationTo->questionl10ns)
                        ? $translationTo->questionl10ns[$tolang]->getAttributes()
                        : $translationAttributesBase;
                } elseif ($class === 'Answer') {
                    $translationAttributesBase = $translationBase->answerl10ns[$baselang]->getAttributes();
                    $translationAttributesBase['question_title'] = $translationBase->question->title; ///this is the question code
                    $translationAttributesTo = array_key_exists($tolang, $translationTo->answerl10ns)
                        ? $translationTo->answerl10ns[$tolang]->getAttributes()
                        : $translationAttributesBase;
                }
                $translationAttributesBase = array_merge($translationAttributesBase, $translationBase->getAttributes());
                $translationAttributesTo = array_merge($translationAttributesTo, $translationTo->getAttributes());

                $textfrom = htmlspecialchars_decode((string)$translationAttributesBase[$translateFieldOptions["dbColumn"]]);

                $textto = $translationAttributesTo[$translateFieldOptions["dbColumn"]];
                if ($associated) {
                    $associatedResultBase = htmlspecialchars_decode((string)$translationAttributesBase[$associatedData["dbColumn"]]);
                    $associatedResultTo = $translationAttributesTo[$associatedData["dbColumn"]];
                }

                $gid = ($translateFieldOptions["gid"] == true) ? $translationAttributesBase['gid'] : null;
                $qid = ($translateFieldOptions["qid"] == true) ? $translationAttributesBase['qid'] : null;

                $textform_length = strlen(trim($textfrom));
                $textfrom2_length = $associated ? strlen(trim((string)$associatedResultBase)) : 0;

                $singleTabFieldsData['all_fields_empty'] = ($textform_length == 0) && ($textfrom2_length == 0);

                $singleTabFieldsData['fieldData'] = [
                    'textfrom'       => $this->cleanup($textfrom),
                    'textto'         => $this->cleanup($textto),
                    'rowfrom'        => $translationAttributesBase,
                    'gid'            => $gid,
                    'qid'            => $qid,
                    'amTypeOptions'  => $translateFieldOptions,
                    'associatedData' => $associatedData,
                    'i'              => $j,
                    'type'           => $tabName,
                    'associatedName' => $associatedName,
                    'associated'     => $associated,
                ];
                $singleTabFieldsData['translateFields'] = [];
                $singleTabFieldsData['translateFields'][] = [
                    'surveyId'      => $survey->sid,
                    'gid'           => $gid,
                    'qid'           => $qid,
                    'type'          => $tabName,
                    'amTypeOptions' => $translateFieldOptions,
                    'textfrom'      => $textfrom,
                    'textto'        => $textto,
                    'j'             => $j,
                    'rowfrom'       => $translationAttributesBase,
                    'nrows'         => max($this->calcNRows($textfrom), $this->calcNRows($textto))
                ];
                if ($associated && strlen(trim((string)$associatedResultBase)) > 0) {
                    $singleTabFieldsData['translateFields'][] = [
                        'surveyId'      => $survey->sid,
                        'gid'           => $gid,
                        'qid'           => $qid,
                        'type'          => $associatedName,
                        'amTypeOptions' => $associatedData,
                        'textfrom'      => $associatedResultBase,
                        'textto'        => $associatedResultTo,
                        'j'             => $j,
                        'rowfrom'       => $translationAttributesBase,
                        'nrows'         => max($this->calcNRows($associatedResultBase), $this->calcNRows($associatedResultTo))
                    ];
                }
                $singleTabData['singleTabFieldsData'][] = $singleTabFieldsData;
            } // end for

            $tabsViewData['bReadOnly'] = !Permission::model()->hasSurveyPermission($survey->sid, 'translations', 'update');
            $tabsViewData['singleTabs'][] = $singleTabData;
        } // end foreach
        return $tabsViewData;
    }

    /**
     *
     *
     * @param string $string
     * @return string|null
     */
    private function cleanup($string): ?string
    {
        if (extension_loaded('tidy')) {
            $oTidy = new tidy();

            $cleansedString = $oTidy->repairString($string, array(), 'utf8');
        } else {
            //We should check for tidy on Installation!
            $cleansedString = $string;
        }

        return $cleansedString;
    }

    /**
     * It loads the correct editor mode (inline, popup, modal).
     * This is used in the view file translateFieldData.
     *
     * @param $htmleditor
     * @param string[] $aData
     * @return mixed
     */
    protected function loadEditor($htmleditor, $aData)
    {
        $editor_function = "";
        $displayType = strtolower((string) $htmleditor["HTMLeditorDisplay"]);
        $displayTypeIsEmpty = empty($displayType);

        if ($displayType == "inline" || $displayTypeIsEmpty) {
            $editor_function = "getEditor";
        } elseif ($displayType == "popup") {
            $editor_function = "getPopupEditor";
            $aData[2] = urlencode((string) $htmleditor['description']);
        } elseif ($displayType == "modal") {
            $editor_function = "getModalEditor";
            $aData[2] = $htmleditor['description'];
        }
        return call_user_func_array($editor_function, $aData);
    }

    /**
     * calcNRows($subject) calculates the vertical size of textbox for survey translation.
     * The function adds the number of line breaks <br /> to the number of times a string wrap occurs.
     * @param string $subject The text string that is being translated
     * @return double
     */
    private function calcNRows($subject)
    {
        if ($subject === null) {
            $subject = "";
        }
        // Determines the size of the text box
        // A proxy for box sixe is string length divided by 80
        $pattern = "(<br..?>)";
        $pattern = '[(<br..?>)|(/\n/)]';

        $nrows_newline = preg_match_all($pattern, $subject, $matches);

        $subject_length = strlen((string) $subject);
        $nrows_char = ceil($subject_length / 80);

        return $nrows_newline + $nrows_char;
    }

    /**
     *
     *
     * @return void
     */
    public function actionAjaxtranslategoogleapi($surveyid)
    {
        // Ensure YII_CSRF_TOKEN, we are in admin, then only user with admin right can post
        /* No Permission check on survey, seems unneded (return a josn with current string posted */

        //todo: check if googletranslate is activated ...
        if (!Permission::model()->hasSurveyPermission($surveyid, 'translations', 'read')) {
            throw new CHttpException(401, "401 Unauthorized");
        }
        if (Yii::app()->request->isPostRequest) {
            echo self::translateGoogleApi();
        }
    }

    /**
     * translateGoogleApi.php
     * Creates a JSON interface for the auto-translate feature
     *
     * @psalm-suppress UndefinedClass TODO: Dead code?
     * @psalm-suppress MissingFile
     */
    private function translateGoogleApi()
    {
        $sBaselang   = Yii::app()->getRequest()->getPost('baselang', '');
        $sTolang     = Yii::app()->getRequest()->getPost('tolang', '');
        $sToconvert  = Yii::app()->getRequest()->getPost('text', '');

        $replacements = array(
            'zh-Hans' => 'zh-CN',
            'zh-Hant-HK' => 'zh-TW',
            'zh-Hant-TW' => 'zh-TW',
            'nl-informal' => 'nl',
            'de-informal' => 'de',
            'de-easy' => 'de',
            'it-formal' => 'it',
            'pt-BR' => 'pt',
            'es-MX' => 'es',
            'nb' => 'no',
            'nn' => 'no',
        );
        $sBaselang = isset($replacements[$sBaselang]) ? $replacements[$sBaselang] : $sBaselang;
        $sTolang = isset($replacements[$sTolang]) ? $replacements[$sTolang] : $sTolang;

        $error = false;

        try {
            require_once(APPPATH . '/../vendor/gtranslate-api/GTranslate.php');
            $gtranslate = new Gtranslate();
            // use curl because http with fopen is disabled
            $gtranslate->setRequestType('curl');
            $objGt = $gtranslate;

            // Gtranslate requires you to run function named XXLANG_to_XXLANG
            $sProcedure = $sBaselang . "_to_" . $sTolang;

            $parts = LimeExpressionManager::SplitStringOnExpressions($sToconvert);

            $sparts = array();
            foreach ($parts as $part) {
                if ($part[2] == 'EXPRESSION') {
                    $sparts[] = $part[0];
                } else {
                    $convertedPart = (string) $objGt->$sProcedure($part[0]);
                    $convertedPart  = str_replace("<br>", "\r\n", $convertedPart);
                    $convertedPart  = html_entity_decode(stripcslashes($convertedPart));
                    if ($sTolang == 'sr-Latn') {
                        $convertedPart = Iuliia::translate($convertedPart, Iuliia::TELEGRAM);
                    }
                    $sparts[] = $convertedPart;
                }
            }
            $sOutput = implode(' ', $sparts);
        } catch (GTranslateException $ge) {
            // Get the error message and build the ouput array
            $error = true;
            $sOutput = $ge->getMessage();
        }

        $aOutput = array(
            'error'     =>  $error,
            'baselang'  =>  $sBaselang,
            'tolang'    =>  $sTolang,
            'converted' =>  $sOutput
        );

        header('Content-type: application/json');
        return ls_json_encode($aOutput);
    }
}