Jump to content

Custom Page Actions


gebeer
 Share

Recommended Posts

As of 2.6.5dev there are these neat extra action buttons 'unpub', 'hide' etc in the page tree.

post-1920-0-84696500-1435224026_thumb.pn

In ListerPro we can define our own actions as modules that extend the new PageAction class.

Ryan mentions in the ListerPro forum that the page actions are a new feature of PW and that they are independent from ListerPro.

It would be great if we could define our own page actions and attach them to the page tree or to lister results.

Looking at the code I am not quite sure how we can achieve that through a hook or the like.

So for all not so code savvy PW "hookers" (pun intended) it would be great to get some documentation on this new feature.  

  • Like 1
Link to comment
Share on other sites

Not tested, but should be a sufficient blueprint.

<?php

/**
 * Add own PageList actions
 * 
 * ProcessWire 2.x 
 * Copyright (C) 2014 by Ryan Cramer 
 * Licensed under GNU/GPL v2, see LICENSE.TXT
 * 
 * http://processwire.com
 *
 */

class AddPageActionsToPageList extends WireData implements Module {

    public static function getModuleInfo() {

        return array(
            'title' => 'Page Actions to Page List', 
            'version' => 1, 
            'summary' => 'Adds PageActions to Page List actions',
            'singular' => true, // Limit the module to a single instance
            'autoload' => true, // Load the module with every call to ProcessWire 
            );
    }

    public function init() {
        $this->addHookAfter('ProcessPageListActions::getExtraActions', $this, 'addActions'); 
        $this->addHookBefore('ProcessPageListActions::processAction', $this, 'exeActions');
    }

    /**
     * Add the new actions
     */
    public function addActions($event) {
        $page = $event->arguments[0]; 
        $actions = $event->return;
        // We do not overwrite existing core actions
        $actions = array_merge($this->createActions($page), $actions);

        // $event->return = $actions // Shouldn't be needed
    }

    /**
     * Create the actions
     */
    protected function createActions($page){
        $actions = array();

        if($page->id == 1 || $page->template == 'admin') return $actions;
        if($page->template->noSettings || !$page->editable('status', false)) return $actions;
        $adminUrl = $this->wire('config')->urls->admin . 'page/';

        if($this->wire('user')->hasPermission('action-email', $page) && !$page->isTrash()) {
            $actions['action-email'] = array(
                'cn'    => 'Email',
                'name'  => 'Email',
                'url'   => "$adminUrl?action=PageActionEmail&id=$page->id",
                'ajax' => true, 
            );
        }

        return $actions;
    }

    /**
     * This is run when an action is initiated
     * 
     * This can only been called if a page is editable.
     */
    public function exeActions($event) {
        list($page, $action) = $event->arguments;

        $actions = $this->createActions($page);

        // This way checking for roles or other access rules is not duplicated.
        // If the action is still created, it's also actionable.
        if(!isset($actions[$action]) || !$page->editable()) return;

        $success = false;
        $needSave = true; 
        $message = '';
        $remove = false;
        $refreshChildren = 0;

        // If $action is the name of the module
        if(strpos($action, "PageAction") === 0){
            $module = $this->modules->get($action);
            if($module){
                $module->action($page);
                $success = true;
                $message = $this->_("Email sent");
            }
        }

        // If no module was supplied select manually
        if(!$success){
            switch($action){
                case "SomeOtherAction"
                    // Do stuff
                    break;
            }
        }

        // Return if success, otherwise move on to the hook function
        if(!$success) return;
        else $event->replace = true; 

        // Return information
        $event->return = array(
            'action' => $action,
            'success' => true, // Fails are managed later by hooked function
            'message' => $message,
            'updateItem' => $page->id, // id of page to update in output
            'remove' => $remove, 
            'refreshChildren' => $refreshChildren,
            // also available: 'appendItem' => $page->id, which adds a new item below the existing
        );
    }

}
  • Like 4
Link to comment
Share on other sites

  • 1 year later...

Hi @LostKobrakai,

I'm trying to get a custom action to work, based on this code, but I'm having some difficulties... The action label appears in the page list tree, with the right link and action, but when I click it, instead of executing the action, it just displays the single page in tree view. It seems that the action is getting ignored, some how. In the Debug Tools, it seems that the right function is being called:

$this->addHookBefore('ProcessPageListActions::processAction', $this, 'executeActions');

But nothing happens. Any ideas?

Thanks.

Link to comment
Share on other sites

I couldn't get @LostKobrakai's blueprint to work.

I putted together some of @LostKobrakai code with some investigated source code from ProcessWire.

Here's a full working solution:

<?php

class PayActions extends Process {

	public static function getModuleInfo() {
		return array(
			'title' => __('Quick Pay/Unpay Method', __FILE__),
			'summary' => __('Adds Pay/Unpay actions to pages that use reservation template.', __FILE__),
			'version' => 102,
			'singular' => true,
			'autoload' => 'template=admin',
		);
	}

	// MODULE INITIALIZATION
	public function ready() {
		// Default actions can't use AJAX
		// $this->addHookAfter("ProcessPageListActions::getActions", $this, 'hookPageListActions');
		// Use Extra Actions if you need AJAX
		$this->addHookAfter('ProcessPageListActions::getExtraActions', $this, 'hookPageListActions');
		$this->addHookAfter('ProcessPageListActions::processAction', $this, 'hookProcessExtraAction');
	}

