Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed issue #17915: Error accessing general settings
- Loading branch information
1 parent
600ff00
commit 28f3890
Showing
5 changed files
with
528 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
|
||
|
||
# GlobalSettings admin module for LimeSurvey | ||
|
||
## Quick introduction | ||
Comming with LS4, it's now possible for 3rd party developpers to extends LimeSurvey controllers, and to override their views and subviews. | ||
Here a very small example to show how to proceed. | ||
|
||
## Directories, namespace and class name | ||
|
||
### The core file name determines the directory name | ||
|
||
You can extend any of the controller in the admin directory: | ||
https://github.com/LimeSurvey/LimeSurvey/tree/85cc7c52479addbffb6a857ddc7aa10f52b0b02c/application/controllers/admin | ||
|
||
For this example, we choose to extend GlobalSettings controller: | ||
https://github.com/LimeSurvey/LimeSurvey/blob/85cc7c52479addbffb6a857ddc7aa10f52b0b02c/application/controllers/admin/globalsettings.php | ||
|
||
Since the name of the file is **globalsettings.php**, we must create a directory **modules/admin/globalsettings/**. Respecting case of original file is important. | ||
Note that LimeSurvey files are not normalized (sometime upper case, sometime lower case, sometime using dash, sometime camel cased, etc ). If we have time, we'll normalize the names before we release LS 4.0.0. | ||
|
||
Then, inside **modules/admin/globalsettings/controller/** we create a file with the exact same name as the core file: **globalsettings.php** | ||
|
||
### The directory path determines the name space | ||
|
||
LimeSurvey is based on yii1, and yii1 doesn't really use name space, but rather aliases for path. This is for historical reasons: PHP prior to 5.3.0 does not support namespace intrinsically. So yii1 rather uses a prefix for all their core classes, and uses path aliases extensively. But, for those who want to use namespace, all the Yii import methods accepts path, alias, or namespace. So ideally, in yii1, it must be possible to easely translate your namespace to aliase so we can easely translate your namespace to aliases. To be clear: | ||
|
||
So first, in **modules/admin/globalsettings/controller/globalsettings.php** we define a namespace: | ||
|
||
```php | ||
namespace lsadminmodules\globalsettings\controller; | ||
``` | ||
|
||
lsadminmodules is the Yii alias for the directory **modules/admin/**. Then **lsadminmodules\globalsettings\controller** is the namespace attached to the path **modules/admin/globalsettings/controller/**, which is indeed the path of our controller so everything is fine :) | ||
|
||
To learn more about the relations between path, aliases and namespace in yii1: | ||
https://www.yiiframework.com/doc/guide/1.1/en/basics.namespace | ||
|
||
To see examples why we need to convert your name space to aliases: | ||
https://github.com/LimeSurvey/LimeSurvey/blob/85cc7c52479addbffb6a857ddc7aa10f52b0b02c/application/controllers/AdminController.php#L176-L179 | ||
https://github.com/LimeSurvey/LimeSurvey/blob/85cc7c52479addbffb6a857ddc7aa10f52b0b02c/application/controllers/AdminController.php#L208-L213 | ||
|
||
|
||
|
||
### The class name | ||
|
||
We give to our controller the same class name as the core controller. It extends the core controller: | ||
```php | ||
class GlobalSettings extends \GlobalSettings | ||
``` | ||
|
||
# GlobalSettings admin module for LimeSurvey | ||
|
||
## Quick introduction | ||
Comming with LS4, it's now possible for 3rd party developpers to extends LimeSurvey controllers, and to override their views and subviews. | ||
Here a very small example to show how to proceed. | ||
|
||
## Directories, namespace and class name | ||
|
||
### The core file name determines the directory name | ||
|
||
You can extend any of the controller in the admin directory: | ||
https://github.com/LimeSurvey/LimeSurvey/tree/85cc7c52479addbffb6a857ddc7aa10f52b0b02c/application/controllers/admin | ||
|
||
For this example, we choose to extend GlobalSettings controller: | ||
https://github.com/LimeSurvey/LimeSurvey/blob/85cc7c52479addbffb6a857ddc7aa10f52b0b02c/application/controllers/admin/globalsettings.php | ||
|
||
Since the name of the file is **globalsettings.php**, we must create a directory **modules/admin/globalsettings/**. Respecting case of original file is important. | ||
Note that LimeSurvey files are not normalized (sometime upper case, sometime lower case, sometime using dash, sometime camel cased, etc ). If we have time, we'll normalize the names before we release LS 4.0.0. | ||
|
||
Then, inside **modules/admin/globalsettings/controller/** we create a file with the exact same name as the core file: **globalsettings.php** | ||
|
||
### The directory path determines the name space | ||
|
||
LimeSurvey is based on yii1, and yii1 doesn't really use name space, but rather aliases for path. This is for historical reasons: PHP prior to 5.3.0 does not support namespace intrinsically. So yii1 rather uses a prefix for all their core classes, and uses path aliases extensively. But, for those who want to use namespace, all the Yii import methods accepts path, alias, or namespace. So ideally, in yii1, it must be possible to easely translate your namespace to aliase so we can easely translate your namespace to aliases. To be clear: | ||
|
||
So first, in **modules/admin/globalsettings/controller/globalsettings.php** we define a namespace: | ||
|
||
```php | ||
namespace lsadminmodules\globalsettings\controller; | ||
``` | ||
|
||
|
||
lsadminmodules is the Yii alias for the directory **modules/admin/**. Then **lsadminmodules\globalsettings\controller** is the namespace attached to the path **modules/admin/globalsettings/controller/**, which is indeed the path of our controller so everything is fine :) | ||
|
||
To learn more about the relations between path, aliases and namespace in yii1: | ||
https://www.yiiframework.com/doc/guide/1.1/en/basics.namespace | ||
|
||
To see examples why we need to convert your name space to aliases: | ||
https://github.com/LimeSurvey/LimeSurvey/blob/85cc7c52479addbffb6a857ddc7aa10f52b0b02c/application/controllers/AdminController.php#L176-L179 | ||
https://github.com/LimeSurvey/LimeSurvey/blob/85cc7c52479addbffb6a857ddc7aa10f52b0b02c/application/controllers/AdminController.php#L208-L213 | ||
|
||
|
||
|
||
### The class name | ||
|
||
We give to our controller the same class name as the core controller. It extends the core controller: | ||
```php | ||
class GlobalSettings extends \GlobalSettings | ||
``` | ||
|
||
notice the backslash in front of the second GlobalSettings: it means that we extend the GlobalSetting class from the global namespace, so from core. | ||
|
||
Indeed, Yii1 core classes, like LimeSurvey core classes, are under the global PHP name space: **/**. Since our module use a namespace. As a consequence, we'll have to use the global name space in front of all the calls to Yii or LS classes. Eg: | ||
```php | ||
\Yii::getPathOfAlias(); | ||
``` | ||
instead of: | ||
```php | ||
Yii::getPathOfAlias(); | ||
``` | ||
|
||
|
||
## Adding new method to the GlobalSettings controller | ||
|
||
Now, any method you'll add to your module will be accessible as if it was part of the core controller. | ||
We added a very simple HelloWorld method that will display the content of the HelloWorld view. | ||
You can reach it via: index.php?r=admin/globalsettings/sa/HelloWorld/ | ||
|
||
notice the backslash in front of the second GlobalSettings: it means that we extend the GlobalSetting class from the global namespace, so from core. | ||
|
||
Indeed, Yii1 core classes, like LimeSurvey core classes, are under the global PHP name space: **/**. Since our module use a namespace. As a consequence, we'll have to use the global name space in front of all the calls to Yii or LS classes. Eg: | ||
```php | ||
\Yii::getPathOfAlias(); | ||
``` | ||
instead of: | ||
```php | ||
Yii::getPathOfAlias(); | ||
``` | ||
|
||
|
||
## Adding new method to the GlobalSettings controller | ||
|
||
Now, any method you'll add to your module will be accessible as if it was part of the core controller. | ||
We added a very simple HelloWorld method that will display the content of the HelloWorld view. | ||
You can reach it via: **index.php?r=admin/globalsettings/sa/HelloWorld/** | ||
|
||
As you can see, it's using its own view, so it's rendered in its own page like if it was a separated module. It's still availabe via the globalsettings route. So it could be a page displayed by clicking on a button or a menu in global setting, it could be an adavanced editing page for some kind of new settings, etc. | ||
|
||
![Full page HelloWorld Module](https://account.limesurvey.org/images/github/full-page-global-setting-extension.png) | ||
|
||
## Extending a method from the GlobalSettings controller | ||
|
||
Of course, most of the time, when you extend a class, what you want is to override one of its methode to add some specific logic to it. Here, we did a very simple exemple. | ||
|
||
### New class parameter | ||
First, we added a new parameter to the GlobalSetting class: | ||
|
||
```php | ||
// Just an example to show how to override a parent method | ||
public $myNewParam = "This was not in global setting core controller"; | ||
``` | ||
https://github.com/LimeSurvey/LimeSurvey/blob/98df1afb094077995e2e3b4426a4b64d06d20d60/modules/admin/globalsettings/controller/globalsettings.php#L31 | ||
|
||
### Override \GlobalSetting::_renderWrappedTemplate() | ||
|
||
We want to display this new param inside the overview pan of global settings. So first, we need to add $myNewParam to the array of data passed to the views. | ||
So, first, we override the \GlobalSetting::_renderWrappedTemplate() : | ||
|
||
```php | ||
protected function _renderWrappedTemplate($sAction = '', $aViewUrls = array(), $aData = array(), $sRenderFile = false) | ||
{ | ||
// We add ou new paramater to the data to parse to the view | ||
$aData["myNewParam"] = $this->myNewParam; | ||
|
||
// Then we just call the parent method | ||
parent::_renderWrappedTemplate($sAction, $aViewUrls, $aData, $sRenderFile); | ||
} | ||
``` | ||
https://github.com/LimeSurvey/LimeSurvey/blob/98df1afb094077995e2e3b4426a4b64d06d20d60/modules/admin/globalsettings/controller/globalsettings.php#L50-L64 | ||
|
||
As you can see, we do only one thing: we add $myNewParam to $aData, then we just call the parent method. This is a very normal way of processing. Then what ever change we do to the core method will also apply to your extension. For exemple, that what we're doing when we override the renderPartial method: | ||
https://github.com/LimeSurvey/LimeSurvey/blob/98df1afb094077995e2e3b4426a4b64d06d20d60/application/controllers/AdminController.php#L200-L221 | ||
|
||
Of course, you can also completly rewrite the logic of the parent method, and not calling at all the parent method. Sometime: you just don't have the choice. Especially when the code is not that much functional oriented, and when the method signature is poor (and let be honnest, it is often the case in LimeSurvey code). For exemple we could also have override the method \GlobalSetting::_displaySettings(). But it accepts no parameter at all, so we would have been forced to rewrite it locally: | ||
https://github.com/LimeSurvey/LimeSurvey/blob/98df1afb094077995e2e3b4426a4b64d06d20d60/application/controllers/admin/globalsettings.php#L68-L110 | ||
|
||
But good news: LimeSurvey is OpenSource. So if you feel blocked because the signature of a core method is too poor to be called as a parent method, just modify the signature, and submit a PR. Then, step by step, all the core code will become much more functionnal and easy to override from modules. | ||
|
||
### Override the views | ||
|
||
We made admin views override very simple for module. You just have to copy paste the views in your local module. If you have a look to the folders **/modules/admin/globalsettings/views** , you can see it contains 3 files: | ||
``` | ||
[HelloWorld.php] | ||
[_overview.php] | ||
[globalSettings_view.php] | ||
``` | ||
https://github.com/LimeSurvey/LimeSurvey/tree/98df1afb094077995e2e3b4426a4b64d06d20d60/modules/admin/globalsettings/views | ||
|
||
**_overview.php** and **globalSettings_view.php** has been copy/paste from core views: | ||
https://github.com/LimeSurvey/LimeSurvey/tree/98df1afb094077995e2e3b4426a4b64d06d20d60/application/views/admin/globalsettings | ||
|
||
Now, the views from the module are the one rendered, not the views from the core. To make it clear, we added an alert in the module views. | ||
|
||
In globalSettings_view.php: | ||
```php | ||
<?php if(YII_DEBUG ): ?> | ||
<p class="alert alert-info "> This view is rendered from the global settings module. This message is shown only when debug mode is on. </p> | ||
<?php endif;?> | ||
``` | ||
https://github.com/LimeSurvey/LimeSurvey/blob/98df1afb094077995e2e3b4426a4b64d06d20d60/modules/admin/globalsettings/views/globalSettings_view.php#L15-L17 | ||
|
||
In _overview.php : | ||
```php | ||
<?php if(YII_DEBUG ): ?> | ||
<p class="alert alert-info "> This subview is rendered from global settings module. This message is shown only when debug mode is on. </p> | ||
<?php endif;?> | ||
``` | ||
https://github.com/LimeSurvey/LimeSurvey/blob/98df1afb094077995e2e3b4426a4b64d06d20d60/modules/admin/globalsettings/views/_overview.php#L15-L17 | ||
|
||
If debug mode is on, it will show you an alert that tells you those views are the one from the module. | ||
|
||
Then, in globalSettings_view.phop, we add the parameter $myNewParam to the data passed to the view _overview.php | ||
```php | ||
$this->renderPartial("./globalsettings/_overview", array( | ||
'usercount'=>$usercount, | ||
'surveycount'=>$surveycount, | ||
'activesurveycount'=>$activesurveycount, | ||
'deactivatedsurveys'=>$deactivatedsurveys, | ||
'activetokens'=>$activetokens, | ||
'deactivatedtokens'=>$deactivatedtokens, | ||
// Here, we pass to the subview the new parameter | ||
'myNewParam'=>$myNewParam, | ||
) | ||
``` | ||
https://github.com/LimeSurvey/LimeSurvey/blob/98df1afb094077995e2e3b4426a4b64d06d20d60/modules/admin/globalsettings/views/globalSettings_view.php#L40-L51 | ||
|
||
That's a bit annoying. Would be better if all the data was passed to the subviews, so we would avoid to force third party coder to override the main views. Again: if you face this situtation, you can make a PR, so step by step LimeSurvey become more modular. | ||
|
||
Now, in _overview.php, we show that data: | ||
```php | ||
<?php if(YII_DEBUG ): ?> | ||
<!-- If debug mode is on, we show the new parameter --> | ||
<tr> | ||
<th >Value of myNewParam :</th><td><?php echo $myNewParam; ?></td> | ||
</tr> | ||
<?php endif;?> | ||
``` | ||
https://github.com/LimeSurvey/LimeSurvey/blob/98df1afb094077995e2e3b4426a4b64d06d20d60/modules/admin/globalsettings/views/_overview.php#L39-L44 | ||
|
||
Now, if debug mode is on, you should see: | ||
![Full page Global Settings view overriden](https://account.limesurvey.org/images/github/global-setting-views-override.png) | ||
|
||
## Conclusion | ||
|
||
That was a very brief introduction. Of course, you can do much more complex things. In global settings, you could add new settings for one of your modules (like the HelloWorld module). You would then need to override the _saveSettings method. Now, you can modify LimeSurvey deeply wihtout waiting for the team to add new events, or without modifying the core files. |
86 changes: 86 additions & 0 deletions
86
modules/admin/globalsettings/controller/globalsettings.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
<?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. | ||
* | ||
* GlobalSettings custom admin module | ||
* This admin module extend the core GlobalSettings controller | ||
*/ | ||
|
||
|
||
// First we define a namespace to avoid collision with core class. | ||
// Since we do that, all our call to the core/framework classes will need the global name space: / | ||
// For exemple: \Yii::app()->getController()->renderPartial... | ||
namespace lsadminmodules\globalsettings\controller; | ||
|
||
if (!defined('BASEPATH')) { | ||
exit('No direct script access allowed'); | ||
} | ||
|
||
/* Note: Class name must identical to folder name and to the core class you want to override*/ | ||
class GlobalSettings extends \GlobalSettings | ||
{ | ||
|
||
public $myNewParam = "This was not in global setting core controller"; // Just an example to show how to override a parent method | ||
|
||
/** | ||
* A brand new helloworld function for GlobalSettings ! | ||
* | ||
* You can reach it via: index.php?r=admin/globalsettings/sa/HelloWorld/ | ||
* | ||
* @param string $sWho who to say hello | ||
* @return array Populated parameters ready to be rendered inside the admin interface | ||
*/ | ||
public function HelloWorld($sWho="World") | ||
{ | ||
// Call to Survey_Common_Action::_renderWrappedTemplate that will generate the "Layout" | ||
$this->_renderWrappedTemplate('globalsettings', 'HelloWorld', array( | ||
'sWho'=>$sWho, | ||
)); | ||
|
||
} | ||
|
||
/** | ||
* Renders template(s) wrapped in header and footer | ||
* | ||
* @param string $sAction Current action, the folder to fetch views from | ||
* @param string $aViewUrls View url(s) | ||
* @param array $aData Data to be passed on. Optional. | ||
*/ | ||
protected function _renderWrappedTemplate($sAction = '', $aViewUrls = array(), $aData = array(), $sRenderFile = false) | ||
{ | ||
// We add ou new paramater to the data to parse to the view | ||
$aData["myNewParam"] = $this->myNewParam; | ||
|
||
// Then we just call the parent method | ||
parent::_renderWrappedTemplate($sAction, $aViewUrls, $aData, $sRenderFile); | ||
} | ||
|
||
/** | ||
* Override Survey_Common_Action::renderCentralContents | ||
* | ||
* If you don't understand what it does, just copy / paste it in your own admin module | ||
* We let it here just in case you're trying to do something different | ||
* | ||
* NOTE: you just need to copy/paste here any view called by the core GlobalSettings to override it. | ||
* | ||
*/ | ||
protected function renderCentralContents($sAction, $aViewUrls, $aData = []) | ||
{ | ||
if ( file_exists ( \Yii::getPathOfAlias('lsadminmodules.'.$sAction.'.views.' . $aViewUrls) . '.php' ) ){ | ||
// Use alias to render a view outisde of application directory. | ||
return \Yii::app()->getController()->renderPartial('lsadminmodules.'.$sAction.'.views.' . $aViewUrls, $aData, true); | ||
}else{ | ||
// var_dump( \Yii::getPathOfAlias('lsadminmodules.' . $sAction. '.views.' . $aViewUrls) ); die(); | ||
return parent::renderCentralContents($sAction, $aViewUrls, $aData ); | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
/* @var $this AdminController */ | ||
/* @var $sWho url parameter */ | ||
?> | ||
|
||
<div class="col-sm-12 "> | ||
|
||
<h3 class="pagetitle"><?php eT('Hello World From Global Settings!'); ?></h3> | ||
<div class="row"> | ||
<div class="col-sm-12 "> | ||
<?php eT('Hello '); echo $sWho; ?> ! | ||
</div> | ||
</div> | ||
|
||
</div> |
Oops, something went wrong.