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

/**
 * This class will generate all the twig cache file from command line, even if LimeSurvey is not installed.
 * The current use case is to generate the translation files using Glot Press.
 * In the future, it could be use to boost performance of first survey rendering (can be useful after a tmp cleanup, an update, etc).
 *
 * To execute this command :
 * php application/commands/console.php Twig generateTwigTmpFiles
 *
 * It will execute all the functions
 */

class TwigCommand extends CConsoleCommand
{

    public $aLogs; // Array of logs

    /**
     * Load the needed helpers, set default vaules, etc
     */
    public function init()
    {
      // Needed helpers for correct rendering
        Yii::import('application.helpers.surveytranslator_helper', true);
        Yii::import('application.helpers.common_helper', true);
        Yii::import('application.helpers.expressions.em_manager_helper', true);
        Yii::import('application.helpers.admin.htmleditor_helper', true);

      // Directories where the assets will be created.
        Yii::app()->assetManager->setBasePath(realpath(__DIR__ . '/../../tmp/assets/'));

      // This command can be used even with no DB installed. So we force the usage of config.xml rather than DB entries
        Yii::app()->setConfig('force_xmlsettings_for_survey_rendering', true);
    }

    public function actionIndex()
    {
        echo "This class will generate all the twig cache file from command line, even if LimeSurvey is not installed.\n";
        echo "The current use case is to generate the translation files using Glot Press.\n";
        echo "In the future, it could be use to boost performance of first survey rendering (can be useful after a tmp cleanup, an update, etc).\n";
        echo "\n";
        echo "\n";
        echo "To execute this command :\n";
        echo "php application/commands/console.php Twig generateTwigTmpFiles \n";
    }

    /**
     * Generate twig cache files for each core Survey Theme and core questions views.
     */
    public function actionGenerateTwigTmpFiles($sThemeDir = null, $bGenerateSurveyCache = true, $bGenerateQuestionsCache = true, $bGenerateAdminCache = true, $bGenerateQuestionThemeCache = true)
    {
        $this->aLogs = array();
        $this->aLogs["action"] = "actionGenerateTwigTmpFiles $sThemeDir $bGenerateSurveyCache $bGenerateQuestionsCache $bGenerateAdminCache $bGenerateQuestionThemeCache";
        if ($bGenerateSurveyCache) {
            $this->actionGenerateSurveyThemesCache($sThemeDir);
        }

        if ($bGenerateQuestionsCache) {
            $this->actionGenerateQuestionsCache(null);
        }

        if ($bGenerateAdminCache) {
            $this->actionGenerateAdminCache(null);
        }

        if ($bGenerateQuestionThemeCache) {
            $this->actionGenerateQuestionThemesCache($sThemeDir);
        }
    }

    /**
     * Generate twig cache files for each core survey theme
     *
     * @param string $sThemeDir the directory to parse, where to find the manifests.
     */
    public function actionGenerateSurveyThemesCache($sThemeDir = null)
    {
        $this->aLogs["action"] = "actionGenerateSurveyThemesCache $sThemeDir";

      // NOTE 1: by default used only for core theme.
      // NOTE 2: Later, we'll can use this function to offer to generate .po files for themes developers
        $sThemeDir = ($sThemeDir == null) ? dirname(__FILE__) . '/../../themes/survey' : $sThemeDir;
        $oThemeDir = new DirectoryIterator($sThemeDir);

        foreach ($oThemeDir as $fileinfo) {
            if ($fileinfo->getFilename() != ".." && $fileinfo->getFilename() != "." && $fileinfo->getFilename() != "index.html") {
                $templatename = $fileinfo->getFilename();
                $oTemplateForPreview = Template::getInstance($templatename, null, null, true, true)->prepareTemplateRendering($templatename, null, true);

              // Render Survey theme
                $this->renderSurveyTheme($oTemplateForPreview);

              // Render all the twig strings inside the XML itself
                $this->renderSurveyThemeManifest($oTemplateForPreview);
            }
        }
    }

