I'm trying to implement a plugin that handles responses to the user on success or failure in a persistance transaction. When the response is false I use a _forward to the action that performed the form's submit and get my placeholder message shown but when the response is true I use a _redirect to the browse with the new record shown.
My problem is that when I use _redirect the browser doesn't show the placeholder message. I'll show the code here:
/**
* Plugin
*/
class Application_Plugin_PostMessage extends Zend_Controller_Plugin_Abstract
{
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
$message = $request->getParam('message');
$error = $request->getParam('error');
if (null !== $message || null !== $error) {
$layout = Zend_Layout::getMvcInstance();
$view = $layout->getView();
$placeHolder = $view->placeholder('message');
$placeHolder->setPostfix('</div>');
if (null !== $error) {
$placeHolder->setPrefix('<div class="errorMessage">')
->append($error);
}
elseif (null !== $message) {
$placeHolder->setPrefix('<div class="infoMessage">')
->append($message);
}
}
}
}
/**
* Controller
*/
class My_FooController extends Zend_Controller_Action
{
public function init()
{
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Application_Plugin_PostMessage());
}
...
public function browseAction()
{
...
// No message is shown here on redirect
...
}
public function newAction()
{
...
// This code shows the placeholder on _forward call
...
}
public function insertAction()
{
if(true) {
return $this->_redirect('/my/foo/browse?message='
. urlencode("success message"));
}
else {
return $this->_forward('new', null, null, array(
'error' => 'error messsage'
));
}
}
}
I can't use _forward on success because I don't want the use of [F5] key repeats the insert action
Thanks in advance
This is what Flash Messenger is for:
http://framework.zend.com/manual/en/zend.controller.actionhelpers.html#zend.controller.actionhelper.flashmessenger.basicusage
It stores messages in your session removing the need for passing messages as you are.
Related
I need to set a custom attribute in the route definition and use it a route middleware. For example, I need to manage the refer page to redirect the user after the login.
This is my routes definition:
return function (App $app) {
$app->get('/', Home::class. ':home')->setName('home');
$app->get('/login', UserAction::class. ':getLogin')->setName('login')->setAttribute('norefer',true);
$app->post('/login', UserAction::class. ':postLogin');
};
The ->setAttribute('norefer',true); is what I'm looking for and seems it doesn't exist.
I need this attribute using ->getAttribute("norefer") in a middleware so I can store the last referable page visited by the user:
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute();
if (!empty($route) && !$routeContext->getRoute()->getAttribute("norefer")) {
$referName = $routeContext->getRoute()->getName();
$referArgs = $routeContext->getRoute()->getArguments();
$this->session->set("referName", $referName);
$this->session->set("referArgs", $referArgs);
}
return $handler->handle($request);
}
So, in the session I can store the last referable page and use it after the login process to redirect the user to his page.
You could add a NoRefererMiddleware to routes you want to exclude from the redirection logic. NoRefererMiddleware just sets a noreferer attribute to the request object if its called.
<?php
use App\Middleware\NoRefererMiddleware;
use Slim\App;
return function (App $app) {
$app->get('/', Home::class. ':home')->setName('home');
$app->get('/login', UserAction::class. ':getLogin')->setName('login')->add(NoRefererMiddleware::class);
$app->post('/login', UserAction::class. ':postLogin');
};
File: src/Middleware/NoRefererMiddleware.php
<?php
namespace App\Middleware;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;
final class NoRefererMiddleware implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$request = $request->withAttribute('noreferer', true);
return $handler->handle($request);
}
}
Usage
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$noReferer = $request->getAttribute('noreferer');
if ($noReferer !== true) {
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute();
if ($route !== null) {
$referName = $routeContext->getRoute()->getName();
$referArgs = $routeContext->getRoute()->getArguments();
$this->session->set('referName', $referName);
$this->session->set('referArgs', $referArgs);
}
}
return $handler->handle($request);
}
In the first place I had to configure parameters using the class "ParametersCompilerPass" to get data from database.Here si my class :
class ParametersCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$em = $container->get('doctrine.orm.default_entity_manager');
$boutique = $em->getRepository('AcmeBundle:Boutique')->findOneByNom($container->getParameter('boutique.config'));
if(null !== $boutique){
$container->setParameter('url_site', $boutique->getUrl());
$container->setParameter('idboutique', $boutique->getId());
}else{
$container->setParameter('url_site', null);
$container->setParameter('idboutique', 0);
}
}
}
and when i set a parameter from request, it dont work, i tried in adding this code :
$request = $container->get('request_stack')->getCurrentRequest();
if($request->getMethod() == 'POST'){
if (null !== $choixbout = $request->get('choixbout')){
// $this->container->setParameter('idboutique',$choixbout);
}
}
the service request_stack return null.
I do not know how to configure a parameter from a POST variable.
Hope you can help me.
thanks
Is it solid requirement to have the parameter set?
It could be handy to create a service which has a request dependency that can act as a boutique parameter holder.
For example
# app/config/services.yml
app.boutique:
class: AppBundle\Boutique\Boutique
arguments: ['#request_stack']
app.boutique_info_dependant1:
class: AppBundle\Boutique\BoutiqueDependant1
arguments: ['#app.boutique']
app.boutique_info_dependant2:
class: AppBundle\Boutique\BoutiqueDependant2
arguments: ['#app.boutique']
This would be a parameter handler.
# AppBundle/Boutique/Boutique.php
class Boutique
{
/** #var RequestStack */
private $requestStack;
/**
* BoutiqueListener constructor.
* #param ContainerInterface $container
*/
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
}
public function getBoutique()
{
$request = $this->requestStack->getCurrentRequest();
/// here you can add an extra check if the request is master etc.
if ($request->getMethod() == Request::METHOD_POST) {
if (null !== $choixbout = $request->get('choixbout')) {
return $choixbout;
}
}
return null;
}
}
Then using the handler
class BoutiqueDependant1
{
public function __construct(Boutique $boutique)
{
$this->myBoutique = $boutique->getBoutique();
}
}
This does not look like the best solution but could work...
Other option would be to rethink the application architecture to handle boutique information somehow differently.
I'm writing REST api using Zend Framework 1.12. I want to check "Authorization" header in controller plugin.
I put code in the preDispatch action of the plugin
$authorizationHeader = $request->getHeader('Authorization');
if(empty($authorizationHeader)) {
$this->getResponse()->setHttpResponseCode(400);
$this->getResponse()->setBody('Hello');
die(); //It doesn't work
}
The problem is that after it controller's action is still being called. I tried 'die()', 'exit'. My question is how to return response from plugin and do not call controller's action.
Did a similar REST API with Zend several weeks ago with this approach:
Class Vars/Consts:
protected $_hasError = false;
const HEADER_APIKEY = 'Authorization';
My preDispatch:
public function preDispatch()
{
$this->_apiKey = ($this->getRequest()->getHeader(self::HEADER_APIKEY) ? $this->getRequest()->getHeader(self::HEADER_APIKEY) : null);
if (empty($this->_apiKey)) {
return $this->setError(sprintf('Authentication required!'), 401);
}
[...]
}
My custom setError Function:
private function setError($msg, $code) {
$this->getResponse()->setHttpResponseCode($code);
$this->view->error = array('code' => $code, 'message' => $msg);
$this->_hasError = true;
return false;
}
Then simply check if a error has been set inside your functions:
public function yourAction()
{
if(!$this->_hasError) {
//do stuff
}
}
If you're using contextSwitch and JSON, then your array with errors will be automatically returned & displayed, if an error occours:
public function init()
{
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$this->_helper->contextSwitch()->initContext('json');
[...]
}
Hope this helps
Since checking headers is typically a low level request operation, you could do the header verification and then throw an exception if not valid in dispatchLoopStartup of the plugin. Then in your error controller, return the appropriate response. This would prevent the action from being dispatched/run and could be applied to any controller/action without modifying any controller code.
Controller plugin:
class AuthHeader extends Zend_Controller_Plugin_Abstract
{
public function dispatchLoopStartup(\Zend_Controller_Request_Abstract $request)
{
// Validate the header.
$authorizationHeader = $request->getHeader('Authorization');
if ($invalid) {
throw new Zend_Exception($error_message, $error_code);
}
}
}
Error handler:
class ErrorController extends Zend_Controller_Action
{
public function init()
{
// Enable JSON output for API originating errors.
if ($this->isApiRequest($this->getRequest())) {
$contextSwitch = $this->_helper->getHelper('contextSwitch');
$contextSwitch->addActionContext('error', 'json')
->setAutoJsonSerialization(true)
->initContext('json');
}
}
public function errorAction()
{
// Handle authorization header errors
// ...
// Handle errors
// ...
}
public function isApiRequest($request)
{
// Determine if request is an API request.
// ...
}
}
I want to restrict my users to edit/delete only the comments which they added. I found an example on youtube by a guy named intergral30 and followed his instruction. And now my admin account has the possibility to edit/delete everything, but my user has no access to his own comment.
Here's the code:
Resource
class Application_Model_CommentResource implements Zend_Acl_Resource_Interface{
public $ownerId = null;
public $resourceId = 'comment';
public function getResourceId() {
return $this->resourceId;
}
}
Role
class Application_Model_UserRole implements Zend_Acl_Role_Interface{
public $role = 'guest';
public $id = null;
public function __construct(){
$auth = Zend_Auth::getInstance();
$identity = $auth->getStorage()->read();
$this->id = $identity->id;
$this->role = $identity->role;
}
public function getRoleId(){
return $this->role;
}
}
Assertion
class Application_Model_CommentAssertion implements Zend_Acl_Assert_Interface
{
public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user=null,
Zend_Acl_Resource_Interface $comment=null, $privilege=null){
// if role is admin, he can always edit a comment
if ($user->getRoleId() == 'admin') {
return true;
}
if ($user->id != null && $comment->ownerId == $user->id){
return true;
} else {
return false;
}
}
}
In my ACL I have a function named setDynemicPermissions, which is called in an access check plugin's preDispatch method.
public function setDynamicPermissions() {
$this->addResource('comment');
$this->addResource('post');
$this->allow('user', 'comment', 'modify', new Application_Model_CommentAssertion());
$this->allow('admin', 'post', 'modify', new Application_Model_PostAssertion());
}
public function preDispatch(Zend_Controller_Request_Abstract $request)
{
$this->_acl->setDynamicPermissions();
}
And I'm calling the ACL-s isAllowed method from my comment model where I return a list of comment objects.
public function getComments($id){
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$commentResource = new Application_Model_CommentResource();
$comments = array();
foreach ($res as $comment) {
$commentResource->ownerId = $comment[userId];
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
if (Zend_Registry::get('acl')->isAllowed($userRole->getRoleId(), $commentResource->getResourceId(), 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
Can anyone tell me what have I done wrong?
Or what should I use if I want to give my admins the right to start a post and other users the right to comment on them. Each user should have the chance to edit or delete his own comment and an admin should have all rights.
You seem to be using the dynamic assertions in a wrong manner, as you are still passing the roleId to isAllowed().
What these dynamic assertions really do, is take a complete object and work with it. Zend will determine which rule has to be used by calling getResourceId() and getRoleId() on your objects.
So all you have to do is pass your objects instead of the strings to isAllowed():
public function getComments($id){
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$commentResource = new Application_Model_CommentResource();
$comments = array();
foreach ($res as $comment) {
$commentResource->ownerId = $comment[userId];
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
// This line includes the changes
if (Zend_Registry::get('acl')->isAllowed($userRole, $commentResource, 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
But in can be done better
You would not have to implement a total new Application_Model_CommentResource, but instead you can use your actual Application_Model_Comment like this:
// we are using your normal Comment class here
class Application_Model_Comment implements Zend_Acl_Resource_Interface {
public $resourceId = 'comment';
public function getResourceId() {
return $this->resourceId;
}
// all other methods you have implemented
// I think there is something like this among them
public function getOwnerId() {
return $this->ownerId;
}
}
Assertion would then use this object and retrieve the owner to compare it with the actually logged in person:
class Application_Model_CommentAssertion implements Zend_Acl_Assert_Interface {
public function assert(Zend_Acl $acl, Zend_Acl_Role_Interface $user=null,
Zend_Acl_Resource_Interface $comment=null, $privilege=null){
// if role is admin, he can always edit a comment
if ($user->getRoleId() == 'admin') {
return true;
}
// using the method now instead of ->ownerId, but this totally depends
// on how one can get the owner in Application_Model_Comment
if ($user->id != null && $comment->getOwnerId() == $user->id){
return true;
} else {
return false;
}
}
And the usage is like this:
public function getComments($id) {
//loading comments from the DB
$userRole = new Application_Model_UserRole();
$comments = array();
foreach ($res as $comment) {
$commentObj = new Application_Model_Comment();
$commentObj->setId($comment[id]);
//setting the data
$commentObj->setLink('');
// no $commentResource anymore, just pure $comment
if (Zend_Registry::get('acl')->isAllowed($userRole, $comment, 'modify')) {
$commentObj->setLink('Edit'.'Delete');
}
$comments[$comment[id]] = $commentObj;
}
}
I need some improvements about my actual way to delete entities:
public function deleteAction($path)
{
$form = $this->createFormBuilder(array('path' => $path))
->add('path')
->setReadOnly(true)
->getForm();
if ($this->getRequest()->getMethod() === 'POST') {
$form->bindRequest($this->getRequest());
if ($form->isValid()) {
$image = $this->getImageManager()->findImageByPath($path);
$this->getImageManager()->deleteImage($image);
return $this->redirect($this->generateUrl('AcmeImageBundle_Image_index'));
}
}
return $this->render('AcmeImageBundle:Image:delete.html.twig', array(
'form' => $form->createView(),
));
}
Two improvements I already found while writting:
CreateFormBuilder in extra method in controller
Hidden field and overgive extra image-entity to get rendered
Are there other thing I could make better?
Regards
(my answer is too long for the comment so i add it here)
First you have to create a Type file (generally in YourApp\YourBundle\Form\yourHandler.php), some basique code to put inside if you don't know:
<?php
namespace ***\****Bundle\Form;
use Symfony\Component\Form\Form;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\EntityManager;
use ***\****Bundle\Entity\your_entity;
class *****Handler
{
protected $form;
protected $request;
protected $em;
public function __construct(Form $form, Request $request, EntityManager $em)
{
$this->form = $form;
$this->request = $request;
$this->em = $em;
}
public function process()
{
if( $this->request->getMethod() == 'POST' )
{
$this->form->bindRequest($this->request);
if( $this->form->isValid() )
{
$this->onSuccess($this->form->getData());
return true;
}
}
return false;
}
public function onSuccess(your_entity $object)
{
// Make your stuff here (remove,....)
}
}
And in your controller i just call it this way:
if (!empty($_POST))
{
$formHandler = new *****Handler($my_form, $this->get('request'), $this->getDoctrine()->getEntityManager());
$formHandler->process();
}
Hope i'm clear enough