Custom backend FormAction handler not found - php

My goal is it to add a custom FormAction to a DataObjects EditForm inside a ModelAdmin, next to the "Save" and "Delete" action.
The setup:
With updateCMSActions I can add that button.
class MyActionEventExtension extends DataExtension {
public function updateCMSActions(FieldList $actions) {
if($this->owner->canEdit(Member::currentUser())) {
$actions->push(FormAction::create('doMyAction', 'Action'));
}
return $actions;
}
}
This works perfectly fine.
Following this answer on stackoverflow I created a LeftAndMainExtension for the actions handler.
class MyActionLeftAndMainExtension extends LeftAndMainExtension {
private static $allowed_actions = array(
'doMyAction'
);
public function doMyAction($data, $form) {
// Do stuff.
// ... I never get here.
// Return stuff
$this->owner->response->addHeader(
'X-Status',
rawurlencode('Success message!')
);
return $this->owner->getResponseNegotiator()
->respond($this->owner->request);
}
}
The corresponding config.yml file looks like this:
LeftAndMain:
extensions:
- MyActionLeftAndMainExtension
TheDataObject:
extensions:
- MyActionEventExtension
The Problem:
When I click the button, the response gives me "404 Not Found".
The requested URL always is the same:
http://localhost/admin/model-admin-url-slug/TheDataObject/EditForm/field/TheDataObject/item/878/ItemEditForm
Some other solutions I found, suggested to extend the ModelAdmins GridField. This sadly is not an option, since the DataObject I need that action for has alot of relations, which means, it's EditForm also appears in other DataObjects EditForms (nested).
I'm really running out of ideas. Did I miss something within my ModelAdmin? The one I created only implements the basic static vars, so I didn't posted it here.
Any help would be great!
Update:
I ended up, providing a getEditForm method on my ModelAdmin.
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$listField = $form->Fields()->fieldByName($this->modelClass);
if ($gridField = $listField->getConfig()->getComponentByType('GridFieldDetailForm')) {
$gridField->setItemRequestClass('MyAdminForm_ItemRequest');
}
return $form;
}
and extending the GridFieldDetailForm_ItemRequest:
class MyAdminForm_ItemRequest extends GridFieldDetailForm_ItemRequest {
private static $allowed_actions = array (
'edit',
'view',
'ItemEditForm'
);
public function ItemEditForm() {
$form = parent::ItemEditForm();
$formActions = $form->Actions();
// Adds all FormActions provided by the model's `getCMSActions` callback
if ($actions = $this->record->getCMSActions()) {
foreach ($actions as $action) {
$formActions->push($action);
}
}
return $form;
}
public function doAction($data, $form) {
// do stuff here
}
}
Sadly this doesn't add an action on has_many or many_many relation gridfields.. and because of that, I'll leave the question opened and unanswered. Maybe sometime there will be a better solution.. :)