    /**
    * Generate twig cache files for each question type.
    * NOTE 1: It's a recursive function, since some directories are the question type itself (it has an answer.twig file) but other containes various question types as subdirectories.
    * NOTE 2: Currenlty arrays are skipped. We need to set default data, so it will be done in LS4, at the same time than Question Theme Editor.
    *
    * @param string $sQuestionDir the directory to parse, where to find the answer.twig file.
    */
    public function actionGenerateQuestionsCache($sQuestionDir = null)
    {

        $this->aLogs["action"] = "actionGenerateQuestionsCache $sQuestionDir";

      // Generate cache for question theme
        $sQuestionDir = $sQuestionDir ?? dirname(__FILE__) . '/../views/survey/questions/answer';
        $oQuestionDir = new DirectoryIterator($sQuestionDir);

        foreach ($oQuestionDir as $fileinfo) {
            if ($fileinfo->getFilename() != ".." && $fileinfo->getFilename() != "." && $fileinfo->getFilename() != "index.html") {
                $sQuestionName = $fileinfo->getFilename();

                $sQuestionDirectory = $sQuestionDir . DIRECTORY_SEPARATOR . $sQuestionName;

              /**
               * TODO for ls4/ls5:
               *  - get the Question XML rather than answer.twig
               *  - load the default data from the XML
               *
               * NOTE 1: as long as this is not done, it's highly probable that some twig files will never be reached (Ex: Conditionally included twig files).
               *
               * NOTE 2: It should be possible to parse the XML to get the different values for the attributes, and then to generate a cache file for each attribue possible value.
               *         Doing this could allow to test easily the rendering for all question type, with all question attribute variations.
               *         Since we're very far to get this with Unit Test (it will imlpy to write around 1000 tests in a row), it could be a first step.
               *         One way to do: for a stable version, save the rendered HTML somwhere, then in unitest, call this function, compare the rendered HTML to the saved one.
               *         Enjoy the 1000 test in a single one :) (sadly, only for HTML rendering, not for JS or DB saving)
               */
                $sTwigFile = $sQuestionDirectory . DIRECTORY_SEPARATOR . "answer.twig";
                $aQuestionData = array(); // See todo
                if (file_exists($sTwigFile)) {
                    $this->aLogs[$sQuestionName] = "$sTwigFile";
                    $line       = file_get_contents($sTwigFile);
                    $sHtml      = Yii::app()->twigRenderer->convertTwigToHtml($line, $aQuestionData);
                } elseif (is_dir($sQuestionDirectory) && $sQuestionName != "arrays") {
                  // Recursive step
                    $this->actionGenerateQuestionsCache($sQuestionDirectory);
                }
            }
        }
    }

    /**
    * Generate twig cache files for admin views.
    * NOTE: It's a recursive function which build every twig file in admin area.
    *
    * @param string $sAdminDir the directory to parse, where to find the twig files.
    */
    public function actionGenerateAdminCache($sAdminDir = null)
    {
        $this->aLogs["action"] = "actionGenerateAdminCache $sAdminDir";

        // Generate cache for admin area
        // Set directories to search for twig files
        $directories = array(
            dirname(__FILE__) . '/../views/admin',
            dirname(__FILE__) . '/../views/questionAdministration',
        );
        foreach ($directories as $sAdminDir) {
            $sAdminDir = $sAdminDir ?? dirname(__FILE__) . '/../views/admin';
            $oAdminDirectory = new RecursiveDirectoryIterator($sAdminDir);
            $oAdminIterator = new RecursiveIteratorIterator($oAdminDirectory);
            $oAdminRegex = new RegexIterator($oAdminIterator, '/^.+\.twig$/i', RecursiveRegexIterator::GET_MATCH);
            $aAdminData = array();
            foreach ($oAdminRegex as $oTwigFile) {
                $sTwigFile = $oTwigFile[0];
                if (file_exists($sTwigFile)) {
                    $this->aLogs["twig"] = "$sTwigFile";
                    $line       = file_get_contents($sTwigFile);
                    $sHtml      = Yii::app()->twigRenderer->convertTwigToHtml($line);
                }
            }
        }
    }

