Yii2: validation errors that are not attribute specific - php

How do I handle generic validation errors in a Yii2 ActiveRecord model that do not relate to a specific attribute? For example I need to completely prohibit saving a model when a related record/modal has been set to inactive.
I can of course just pick a more or less random attribute and assign the error message to it, but what if the front end form doesn't have that attribute and therefore doesn't show the error? Or even worse, if a scenario later disables validation of that attribute (by not including it in the list of active attributes)?
I can of course return false in beforeSave() or beforeValidate() but then I have no option to specify a message to the user about why the model couldn't be saved, so I really don't like that idea.
Also, I don't want to throw Exceptions, it should just be a soft error message shown to the user.
What is the intended/best approach to handle this?

have you looked into flash data they can be used for this purpose to show messages like success, errors and warnings which are or are not specific to your model attributes. It's up to you where you want o use them.
I mostly insert data or save models within transaction block and there when I am saving multiple models I use try catch block to get the errors for any models that occur and use session flash along with ArrayHelper to add errors in the flash message.
For your purpose you can use it the following way.
Set the flash message with
Yii::$app->session->setFlash('error','you are not allowed to perform this message');
and you can get it using the following inside your view
if(Yii::$app->session->hasFlash('error')){
echo Yii::$app->session->gettFlash('error');
}
a more sophisticated approach towards this is \kartik\widgets\AlertBlock
install it using composer
php composer.phar require kartik-v/yii2-widget-alert "*"
Then create a file called alerts.php in layouts folder with the following code
use kartik\widgets\AlertBlock;
AlertBlock::widget (
[
'useSessionFlash' => false ,
'type' => AlertBlock::TYPE_GROWL ,
'alertSettings' => [
'settings' => [
'type' => kartik\widgets\Growl::TYPE_SUCCESS ,
'icon' => 'glyphicon glyphicon-ok-sign' ,
'title' => 'Note' ,
'showSeparator' => true ,
'body' => Yii::$app->session->getFlash ( 'success' )
] ,
]
] );
AlertBlock::widget (
[
'useSessionFlash' => false ,
'type' => AlertBlock::TYPE_GROWL ,
'alertSettings' => [
'settings' => [
'type' => kartik\widgets\Growl::TYPE_INFO ,
'icon' => 'glyphicon glyphicon-ok-sign' ,
'title' => 'Note' ,
'showSeparator' => true ,
'body' => Yii::$app->session->getFlash ( 'info' )
] ,
]
] );
AlertBlock::widget (
[
'useSessionFlash' => false ,
'type' => AlertBlock::TYPE_GROWL ,
'alertSettings' => [
'settings' => [
'type' => kartik\widgets\Growl::TYPE_DANGER ,
'icon' => 'glyphicon glyphicon-ok-sign' ,
'title' => 'Note' ,
'showSeparator' => true ,
'body' => Yii::$app->session->getFlash ( 'error' )
] ,
]
] );
AlertBlock::widget (
[
'useSessionFlash' => false ,
'type' => AlertBlock::TYPE_GROWL ,
'alertSettings' => [
'settings' => [
'type' => kartik\widgets\Growl::TYPE_DANGER ,
'icon' => 'glyphicon glyphicon-ok-sign' ,
'title' => 'Note' ,
'showSeparator' => true ,
'body' => Yii::$app->session->getFlash ( 'danger' )
] ,
]
] );
AlertBlock::widget (
[
'useSessionFlash' => false ,
'type' => AlertBlock::TYPE_GROWL ,
'alertSettings' => [
'settings' => [
'type' => kartik\widgets\Growl::TYPE_WARNING ,
'icon' => 'glyphicon glyphicon-ok-sign' ,
'title' => 'Note' ,
'showSeparator' => true ,
'body' => Yii::$app->session->getFlash ( 'warning' )
] ,
]
] );
and then include it in your layout file after $this->beginBody() call like below
<?= Yii::$app->view->renderFile ( '#frontend/views/layouts/alerts.php' ); ?>
then you just have to set the flash message and that is it you won't even have to call getFlash(), the extension will display it automatically you have the following variables available
success
info
danger
warning
error
set using Yii::$app->session->setFlash('danger','You cannot do this');
EDIT:
Note: Whenever you are redirecting after setting the flash message remember to use return along with redirect() otherwise you may face the problem that the messages are not showing up.
return $this->redirect(['index']);
EDIT 2:
you are trying to add an error message for any particular model that is related to the model currently being saved and you want some soultion that would allow you to throw exceptions and show them in a nicely formatted error message, so the problem you are facing is setting the error message, if i would do it i would use the following approach. Lets say i have a actionTest() like below which is saving the form on submission.
public function actionTest() {
$model = new Campaign();
if ($model->load(Yii::$app->request->post())) {
//start transaction block
$transaction = Yii::$app->db->beginTransaction();
try {
if (!$model->save()) {
//throw an exception if any errors
throw new \Exception(implode("<br />",\yii\helpers\ArrayHelper::getColumn($model->errors, 0,false)));
}
//commit the transaction if there arent any erorrs to save the record
$transaction->commit();
} catch (\Exception $ex) {
//roll back the transaction if any exception
$transaction->rollBack();
//catch the error and display with session flash
Yii::$app->session->setFlash('danger',$ex->getMessage());
}
}
$this->render('test');
}

