Yii2 - Quiz - Work with many-to-many table - php

I need development something similar a quiz.
I have 3 tables:
quiz, quiz_questions, quiz_questions_answers.
I render the questions / answers like this in form.
<?php foreach ($modelQuestions as $modelQuestion): ?>
<?= $modelQuestion->question ?> <br/>
<?= $form->field($modelMatch, 'answer[]')->textarea(['rows' => 6]) ?>
<?php endforeach; ?>
in controller i need save id_quiz and obs in table quiz, but i need save in many-to-many table quiz_questions_answers like id_quiz, id_question and answer for each answers.
I'm trying this in foreach loop, but how i get the "id_question" for each answer?
public function actionCreate()
{
$model = new Quiz();
$modelMatch = new QuizQuestionsAnswers();
$modelQuestions = QuizQuestions::find()->all();
if ($model->load(Yii::$app->request->post()) && $modelMatch->load(Yii::$app->request->post())){
$model->save();
foreach ($modelMatch->answer as $answer) {
$modelMatch = new QuizQuestionsAnswers();
$modelMatch->id_quis = $model->id;
$modelMatch->id_question = ????;
$modelMatch->answer = $answer;
$modelMatch->save();
}
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
'modelMatch' => $modelMatch,
'modelQuestions' => $modelQuestions,
]);
}
}
this is a scenario create, and after i need prepare this for the scenario update.
i'm lost..
the schema database is:

You should add the id_question as the hidden input and pre-populate the field as you have the question already saved in the database and use tabular approach for field names you should have the [] in the start not in the end, also you should group the answer with the question by using the $modelQuestions index so the answer and the question that it belongs to all are in separate arrays see the below code
<?php foreach ($modelQuestions as $index=>$modelQuestion): ?>
<?php echo $modelQuestion->question ?> <br/>
<?php echo $form->field($modelMatch, "[$index]answer")->textarea(['rows' => 6]) ?>
<?php echo Html::activeHiddenInput($model,"[$index]id_question",['value'=>$modelQuestion->id]) ?>
<?php endforeach;?>
Now when you will submit your for you will see the answers in the post array like below
Array(
.....
'QuizQuestionsAnswers'=>[
[0]=>[
'answer'=>'some answer'
'id_question'=>1,
],
[1]=>[
'answer'=>'some answer',
'id_question'=>2,
],
],
.....
)
Now you should save every answer. And for doing this you should use the transactions so that if any of the answers is not validated according to the model rules it should throw an error and should not save any of the answers or the quiz.
So you should change the code to the below
public function actionCreate()
{
$model = new Quiz();
$modelMatch = new QuizQuestionsAnswers();
$modelQuestions = QuizQuestions::find()->all();
if ($model->load(Yii::$app->request->post())) {
$transaction = Yii::$app->db->beginTransaction();
try {
if (!$model->save()) {
throw new \Exception(implode("<br />", ArrayHelper::getColumn($model->errors, '0')));
}
foreach (Yii::$app->request->post('QuizQuestionsAnswers', []) as $answer) {
$modelMatch = new QuizQuestionsAnswers();
$modelMatch->id_quis = $model->id;
$modelMatch->id_question = $answer['id_question'];
$modelMatch->answer = $answer['answer'];
if (!$modelMatch->save()) {
throw new \Exception(implode("<br />", ArrayHelper::getColumn($modelMatch->errors, '0')));
}
}
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
} catch (\Exception $e) {
$transaction->rollBack();
Yii::$app->session->setFlash('danger', $e->getMessage());
}
}
return $this->render('create', [
'model' => $model,
'modelMatch' => $modelMatch,
'modelQuestions' => $modelQuestions
]);
}

Related

yii2 ajax validation error in widget, yii\web\Response