    /**
     * Generate twig cache files for each core question theme
     *
     * @param string $themesRootDir the directory to parse, where to find the manifests.
     */
    public function actionGenerateQuestionThemesCache($themesRootDir = null)
    {
        $this->aLogs["action"] = "actionGenerateQuestionThemesCache $themesRootDir";

        $themesRootDir = ($themesRootDir == null) ? dirname(__FILE__) . '/../../themes/question' : $themesRootDir;

        // Iterate over all folders in the themes root dir (first level only)
        $themesRootDirIterator = new DirectoryIterator($themesRootDir);
        foreach ($themesRootDirIterator as $fileinfo) {
            // Skip files as well as ".." and "."
            if (!$fileinfo->isDir() || $fileinfo->getFilename() == ".." || $fileinfo->getFilename() == ".") {
                continue;
            }
            $themeName = $fileinfo->getFilename();
            $themeDir = $fileinfo->getPathname();

            // Get paths to folders containing config.xml files inside this theme.
            $questionPaths = $this->getQuestionThemePaths($themeDir);

            $loader = Yii::app()->twigRenderer->getLoader();

            foreach ($questionPaths as $themePath) {
                /**
                 * TODO for LS5:
                 *  - load the default data from the XML
                 *
                 * NOTE 1: as long as this is not done, it's highly probable that some twig files will never be reached (Ex: Conditionally included twig files).
                 */
                $twigFile = $themePath . DIRECTORY_SEPARATOR . "answer.twig";
                $questionData = []; // See todo
                if (file_exists($twigFile)) {
                    $this->aLogs[$themeName] = "$twigFile";
                    $line = file_get_contents($twigFile);
                    // We need to add the theme directory to the Twig loader paths.
                    // Keep the original paths to restore them later
                    $originalTwigPaths = $loader->getPaths();
                    // Add the path
                    $loader->addPath($themeDir);
                    // Convert the twig to generate the cache
                    Yii::app()->twigRenderer->convertTwigToHtml($line, $questionData);
                    // Restore paths
                    $loader->setPaths($originalTwigPaths);
                }
            }
        }
    }

    /**
     * Generate the cache for a given survey theme
     * @param TemplateManifest
     */
    private function renderSurveyTheme($oTemplateForPreview)
    {
        $thissurvey = $oTemplateForPreview->getDefaultDataForRendering();
        $thissurvey['templatedir'] = $oTemplateForPreview->sTemplateName; // $templatename;

        $aScreenList = $oTemplateForPreview->getScreensDetails();

        foreach ($aScreenList as $sScreenName => $aTitleAndLayouts) {
            foreach ($aTitleAndLayouts['layouts'] as $sLayout => $sContent) {
                $this->aLogs[$oTemplateForPreview->sTemplateName][$sScreenName][$sLayout] =  $sContent;
                $sLayoutFile  = $sLayout ;
                $thissurvey['include_content'] = $sContent;

                $myoutput = Yii::app()->twigRenderer->renderTemplateForTemplateEditor(
                    $sLayoutFile,
                    array(
                    'aSurveyInfo' => $thissurvey,
                    ),
                    $oTemplateForPreview
                );
            }
        }
    }

    /**
     * Generate the cache for the twig strings inside the manifest itself
     * @param TemplateManifest
     */
    private function renderSurveyThemeManifest($oTemplateForPreview)
    {
      // So the twig string inside the theme manifest will be added to the .po file
        $aTwigFromXml = $oTemplateForPreview->getTwigStrings();

        foreach ($aTwigFromXml as $sTwig) {
            Yii::app()->twigRenderer->convertTwigToHtml($sTwig);
        }

        $this->aLogs[$oTemplateForPreview->sTemplateName]['manifest'] =  "done";
    }

    /**
     * Finds all subfolders of $questionThemeDirectory containing XML files
     * @param string $questionThemeDirectory
     * @return array
     */
    private function getQuestionThemePaths($questionThemeDirectory)
    {
        $questionDirectoriesAndPaths = [];

        $directory = new RecursiveDirectoryIterator($questionThemeDirectory);
        $iterator = new RecursiveIteratorIterator($directory);
        foreach ($iterator as $info) {
            if ($info->getFileName() == 'config.xml') {
                $questionDirectoriesAndPaths[] = dirname((string) $info->getPathname());
            }
        }

        return $questionDirectoriesAndPaths;
    }
}