Skip to content

Commit

Permalink
Dev #T315: Make xml_path relative for question theme (#2253)
Browse files Browse the repository at this point in the history
Co-authored-by: encuestabizdevgit <devgit@encuesta.biz>
Co-authored-by: Lajos Arpad <arpad@endpoint.com>
  • Loading branch information
3 people committed Nov 29, 2023
1 parent f0d6481 commit b285c6b
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 17 deletions.
8 changes: 7 additions & 1 deletion application/controllers/ThemeOptionsController.php
Expand Up @@ -560,9 +560,15 @@ public function actionImportManifest()
if (Permission::model()->hasGlobalPermission('templates', 'update')) {
if ($theme === 'questiontheme') {
$templateFolder = App()->request->getPost('templatefolder');
if (strpos($templateFolder, "../") !== false) {
throw new CHttpException(eT("Unsafe path"));
}
//$themeType is being sanitized inside getAbsolutePathForType
$themeType = App()->request->getPost('theme_type');
$fullTemplateFolder = QuestionTheme::getAbsolutePathForType($templateFolder, $themeType);
$questionTheme = new QuestionTheme();
//skip convertion LS3ToLS4 (this should have been happen BEFORE theme was moved to the uninstalled themes
$themeName = $questionTheme->importManifest($templateFolder, true);
$themeName = $questionTheme->importManifest($fullTemplateFolder, true);
if (isset($themeName)) {
App()->setFlashMessage(sprintf(gT('The Question theme "%s" has been successfully installed'), "$themeName"), 'success');
} else {
Expand Down
109 changes: 94 additions & 15 deletions application/models/QuestionTheme.php
Expand Up @@ -33,6 +33,10 @@
*/
class QuestionTheme extends LSActiveRecord
{
const THEME_TYPE_CORE = 'coreQuestion';
const THEME_TYPE_CUSTOM = 'customCoreTheme';
const THEME_TYPE_USER = 'customUserTheme';

/**
* @return string the associated database table name
*/
Expand Down Expand Up @@ -229,7 +233,8 @@ public function getVisibilityButton()
public function getManifestButtons()
{
$sLoadLink = CHtml::form(array("themeOptions/importManifest/"), 'post', array('id' => 'forminstallquestiontheme', 'name' => 'forminstallquestiontheme')) .
"<input type='hidden' name='templatefolder' value='" . $this->xml_path . "'>
"<input type='hidden' name='templatefolder' value='" . $this->getRelativeXmlPath() . "'>
<input type='hidden' name='theme_type' value='" . $this->getThemeType() . "'>
<input type='hidden' name='theme' value='questiontheme'>
<button id='template_options_link_" . $this->name . "'class='btn btn-outline-secondary btn-block'>
<span class='ri-download-fill'></span>
Expand Down Expand Up @@ -426,14 +431,14 @@ public static function getQuestionMetaData($pathToXmlFolder)
]);

// override MetaData depending on directory
if (substr($pathToXmlFolder, 0, strlen((string) $questionDirectories['coreQuestion'])) === $questionDirectories['coreQuestion']) {
if (substr($pathToXmlFolder, 0, strlen((string) $questionDirectories[self::THEME_TYPE_CORE])) === $questionDirectories[self::THEME_TYPE_CORE]) {
$questionMetaData['coreTheme'] = 1;
$questionMetaData['image_path'] = App()->getConfig("imageurl") . '/screenshots/' . self::getQuestionThemeImageName($questionMetaData['questionType']);
}
if (substr($pathToXmlFolder, 0, strlen((string) $questionDirectories['customCoreTheme'])) === $questionDirectories['customCoreTheme']) {
if (substr($pathToXmlFolder, 0, strlen((string) $questionDirectories[self::THEME_TYPE_CUSTOM])) === $questionDirectories[self::THEME_TYPE_CUSTOM]) {
$questionMetaData['coreTheme'] = 1;
}
if (substr($pathToXmlFolder, 0, strlen((string) $questionDirectories['customUserTheme'])) === $questionDirectories['customUserTheme']) {
if (substr($pathToXmlFolder, 0, strlen((string) $questionDirectories[self::THEME_TYPE_USER])) === $questionDirectories[self::THEME_TYPE_USER]) {
$questionMetaData['coreTheme'] = 0;
}

Expand All @@ -459,15 +464,15 @@ public static function getAllQuestionXMLPaths($core = true, $custom = true, $use
$questionDirectories = self::getQuestionThemeDirectories();
$questionDirectoriesAndPaths = [];
if ($core) {
$coreQuestionsPath = $questionDirectories['coreQuestion'];
$coreQuestionsPath = $questionDirectories[self::THEME_TYPE_CORE];
$selectedQuestionDirectories[] = $coreQuestionsPath;
}
if ($custom) {
$customQuestionThemesPath = $questionDirectories['customCoreTheme'];
$customQuestionThemesPath = $questionDirectories[self::THEME_TYPE_CUSTOM];
$selectedQuestionDirectories[] = $customQuestionThemesPath;
}
if ($user) {
$userQuestionThemesPath = $questionDirectories['customUserTheme'];
$userQuestionThemesPath = $questionDirectories[self::THEME_TYPE_USER];
if (!is_dir($userQuestionThemesPath)) {
mkdir($userQuestionThemesPath, 0777, true);
}
Expand Down Expand Up @@ -662,7 +667,7 @@ public static function findAllQuestionMetaDataForSelector()
$baseQuestionsModified = [];
foreach ($baseQuestions as $baseQuestion) {
//TODO: should be moved into DB column (question_theme_settings table)
$sQuestionConfigFile = @file_get_contents($baseQuestion->xml_path . DIRECTORY_SEPARATOR . 'config.xml'); // @see: Now that entity loader is disabled, we can't use simplexml_load_file; so we must read the file with file_get_contents and convert it as a string
$sQuestionConfigFile = @file_get_contents($baseQuestion->getXmlPath() . DIRECTORY_SEPARATOR . 'config.xml'); // @see: Now that entity loader is disabled, we can't use simplexml_load_file; so we must read the file with file_get_contents and convert it as a string
if (!$sQuestionConfigFile) {
/* Not readable file : don't break */
continue;
Expand Down Expand Up @@ -704,9 +709,9 @@ public static function findAllQuestionMetaDataForSelector()
*/
public static function getQuestionThemeDirectories()
{
$questionThemeDirectories['coreQuestion'] = App()->getConfig('corequestiontypedir') . '/survey/questions/answer';
$questionThemeDirectories['customCoreTheme'] = App()->getConfig('customquestionthemedir');
$questionThemeDirectories['customUserTheme'] = App()->getConfig('userquestionthemerootdir');
$questionThemeDirectories[self::THEME_TYPE_CORE] = App()->getConfig('corequestiontypedir') . '/survey/questions/answer';
$questionThemeDirectories[self::THEME_TYPE_CUSTOM] = App()->getConfig('customquestionthemedir');
$questionThemeDirectories[self::THEME_TYPE_USER] = App()->getConfig('userquestionthemerootdir');

return $questionThemeDirectories;
}
Expand Down Expand Up @@ -797,13 +802,14 @@ public static function getAnswerColumnDefinition($name, $type)
}

$answerColumnDefinition = '';
if (isset($questionTheme->xml_path)) {
$xmlPath = $questionTheme->getXmlPath();
if (isset($xmlPath)) {
if (\PHP_VERSION_ID < 80000) {
$bOldEntityLoaderState = libxml_disable_entity_loader(true);
}
// If xml_path is relative, cwd is assumed to be ROOTDIR.
// TODO: Make it always relative depending on question theme type (core, custom, user).
$sQuestionConfigFile = file_get_contents($questionTheme->xml_path . DIRECTORY_SEPARATOR . 'config.xml'); // @see: Now that entity loader is disabled, we can't use simplexml_load_file; so we must read the file with file_get_contents and convert it as a string
$sQuestionConfigFile = file_get_contents($xmlPath . DIRECTORY_SEPARATOR . 'config.xml'); // @see: Now that entity loader is disabled, we can't use simplexml_load_file; so we must read the file with file_get_contents and convert it as a string
$oQuestionConfig = simplexml_load_string($sQuestionConfigFile);
if (isset($oQuestionConfig->metadata->answercolumndefinition)) {
// TODO: Check json_last_error.
Expand Down Expand Up @@ -834,7 +840,7 @@ public static function getQuestionXMLPathForBaseType($type)
if (empty($questionTheme)) {
throw new \CException("The Database definition for Questiontype: " . $type . " is missing");
}
$configXMLPath = App()->getConfig('rootdir') . '/' . $questionTheme->xml_path . '/config.xml';
$configXMLPath = App()->getConfig('rootdir') . '/' . $questionTheme->getXmlPath() . '/config.xml';

return $configXMLPath;
}
Expand Down Expand Up @@ -1013,7 +1019,7 @@ public static function getAdditionalAttrFromExtendedTheme($sQuestionThemeName, $
}
$questionTheme = QuestionTheme::model()->findByAttributes([], 'name = :name AND extends = :extends', ['name' => $sQuestionThemeName, 'extends' => $type]);
if ($questionTheme !== null) {
$xml_config = simplexml_load_file($questionTheme->xml_path . '/config.xml');
$xml_config = simplexml_load_file($questionTheme->getXmlPath() . '/config.xml');
$attributes = json_decode(json_encode((array)$xml_config->attributes), true);
}
if (\PHP_VERSION_ID < 80000) {
Expand Down Expand Up @@ -1101,4 +1107,77 @@ public static function getDummyInstance($questionType)
$questionTheme->settings = $settings;
return $questionTheme;
}

/**
* Returns the type of question theme (coreQuestion, customCoreTheme, customUserTheme)
*
* coreQuestion = Themes shipped with Limesurvey that don't extend other theme
* customCoreTheme = Themes shipped with Limesurvey that extend other theme
* customUserTheme = User provided question themes
*
* @return string
*/
public function getThemeType()
{
if ($this->core_theme) {
if (empty($this->extends)) {
return self::THEME_TYPE_CORE;
} else {
return self::THEME_TYPE_CUSTOM;
}
} else {
return self::THEME_TYPE_USER;
}
}

/**
* Returns the XML path relative to the path configured for the question theme type
* @return string
*/
public function getRelativeXmlPath()
{
$type = $this->getThemeType();
$typeDirectory = self::getQuestionThemeDirectoryForType($type);

// xml_path is supposed to contain the type directory, so we extract the rest of the path
$relativePath = substr($this->xml_path, strpos($this->xml_path, $typeDirectory) + strlen($typeDirectory));
$relativePath = ltrim($relativePath, "\\/");
return $relativePath;
}

/**
* Returns the XML path
* It may be absolute or relative to the Limesurvey root
* @return string
*/
public function getXmlPath()
{
return $this->xml_path;
}

/**
* Returns the path for the specified question theme type
* @param string $themeType
* @return string
* @throws Exception if no directory is found for the given type
*/
public static function getQuestionThemeDirectoryForType($themeType)
{
$directories = self::getQuestionThemeDirectories();
if (!isset($directories[$themeType])) {
throw new Exception(sprintf(gT("No question theme directory found for theme type '%s'"), $themeType));
}
return $directories[$themeType];
}

/**
* Returns the corresponding absolute path, given a relative path and the theme type.
* @param string $relativePath
* @param string $themeType
* @return string
*/
public static function getAbsolutePathForType($relativePath, $themeType)
{
return self::getQuestionThemeDirectoryForType($themeType) . '/' . $relativePath;
}
}
Expand Up @@ -41,7 +41,7 @@ protected function getAttributesFromQuestionTheme($questionThemeName, $questionT

$questionTheme = \QuestionTheme::model()->findByAttributes([], 'name = :name AND extends = :extends', ['name' => $questionThemeName, 'extends' => $questionType]);
if ($questionTheme !== null) {
$xmlFilePath = $questionTheme['xml_path'] . '/config.xml';
$xmlFilePath = $questionTheme->getXmlPath() . '/config.xml';
$extensionConfig = \ExtensionConfig::loadFromFile($xmlFilePath);
}

Expand Down

0 comments on commit b285c6b

Please sign in to comment.