One very simple answer to this question (if it's an option for you) is to use the Better Buttons module: https://github.com/unclecheese/silverstripe-gridfield-betterbuttons#creating-a-custom-action
It lets you define the actions on the model, which is a bit questionable from an architecture standpoint, but also works pretty well in the context of Silverstripe and ModelAdmin.

Related

My SilverStripe module overrides other modules display

I'm writing a subscribe module to plugin to the SilverStripe Blog module. So far I have my yml as:
---
Name: subscription
After: 'framework/*','cms/*'
---
Blog:
extensions:
- Subscription
Page_Controller:
extensions:
- SubscriptionWidget
And my SubscriptionWidget.php:
<?php
class SubscriptionWidget extends DataExtension {
public function SubscriptionWidget() {
$controller = SubscriptionWidget_Controller::create();
$form = $controller->SubscriptionWidget();
return $form;
}
}
class SubscriptionWidget_Controller extends Controller {
private static $allowed_actions = array('SubscriptionWidget');
public function SubscriptionWidget () {
$form = Form::create(
$this,
__FUNCTION__,
FieldList::create(
TextField::create('Email', 'Email'),
TextField::create('Name', 'Name')
),
FieldList::create(
FormAction::create('submit', 'Subscribe')
)
);//->setTemplate('SubscriptionWidget');
return $form;
}
public function submit($data, $form) {
return $this->redirect('/subscribed');
}
}
At the moment this works as intended however another plugin I use called BetterNavigator disappears from the screen. If I take out
Page_Controller:
extensions:
- SubscriptionWidget
from my yml it reappears. I've looked through both code bases which are quite simple and there are no conflicting functions. I've also tried using ContentController instead of Page_Controller and my template disappears until I disable BetterNavigator and then it reappears. I do have one or two pretty empty classes but all are called some variation of Subscriber while there is only one function in BetterNavigator called BetterNavigator.
Why would this be happening?
I see only one clash in your code that results in incorrect runtime behaviour. Your method SubscriptionWidget::SubscriptionWidget() is treated as legacy class constructor. So I suggest you thinking about better class and method names.
class SubscriptionWidget extends Extension
{
// explicitly defined constructor
public function __construct() {
parent::__construct();
}
// now this one is normal function
public function SubscriptionWidget() {
$controller = SubscriptionWidget_Controller::create();
$form = $controller->SubscriptionWidget();
return $form;
}
}

Redirect to DataObject edit page inside BetterButton custom action

I'm trying to create a custom button that clones a DataObject using the unclecheese/betterbuttons v.1.2 module.
Everything is working fine but at the end I would like to redirect the user to the newly created DataObject edit page instead of refresh/go back. How can I do this?
Here is my custom button code:
class GridFieldCloneBetterButton extends DataExtension {
private static $better_buttons_actions = array(
'clone_do'
);
public function updateBetterButtonsActions($actions) {
$actions->push(
BetterButtonCustomAction::create('clone_do', 'Clone')
->setSuccessMessage('Object cloned')
->setRedirectType(BetterButtonCustomAction::GOBACK)
);
return $actions;
}
public function clone_do() {
$current_record = $this->owner;
$clone = $this->owner->duplicate();
}
}
Maybe if I can access GridFieldDetailForm_ItemRequest from inside the DataExtension I can make this work, but I really don't know how to do it.

Yii, several instances of model in one form

We have two models
class Base extends AbstractModel
{
public $id;
public $name;
public function tableName()
{
return 'table_base';
}
}
class ExtendBase extends AbstractModel
{
public $id;
public $baseID;
public function tableName()
{
return 'table_base_extend';
}
}
I won't describe all, will describe only main things.
So, now I want create form that will combine this forms. And want in action do something like this
public function actionSome ()
{
$oBase = new Base();
$aExtendBase = array(); // important this is array
for ($i = 1; $i <= 3; $i++) {
$aExtendBase[] = new Extend(); // fill in array, pre-create for form
}
if ($this->isPost()) { // protected custom method
if (isset($_POST['Base'], $_POST['ExtendBase'])) // here should be specific checker because we have several ExtendBase instances in form
{
// save Base model
// save each ExtendBase model
}
}
$this->render('formView', array('oBase' => $oBase, 'aExtendBase' => $aExtendBaes))
}
So question is
How to create such 'combined' form in view;
How to get all forms data (I mean each) after POST action;
See the solution on the Yii WIKI
http://www.yiiframework.com/wiki/19/how-to-use-a-single-form-to-collect-data-for-two-or-more-models/
You can create a model (CFormModel) which does not represent an ActiveRecord just for a single form.
I think it would be the best way for you to proceed (assuming I've understood your problem correctly).
If I were you, I would create a Model to represent the rules for this form only (like I said, an instance of CFormModel, not ActiveRecord) and then, in your controller, take the elements that got submitted and manually create and/or update objects for both classes.

In Zend Framework 1.12, how to avoid getting an action?

I have an action method that only triggers when post data to it.
So I add some logic code to prevent the get request.
public function myAction()
{
if($_SERVER['REQUEST_METHOD'] == "GET")
{
echo "No get!";
die();
}
else
{
//some other codes
}
}
It works.
But I have to write these code snippet to many action method. It looks so redundant.
So, is there a better way to implement it like the above code?
Add this method to your controller
public function preDispatch(){
if(!$this->_request->isPost() and in_array($this->_request->getParam('action'), array('action1', 'action2'))){
exit('only post');
}
}
Its too late to answer this question now but hope it will help someone else.
If you want to do this for just one controller then your better off using the controllers _init() method as shown below.
public function _init() {
if(!$this->getRequest()->isPost()){
//The request is not post. Do what you like.
}
}
If you want the same thing for multiple controllers you can create a frontController plugin like this.
class Application_Plugin_Request extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request){
if(!$request->$isPost()){
//The request is not post. Do what you like.
}
}
}
Add this method to your Bootstrap class for activate the plugin.
protected function _initPlugins(){
$front = Zend_Controller_Front::getInstance();
$front->registerPlugin(new Application_Plugin_Request(), 17);
}

