Yii: Multimodelform Extension - How to create more than 1 member - yii

I'm using Multimodelform extension to create multiple model in a single form.
This extension is working great but unfortunately i would like more than 1 member instead.
I have tried it without success at all.
My problem is I could not make more than 1 member by this extension.
Here's my code :
From Controller
public function actionCreate()
{
Yii::import('ext.multimodelform.MultiModelForm');
$model=new Endheader;
$member = new Enddetail;
$member2 = new Enddetailnq; <-- i just ant to this new member.
$validatedMembers = array();
//$validatedMembers2 = array();
// Uncomment the following line if AJAX validation is needed
$this->performAjaxValidation($model);
if(isset($_POST['Endheader']))
{
$model->attributes=$_POST['Endheader'];
if(isset($_POST['sav'])){
if((MultiModelForm::validate($member, $validatedMembers, $deleteItems) && MultiModelForm::validate($member2, $validatedMembers, $deleteItems)) && $model->save())
{
$masterValues = array('HEH_ID'=>$model->HEH_ID);
if(MultiModelForm::save($member,$validatedMembers,$deleteItems,$masterValues) && MultiModelForm::save($member2,$validatedMembers,$deleteItems,$masterValues))
$msg2 = CHtml::link('View Details',array('view','id'=>$model->HEH_ID));
// $this->redirect(array('view','id'=>$model->HCO_ID));
Yii::app()->user->setFlash('success','You data have been saved successfully. '.$msg2);
$this->redirect(array('update','id'=>$model->HEH_ID));
}
}
}
$this->render('create',array(
'model'=>$model,'transport'=>$transport,
'member2'=>$member2,
'member'=>$member,
// 'validatedMembers2' => $validatedMembers2,
'validatedMembers' => $validatedMembers,
));
}
From View
$memberFormConfig = array(
'elements'=>array(
'HED_RPASS'=>array(
'type'=>'text',
'maxlength'=>11,
),
'HED_PCS'=>array(
'type'=>'text',
'maxlength'=>5,
),
));
$this->widget('ext.multimodelform.MultiModelForm',array(
'id' => 'id_member', //the unique widget id
'formConfig' => $memberFormConfig, //the form configuration array
'model' => $member, //instance of the form model
'tableView' => true,
//if submitted not empty from the controller,
//the form will be rendered with validation errors
'validatedItems' => $validatedMembers,
//'sortAttribute' => 'position',
//array of member instances loaded from db
'data' => $member->findAll('HEH_ID=:HEH_ID', array(':HEH_ID'=>$model->HEH_ID)),
));
$memberFormConfig2 = array(
'elements'=>array(
'HED_ARV_PCS'=>array(
'type'=>'text',
'maxlength'=>5,
),
'HED_ARV_VOL'=>array(
'type'=>'text',
'maxlength'=>10,
),
));
$this->widget('ext.multimodelform.MultiModelForm',array(
'id' => 'id_member2', //the unique widget id
'formConfig' => $memberFormConfig2, //the form configuration array
'model' => $member2, //instance of the form model
'tableView' => true,
//if submitted not empty from the controller,
//the form will be rendered with validation errors
'validatedItems' => $validatedMembers,
//'sortAttribute' => 'position',
//array of member instances loaded from db
'data' => $member->findAll('HEH_ID=:HEH_ID', array(':HEH_ID'=>$model->HEH_ID)),
));

Related

Prestashop 1.7.7 - HelperForm in a Multistore Context

