Confessions of an apprentice: Custom actions and restrictions on content browsing in eZ Publish

by Tomislav Buljević -


I have a present for you all. In this article we will cover not one, but two topics which will actually be wrapped in a nice little functionality which you can use. So, we will cover the custom actions of eZ Publish, and the restrictions on content browsing.

It’s that time of the year; time for festivities and good will towards people. In that spirit, I have decided to divulge a piece of knowledge for all the young eZ Publish apprentices as a sort of holiday present for all of you.

Everyone who follows this blog and especially this line of articles will wonder: Why now? Why didn’t I write an article about this sooner? Well, the answer is simple: I didn’t have the time. You see, things are buzzing with work here at Netgen and because of that we hardly have any time for anything else but work. But right now, I have some extra time for a new article and I told myself I’d better write it or else. What is or else, you may wonder? Well, I don’t know either, but it’s probably something nasty.

All joking aside, however, in this article we will cover not one, but two topics which will actually be wrapped in a nice little functionality which you can use. I told you I had a present for you all. So, we will cover the custom actions of eZ Publish, and the restrictions on content browsing.

What are custom actions, exactly?

The built-in content module of eZ Publish is great because it handles not only the views on which the whole content system for eZ Publish relies on, but also the actions connected to that content. Using the content actions you can create new nodes and objects, move or edit existing ones, swap nodes, delete existing objects, etc. But sometimes, the existing actions just aren’t enough for you, and that is when you need to rely on custom content actions.

Simply put, a custom action is the extended functionality for the content actions. This means that, if you don’t want to be bothered with creating a new module and views for some minor thing you want to achieve with eZ Publish, you can rely on custom actions to make your life a whole lot easier.

Restrictions on content browsing in eZ Publish

Content browsing in eZ Publish is a powerful tool which is often a bit underrated because it’s so cleverly integrated into the whole system that it pretty much goes unnoticed most of the time. You probably use content browsing on a regular basis, but don’t give it much thought.

For example, you have just created a content class which has an Object Relation attribute in it. When you create a new object of that class, and click on the Add existing objects in the Add objects in the relations section of the Object relation datatype, you actually get redirected to the content browse view which lets you pick and choose one of the objects you want to put into the relation. Additionally, the Object relations datatype even goes a step further and has a restriction for a content class or classes which you can put into the relation. As I said, this is something people rarely ever think about. This system just works and why change it?

But, as with any work you start in eZ Publish, sometimes you just need to create a custom feature which will work for your website only.

How does this all add up, then?

You’re probably wondering at this point how this all adds up. Why did I start writing about content actions and then restrictions on content browsing without even a line of code? Well, as with every other article so far, we are going to include a very specific functionality which will allow you to move a content object of a certain class, but only under a single type content class, and we will create a functionality which will restrict that move to start at a specific node inside the content tree of your website.

This will come in very handy if you have a bunch of users on your site, and for example, they are only allowed to move certain content objects into certain places or inside a certain content tree. It will all be much clearer in a second.

Prepare your ground

Of course, you’re probably eager to see the code for this cool feature. Well, before you do the actual coding part, you need to prepare the turf, as they say. So, in your extension, the mypage extension, go to settings, and then open the content.ini.append.php file. If there isn’t one, create it. In it, just put the following lines of code:


With these two lines of code, you have basically told eZ Publish that you want mypage to be the extension which will add some content actions to the existing ones.

After that, go up one level, and create a folder named actions in your extension. In it, create a PHP file which will be named content_actionhandler.php. This is effectively a look-for-code-here sign for eZ Publish. And then, when you open it up, just put in these few lines of code:


function mypage_ContentActionHandler( $module, $http, $objectID )
    /* insert some code here */


On to the code!

You need to enable your user to move an object. So, first off, go into the administration, choose the group of User you wish to be able to move, and create a content move policy for them. You probably know this by now. Also, open the template for the actual content class for the objects you wish to move. Let’s say it’s the extension/mypage/design/mydesign/override/templates/full/myclass.tpl. Now, insert the following code into the template:

/* Changing the basic move form */
<form method="post" action={"/content/action"|ezurl()} id="move">
<input type="hidden" name="ContentNodeID" value="{$node.node_id}" />
<input type="hidden" name="ContentObjectID" value="{$}" />
<input type="hidden" name="ContentObjectLanguageCode" value="{$node.object.current_language}" />
/* The bottom submit is usually named MoveButton, but for our new action we need a different name. */
<input type="submit" name="MyMoveAction" value="Move" />