I'm getting error from yii\web\Response when I use ajax validation.
Object of class yii\web\Response could not be converted to string
widget:
public function run()
{
$model = new Participants();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
if ($model->load(Yii::$app->request->post())) {
$list = implode( ", ",$model->sections);
$model->sections = $list;
$model->save();
Yii::$app->session->setFlash(Alert::TYPE_SUCCESS, [
[
'title' => 'Congratulations!',
'text' => 'You are registered on the forum. Check out your email to get news about forum.',
'confirmButtonText' => 'Done!',
]
]);
return Yii::$app->controller->redirect(Yii::$app->request->referrer ?: Yii::$app->homeUrl);
}
return $this->render('header', [
'model' => $model,
]);
}
view of widget:
<?php $form = ActiveForm::begin();?>
...
<?= $form->field($model, 'email', ['enableAjaxValidation' => true])->textInput(['placeholder' => 'Email']); ?>
how I can solve this error? P.S. yii version - 2.0.17-dev
\yii\base\Widget::run() must return a string (all widgets basically)
All you should do within method run() is output or render content and you're attempting to return a Response object by way of return ActiveForm::validate($model); and return Yii::$app->controller->redirect(..)
i suggest you move all the code that supposed to handle form submission to it's own action
SiteController extends controller {
public function actionRegisterParticipant {
$model = new Participants();
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->response->format = Response::FORMAT_JSON;
return ActiveForm::validate($model);
}
if ($model->load(Yii::$app->request->post())) {
$list = implode(", ", $model->sections);
$model->sections = $list;
$model->save();
Yii::$app->session->setFlash(Alert::TYPE_SUCCESS, [
[
'title' => 'Congratulations!',
'text' => 'You are registered on the forum. Check out your email to get news about forum.',
'confirmButtonText' => 'Done!',
]
]);
return Yii::$app->controller->redirect(Yii::$app->request->referrer ?: Yii::$app->homeUrl);
}
// ...
}
and the form in the widget view should always point to this action:
<?php $form = ActiveForm::begin(['action' => 'site/register-participant']);?>
...
<?= $form->field($model, 'email', ['enableAjaxValidation' => true])->textInput(['placeholder' => 'Email']); ?>
Widget should return string as a result, but return Yii::$app->controller->redirect() returns Response object with configured redirection. If you want to redirect inside of widget you should use something like that:
if (Yii::$app->request->isAjax && $model->load(Yii::$app->request->post())) {
Yii::$app->controller->asJson(ActiveForm::validate($model));
Yii::$app->end();
}
// ...
Yii::$app->session->setFlash(/* ... */);
Yii::$app->controller->redirect(Yii::$app->request->referrer ?: Yii::$app->homeUrl);
Yii::$app->end();
But this smells like a bad design - widget should not be responsible for controlling application flow. It is better to handle user input in regular action/controller.

How can I pass both id and value to the controller from my input form in cakePHP?