I'm testing a first simple version for a Multistore-compatible module. It has just two settings, which have to be saved differently depending on the current shop Context (a single shop mainly).
Now, I know that from 1.7.8 there are additional checkbox for each setting in the BO Form, but I have to manage to get it work also for 1.7.7.
Now, both Configuration::updateValue() and Configuration::get() should be multistore-ready, meaning that they update or retrieve the value only for the current context, so it should be fine.
The weird thing is that, after installing the test module, if I go to the configuration page, it automatically redirects to an All-Shop context and, if I try to manually switch (from the dropdown in the top right), it shows a blank page. Same thing happens if I try to deactivate the bottom checkbox "Activate this module in the context of: all shops".
Here is my code:
class TestModule extends Module
{
public function __construct()
{
$this->name = 'testmodule';
$this->tab = 'front_office_features';
$this->version = '1.0.0';
$this->author = 'Test';
$this->need_instance = 1;
$this->ps_versions_compliancy = [
'min' => '1.7.0.0',
'max' => '1.7.8.0',
];
$this->bootstrap = true;
parent::__construct();
$this->displayName = $this->l("Test Module");
$this->description = $this->l('Collection of custom test extensions');
$this->confirmUninstall = $this->l('Are you sure you want to uninstall?');
if (!Configuration::get('TESTM_v')) {
$this->warning = $this->l('No version provided');
}
}
public function install()
{
if (Shop::isFeatureActive()) {
Shop::setContext(Shop::CONTEXT_ALL);
}
return (
parent::install()
&& $this->registerHook('header')
&& $this->registerHook('backOfficeHeader')
&& Configuration::updateValue('TESTM_v', $this->version)
);
}
public function uninstall()
{
if (Shop::isFeatureActive()) {
Shop::setContext(Shop::CONTEXT_ALL);
}
return (
parent::uninstall()
&& $this->unregisterHook('header')
&& $this->unregisterHook('backOfficeHeader')
&& Configuration::deleteByName('TESTM_v')
);
}
public function getContent()
{
// this part is executed only when the form is submitted
if (Tools::isSubmit('submit' . $this->name)) {
// retrieve the value set by the user
$configValue1 = (string) Tools::getValue('TESTM_CONFIG_1');
$configValue2 = (string) Tools::getValue('TESTM_CONFIG_2');
// check that the value 1 is valid
if (empty($configValue1)) {
// invalid value, show an error
$output = $this->displayError($this->l('Invalid Configuration value'));
} else {
// value is ok, update it and display a confirmation message
Configuration::updateValue('TESTM_CONFIG_1', $configValue1);
$output = $this->displayConfirmation($this->l('Settings updated'));
}
// check that the value 2 is valid
Configuration::updateValue('TESTM_CONFIG_2', $configValue2);
$output = $this->displayConfirmation($this->l('Settings updated'));
}
// display any message, then the form
return $output . $this->displayForm();
}
public function displayForm()
{
// Init Fields form array
$form = [
'form' => [
'legend' => [
'title' => $this->l('Settings'),
],
'input' => [
[
'type' => 'text',
'label' => $this->l('Custom CSS file-name.'),
'name' => 'TESTM_CONFIG_1',
'size' => 20,
'required' => true,
],
[
'type' => 'switch',
'label' => $this->l('Enable custom CSS loading.'),
'name' => 'TESTM_CONFIG_2',
'is_bool' => true,
'desc' => $this->l('required'),
'values' => array(
array(
'id' => 'sw1_on',
'value' => 1,
'label' => $this->l('Enabled')
),
array(
'id' => 'sw1_off',
'value' => 0,
'label' => $this->l('Disabled')
)
)
],
],
'submit' => [
'title' => $this->l('Save'),
'class' => 'btn btn-default pull-right',
],
],
];
$helper = new HelperForm();
// Module, token and currentIndex
$helper->table = $this->table;
$helper->name_controller = $this->name;
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->currentIndex = AdminController::$currentIndex . '&' . http_build_query(['configure' => $this->name]);
$helper->submit_action = 'submit' . $this->name;
// Default language
$helper->default_form_language = (int) Configuration::get('PS_LANG_DEFAULT');
// Load current value into the form or take default
$helper->fields_value['TESTM_CONFIG_1'] = Tools::getValue('TESTM_CONFIG_1', Configuration::get('TESTM_CONFIG_1'));
$helper->fields_value['TESTM_CONFIG_2'] = Tools::getValue('TESTM_CONFIG_2', Configuration::get('TESTM_CONFIG_2'));
return $helper->generateForm([$form]);
}
/**
* Custom CSS & JavaScript Hook for FO
*/
public function hookHeader()
{
//$this->context->controller->addJS($this->_path.'/views/js/front.js');
if (Configuration::get('TESTM_CONFIG_2') == 1) {
$this->context->controller->addCSS($this->_path.'/views/css/'.((string)Configuration::get('TESTM_CONFIG_1')));
} else {
$this->context->controller->removeCSS($this->_path.'/views/css/'.((string)Configuration::get('TESTM_CONFIG_1')));
}
}
}
As you can see it's a pretty simple setting: just load a custom CSS file and choose if loading it or not. I've red official PS Docs per Multistore handling and searched online, but cannot find an answer to this specific problem.
I've also tried to add:
if (Shop::isFeatureActive()) {
$currentIdShop = Shop::getContextShopID();
Shop::setContext(Shop::CONTEXT_SHOP, $currentIdShop);
}
To the 'displayForm()' function, but without results.
Thank you in advance.
It seemsit was a caching error.
After trying many variations, I can confirm that the first solution I've tried was the correct one, meaning that:
if (Shop::isFeatureActive()) {
$currentIdShop = Shop::getContextShopID();
Shop::setContext(Shop::CONTEXT_SHOP, $currentIdShop);
}
needs to be added ad the beginning of the "displayForm()" function for it to work when selecting a single shop. Values are now correctly saved in the database. With a little bit extra logic it can be arranged to behave differently (if needed) when saving for "All shops" context.

