so my class looks like this:
class Myclass{
private $nonce;
public function __construct(){
if( get_current_screen()->id == 'nav-menus' ){
$this->nonce = 'my-plugin-nonce';
}
add_action( 'wp_ajax_run_action', array( $this, 'run' ) );
wp_localize_script(
'my-script',
'my_script',
array( 'nonce' => wp_create_nonce( $this->nonce ),
'ajaxurl' => admin_url('admin-ajax.php'),
)
);
}
public function run(){
if( ! wp_verify_nonce( $_POST['nonce'], $this->nonce ) )
return false;
wp_send_json_success();
}
}
new Myclass;
And here is the javascript code:
$.ajax({
type: 'POST',
dataType: 'json',
url: my_script.ajaxurl,
data: {
'action': 'run_action',
'nonce' : my_script.nonce,
},
complete: function( object ) {
console.log( object.responseJSON )
}
});
The problem is that when i try to call the run_action action from within my javascript ajax function it does not return true as it should.
Note that i have properly localized my script and any data contained in the object is being rendered correctly.
Why is that happening ?
Localization of the script must be done on the pages you are including the script on (ie. on the nav-menus.php admin page in this case) - you do not include the code that actually enqueues your javascript, but the code you did post suggests to me that you are actually attempting to localize the script in the ajax-call itself - which won't work.
I've rearranged your code below and added comments to explain each change:
class Myclass {
/**
* No reason not to assign this already (and I've renamed it to explicitly,
* let people reading the code know that it is not the nonce itself, but the
* name of the nonce action - fooled me for a minute or two :p
*/
private $nonce_action_name = 'my-plugin-nonce';
/**
* The __construct function is great for hooking everything you need to
* hook, and for setting initial variables. Pretty much anything else
* should NOT be in this function though!
*/
public function __construct(){
// Register your ajax action.
add_action( 'wp_ajax_run_action', array( $this, 'run' ) );
// Hook into the appropriate action for admin scripts
add_action( 'admin_enqueue_scripts', array( $this, 'scripts' ) );
}
public function scripts() {
/**
* I've negated your if-statement here - basically we don't want to do
* anything at all if we are not on the correct page - which is clearer
* this way - also you had it in the __construct function, which will
* actually produce a fatal error, since get_current_screen is not
* usually defined yet at that point(!)
*/
if( get_current_screen()->id !== 'nav-menus' ){
return;
}
//Now enqueue (or register) the script
wp_enqueue_script('my-script', plugins_url('/my-script.js', __FILE__));
//Then localize it
wp_localize_script(
'my-script',
'my_script',
array(
'nonce' => wp_create_nonce( $this->nonce_action_name ),
'ajaxurl' => admin_url('admin-ajax.php'),
)
);
}
/**
* Actual ajax handler
*/
public function run(){
if( ! wp_verify_nonce( $_POST['nonce'], $this->nonce_action_name ) ) {
//This is a guess, but I think you'll want to wp_send_json_error
//here to match up with your success below
wp_send_json_error();
} else {
wp_send_json_success();
}
/**
* Always recommended to explicitly die() after handling ajax - though
* the wp_send_json_* family probably does it for you.
*/
die();
}
}
new Myclass;
A final note is that ajaxurl is actually always defined in the admin, so you don't really need to add that to your localization (though it only hurts by adding a few extra bytes).
Note from the codex:
wp_localize_script() MUST be called after the script has been registered using wp_register_script() or wp_enqueue_script().
So your workflow should be as follows:
Register script
Localize it
Enqueue your localized script.
Tie it to appropriate action: wp_enqueue_scripts or admin_enqueue_scripts
For example:
Add action to your __construct method:
add_action('wp_enqueue_scripts', array($this, 'registerAjaxScript'));
Then create a method that registers and localizes your script:
function registerAjaxScript() {
wp_register_script('my-script',
string $src,
array $deps,
string or bool $ver,
bool $in_footer
);
wp_localize_script('my-script',
'my_script',
array( 'nonce' => wp_create_nonce( $this->nonce ),
'ajaxurl' => admin_url('admin-ajax.php'),
)
);
}
Related
sorry if this has been asked before, I looked around but haven't found this specific question on StackOverFlow.com.
I have a view called 'view-post-wall' which I'm trying to add the form that submits posts to this view called 'post' via ajax submit, though I haven't begun adding ajax yet.
My module's name is 'friendicate'
I don't understand what I'm missing here, I'm following a tutorial and have been unable to get past this issue for 2 days now.
I don't get any errors either.
Here is the module code in full
function _form_post_ajax_add() {
$form = array();
$form['title'] = array(
'#type' => 'textfield',
'#title' => 'Title of post',
);
$form['body'] = array(
'#type' => 'textarea',
'#title' => 'description',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => 'Submit post',
);
return $form;
}
function post_ajax_preprocess_page(&$variables) {
//krumo($variables);
$arg = arg();
if($arg[0] == 'view-post-wall') {
$variables['page']['content']['system_main']['main']['#markup'] = drupal_render(drupal_get_form('_form_post_ajax_add'));
}
}
There are multiple ways to accomplish this, and I'll outline those methods below. Also, if nothing works from my suggestions below, it's possible that you have an invalid form function name. Im not sure if that throws off Drupal or not. The correct format for the function name should end in _form and contain the arguments $form and $form_state, like so:
_form_post_ajax_add_form($form, &$form_state) { ... }
Also, if you want to use a hook, Steff mentioned in a comment to your question that you'll need to use your module name in the function name.
friendicate_preprocess_page(&$variables) { ... }
Ok, now for a few ideas how to get the form on the page.
Block
You can create a custom block within your module, and then assign it to a region in admin/structure/blocks
<?php
/**
* Implements hook_block_info().
*/
function friendicate_block_info() {
$blocks = array();
$blocks['post_ajax'] = array(
'info' => t('Translation Set Links'),
'cache' => DRUPAL_NO_CACHE,
);
return $blocks;
}
/**
* Implements hook_block_view().
*/
function friendicate_block_view($delta = '') {
$block = array();
if ($delta == 'post_ajax') {
$form = drupal_get_form('_form_post_ajax_add_form');
$block['content'] = $form;
}
return $block;
}
Clear the cache and your block should appear in admin/structure/blocks
Views attachment before/after
You can add markup before and after a view using the Views hook hook_views_pre_render()
<?php
/**
* Implements hook_view_pre_render().
*/
function frendicate_views_pre_render(&$view) {
if($view->name == 'view_post_wall') { // the machine name of your view
$form = drupal_get_form('_form_post_ajax_add_form');
$view->attachment_before = render($form);
}
}
Or maybe use view post render
function friendicate_views_post_render(&$view, &$output, &$cache) {
//use the machine name of your view
if ($view->name == 'view_post_wall') {
$output .= drupal_render(drupal_get_form('_form_post_ajax_add'));
}
}
I have made a drupal 7 module recently and I want to customise the way drupal show validation errors. I want to place error message in a popup. And I put the popup generation code in form alter hook.
But I cannot make drupal hook_form_alter run after validation hook. I have tried to clear the form cache and tried to use "#after_build" instead of form alter hook, but they are all run before the validation. It seems that the validation is last thing to run in that process. I have put my code below, please help me on this.
Thank you very much in advance.
function quote_line_menu() {
$items ['quote_line'] = array (
'page callback' => 'drupal_get_form',
'page arguments' => array (
'quote_line_form'
),
'access arguments' => array (
'access content'
),
'type' => MENU_CALLBACK
);
$items ['ajax_manually_get_price'] = array(
'page callback' => 'ajax_update_price_callback',
'access arguments' => array (
'access content'
),
'type' => MENU_CALLBACK
);
return $items;
}
function quote_line_form($form, &$form_state) {
// Initialize.
if ($form_state ['rebuild']) {
// Don't hang on to submitted data in form state input.
$form_state ['input'] = array ();
}
if (empty ( $form_state ['storage'] )) {
$form_state ['storage'] = array (
'step' => 'quote_line_form_first'
);
}
// Add general settings
$form['#attributes']['class'][] = 'separate-border';
// No cache
/*
$form['#cache'] = FALSE;
$form['#no_cache'] = TRUE;
$form_state['cache'] = FALSE;*/
$form['#after_build'][] = 'quote_line_form_after_build';
// $form['#validate'][] = 'quote_line_form_validate';
// Return the form for the current step.
$function = $form_state ['storage'] ['step'];
$form = $function ( $form, $form_state );
return $form;
}
function quote_line_form_after_build($form, &$form_state) {
error_log(0);
return $form;
}
function quote_line_form_quote_line_form_alter(&$form, &$form_state, $form_id) {
error_log(1);
quote_line_handle_form_set_error($form, $form_state, $form_id); //generate popup here
// Preset form state
if($form_state['storage']['step'] == 'quote_line_form_bike_info'){
if(isset($form_state['storage']['bike_0_oversea_travel_days']) && !empty($form_state['storage']['bike_0_oversea_travel_days'])){
$form ['default_open_oversea'] = array (
'#markup' => '<script>jQuery("a#over0-yes").click();</script>'
);
}
}
}
function quote_line_form_validate($form, &$form_state) {
error_log(2);
error_log($_POST['form_build_id']);
//cache_clear_all('form', 'cache_form', true);
$values = $form_state ['values'];
if(isset($_POST['back']) && !empty($_POST['back'])){
if(!isset($form_state ['values']['back']) || $form_state ['values']['back'] != $_POST['back']){
$form_state ['values']['back'] = $_POST['back'];
}
if(!isset($form_state ['values']['op']) || $form_state ['values']['op'] != $_POST['back']){
$form_state ['values']['op'] = $_POST['back'];
}
$function = 'quote_line_form_submit';
$function ( $form, $form_state );
return;
}
// Call step validate handler if it exists.
if (function_exists ( $form_state ['storage'] ['step'] . '_validate' )) {
$function = $form_state ['storage'] ['step'] . '_validate';
$function ( $form, $form_state );
}
return;
}
You can stop the error messages being printed to the theme using drupal_get_messages('error'). Then, display custom error messages in anyway you like.
I just found a dirty fix. The validation and form build process has been defined in drupal_process_form. And I just hacked that function to make sure the hook_form_alter is called again after the validation failed.
If anyone has a better solution, please post here. Thanks.
I've got an action handler in my plugin which catches a form being posted to admin-ajax.php. The action namespace is 'handle_custom_form_submission'.
I can easily pass arguments to my action handlers in php via the do_action( 'namespace', 'argument' ) function; However, if I'm catching an action via AJAX, all I can do, to my knowledge, is have a $_POST var whose name="action" and value="namespace". Is there any way to pass a parameter into my action call this way? Like
<input name="action" value="handle_custom_form_submission( 'a' )"/>
Here's the situation in more detail:
I'm trying to write a custom form plugin so that I can easily handle multiple unique forms on a Wordpress site.
On my latest project, I've got a contact form on one page, and a testimonial submission form on another. In my code, I'm just designating them as 'custom-form-a' and 'custom-form-b'.
I've got 3 main action hooks:
include_custom_form_js( $form_id )
include_custom_form_html( $form_id )
handle_custom_form_submission()
I'd like to be able to pass #3, handle_custom_form_submission(), a $form_id as well. The only thing I can think of right now is to declare two seperate functions, handle_custom_form_a_submission() and handle_custom_form_b_submission(). Is there a cleaner way?
My main plugin file:
<?php
/*
Plugin Name: Custom Form
Plugin URI:
Description: Add custom form HTML and handle its submission on the backend.
Version: 0.1
Author: James Heston
*/
require_once( dirname( __FILE__ ) . '/php/constants.php' );
class CustomForm{
// paths
private $plugin_dir = CUSTOMFORM_DIRNAME;
private $plugin_path = CUSTOMFORM_URLPATH;
// action names
private $action_include_js = 'include_custom_form_js';
private $action_include_html = 'include_custom_form_html';
private $action_handle_submission = 'handle_custom_form_submission';
// other vars
private $recipient_email = ''; // probably should pull from some WP option
public function __construct(){
$this->add_hooks();
}
private function add_hooks(){
// include necessary js with appropriate MyAjax properties on page where `do_action( 'include_custom_form_js', $form_id )` action is called
add_action( $this->action_include_js, array( $this, $this->action_include_js ), 10, 1 );
// include form html on page where `do_action( 'include_custom_form_html', $form_id )` action is called
add_action( $this->action_include_html, array( $this, $this->action_include_html ), 10, 1 );
// handle contact form submission
add_action( 'wp_ajax_' . $this->action_handle_submission, array( $this, $this->action_handle_submission ) );
add_action( 'wp_ajax_nopriv_' . $this->action_handle_submission, array( $this, $this->action_handle_submission ) );
}
function include_custom_form_js( $form_id ){
wp_enqueue_script(
$this->action_include_js,
$this->plugin_path . '/js/custom-form-' . $form_id . '.js',
array(),
false,
true
);
$localize_array = array(
'ajaxurl' => admin_url( 'admin-ajax.php' ),
'namespace' => $this->action_handle_submission
);
wp_localize_script( $this->action_include_js, 'MyAjax', $localize_array );
}
function include_custom_form_html( $form_id ){
include( $this->plugin_dir . '/html/custom-form-' . $form_id . '.html' );
}
function handle_custom_form_submission(){
$form_id = 'a';
include( $this->plugin_dir . '/php/handle-custom-form-' . $form_id . 'submission.php' );
include( $this->plugin_dir . '/php/handle-custom-form-' . $form_id . 'submission.php' );
}
function send_email(){
//
}
static function instance(){
global $CustomForm;
if(! isset($CustomForm) ){
$CustomForm = new CustomForm();
}
}
}// class CustomForm
if(! isset($CustomForm) ){
CustomForm::instance();
}
?>
I'm currently learning ZF2 by developing a small MVC application roughly based on the skeleton app. Right now I'm trying to hide some fixed HTML elements based on the route matched: just as an example, I don't want the main menu to show during the login phase.
I can do that easily by passing toggle parameters as return values from the controller actions, but it doesn't feel right, so I'd like to just check the matched route from the layout and compose the layout accordingly.
Problem is, I don't know how to get the matched route in the template. Is this possible? Are there other solutions to avoid adding layout logic into controllers?
Edit after some good Frankenstein work, I was able to find a solution for this. I like the idea of using a helper, so I just tried to pass it the Application object, from the boostrap function in the main module:
$app = $e->getApplication();
$serviceManager = $app->getServiceManager();
....
$serviceManager->get('viewhelpermanager')->setFactory('getRoute', function($sm) use ($app) {
return new Helper\GetRoute($app);
});
and the helper function:
use Zend\View\Helper\AbstractHelper;
class GetRoute extends AbstractHelper {
private $sm;
public function __construct($app) {
$this->sm = $app->getServiceManager();
}
public function echoRoute() {
$router = $this->sm->get('router');
$request = $this->sm->get('request');
$routeMatch = $router->match($request);
if (!is_null($routeMatch))
echo $routeMatch->getMatchedRouteName();
}
}
perhaps there's a cleaner, more ZF2ish way to do this...
Another solution without a new match
$routeMatch = $serviceLocator->get('Application')->getMvcEvent()->getRouteMatch();
echo $routeMatch->getMatchedRouteName();
There is a way to get service manager in layout:
$sm = $this->getHelperPluginManager()->getServiceLocator();
and then you can access $sm->get('router') etc.
You could create a View helper that implements ServiceManagerAwareInterface. Then inside the View helper using the ServiceManager instance to retrieve both the router and request objects then reconstruct the route match.
$services = $this->getServiceManager();
$router = $services->get('router');
$request = $services->get('request');
$routeMatch = $router->match($request);
echo $routeMatch->getMatchedRouteName();
I'd also recommend writing the View helper so that code only triggers once per request.
When moving to ZF3, you should consider use this method... since getLocator isn't available anymore (and it's not correct inject it).
Create the Helper
namespace Application\View\Helper;
use Zend\Http\Request;
use Zend\Router\RouteStackInterface;
use Zend\View\Helper\AbstractHelper;
/**
* Helper to get the RouteMatch
*/
class RouteMatch extends AbstractHelper
{
/**
* RouteStackInterface instance.
*
* #var RouteStackInterface
*/
protected $router;
/**
* #var Request
*/
protected $request;
/**
* RouteMatch constructor.
* #param RouteStackInterface $router
* #param Request $request
*/
public function __construct(RouteStackInterface $router, Request $request)
{
$this->router = $router;
$this->request = $request;
}
/**
* #return \Zend\Router\RouteMatch
*/
public function __invoke()
{
return $this->router->match($this->request);
}
}
Create a Factory for this helper
namespace Application\View\Helper;
use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
class RouteMatchFactory implements FactoryInterface
{
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$router = $container->get('router');
$request = $container->get('request');
return new RouteMatch($router, $request);
}
}
Call your Factory on your Module.php and create an Alias for it.
public function getViewHelperConfig()
{
return array(
'factories' => array(
RouteMatch::class => RouteMatchFactory::class,
),
'aliases' => array(
'routeMatch' => RouteMatch::class,
)
);
}
That's it... you have a RouteMatch Helper using the new ZF3 standards.
Bye!
In view you can use:
$this->getHelperPluginManager()->getServiceLocator()->get('request')->getUri()->getPath();
or
$this->getHelperPluginManager()->getServiceLocator()->get('request')->getUri()->toString();
I believe you can solve it by finding the action/controller names:
$controller = $this->getRequest()->getControllerName();
$action = $this->getRequest()->getActionName();
Once you know the action, you can have specific conditions to enable sections of the layout.
I view you can use
$this->getHelperPluginManager()->getServiceLocator()->get('Application')->getMvcEvent()->getRouteMatch()->getMatchedRouteName();
Additional information about "Rodrigo Boratto" post for integrating getRouteMatch in ZF3 (I can't comment because I have under 50 repo...)
In view helper file these line:
use Zend\Mvc\Router\RouteMatch as MvcRouteMatch;
use Zend\Mvc\Router\RouteStackInterface;
should be:
use Zend\Router\RouteMatch as MvcRouteMatch;
use Zend\Router\RouteStackInterface;
I don't know when they made that change but the files are in Zend\Router namespace.
P.S. I use composer if that matters.
My controller:
<?PHP
namespace SomeName\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
class SomeController extends AbstractActionController
{
public function getIdAction()
{
$id = $this->params()->fromRoute('id', 0);
return new ViewModel(array(
'id' => $id,
));
}
}
My Router:
<?php
return array(
'controllers' => array(
'invokables' => array(
'SomeName\Controller\Some' => 'SomeName\Controller\SomeController',
),
),
'router' => array(
'routes' => array(
'testId' => array(
'type' => 'segment',
'options' => array(
'route' => '/[:id]',
'constraints' => array(
'id' => '\d*',
),
'defaults' => array(
'controller' => 'SomeName\Controller\Some',
'action' => 'getId',
),
),
),
),
),
'view_manager' => array(
'template_path_stack' => array(
'album' => __DIR__ . '/../view',
),
),
);
At any view or layout, you are able to test route with this function:
<?php function itsRoute($routeName){
$flag = false;
if($this->serverUrl(true) == $this->url($route,[],['force_canonical'=>true]))){
$flag = true;
}
return $flag;
}
I'm using the technique of saving codeigniter form validation rules to a config file as specified here, but can't seem to get it working.
I am also attempting to invoke validation callback functions from a Validation_Callbacks.php library which I'm autoloading via the autoload.php mechanism.
Below is a snippet from my form_validation.php form validation rules config file:
<?php
/* This config file contains the form validation sets used by the application */
$config = array(
'register_user' => array(
array(
'field' => 'dob',
'label' => 'lang:register_dob',
'rules' => 'trim|required|date_check|age_check|xss_clean'
), ...
)
)
And here is the Validation_Callbacks.php file, that lives under application/libraries:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Custom validator library containing app-specific validation functions
*/
class Validation_Callbacks {
/* Checks that the given date string is formatted as expected */
function date_check($date) {
$ddmmyyyy='(0[1-9]|[12][0-9]|3[01])[- \/.](0[1-9]|1[012])[- \/.](19|20)[0-9]{2}';
if(preg_match("/$ddmmyyyy$/", $date)) {
return TRUE;
} else {
$this->form_validation->set_message('date_check', $this->lang->line('error_register_dob_format'));
return FALSE;
}
}
/* Checks that the given birthday belongs to someone older than 18 years old. We assume
* that the date_check method has already been run, and that the given date is in the
* expected format of dd/mm/yyyy */
function age_check($date) {
$piecesDate = explode('/', $date);
$piecesNow = array(date("d"), date("m"), date("Y"));
$jdDate = gregoriantojd($piecesDate[1], $piecesDate[0], $piecesDate[2]);
$jdNow = gregoriantojd($piecesNow[1], $piecesNow[0], $piecesNow[2]);
$dayDiff = $jdNow - $jdDate;
if ($dayDiff >= 6570) {
return TRUE;
} else {
$this->form_validation->set_message('age_check', $this->lang->line('error_register_age_check'));
return FALSE;
}
}
}
I'm invoking this using the standard:
if ($this->form_validation->run('register_user') == FALSE) {
My callback functions are not being called. Is there something I'm missing here? Thanks in advance for any help you may be able to provide!
Ensure that:
When a rule group is named identically
to a controller class/function it will
be used automatically when the run()
function is invoked from that
class/function.
To test you can try using:
$this->load->config('configname');