GET and POST functions in PHP / CodeIgniter

I like MVC (a lot), and I am trying to teach myself a framework of MVC architecture in all the major web languages of today.
I am currently on CodeIgniter and PHP. I searched online for a way to make the same function behave different for a POST and GET but couldn't find anything. Does CodeIgniter have this feature?
If you've used Ruby On Rails or ASP.NET MVC you'll know what I'm talking about, in them frameworks we can do this:
[GET]
public ActionResult Edit(int Id)
{
// logic here for GET
}
[POST]
public ActionResult Edit(EntityX EX)
{
// logic here for POST
}
I am so used to this, that I am finding it hard wrapping my head around how to get the same smooth functionality without that useful ability.
Am I missing something? How can I achieve the same thing in CodeIgniter?
Thanks
Am I missing something? How can I achieve the same thing in
CodeIgniter?
if you want to learn how to truly approach MVC in PHP, you can learn it from Tom Butler articles
CodeIgniter implements Model-View-Presenter pattern, not MVC (even if it says so). If you want to implement a truly MVC-like application, you're on the wrong track.
In MVP:
View can be a class or a html template. View should never be aware of a Model.
View should never contain business logic
A Presenter is just a glue between a View and the Model. Its also responsible for generating output.
Note: A model should never be singular class. Its a number of classes. I'll call it as "Model" just for demonstration.
So it looks like as:
class Presenter
{
public function __construct(Model $model, View $view)
{
$this->model = $model;
$this->view = $view;
}
public function indexAction()
{
$data = $this->model->fetchSomeData();
$this->view->setSomeData($data);
echo $this->view->render();
}
}
In MVC:
Views are not HTML templates, but classes which are responsible for presentation logic
A View has direct access to a Model
A Controller should not generate a response, but change model variables (i.e assign vars from $_GET or $_POST
A controller should not be aware of a view
For example,
class View
{
public function __construct(Model $model)
{
$this->model = $model;
}
public function render()
{
ob_start();
$vars = $this->model->fetchSomeStuff();
extract($vars);
require('/template.phtml');
return ob_get_clean();
}
}
class Controller
{
public function __construct(Model $model)
{
$this->model = $model;
}
public function indexAction()
{
$this->model->setVars($_POST); // or something like that
}
}
$model = new Model();
$view = new View($model);
$controller = new Controller($model);
$controller->indexAction();
echo $view->render();
The parameters only allow you to retrieve GET variables. If you want to get the POST variables, you need to use the Input library which is automatically loaded by CodeIgniter:
$this->input->post('data');
So, in your case, it would be:
public function edit($id = -1)
{
if($id >= 0 && is_numeric($id))
{
// logic here for GET using $id
}
else if($id === -1 && $this->input->post('id') !== false)
{
// logic here for POST using $this->input->post('id')
}
}
Note that you can also use this library to obtain GET, COOKIE and SERVER variables:
$this->input->get('data');
$this->input->server('data');
$this->input->cookie('data');

Categories