How to use Yii trait in controller

In my controllers a lot of code, about 1000 lines
Advise how you can make more convenient, for example to make a piece of code in trait
components/ProductTrait.php
trait ProductTrait{
protected function getProductProvider(Product $model){
$dataProductProvider = new CActiveDataProvider('Product', array(
'criteria' => array(
'limit' => $pageLimit,
'condition' => 't.creatorId = :creatorId AND t.categoryId =:categoryId',
'order' => 't.created DESC',
'params' => array(
':creatorId' => $model->creatorId,
':categoryId' => $model->categoryId,
),
),
'pagination' => false,
'sort' => false,
));
return $dataProductProvider;
}
}
Controller
class DealerController extends Controller{
use ProductTrait;
public function actionView($id){
$model = $this->loadModel($id);
if ($model === null) {
throw new CHttpException(404, 'The requested page does not exist.');
}
$renderParams['productProvider'] = $this->getProductProvider($model);
}
}
You can use Trait, but you can also use behaviors.
First you declare your behavior
class ProductBehavior extends CBehavior
{
protected function getProductProvider(Product $model){
$dataProductProvider = new CActiveDataProvider('Product', array(
'criteria' => array(
'limit' => $pageLimit,
'condition' => 't.creatorId = :creatorId AND t.categoryId =:categoryId',
'order' => 't.created DESC',
'params' => array(
':creatorId' => $model->creatorId,
':categoryId' => $model->categoryId,
),
),
'pagination' => false,
'sort' => false,
));
return $dataProductProvider;
}
}
Then you use it in your controller (don't forget to attach it, I've done it in the init method)
class DealerController extends Controller{
public function init() {
//Attach the behavior to the controller
$this->attachBehavior("productprovider",new ProductBehavior);
}
public function actionView($id){
$model = $this->loadModel($id);
if ($model === null) {
throw new CHttpException(404, 'The requested page does not exist.');
}
//We use the behavior methods as if it is one of the controller methods
$renderParams['productProvider'] = $this->getProductProvider($model);
}
}
The main point of behaviors is it's working in php 5.3 whereas trait are not.
Now here's some difference between traits and behaviors:
A first difference with behaviors is that traits can not be parameterized.
In your controller you could declare the behaviors this way:
public function behaviors(){
return array(
'ProductBehavior ' => array(
'class' => 'components.behaviors.ProductBehavior',
'firstAttribute' => 'value',
'secondAttribute' => 'value',
)
);
}
Your ProductBehavior class would have 2 public attributes: firstAttribute and secondAttribute.
One thing traits lack when compared to behaviors is runtime attachement. If you want to extend a given (let's say 3rdParty) class with some special functionality, behaviors give you a chance to attach them to the class (or more specifically to instances of the class). Using traits, you had to to modify the source of the class.
A Wiki about behaviors
The Yii Guide
The CBehavior doc

Creating a PDF document from a filtered CGridView - Yii

I am trying to create a PDF from a filtered CGridView. The value will be passed via dropdown in Advanced search but the problem is that i am unable to filter the search by my pdf function.
Controller
public function actionPrint() {
$mPDF1 = Yii::app()->ePdf->mpdf('ar','A4','14','dejavusanscondensed');
$model=new Country('search');
$model->center_id = 1;// This value will be passed from dropdown
//and i want the report to be made on this
$model->unsetAttributes();
if(isset($_GET['Country']))
$model->attributes=$_GET['Country'];
$html = '';
$html .= $this->renderPartial('candidates', array('model'=>$model, 'enablePagination' => false),true);
$mPDF1->WriteHTML($html, false);
$mPDF1->Output('list.pdf','D');
}
View
$this->widget('zii.widgets.grid.CGridView', array(
'id'=>'country-grid',
'dataProvider'=>$model->search($enablePagination),
'summaryText' => '',
// 'enablePagination' => false,
'filter'=>$model,
'columns'=>array(
'name',
array(
'header'=>' Total Registered Candidates',
'value'=>'$data->itemsTotal',
),
),
));
echo CHtml::link(
'Save as PDF',
Yii::app()->createUrl('country/print'),
array('class'=>'btnPrint btn btn-danger','target'=>'_blank'));
Model
public function search($enablePagination = true)
{
$criteria->together= true;
$criteria->with=array('center');
$criteria->compare('center.name', $this->center_id, true);
..........
if ($enablePagination)
{
$pagination = array(
'pageSize' => 30,
);
}
else
{
$pagination = false;
}
return new CActiveDataProvider($model, array(
'criteria' => $criteria,
'pagination' => $pagination,
));
}
Since center_id is a foreign key the line
$criteria->compare('center.name', $this->center_id, true);
should read
$criteria->compare('center_id', $this->center_id);
You could also do the following but this adds a condition on the joined table and could lead to slower queries.
$criteria->compare('center.id', $this->center_id);

passing value from Yii CController class to CForm (Form Builder) config array

I'm new to Yii, and I'm trying to do my initial project the "right" way. I've created a CFormModel class that needs three fields to query for some data, a CForm config to construct the form, and a CController to tie it together (all given below).
The data request needs an account, and this can come from a couple of different places. I think retrieving it should be in the controller. However, I don't know how to get it into the form's hidden "account" field from the controller, so that it makes it to the arguments assigned to the CFormModel after submission. More generally, I know how to pass from CController to view script, but not to CForm. Is the registry (Yii::app()->params[]) my best bet?
I suppose I can just leave it out of the form (and required fields) and wait to populate it in the submit action (actionSummaries). Does that break the intention of CForm? Is there a best practice? Even taking this solution, can someone address the first issue, in case it comes up again?
Any other, gentle critique is welcome.
models/SummariesForm.php
class SummariesForm extends CFormModel
{
public $account;
public $userToken;
public $year;
public function rules () {...}
public function fetchSummary () {...}
static public function getYearOptions () {...}
}
views/account/select.php
<?php
$this->pageTitle=Yii::app()->name;
?>
<div class="form">
<?php echo $form->render(); ?>
</div>
controllers/AccountController.php
class AccountController extends CController
{
public $layout = 'extranet';
public function actionSelect ()
{
$model = new SummariesForm();
// retrieve account
require_once 'AccountCookie.php';
/*
*
* Here, I insert the account directly into the
* model used to build the form, but $model isn't
* available to selectForm.php. So, it doesn't
* become part of the form, and this $model doesn't
* persist to actionSummaries().
*
*/
$model->account = AccountCookie::decrypt();
if ($model->account === false) {
throw new Exception('Unable to retrieve account.');
}
$form = new CForm('application.views.account.selectForm', $model);
$this->render('select', array(
'form' => $form,
'account' => $model->account,
));
}
public function actionSummaries ()
{
$model = new SummariesForm();
if (isset($_POST['SummariesForm'])) {
$model->attributes = $_POST['SummariesForm'];
/*
*
* Should I just omit "account" from the form altogether
* and fetch it here? Does that break the "model"?
*
*/
if ($model->validate() === true) {
try {
$summaries = $model->fetchSummary();
} catch (Exception $e) {
...
CApplication::end();
}
if (count($summaries) === 0) {
$this->render('nodata');
CApplication::end();
}
$this->render('summaries', array('model' => $model, 'summaries' => $summaries));
} else {
throw new Exception('Invalid year.');
}
}
}
}
views/account/selectForm.php
<?php
return array(
'title' => 'Select year',
'action' => Yii::app()->createUrl('Account/Summaries'),
'method' => 'post',
'elements' => array(
'account' => array(
'type' => 'hidden',
'value' => $account,
),
'userToken' => array(
'type' => 'hidden',
'value' => /* get token */,
),
'year' => array(
'type' => 'dropdownlist',
'items' => SummariesForm::getYearOptions(),
),
),
'buttons' => array(
'view' => array(
'type' => 'submit',
'label' => 'View summaries',
),
),
);
The answer is NO to do what you asked. You can see $form variable which acted almost like array when it was passed from controller to view. The solution is you add more property $account into selectForm model and treat it like other elements. I don't think leaving the new field outside the form will be properly way if you want to submit its value also.
Edited:

Show "page=1" params in Yii CLinkPager first page link

I want to make a CListView that remembers search form input. My application saves every search input (and the current page viewed by user) into user session and add the search id into the pagination params.
$pagination = array(
'pageVar' => 'page',
'currentPage' => $currentPage - 1,
'params' => array(
'search' => $searchId,
)
);
But since the first page link at the CLinkPager don't show 'page=1' parameter, everytime I click the link from other page (say page 2), the application will think that I need the current 'search' (whose current page value is 2).
To rephrase the question, is there any way to make CLinkPager shows param 'page=1' at the first page link? Or do you have another suggestion on handling this :)
Thanks
Edited
My overall code is below. If you want to give a better suggestion, please do :)
public function actionIndex() {
$model = new User();
if (isset($_POST['User'])) {
$model->setAttributes($_POST['User']);
} else {
if (isset($_GET['search'])) {
$searchId = $_GET['search'];
//Just getting a variable stored in the session
//array(
// $searchId => array(
// 'model' => $model //the search model
// 'pageSize' => $pageSize //current pagination page size
// 'currentPage' => $currentPage //the search model
// )
//);
$search = Yii::app()->user->getSearch($searchId);
$model->setAttributes($search['model'], false);
$currentPage = $search['currentPage'];
$pageSize = $search['pageSize'];
} else {
//create default search
}
}
//Managing pageSize parameter
if (isset($_GET['pageSize'])) { //Check whether there is pageSize in $_GET parameters
if (in_array($_GET['pageSize'], $this->getPageSizes())) {
$pageSize = $_GET['pageSize'];
} else {
$pageSize = self::DEFAULT_SEARCH_PAGESIZE;
}
} else if (!isset($pageSize)) { //Check whether pageSize is already defined before
$pageSize = self::DEFAULT_SEARCH_PAGESIZE;
}
//Managing currentPage parameter
if (isset($_GET['page'])) { //Check whether there is page parameter in $_GET parameter
$currentPage = $_GET['page'];
} else if (!isset($currentPage)) { //Check whether page is already defined before
$currentPage = self::DEFAULT_CURRENT_PAGE;
}
//Saving all into new session
$newSearch = array(
'model' => $model->getAttributes(),
'currentPage' => $currentPage,
'pageSize' => $pageSize,
);
//Check whether if there is a search before and if it exists check whether the two search is the same
if (!isset($search) || (serialize($search) != serialize($newSearch))) {
//Saves into session
$searchId = Yii::app()->user->saveSearch($newSearch);
} //else use the old search id
//-----------------------------------------------------
//Building the search
//Setting criteria
$criteria = new CDbCriteria(array(
'select' => array(
'*', //just to simplify things
),
));
//Appending criteria to search model used
$criteria = $model->search($criteria);
//Appending pagination to current variables
$pagination = array(
'pageSize' => $pageSize,
'pageVar' => 'page',
'currentPage' => $currentPage - 1,
'params' => array(
'search' => $searchId, //include the search ID in the link pager request URL
)
);
//Creating dataProvider for the search
$dataProvider = new CActiveDataProvider('Property', array(
'criteria' => $criteria,
'pagination' => $pagination,
));
//Rendering
$this->render('index', array(
'dataProvider' => $dataProvider,
'pageSize' => $pageSize,
'model' => $model,
'searchId' => $searchId,
));
}
You could probably extend CPagination by modifying the createPageUrl method and remove/change the if($page>0) condition to always be true. (Or better yet, add an additional parameter to turn on/off that behavior.)