	// ADD ACTIONS
	public function hookPageListActions(HookEvent $event) {

		$page = $event->arguments[0];
		$actions = array();

		// Apply only to reservation pages
		if($page->template == 'reservation') {

			if($page->reservation_paid) {
				$actions['unpay'] = array(
					'cn' => 'Unpay',
					'name' => 'Unpay',
					'url' => $this->config->urls->admin . "page/?action=unpay&id={$page->id}",
					'ajax' => true,
				);
			}
			else {
				$actions['pay'] = array(
					'cn' => 'Pay',
					'name' => 'Pay',
					'url' => $this->config->urls->admin . "page/?action=pay&id={$page->id}",
					'ajax' => true,
				);
			}

		}

		if(count($actions)) $event->return = $actions + $event->return;

	}

	// PREPARE ACTION
	public function hookProcessExtraAction(HookEvent $event) {

		$page = $event->arguments(0);
		$action = $event->arguments(1);
		$success = false;

		switch($action) {
			case 'pay':
				$page->setAndSave('reservation_paid', 1);
				$success = true;
				$message = $this->_('Paid');
				break;
			case 'unpay':
				$page->setAndSave('reservation_paid', 0);
				$success = true;
				$message = $this->_('Unpaid');
				break;
			default:
				$message = $this->_('Failed');
		}

		// If this action was successfull return result
		if($success) {

			$result = array(
				'action' => $action,
				'success' => $success,
				'message' => $message,
				'page' => $page->id,
				'updateItem' => $page->id,
				'remove'          => false,
				'refreshChildren' => false,
			);

			$event->return = $result;

		}

	}

}

What this module does:

For each page with the template "reservation" you're able to mark it as "Payed" or "Unpayed" without having to edit it:

print.jpg

 

Edited by Xonox
Clearer result.
  • Like 4
Link to comment
Share on other sites

15 hours ago, LostKobrakai said:

You cannot return things from hooks via the return statement. Instead pass the return value into $event->return like I did it in my example.

With return via $event->return, the module stops working. The used return is inside the Ajax process, not the hook. If we change the return, the module doesn't get an ajax response and doesn't update the page (keeps the ajax spinner forever). However, I'm not that knowledgeable in ProcessWire. Does this make sense?

  • Like 1
Link to comment
Share on other sites

After some testing I came to the conclusion that we don't need the AJAX code. In fact it makes the code incompatible with the other extra actions. At the end of the day, the code becomes even simpler! :) It's been edited to show the final, simpler version. 

  • Like 1
Link to comment
Share on other sites

57 minutes ago, Xonox said:

After some testing I came to the conclusion that we don't need the AJAX code. In fact it makes the code incompatible with the other extra actions. At the end of the day, the code becomes even simpler! :) It's been edited to show the final, simpler version. 

Cool, thanks for sharing!

Could you please also edit the first sentence of the post above the code? Maybe by crossing it out, and explaining the situation right after it. Leaving it like this is misleading when skimming through the posts and not reading every single character.

  • Like 1
Link to comment
Share on other sites

  • 4 weeks later...

@Xonox code works here but I always got these notices (using TracyDebugger):

1×	
PHP Notice: Undefined variable: message in .../Process/ProcessPageList/ProcessPageListActions.php:279
1×	
PHP Notice: Undefined variable: remove in .../Process/ProcessPageList/ProcessPageListActions.php:281
1×	
PHP Notice: Undefined variable: refreshChildren in .../Process/ProcessPageList/ProcessPageListActions.php:282

I couldn't get rid of these.  Here is my code:

Spoiler

            $this->addHookAfter("ProcessPageListActions::getExtraActions", function (HookEvent $event) {

                $page = $event->arguments("page");

                if(!$page->trashable() || !$this->user->isSuperUser()) {
                    return false;
                }
                
                $actions = array();
                $adminUrl = $this->wire('config')->urls->admin . 'page/';
                $deleteIcon = "<i class='fa fa-trash'></i>&nbsp;";

                $actions['delete'] = array(
                    'cn'   => 'Delete aos',
                    'name' => $deleteIcon . 'Wipe',
                    'url'  => $adminUrl . '?action=wipe&id='  .$page->id,
                    'ajax' => true
                );

                $event->return += $actions;
            });

            $this->addHookAfter('ProcessPageListActions::processAction', function(HookEvent $event) {
                
                $page = $event->arguments(0);
                $action = $event->arguments(1);

                if($action == 'wipe') {

                    $page->setAndSave('title', $page->title . '-hello');

                    $event->return = array(
                        'action' => $action,
                        'success' => true,
                        'page' => $page->id,
                        'updateItem' => $page->id,
                        'message' => 'Wiped!',
                        'remove'          => false,
                        'refreshChildren' => false
                    );
                }
            });

 

Link to comment
Share on other sites

9 hours ago, tpr said:

I couldn't get rid of these.

The name (array key) you set in your getExtraActions hook needs to match what is passed as $action in the processAction hook. You're using "delete" in one place and "wipe" in the other.

  • Like 1
Link to comment
Share on other sites

Oh great, thanks! I've spent a lot of time to figure this out but haven't spot that. What a relief! :)

Apparently everything worked fine but there were these PHP notices that showed something's not right.

  • Like 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...