Another Option then picking a random attribute is to throw exceptions with a specific errorcode. Create a final class with Error Code constants with their messages. Then Put your save function call within a try Catch Block and Catch all specific exceptions and Return the Message to the Frontend

Related

Get CHAT_WRITE_FORBIDDEN using MadelineProto with Telegram API

I'm using the MadelineProto project for php to interact with Telegram API.
Maybe this error has nothing to do with MadelineProto... anyway, I successfully created a supergroup but any other method I call after, gives me a CHAT_WRITE_FORBIDDEN error.
Can't find anything in Telegram Documentation about why I'm getting this error and how to solve.
This is my code:
$MadelineProto = new MadelineProtoAPI('session.madeline', $settings);
$MadelineProto->async(false);
$MadelineProto->start();
// successfully create the group
$updates = $MadelineProto->channels->createChannel([
'megagroup' => true,
'title' => 'Test group',
'about' => 'Test group description',
]);
foreach($updates as $update) {
// try to invite other users --> CHAT_WRITE_FORBIDDEN
$updates = $MadelineProto->channels->inviteToChannel([
'channel' => $update,
'users' => ['########']
]);
// try to change admin rights --> CHAT_WRITE_FORBIDDEN
$updates = $MadelineProto->channels->editAdmin([
'channel' => $update,
'user_id' => '########',
'admin_rights' => [
'_' => 'chatAdminRights',
'change_info' => true,
'post_messages' => true,
'edit_messages' => true,
'delete_messages' => true,
'ban_users' => true,
'invite_users' => true,
'pin_messages' => true,
'add_admins' => true,
'anonymous' => true,
],
'rank' => ''
]);
}
What I'm doing wrong?
Thanks
It seems that the problem was due to the value passed to the 'channel' property. Instead of passing the $update variable, I passed directly the channel id in the form "channel#1234567890" and finally got a correct answer from Telegram.

Unable to access comment meta?

