diff --git a/application/models/TemplateConfiguration.php b/application/models/TemplateConfiguration.php index 3c1c173198e..cb83fdc4e2e 100644 --- a/application/models/TemplateConfiguration.php +++ b/application/models/TemplateConfiguration.php @@ -12,22 +12,33 @@ * See COPYRIGHT.php for copyright notices and details. */ -/** - * Template Configuration Model +/* + * This is the model class for table "{{template_configuration}}". + * + * The followings are the available columns in table '{{template_configuration}}': + * @property string $id + * @property string $templates_name + * @property string $gsid + * @property string $sid + * @property string $files_css + * @property string $files_js + * @property string $files_print_css + * @property string $options + * @property string $cssframework_name + * @property string $cssframework_css + * @property string $cssframework_js + * @property string $viewdirectory + * @property string $filesdirectory + * @property string $packages_to_load + * @property string $packages_ltr + * @property string $packages_rtl * - * This model retrieves all the data of template configuration from the configuration file * * @package LimeSurvey * @subpackage Backend */ class TemplateConfiguration extends CActiveRecord { - /** @var boolean $bFromDb is the template set in db? */ - public $bFromDb=false; - - /** @var Template $oTemplate the template AR for this configuration */ - public $oTemplate=''; - /** @var string $sTemplateName The template name */ public $sTemplateName=''; @@ -58,8 +69,6 @@ class TemplateConfiguration extends CActiveRecord /** @var TemplateConfiguration $oMotherTemplate The template name */ public $oMotherTemplate; - public $templateEditor; - /** @var SimpleXMLElement $oOptions The template options */ public $oOptions; @@ -97,15 +106,17 @@ public function rules() // NOTE: you should only define rules for those attributes that // will receive user inputs. return array( - array('templates_id', 'required'), - array('templates_id, gsid, sid', 'length', 'max'=>11), - array('engine_cssframework_name', 'length', 'max'=>45), + array('templates_name, cssframework_js', 'required'), + array('templates_name', 'length', 'max'=>150), + array('gsid, sid', 'length', 'max'=>11), + array('cssframework_name', 'length', 'max'=>45), array('viewdirectory, filesdirectory', 'length', 'max'=>255), - array('files_css, files_js, files_print_css, options, engine_cssframework_css, packages, packages_ltr, packages_rtl', 'safe'), + array('files_css, files_js, files_print_css, options, cssframework_css, packages_to_load, packages_ltr, packages_rtl', 'safe'), // The following rule is used by search(). // @todo Please remove those attributes that should not be searched. - array('id, templates_id, gsid, sid, files_css, files_js, files_print_css, options, engine_cssframework_name, engine_cssframework_css, viewdirectory, filesdirectory, packages, packages_ltr, packages_rtl', 'safe', 'on'=>'search'), + array('id, templates_name, gsid, sid, files_css, files_js, files_print_css, options, cssframework_name, cssframework_css, cssframework_js, viewdirectory, filesdirectory, packages_to_load, packages_ltr, packages_rtl', 'safe', 'on'=>'search'), ); + } /** @@ -113,9 +124,8 @@ public function rules() */ public function relations() { - // NOTE: you may need to adjust the relation name and the related - // class name for the relations automatically generated below. return array( + 'template' => array(self::HAS_ONE, 'Template', array('name' => 'templates_name')), ); } @@ -126,18 +136,19 @@ public function attributeLabels() { return array( 'id' => 'ID', - 'templates_id' => 'Templates', + 'templates_name' => 'Templates Name', 'gsid' => 'Gsid', 'sid' => 'Sid', 'files_css' => 'Files Css', 'files_js' => 'Files Js', 'files_print_css' => 'Files Print Css', 'options' => 'Options', - 'engine_cssframework_name' => 'Engine Cssframework Name', - 'engine_cssframework_css' => 'Engine Cssframework Css', + 'cssframework_name' => 'Cssframework Name', + 'cssframework_css' => 'Cssframework Css', + 'cssframework_js' => 'Cssframework Js', 'viewdirectory' => 'Viewdirectory', 'filesdirectory' => 'Filesdirectory', - 'packages' => 'Packages', + 'packages_to_load' => 'Packages To Load', 'packages_ltr' => 'Packages Ltr', 'packages_rtl' => 'Packages Rtl', ); @@ -162,18 +173,19 @@ public function search() $criteria=new CDbCriteria; $criteria->compare('id',$this->id,true); - $criteria->compare('templates_id',$this->templates_id,true); + $criteria->compare('templates_name',$this->templates_name,true); $criteria->compare('gsid',$this->gsid,true); $criteria->compare('sid',$this->sid,true); $criteria->compare('files_css',$this->files_css,true); $criteria->compare('files_js',$this->files_js,true); $criteria->compare('files_print_css',$this->files_print_css,true); $criteria->compare('options',$this->options,true); - $criteria->compare('engine_cssframework_name',$this->engine_cssframework_name,true); - $criteria->compare('engine_cssframework_css',$this->engine_cssframework_css,true); + $criteria->compare('cssframework_name',$this->cssframework_name,true); + $criteria->compare('cssframework_css',$this->cssframework_css,true); + $criteria->compare('cssframework_js',$this->cssframework_js,true); $criteria->compare('viewdirectory',$this->viewdirectory,true); $criteria->compare('filesdirectory',$this->filesdirectory,true); - $criteria->compare('packages',$this->packages,true); + $criteria->compare('packages_to_load',$this->packages_to_load,true); $criteria->compare('packages_ltr',$this->packages_ltr,true); $criteria->compare('packages_rtl',$this->packages_rtl,true); @@ -203,55 +215,17 @@ public static function model($className=__CLASS__) * @param string $iSurveyId the id of the survey. If * @return $this */ - public function setTemplateConfiguration($sTemplateName='', $iSurveyId='', $iTemplateId='') + public function setTemplateConfiguration($sTemplateName='', $iSurveyId='') { - $this->setDb($sTemplateName, $iSurveyId, $iTemplateId); // Check if this template is set inside DB - $this->setTemplateName($sTemplateName, $iSurveyId); // Check and set template name + $this->sTemplateName = $this->template->name; $this->setIsStandard(); // Check if it is a CORE template - $this->setPath(); // Check and set path - $this->readManifest(); // Check and read the manifest to set local params + $this->path = ($this->isStandard)?Yii::app()->getConfig("standardtemplaterootdir").DIRECTORY_SEPARATOR.$this->template->folder:Yii::app()->getConfig("usertemplaterootdir").DIRECTORY_SEPARATOR.$this->template->folder; $this->setMotherTemplates(); // Recursive mother templates configuration $this->setThisTemplate(); // Set the main config values of this template $this->createTemplatePackage($this); // Create an asset package ready to be loaded return $this; } - public function setDb($sTemplateName, $iSurveyId, $iTemplateId) - { - if (!empty($iTemplateId)){ - $oTemplate = Template::model()->findByPk($iTemplateId); - }elseif(!empty($sTemplateName)){ - $oTemplate = Template::model()->find('name=:name', array(':name'=>$sTemplateName)); - }elseif(!empty($iSurveyId)){ - // TODO: link survey to templateid, not template name - $oSurvey = Survey::model()->findByPk($iSurveyId); - if($oSurvey) { - $oTemplate = Template::model()->find('name=:name', array(':name'=>$oSurvey->template)); - } - } - - if (is_a($oTemplate, 'Template')){ - $this->bFromDb = true; - $this->oTemplate = $oTemplate; - }else{ - $this->bFromDb = false; - } - } - - /** - * Update the configuration file "last update" node. - * For now, it is called only from template editor - */ - public function actualizeLastUpdate() - { - $date = date("Y-m-d H:i:s"); - $config = simplexml_load_file(realpath ($this->xmlFile)); - $config->metadatas->last_update = $date; - $config->asXML( realpath ($this->xmlFile) ); // Belt - touch ( $this->path ); // & Suspenders ;-) - } - - /** * get the template API version * @return integer @@ -275,74 +249,8 @@ public function getTemplateURL() return $this->sTemplateurl; } - /** - * Used from the template editor. - * It returns an array of editable files by screen for a given file type - * - * @param string $sType the type of files (view/css/js) - * @param string $sScreen the screen you want to retreive the files from. If null: all screens - * @return array array ( [screen name] => array([files]) ) - */ - public function getValidScreenFiles($sType = "view", $sScreen=null) - { - $aScreenFiles = array(); - - $filesFromXML = (is_null($sScreen)) ? (array) $this->templateEditor->screens->xpath('//file') : $this->templateEditor->screens->xpath('//'.$sScreen.'/file'); - - foreach( $filesFromXML as $file){ - - if ( $file->attributes()->type == $sType ){ - $aScreenFiles[] = (string) $file; - } - } - - $aScreenFiles = array_unique($aScreenFiles); - return $aScreenFiles; - } - - /** - * Returns the layout file name for a given screen - * - * @param string $sScreen the screen you want to retreive the files from. If null: all screens - * @return string the file name - */ - public function getLayoutForScreen($sScreen) - { - $filesFromXML = $this->templateEditor->screens->xpath('//'.$sScreen.'/file'); - - foreach( $filesFromXML as $file){ - - if ( $file->attributes()->role == "layout" ){ - return (string) $file; - } - } - - return false; - } - - /** - * Retreives the absolute path for a file to edit (current template, mother template, etc) - * Also perform few checks (permission to edit? etc) - * - * @param string $sfile relative path to the file to edit - */ - public function getFilePathForEdition($sFile, $aAllowedFiles=null) - { - - // Check if the file is allowed for edition ($aAllowedFiles is produced via getValidScreenFiles() ) - if (is_array($aAllowedFiles)){ - if (!in_array($sFile, $aAllowedFiles)){ - return false; - } - } - - return $this->getFilePath($sFile, $this); - } - - public function extendsFile($sFile) { - if( !file_exists($this->path.'/'.$sFile) && !file_exists($this->viewPath.$sFile) ){ // Copy file from mother template to local directory @@ -379,6 +287,8 @@ public function getTemplateForFile($sFile, $oRTemplate) * 4. Change the autor name to the current logged in user * 5. Change the author email to the admin email * + * TODO: MOVE TO TEMPLATE MODEL, AND CREATE NEEDED DATABASE ROWS + * * Used in template editor * Both templates and configuration files must exist before using this function * @@ -447,6 +357,8 @@ static public function extendsConfig($sToExtends, $sNewName) $oNewManifest->save($sConfigPath."/config.xml"); libxml_disable_entity_loader(true); + + // TODO: Add to db } @@ -466,25 +378,28 @@ private function createTemplatePackage($oTemplate) Yii::setPathOfAlias($sPathName, $oTemplate->path); Yii::setPathOfAlias($sViewName, $oTemplate->viewPath); - $aCssFiles = isset($oTemplate->config->files->css->filename)?(array) $oTemplate->config->files->css->filename:array(); // The CSS files of this template - $aJsFiles = isset($oTemplate->config->files->js->filename)? (array) $oTemplate->config->files->js->filename:array(); // The JS files of this template + $aCssFiles = array(); + if(!empty($this->files_css)){ + $oCssFiles = json_decode($this->files_css); + foreach($oCssFiles as $action => $aFiles){ + $aCssFiles[$action] = $aFiles; + } + } + + $aJsFiles = array(); + if(!empty($this->files_js)){ + $oJsFiles = json_decode($this->files_js); + foreach($oJsFiles as $action => $aFiles){ + $aJsFiles[$action] = $aFiles; + } + } + $dir = getLanguageRTL(App()->language) ? 'rtl' : 'ltr'; // Remove/Replace mother files $aCssFiles = $this->changeMotherConfiguration('css', $aCssFiles); $aJsFiles = $this->changeMotherConfiguration('js', $aJsFiles); - if (isset($oTemplate->config->files->$dir)) { - $aCssFilesDir = isset($oTemplate->config->files->$dir->css->filename) ? (array) $oTemplate->config->files->$dir->css->filename : array(); - $aJsFilesDir = isset($oTemplate->config->files->$dir->js->filename) ? (array) $oTemplate->config->files->$dir->js->filename : array(); - $aCssFiles = array_merge($aCssFiles,$aCssFilesDir); - $aJsFiles = array_merge($aJsFiles,$aJsFilesDir); - } - - if (Yii::app()->getConfig('debug') == 0) { - Yii::app()->clientScript->registerScriptFile( Yii::app()->getConfig("generalscripts"). 'deactivatedebug.js', CClientScript::POS_END); - } - $this->sPackageName = 'survey-template-'.$this->sTemplateName; $sTemplateurl = $oTemplate->getTemplateURL(); @@ -508,27 +423,27 @@ private function createTemplatePackage($oTemplate) */ private function changeMotherConfiguration( $sType, $aSettings ) { - foreach( $aSettings as $key => $aSetting){ - if (!empty($aSetting['replace']) || !empty($aSetting['remove'])){ - Yii::app()->clientScript->removeFileFromPackage($this->oMotherTemplate->sPackageName, $sType, $aSetting['replace'] ); - unset($aSettings[$key]); + $aSettingsToAdd = array(); + + if (is_a($this->oMotherTemplate, 'TemplateConfiguration')){ + foreach( $aSettings as $key => $aFiles){ + + if ($key == "replace" || $key = "remove"){ + foreach($aFiles as $sFile) + Yii::app()->clientScript->removeFileFromPackage($this->oMotherTemplate->sPackageName, $sType, $sFile ); + } } } - return $aSettings; - } + foreach( $aSettings as $key => $aFiles){ + if ($key == "add" || $key == "replace"){ + foreach($aFiles as $sFile) + $aSettingsToAdd[] = $sFile; + } - /** - * Read the config.xml file of the template and push its contents to $this->config - */ - private function readManifest() - { - $this->xmlFile = $this->path.DIRECTORY_SEPARATOR.'config.xml'; - $bOldEntityLoaderState = libxml_disable_entity_loader(true); // @see: http://phpsecurity.readthedocs.io/en/latest/Injection-Attacks.html#xml-external-entity-injection - $sXMLConfigFile = file_get_contents( realpath ($this->xmlFile)); // @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 - $this->config = simplexml_load_string($sXMLConfigFile); // Using PHP >= 5.4 then no need to decode encode + need attributes : then other function if needed :https://secure.php.net/manual/en/book.simplexml.php#108688 for example + } - libxml_disable_entity_loader($bOldEntityLoaderState); // Put back entity loader to its original state, to avoid contagion to other applications on the server + return $aSettingsToAdd; } /** @@ -537,70 +452,24 @@ private function readManifest() */ private function setMotherTemplates() { - if (isset($this->config->metadatas->extends)){ - $sMotherTemplateName = (string) $this->config->metadatas->extends; + if(!empty($this->template->extends_templates_name)){ + $sMotherTemplateName = $this->template->extends_templates_name; $this->oMotherTemplate = new TemplateConfiguration; - $this->oMotherTemplate->setTemplateConfiguration($sMotherTemplateName); // Object Recursion - } - } - /** - * Set the path of the current template - * It checks if it's a core or a user template, if it exists, and if it has a config file - */ - private function setPath() - { - // If the template is standard, its root is based on standardtemplaterootdir, else, it is a user template, its root is based on usertemplaterootdir - $this->path = ($this->isStandard)?Yii::app()->getConfig("standardtemplaterootdir").DIRECTORY_SEPARATOR.$this->sTemplateName:Yii::app()->getConfig("usertemplaterootdir").DIRECTORY_SEPARATOR.$this->sTemplateName; - - // If the template directory doesn't exist, we just set Default as the template to use - // TODO: create a method "setToDefault" - if (!is_dir($this->path)) { - $this->sTemplateName = 'default'; - $this->isStandard = true; - $this->path = Yii::app()->getConfig("standardtemplaterootdir").DIRECTORY_SEPARATOR.$this->sTemplateName; - if(!$this->iSurveyId){ - setGlobalSetting('defaulttemplate', 'default'); + if ($this->oMotherTemplate->checkTemplate()){ + $this->oMotherTemplate->setTemplateConfiguration($sMotherTemplateName); // Object Recursion + }else{ + // Throw exception? Set to default template? } } - - // If the template doesn't have a config file (maybe it has been deleted, or whatever), - // then, we load the default template - $this->hasConfigFile = (string) is_file($this->path.DIRECTORY_SEPARATOR.'config.xml'); - if (!$this->hasConfigFile) { - $this->path = Yii::app()->getConfig("standardtemplaterootdir").DIRECTORY_SEPARATOR.$this->sTemplateName; - - } } - /** - * Set the template name. - * If no templateName provided, then a survey id should be given (it will then load the template related to the survey) - * - * @var $sTemplateName string the name of the template - * @var $iSurveyId int the id of the survey - */ - private function setTemplateName($sTemplateName='', $iSurveyId='') + public function checkTemplate() { - // If it is called from the template editor, a template name will be provided. - // If it is called for survey taking, a survey id will be provided - if ($sTemplateName == '' && $iSurveyId == '') { - /* Some controller didn't test completely survey id (PrintAnswersController for example), then set to default here */ - $sTemplateName = Template::templateNameFilter(Yii::app()->getConfig('defaulttemplate','default')); - } - - $this->sTemplateName = $sTemplateName; - $this->iSurveyId = (int) $iSurveyId; - - if ($sTemplateName == '') { - $oSurvey = Survey::model()->findByPk($iSurveyId); - - if($oSurvey) { - $this->sTemplateName = $oSurvey->template; - } else { - $this->sTemplateName = Template::templateNameFilter(App()->getConfig('defaulttemplate','default')); - } + if (!is_dir(Yii::app()->getConfig("standardtemplaterootdir").DIRECTORY_SEPARATOR.$this->template->folder) && !is_dir(Yii::app()->getConfig("usertemplaterootdir").DIRECTORY_SEPARATOR.$this->template->folder)){ + return false; } + return true; } /** @@ -609,23 +478,34 @@ private function setTemplateName($sTemplateName='', $iSurveyId='') private function setThisTemplate() { // Mandtory setting in config XML (can be not set in inheritance tree, but must be set in mother template (void value is still a setting)) - $this->apiVersion = (isset($this->config->metadatas->apiVersion)) ? $this->config->metadatas->apiVersion : $this->oMotherTemplate->apiVersion; - $this->viewPath = (!empty($this->config->xpath("//viewdirectory"))) ? $this->path.DIRECTORY_SEPARATOR.$this->config->engine->viewdirectory.DIRECTORY_SEPARATOR : $this->path.DIRECTORY_SEPARATOR.$this->oMotherTemplate->config->engine->viewdirectory.DIRECTORY_SEPARATOR; - $this->filesPath = (!empty($this->config->xpath("//filesdirectory"))) ? $this->path.DIRECTORY_SEPARATOR.$this->config->engine->filesdirectory.DIRECTORY_SEPARATOR : $this->path.DIRECTORY_SEPARATOR.$this->oMotherTemplate->config->engine->filesdirectory.DIRECTORY_SEPARATOR; - $this->templateEditor = (!empty($this->config->xpath("//template_editor"))) ? $this->config->engine->template_editor : $this->oMotherTemplate->templateEditor; + $this->apiVersion = (!empty($this->template->api_version))? $this->template->api_version : $this->oMotherTemplate->apiVersion; + $this->viewPath = (!empty($this->template->view_folder)) ? $this->path.DIRECTORY_SEPARATOR.$this->template->view_folder.DIRECTORY_SEPARATOR : $this->path.DIRECTORY_SEPARATOR.$this->oMotherTemplate->view_folder.DIRECTORY_SEPARATOR; + $this->filesPath = (!empty($this->template->files_folder)) ? $this->path.DIRECTORY_SEPARATOR.$this->template->files_folder.DIRECTORY_SEPARATOR : $this->path.DIRECTORY_SEPARATOR.$this->oMotherTemplate->file_folder.DIRECTORY_SEPARATOR; + // Options are optional - if (!empty($this->config->xpath("//options"))){ - $this->oOptions = $this->config->xpath("//options"); + // TODO: twig getOption should return mother template option when option = inherit + $this->oOptions = array(); + if (!empty($this->options)){ + $this->oOptions[] = (array) json_decode($this->options); }elseif(!empty($this->oMotherTemplate->oOptions)){ - $this->oOptions = $this->oMotherTemplate->oOptions; - }else{ - $this->oOptions = ""; + $this->oOptions[] = $this->oMotherTemplate->oOptions; } // Not mandatory (use package dependances) - $this->cssFramework = (!empty($this->config->xpath("//cssframework"))) ? $this->config->engine->cssframework : ''; - $this->packages = (!empty($this->config->xpath("//packages"))) ? $this->config->engine->packages : array(); + if (!empty($this->cssframework_name)){ + $this->cssFramework = new \stdClass(); + $this->cssFramework->name = $this->cssframework_name; + $this->cssFramework->css = json_decode($this->cssframework_css); + $this->cssFramework->js = json_decode($this->cssframework_js); + + }else{ + $this->cssFramework = ''; + } + + if (!empty($this->packages_to_load)){ + $this->packages = json_decode($this->packages_to_load); + } // Add depend package according to packages $this->depends = array_merge($this->depends, $this->getDependsPackages($this)); @@ -673,8 +553,8 @@ private function getDependsPackages($oTemplate) $packages = array_merge ($packages, (array)$this->packages->$dir->package); } - if (isset($this->config->metadatas->extends)){ - $sMotherTemplateName = (string) $this->config->metadatas->extends; + if (!empty($this->template->extends_templates_name)){ + $sMotherTemplateName = (string) $this->template->extends_templates_name; $packages[] = 'survey-template-'.$sMotherTemplateName; } @@ -702,8 +582,9 @@ private function getFrameworkPackages($oTemplate, $dir="") $cssFrameworkCsss = isset ( $oTemplate->cssFramework->$dir->css ) ? $oTemplate->cssFramework->$dir->css : array(); $cssFrameworkJss = isset ( $oTemplate->cssFramework->$dir->js ) ? $oTemplate->cssFramework->$dir->js : array(); } else { - $cssFrameworkCsss = isset ( $oTemplate->cssFramework->css ) ? $oTemplate->cssFramework->css : array(); - $cssFrameworkJss = isset ( $oTemplate->cssFramework->js ) ? $oTemplate->cssFramework->js : array(); + + $cssFrameworkCsss = ! empty ( $oTemplate->cssframework_css ) ? json_decode($oTemplate->cssframework_css) : array(); + $cssFrameworkJss = ! empty ( $oTemplate->cssframework_js ) ? json_decode($oTemplate->cssframework_js) : array(); } if (empty($cssFrameworkCsss) && empty($cssFrameworkJss)) { @@ -715,12 +596,12 @@ private function getFrameworkPackages($oTemplate, $dir="") $packageJs = array(); // css file to replace from default package */ $cssDelete = array(); - foreach($cssFrameworkCsss as $cssFrameworkCss) { - if(isset($cssFrameworkCss['replace'])) { - $cssDelete[] = $cssFrameworkCss['replace']; - } - if((string)$cssFrameworkCss) { - $packageCss[] = (string) $cssFrameworkCss; + // foreach($cssFrameworkCsss as $cssFrameworkCss) { + foreach($cssFrameworkCsss as $action => $aFiles) { + // if(isset($cssFrameworkCss['replace'])) { + if($action == 'replace') { + foreach($aFiles as $aFile) + $cssDelete[] = $aFile; } } @@ -747,6 +628,24 @@ private function getFrameworkPackages($oTemplate, $dir="") $framework, ); + // Remove CSS + foreach($cssFrameworkCsss as $action => $aFiles) { + if($action == 'replace' || $key = "remove") { + foreach($aFiles as $sFile) + unset($packageCss[$sFile]); + } + } + + // Add CSS + foreach($cssFrameworkCsss as $action => $aFiles) { + if($action == 'add' || $key = "replace") { + foreach($aFiles as $sFile) + $packageCss[] = $sFile; + } + } + + // TODO: refactorize remove/add CSS (see changeMotherConfiguration) + add JS + $sTemplateurl = $oTemplate->getTemplateURL(); $sPathName = 'survey.template-'.$oTemplate->sTemplateName.'.path'; @@ -760,6 +659,7 @@ private function getFrameworkPackages($oTemplate, $dir="") ) ); $frameworkPackages[]=$framework.'-template'; + } return $frameworkPackages; }/*elseif($framework){