Here is my code
View (edit.ctp):
<?php echo $this->Form->create('Answer'); ?>
<?php echo $this->Form->input('Answer.0.value', array('class' => 'validate', 'type' => 'text', 'id' => 'Answer.0.id', 'label' => false)) ?>
<?php echo $this->Form->end('Edit Answer'); ?>
Controller:
public function edit($id) {
$this->layout = 'request_processing_edit';
$data = $this->Response->findById($id);
if ($this->request->is(array('post', 'put'))) {
$this->Response->id = $id;
if ($this->Answer->save($this->request->data)) {
$this->Session->setFlash('Answer Editted');
$this->redirect('index');
}
}
This is how $this->request->data array looks like:
I need the id to be in the same array as value upon clicking submit in the view
Is there any way to achieve this without having to build the array in the controller? Looking for a way to have both id and value passed in the request upon clicking submit from the view/form.
So, Here you can use this way
In your Controller :
$data = $this->Post->findById($id);
if ($this->request->is(array('post', 'put'))) {
$this->Answer->id = $id;
if ($this->Answer->save($this->request->data)) {
$this->Session->setFlash('Answer Editted');
return $this->redirect(array('action' => 'index'));
}
$this->Session->setFlash('Answer Not Editted');
}
if (!$this->request->data) {
$this->request->data = $data;
In Your View File Pass Hidden Field ID :
echo $this->Form->input('id', array('type' => 'hidden'));
Found a solution by adding this line of code in my ctp file:
<?php echo $this->Form->input('Answer.0.id', array('hidden' => true)) ?>

How to set multi select value from array object in yii2 while updating

I have table which have multiple reference to ohter tables like
user
id name email
categories
id title
user_categories
user_id category_id
Here a user will have multiple category associated with him/her
I am able to save these successfully with new records like following
View File:
echo $form->field($package_categories, 'category_id')->dropDownList( ArrayHelper::map(
StudyMaterialCategories::find()->all(), 'id', 'title'),
['multiple' => true]
);
Save New record:
$model = new Packages();
$package_categories = new PackageCategories();
$request = Yii::$app->request;
if ($request->isPost) {
$transaction = Yii::$app->db->beginTransaction();
try {
$post = $request->post();
$model->load($post);
$model->save();
foreach ($post['PackageCategories']['category_id'] as $key => $value) {
$package_categories = new PackageCategories();
$package_categories->category_id = $value;
$package_categories->package_id = $model->id;
$package_categories->save();
}
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
} catch (Exception $ex) {
$transaction->rolback();
Yii::$app->session->setFlash("error", $ex->getMessage());
}
}
Till now It's running successfully.
But I'm stuck when going to update the table. The problem part is dropdown list. How to set multiple selected option as per database if I'm coming with array of object.
Have a look on the following code
$package_categories = PackageCategories::find()
->where('package_id=:package_id', ['package_id' => $id])->all();
if (count($package_categories) < 1) {
$package_categories = new PackageCategories();
}
$request = Yii::$app->request;
if ($request->isPost) {
$transaction = Yii::$app->db->beginTransaction();
try {
$post = $request->post();
$model->load($post);
$model->save();
$package_categories = new PackageCategories();
$package_categories->deleteAll(
"package_id=:package_id",
[':package_id' => $model->id]
);
foreach ($post['PackageCategories']['category_id'] as $key => $value) {
$package_categories = new PackageCategories();
$package_categories->category_id = $value;
$package_categories->package_id = $model->id;
$package_categories->save();
}
$transaction->commit();
return $this->redirect(['view', 'id' => $model->id]);
} catch (Exception $ex) {
$transaction->rolback();
Yii::$app->session->setFlash("error", $ex->getMessage());
}
}
if I try to get first object of the array $package_categories of only able to set selected one option
This is an example code of a model class Permit which has a many to many relationship with Activity through PermitActivity (pivot table model).
Model Class Activity
public class Permit extends \yii\db\ActiveRecord {
public $activities_ids;
...
public function rules() {
return [
...
[['activities_ids'], 'safe'],
...
];
}
...
// Method called after record is saved, be it insert or update.
public function afterSave($insert, $changedAttributes) {
// If this is not a new record, unlink all records related through relationship 'activities'
if(!$this->isNewRecord) {
// We unlink all related records from the 'activities' relationship.
$this->unlinkAll('activities', true);
// NOTE: because this is a many to many relationship, we send 'true' as second parameter
// so the records in the pivot table are deleted. However on a one to many relationship
// if we send true, this method will delete the records on the related table. Because of this,
// send false on one to many relationships if you don't want the related records deleted.
}
foreach($this->activities_ids as $activity_id) {
// Find and link every model from the array of ids we got from the user.
$activity = Activity::findOne($activity_id);
$this->link('activities', $activity);
}
parent::afterSave($insert, $changedAttributes);
}
...
// Declare relationship with Activity through the pivot table permitActivity
public function getActivities(){
return $this->hasMany(Activitiy::className(), ['id' => 'activity_id'])
->viaTable('permitActivity',['permit_id' => 'id']);
}
...
public function afterFind(){
parent::afterFind();
$this->activities_id = ArrayHelper::getColumn($this->activities, 'id');
}
}
This way the model class is the one responsible for creating and updating the relationship using the pivot table.
The most important thing is to have the relationship method declared correctly.
Edit
This is an example of the view using kartikv\widgets\Select2. I don't really know if dropDownList supports multiple select, however Select2 has so many useful features i usually use it over other options.
echo $form->field($model, 'activities')->widget(Select2::classname(), [
'data' => $data,
'options' => [
'placeholder' => '...'
],
'pluginOptions' => [
'allowClear' => true,
'multiple' => true,
],
]);

Sending SMS in yii2

I'm using two models in one controller. one of them is database model (model) another model is for sending sms (smsModel).
I have problem in smsModel.
I got this error in my result:
Class 'fcadmin\models\SoapClient' not found
How can I fix it?
My controller:
public function actionCreate($id) {
$model = new Requestresult();
$smsModel = new SmsSender();
$request_model = Request::findOne(['id' => $id]);
$model->CodeKargah = $request_model->CodeKargah;
$model->month = $request_model->month;
$model->trackingCode = $request_model->trackingCode;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
$smsModel->sendSms('09193452126', 'sdf');
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
smsModel:
public function sendSms($to, $text) {
$options = [
'login' => 'myusername',
'password' => 'mypassword'
];
$client = new SoapClient('http://sms.hostiran.net/webservice/?WSDL', $options);
$messageId = $client->send($to, $text);
sleep(3);
return ($client->deliveryStatus($messageId));
}
You need to read up about namespaces. If you're in a namespace and don't tell PHP that you want to use the global namespace, it will look for classes of name x in the current namespace.
In your case you need to be using new \SoapClient.

Displaying the fields of tables three times and saving the value uniquely

I have a problem when it comes to saving the fields of tables being displayed three times. Cant save the unique value being saved in the text fields. Kindly someone direct me to the right answer please.
View code :
<h2>List of Documents</h2>
<table class="table">
<?php foreach($formlist as $item) { ?>
<tr>
<td><?= $form->field($model, '['.$item->id.']value')->radioList(['yes'=>' yes','no'=>' no'])->label($item['title']); ?></td>
</tr>
<?php } ?>
</table>
Controller code :
public function actionCreate()
{
$model = new Form();
$forminfo = new Forminfo();
$forminfo->id = $model->forminfo_id;
/*$sql = 'SELECT * FROM formlist ORDER BY id ASC';
$db = Yii::$app->db;
$formlist = $db->createCommand($sql)->queryAll();*/
// same of ->
$formlist = Formlist::find()->orderBy(['id'=>SORT_ASC])->all();
if ($forminfo->load(Yii::$app->request->post()) && $model->load(Yii::$app->request->post())) {
$forminfo->save(false); // skip validation as model is already validated
$model->forminfo_id = $forminfo->id; // no need for validation rule on user_id as you set it yourself
$model->save(false);
Yii::$app->getSession()->setFlash('success', 'You have successfully saved your data.');
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
'forminfo' => $forminfo,
'formlist' => $formlist,
]);
}
}
For accessing tabular input you should use loadMultiple('yourModel')
or a loop on
$post= Yii::$app->request->post());
foreach ($post['your_model'] as $key => $myModel) {
// $myModel contain the current model
}
this yii2 guide could be useful http://www.yiiframework.com/doc-2.0/guide-input-tabular-input.html

Categories