The Setup
Using the Reviews extension for WP Job Manager, I have comments with the meta field review_average which is an average of `review_stars. I'm customizing the WP REST API so I can read and write comments remotely via the mobile app I'm building.
I've exposed these fields with
register_meta( 'comment', 'review_average', array( 'show_in_rest' => true,
'type' => 'string',
'description' => 'review_average',
'single'=> false));
register_meta( 'comment', 'review_stars', array( 'show_in_rest' => true,
'type' => 'string',
'description' => 'review_stars',
'single'=> false));
which results in this field in the REST API response for comments:
meta: {
review_average: [
"4"
],
review_stars: [
"Array"
]
},
(I can't seem to break down that array, but there's only one stars category so the average is fine)
I've written a create_review function that uses add_comment_meta to write to review_average and review_stars which successfully gives the comment the right stars. Both those meta values are required for it to work.
function create_review($param) {
$id = wp_insert_comment(array('comment_post_ID' => $param['post_id'],
'comment_author' => $param['username'],
'comment_author_email' => $param['email'],
'user_id' => $param['user_id'],
'comment_content' => $param['content']));
if ($id) add_comment_meta($id, 'review_average', $param['rating']);
if ($id) add_comment_meta($id, 'review_stars', array('Your Rating'=>$param['rating']));
return get_comment($id);
}
The Problem
I can't seem to get the ratings meta info into a response for the comments. On my way to writing the "index" function get_comments, I've written the "show" function, get_commment:
function get_review($param) {
$id = $param['id'];
$info = get_comment($id);
$res = array(
'id' => $id,
'author_name' => $info->comment_author,
'author_email' => $info->comment_author_email,
'author_id' => $info->user_id,
'date' => $info->comment_date,
'rating' => $info->review_average
);
return $res;
}
The response has rating: null. Same result with 'rating' => $info->meta->review_average, as well as using _review_average in both those scenarios.
I have another function for my custom posts, which are job_listings that in my app are customers.job_listing has a meta field that shows up in the default REST API response under meta as _job_location, but inside my get_customer function, $res['address'] = $info->_job_location; works just fine!
How do I get the damn rating_average meta!?
Well, 'rating' => get_comment_meta($id) inside my get_review method gives me this:
"rating": {
"review_average": [
"4"
],
"review_stars": [
"a:1:{s:11:\"Your Rating\";s:1:\"4\";}"
]
}
And then
'rating' => get_comment_meta($id)['review_average'][0],
'rating_info' => get_comment_meta($id),
Gives me a nice full
"rating": "4",
"rating_info": {
"review_average": [
"4"
],
"review_stars": [
"a:1:{s:11:\"Your Rating\";s:1:\"4\";}"
]
}
I'm learning php as I go, so I'd love if someone could post a comment about why
get_comment_meta($id)->review_average
returns null but
get_comment_meta($id)['review_average']
works.

HasMany - save association data

I need help. I have a two tables business_departments and companies with association type hasMany.
I need to modify companies list consisting in the department. Code was generated via bake, after that I modified it.
Controller.
$businessDepartment = $this->BusinessDepartments->get($id, [
'contain' => ['Companies']
]);
$companies = $this->BusinessDepartments->Companies->find('list')->where([
'Companies.active' => true,
'Companies.type IS NOT' => 'service',
'OR' => [
'business_department_id IS NULL',
'business_department_id' => $id
]
])->distinct('Companies.id');
if ($this->request->is(['patch', 'post', 'put'])) {
debug($this->request->getData());
$businessDepartment = $this->BusinessDepartments->patchEntity($businessDepartment, $this->request->getData(), ['associated' => ['Companies']]);
debug($businessDepartment);
if ($this->BusinessDepartments->save($businessDepartment)) {
$this->Flash->success(__('The business department has been saved.'));
return $this->redirect(['action' => 'index']);
}
$this->Flash->error(__('The business department could not be saved. Please, try again.'));
}
$this->set(compact('businessDepartment', 'companies'));
Entity.
protected $_accessible = [
'name' => true,
'companies' => true
];
Table
$this->hasMany('Companies', [
'foreignKey' => 'business_department_id',
// Tried it
/*'dependent' => true,
'cascadeCallbacks' => true,
'saveStrategy' => 'replace'*/
]);
template.
echo $this->Form->control('companies._ids', ['options' => $companies, 'multiple' => true, 'class' => 'multiple-find']);
First save with added companies is success, but when I tried to modify companies list (And if try to save without changes) I get error.
Can I save via *._ids or I need to make a custom code for it?
Below debug($this->request->getData())
[
'name' => 'Office',
'companies' => [
'_ids' => [
(int) 0 => '21',
(int) 1 => '29'
]
]
]
But after patchEntity, instead of searching for companies and changing the business_department_id fields in them, patchEntity tries to create new companies and displays an error. Below is a fragment of screenshot.
debug($businessDepartment) and screenshot page
Thank you. I hope for quick answer.
Maybe someone will come in handy!
you have validation errors in your company related data, thats why you
cant save it, if you want to just use _ids as save try clearing
companies field in your $businessDepartment i.e.
$businessDepartment->unsetProperty('companies');
before patchEntity
Graziel

Error getting user id from messenger webview even after white listing domain

i have white listed my domain and i get a message showing it was successful
{"result": "Successfuly updated whitelisted domains"}
but when i try getting the user id I get the error message
An error occuredMessenger Extensions are not enabled - could be "messenger_extensions" was not set on a url, the domain was not whitelisted or this is an outdated version of Messenger client
i am using A PC so an outdated version might not be it, and i have the messenger extension set this way
$get_started_display = "{
'recipient':{
'id': $sender_id
},
'message':{
'attachment':{
'type':'template',
'payload':{
'template_type':'button',
'text':'Click a button below to continue',
'buttons':[
{
'type':'web_url',
'title':'Add Leader Profile',
'url':'https://aadb-3120.herokuapp.com/login.html',
'webview_height_ratio' : 'full',
'messenger_extensions': true
},
{
'type':'postback',
'title':'Review Added Profile',
'payload':'review'
},
{
'type':'postback',
'title':'Help',
'payload':'help'
},
]
}
}
}
}";
please what are my doing wrong?
one of the Admins at the messenger platform community just confirmed that webviews extension don't work on PC, so the only way i can get the User ID is by adding it to the URL on the URL button or through session variables.
I don't think that is a valid json format. It should be in double quotes not single quotes. Why don't you write in php array instead and convert to json to reduce your chances of making mistakes.
eg.
$data = [
'recipient' => [
'id' => $sender_id
],
'message' => [
'attachment' => [
'type' => 'template',
'payload' => [
'template_type' => 'button',
'text' => 'Click a button below to continue',
'buttons' => [
[
'type' => 'web_url',
'url' => 'https://google.com',
'title' => 'Visit Google',
"webview_height_ratio" => "compact"
]
]
]
]
]];
$json = json_encode($data);

Log Sql errors Cakephp

I am using a Plugin to log the errors and warnings in a Cakephp-Application, and it is working as it supposed, but I was thinknig ,if the Connection with my DB is not working well, then still I need to log these errors , but this time in an external file,
The Idea should be easy, I call CaeLog::write() and in these Method(wich has been reimplemented by the Plugin) I would put "try/Catch", which would help me in the case an Exception is thrown, to write the message to a logfile.
My bootstrap.php looks like this :`
App::uses('CakeLog', 'Log');
CakeLog::config('debug', array(
'engine' => 'File',
'types' => array('notice', 'info', 'debug'),
'file' => 'debug',
));
CakeLog::config('fileLog', array(
'engine' => 'File',
'types' => array('fileLog'),
'file' => 'fileLog',
'scopes' => array('fileLog')
));
CakeLog::config('default', array(
'engine' => 'DatabaseLogger.DatabaseLog',
'types' => array('warning', 'error', 'critical', 'alert', 'emergency')`
In the write methode i have this Code
function write($type, $message){
try {
//Save In Db
} catch (Exception $exc) {
//If the save in DB fails call log in fileLog(which has been defined in bootstrap.php)
CakeLog::write('fileLog', "***************************\n".$exc."\n***************************\n");
}
}
The problem now : I am getting a loop, cause of the call in the catch block, which is trying again to save in db, how could i solve this without a loop?

Categories