I use the following code over and over in my zend framework application. It is used in action() to check if an article exists. If not, the user shall see an error message:
$article = ArticleQuery::create()->findOneByUrl($this->_getParam('url', ''));
if (!$article) {
$this->getResponse()
->setRawHeader('HTTP/1.1 404 Not Found');
return $this->_forward('error', null, null, array(
'message' => 'Article not found',
));
}
I was wondering how to factor this out into an own method to reduce the code load in all actions.
I came to something like this:
protected function myAction() {
$article = $this->getArticleIfExists($this->_getParam('url', ''));
if ($article == null) {
return;
}
}
protected function getArticleIfExists($url) {
$article = ArticleQuery::create()->findOneByUrl($this->_getParam('url', ''));
if ($article) {
return $article;
} else {
$this->getResponse()
->setRawHeader('HTTP/1.1 404 Not Found');
$this->_forward('error', null, null, array(
'message' => 'Article not found',
));
return nulL;
}
}
I still would like to get rid of the if case in myAction(), but _forward() does not allow to exit the execution (of course, because it still needs to execute the other actions).
Another possibility (I have implemented in some other controllers) is this:
protected function myAction() {
$article = ArticleQuery::create()->findOneByUrl($this->_getParam('url', ''));
if (!$article) {
return $this->notFound('Article does not exist');
}
}
protected function notFound($message) {
$this->getResponse()
->setRawHeader('HTTP/1.1 404 Not Found');
return $this->_forward('error', null, null, array(
'message' => $message,
));
}
Again we have this if check in the action. It’s already better than before, but can we make it even better?
How can I circumvent this? Is there a possibility to do it without losing the current URL? With a Redirector I can of course exit, but then I would lose the current URL (/controller/myaction/url/hello -> /error/error/message/Article%20not%20found)
A possible approach would be to throw an Exception. Because of the Zend_Controller_Plugin_ErrorHandler this will automatically redirect you to the ErrorController without any further code being executed.
If you don't want to get to the ErrorController but only to the current controller's error actions, you can simply modify the plugin in the controller's init method:
public function init()
{
$plugin = Zend_Controller_Front::getInstance()->getPlugin('Zend_Controller_Plugin_ErrorHandler');
$plugin->setErrorHandlerModule('default')
->setErrorHandlerController('MyController')
->setErrorHandlerAction('error');
}
But of course you can also write your own ErrorHandler plugin for a more fine grained error handling. This is described in the Zend Framework Manual on Plugins
For something as simple as just displaying a "* does not exist" against a user request I prefer to leave the user in the application and just hit them with a flashmessenger notice and leave them on the page to make another request (if appropriate):
public function indexAction() {
//get form and pass to view
$form = new Form();
$this->view->form = $form;
try {
//get form values from request object
if ($this->getRequest()->isPost()) {
if ($form->isValid($this->getRequest()->getPost())) {
$data = $form->getValues();
// Do some stuff
}
}
} catch (Zend_Exception $e) {
$this->_helper->flashMessenger->addMessage($e->getMessage());//add message to flashmessenger
$this->_redirect($this->getRequest()->getRequestUri());//perform redirect to originating page so the messenger will flash
}
}
This is simple and works well when the possibility for incorrect user input exists.
Related
I have used 'use-ajax' class to render login form in a modal. I want to handle validation errors on same modal without closing it. On successful login it is redirecting correctly, but when an error occurs it closes modal and redirecting to login page i.e user/login and displaying errors on that page. I tried to use ajax callback to display error on modal itself by altering the form which is working. But, it is giving me drupal ajax error. Here is my code :
$form['#prefix'] = '<div id="modal-form">';
$form['#suffix'] = '</div>';
$form['status_messages'] = [
'#type' => 'status_messages',
'#weight' => -10,
];
$form['actions']['submit']['#ajax'] = array(
'callback' => 'setMessage',
'wrapper' => 'modal-form',
);
=========================================================================
function setMessage(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$response = new AjaxResponse();
if ($form_state->hasAnyErrors()) {
$response->addCommand(new ReplaceCommand('#modal-form', $form));
}
else {
$command = new CloseModalDialogCommand('#modal-form', FALSE);
$response->addCommand($command);
}
return $response;
}
The above code giving me session id also but due to drupal ajax error it does not redirect on success by closing modal.
If I go with non-ajax ie. if I remove the ajax callback function it works on success but errors are not displaying on modal.
First, check if you have added redirection related changes using hook_login in your module file. You can remove that redirection related changes and handle redirection in your callback function.
function setMessage(&$form, \Drupal\Core\Form\FormStateInterface $form_state) {
$response = new AjaxResponse();
$current= \Drupal::currentUser();
if ($form_state->hasAnyErrors()) {
$response->addCommand(new ReplaceCommand('#modal-form', $form));
}
else if (!$current->id()) {
$response->addCommand(new ReplaceCommand('#modal-form', $form));
}
else {
$command = new RedirectCommand(' ');
return $response->addCommand($command);
}
return $response;
}
On success it will close the modal and will redirect correctly. If you found any error or if you are not logged in it will stay on modal form.
Is it possible to fire a FormAction button from within CMSFields? I'm currently using the following code but the button click doesn't want to fire the form action. Does this need to be called through a custom entwine function or is there something already built in that I'm missing?
class NotesPage extends Page {
function getCMSFields() {
$fields = parent::getCMSFields();
// Page Sections
$fields->addFieldsToTab('Root.Notes', array(
// Send for approval
LiteralField::create('', '<h3>Notify Admin Of Content To Approve</h3>'),
FormAction::create('NotifyAdmin', 'Notify Admin'),
));
return $fields;
}
}
class NotesPageExtension extends LeftAndMainExtension {
private static $allowed_actions = array(
'NotifyAdmin'
);
// Email the admin about content to moderate
public function NotifyAdmin($data, $form){
$className = $this->owner->stat('tree_class');
$SQL_id = Convert::raw2sql($data['ID']);
$record = DataObject::get_by_id($className, $SQL_id);
if(!$record || !$record->ID){
throw new SS_HTTPResponse_Exception(
"Bad record ID #" . (int)$data['ID'], 404);
}
// at this point you have a $record,
// which is your page you can work with!
// this generates a message that will show up in the CMS
$this->owner->response->addHeader(
'X-Status',
rawurlencode('Notifying admin')
);
return $this->owner->getResponseNegotiator()->respond($this->owner->request);
}
}
I have a form for the creation of new "groups". I now added a small "go back" image with which the user should be able to go back one step. I don't know why, but when I click this new image, the controller and action used for the form which I want to leave (/admin/creategroup) is called again with HTTP POST set. Therefore, the form validation is done, and I'm stuck at this form with the validation errors displayed.
This is a snippet of the code from my form with both image-buttons. I wan't the "go back"-image to redirect me to the specified controller without validating the form:
$this->addElement('image', 'btnBack', array (
'name' => 'btnBack',
'id' => 'btnBack',
'label' => '',
'title' => 'Go back',
'alt' => 'Go back',
'src' => '/img/undo.png',
'onClick' => "window.location='/admin/groupoverview'"
));
$this->addElement('image', 'btnSave', array (
'name' => 'btnSave',
'id' => 'btnSave',
'label' => '',
'title' => 'Save this new group',
'alt' => 'Save this new group',
'src' => '/img/save.png',
'onClick' => "document.forms[0].submit();"
));
Edit:
I already thought of the possibility to check in /admin/creategroup whether it was called from the 'btnBack'-image or the 'btnSave'-image and skip form validation and redirect correctly if the source was the 'btnBack'-image.
I just think that there should be a nicer solution to directly redirect from the form and circumvent calling /admin/creategroup again.
Edit2:
My view script:
<div id="createGroupMask">
<br/>
Use the form below to create a new group
<?php
$this->form->setAction($this->url());
echo $this->form;
?>
</div>
My action in the controller:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
Now there is something to work with:
The isValid() loop is incorrect, your form will never evaluate as inValid with respect to the elements you've presented, you will never get to the else.
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
/* This is incorrect */
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
My problem is that I'm not sure what is going to be submitted from your form, I'm not really familiar with how your using "onClick" and what I presume is javascript. It looks like element btnBack should redirect on click and element btnSave should POST. However this does not seem to be happening.
I have done this type of thing in PHP and ZF with submit buttons, perhaps the flow of what I did will help:
NOTE: for this type of flow to work you must give the button element a label. The label is used as the submit value.
//psuedoCode
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
//I would probably opt to perform this task with a switch loop
if ($form->getValue('btnBack') === some true value) {
$this->_redirect('new url');
}
if ($form->getValue('btnSave') === some true value) {
//Process and save data
}
} else {
//Display form errors
}
$this->view->form = $form;
}
I think when all is said and done the crux of your problem is that you did not give your button elements a label.
I tried adding labels to my images, but this didn't work.
I also tried to use the isChecked() method on my btnBack-image like this:
if ($form->btnBack->isChecked()) {
// 'Go back' image was clicked so this is no real error, just redirect
}
This didn't work either.
I finally was able to check which image was clicked via the following method as answered in Zend form: image as submit button:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
if (isset($this->_request->btnBack_x)) {
// 'Go back' image was pressed, so this is no error
// -> redirect to group overview page
$this->_redirect('/admin/groupoverview');
}
}
}
$this->view->form = $form;
}
I guess this doesn't thoroughly answer the original question as the validation is still done and I'm only checking for this 'special case' where the 'Go back' image was clicked, but I'll mark it as answered anyways.
Tim Fountain suggested an even cleaner approach in my somewhat related question:
Zend forms: How to surround an image-element with a hyperlink?
I am implementing an updatePasswordAction and its not displaying an error with an invalid current password. I could not implement this with a Zend_Validate class to use with a supplied record->password, so i just validated for now in my controller action and if failed then i add the error message to the form element. this is just before i run $form->isValid. In any case, its working. but when the validation fails, its not displaying the error message on this on the element. any help would be greatly appreciated.
FYI: When I submit a blank current password, it shows the validation
class Admin_Form_UserPassword extends Katana_Form
{
public function init()
{
$element = $this->createElement('hidden', 'id');
$this->addElement($element);
$element = $this->createElement('password','password');
$element->setLabel('Current Password:');
$element->setRequired(true);
$this->addElement($element);
$element = $this->createElement('password','new_password');
$element->setLabel('New Password:');
$element->addValidator('StringLength', false, array(6,24));
$element->setRequired(true);
$element->addValidator('NotEmpty');
$this->addElement($element);
$element = $this->createElement('password','new_password_confirm');
$element->setLabel('Confirm:');
$element->addValidator('StringLength', false, array(6,24));
$element->addValidator('IdenticalField', false, array('new_password', 'Confirm Password'));
$element->setRequired(true);
$this->addElement($element);
$this->addElement('submit', 'submit', array('label' => 'Submit'));
}
}
public function updatePasswordAction()
{
$resourceModel = new Core_Model_Resource_User();
$form = new Admin_Form_UserPassword();
$form->setMethod(Katana_Form::METHOD_POST);
$form->setAction($this->getActionUrl('update-password'));
if($this->getRequest()->isPost()){
$id = $this->getRequest()->getParam('id');
$record = $resourceModel->find($id)->current();
$currPassword = $record->password;
$typedPassword = md5($this->getRequest()->getParam('password'));
if($currPassword !== $typedPassword){
$form->getElement('password')->addError('Current password is incorrect.');
}
if($form->isValid($_POST)){
$data = $form->getValues();
$result = $resourceModel->updatePassword($id, $data['new_password']);
if($result){
$this->redirectSimple('list');
}
}
} else {
$id = $this->getRequest()->getParam('id');
$recordData = array(
'id' => $id
);
$form->populate($recordData);
}
$this->getView()->form = $form;
}
Adding an error to the element doesn't cause the form itself to then be invalid.
There are at least 2 methods I use to get around this:
if($currPassword !== $typedPassword){
$form->getElement('password')->addError('Current password is incorrect.');
$form->markAsError();
}
// or
if ($form->isValid($_POST) && 0 == sizeof($form->getMessages()) {
// form was valid, and no errors were set on elements
}
To clarify, when you add the error to the form ELEMENT, there is an error attached to that element, but Zend_Form::isValid only runs the validators and sets appropriate errors, it doesn't check to see if you had set an error on a particular element.
You can however call $form->getMessages() to get all the error messages attached to the form or its child elements. If this is 0 and you have validated your form, then it means there were no errors. If your form passed isValid but you added an error to an element, it will include the error message you added.
I made it work this way.
Controller:
if ($trial->getKind() != 'debt' && $_POST['kind'] == 'debt')
{
$editForm->getElement('kind')->markAsError();
}
if ($editForm->isValid($_POST)) { ... }
Form:
public function isValid($data)
{
$valid = parent::isValid($data);
if ($this->getElement('kind')->hasErrors()) {
$this->getElement('kind')->addError($this->_translate->translate('You can\'t change trial kind to debt.'));
$valid = false;
}
return $valid;
}
And this comment helped me.
I have a PHPunit test like this:
public function testUsersCanRegisterWhenUsingValidData()
{
$this->request->setMethod('POST')
->setPost(array(
'username' => 'user123',
'zip_code' => '43215',
'email' => 'me1#something.com',
'password' => 'secret',
'confirm_pswd' => 'secret',
));
$this->dispatch('/account/register');
$this->assertRedirectTo('/account/login');
}
and a User controller action called register like this :
public function registerAction()
{
// Instantiate the registration form model
$form = new Application_Model_FormRegister();
// Has the form been submitted?
if ($this->getRequest()->isPost()) {
// If the form data is valid, process it
if ($form->isValid($this->_request->getPost())) {
// Does an account associated with this username already exist?
$account = $this->em->getRepository('Entities\Account')
->findOneByUsernameOrEmail($form->getValue('username'), $form->getValue('email'));
if (! $account)
{ // do something
.............
..............
} else {
$this->view->errors = array(
array("The desired username {$form->getValue('username')} has already been taken, or
the provided e-mail address is already associated with a registered user.")
);
}
} else {
$this->view->errors = $form->getErrors();
}
}
$this->view->form = $form;
}
I get an error in this line :
$account = $this->em->getRepository('Entities\Account')
->findOneByUsernameOrEmail($form->getValue('username'), $form->getValue('email'));
It's caused by $form->getValue('username') being NULL because the form has not actually been submitted, instead PHPunit has dispatched the action and set up the POST variables.
How can I get this working?
Sorry everyone. I had commented out this line to try and study my problem:
// If the form data is valid, process it
if ($form->isValid($this->_request->getPost())) {
and it turns out that my input test input was not valid and you can't use $form->getValue to get the value of an invalid form.
I didn't get any answers because this line was not commented out in my post and would have worked. Slap head............MODS feel free to delete this post if you think it is no help to anybody.