Now, open content_actionhandler.php again, and insert the following code inside the function we created earlier:

if( $http->hasPostVariable("MyMoveAction") ) 

if ( $module->isCurrentAction("MyMoveAction") )
    $viewMode = 'full';
    $languageCode = $http->postVariable('ContentObjectLanguageCode');
    $nodeID = $http->postVariable( 'ContentNodeID' );
    $node = eZContentObjectTreeNode::fetch( $nodeID );
    $object = $node->object();
    $objectID = $object->attribute( 'id' );
    $class = $object->contentClass();
    $ignoreNodesSelect = array();
    $ignoreNodesSelectSubtree = array();
    $ignoreNodesClick = array();

    $publishedAssigned = $object->assignedNodes( false );
    foreach ( $publishedAssigned as $element )
        $ignoreNodesSelect[] = $element['node_id'];
        $ignoreNodesSelectSubtree[] = $element['node_id'];
        $ignoreNodesClick[]  = $element['node_id'];
        $ignoreNodesSelect[] = $element['parent_node_id'];

    $ignoreNodesSelect = array_unique( $ignoreNodesSelect );
    $ignoreNodesSelectSubtree = array_unique( $ignoreNodesSelectSubtree );
    $ignoreNodesClick = array_unique( $ignoreNodesClick );
    eZContentBrowse::browse( array( 'action_name' => $module->currentAction(),
                                    'description_template' => 'design:content/browse_move_node.tpl',
                                    'keys' => array( 'class' => $class->attribute( 'id' ),
                                                     'class_id' => $class->attribute( 'identifier' ),
                                                     'classgroup' => $class->attribute( 'ingroup_id_list' ),
                                                     'section' => $object->attribute( 'section_id' ) ),
                                    'ignore_nodes_select' => $ignoreNodesSelect,
                                    'ignore_nodes_select_subtree' => $ignoreNodesSelectSubtree,
                                    'ignore_nodes_click'  => $ignoreNodesClick,
                                    'persistent_data' => array( 'ContentNodeID' => $nodeID,
                                                                'ViewMode' => $viewMode,
                                                                'ContentObjectLanguageCode' => $languageCode,
                                                                'MoveNodeAction' => '1' ),
                                    'permission' => array( 'access' => 'create',
                                                           'contentclass_id' => $class->attribute( 'id' ) ),
                                    'content' => array( 'object_id' => $objectID,
                                                        'object_version' => $object->attribute( 'current_version' ),
                                                        'object_language' => $languageCode ),
                                    'start_node' => $node->attribute( 'parent_node_id' ),
                                    'cancel_page' => $module->redirectionURIForModule( $module, 'view', array( $viewMode, $nodeID, $languageCode ) ),
                                    'from_page' => "/content/action" ),
                             $module );



This is basically the same move code the content action usually uses but with our parameters and a setting of our custom action. The interesting part here is the eZContentBrowse::browse() method, which enables fast access to the content browsing without the need for any custom coding. And in our custom function we have fetched the node for our object and accessed the object, and then defined that the whole node and its subtree will be ignored in the browsing process. That takes care of the custom action part. Now, on to the browse restriction part.

Let’s say that, for example, you need to restrict the class with an ID of 30 to only be moved to a subtree of a different class, let’s call it my_class_tree and for the browsing to start at node 4 (let’s call it my_node). To limit your action in this way, you’ll need the browse.ini file. Which means you need to create a browse.ini.append.php file inside your extension. Type in the following lines in it:



After that, clear all caches. With these, you have just set up the browsing restrictions throughout the site for your custom action.

But, wait, you might say. Why did we need to create a custom action here? Why didn’t we just apply these changes for the regular move action in eZ Publish? The answer is very simple. We needed to create a custom action because every setting in the browse.ini file or a certain action behaviour applies each and every time that particular action is executed. So, if we just input the restrictions for an already existing action on the website, we apply that restriction everywhere. And in this particular case, we didn’t want that. We wanted to just apply the restriction for a certain type of content. And when you try to move your object after that, you’ll see that the browsing engine for eZ Publish starts at our desired node, and displays only the classes we told it to display.

Until next time, I wish you merry Christmas and happy coding in the upcoming year,


This site uses cookies. Some of these cookies are essential, while others help us improve your experience by providing insights into how the site is being used.

For more detailed information on the cookies we use, please check our Privacy Policy.

  • Necessary cookies enable core functionality. The website cannot function properly without these cookies, and can only be disabled by changing your browser preferences.