diff --git a/lib/plugins/acl/action.php b/lib/plugins/acl/action.php
new file mode 100644
index 0000000000000000000000000000000000000000..5e186fb618ea2697a5c29db9ab89f1fa298ae7bf
--- /dev/null
+++ b/lib/plugins/acl/action.php
@@ -0,0 +1,88 @@
+<?php
+/**
+ * AJAX call handler for ACL plugin
+ *
+ * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author     Andreas Gohr <andi@splitbrain.org>
+ */
+
+// must be run within Dokuwiki
+if(!defined('DOKU_INC')) die();
+
+/**
+ * Register handler
+ */
+class action_plugin_acl extends DokuWiki_Action_Plugin {
+
+    /**
+     * Registers a callback function for a given event
+     *
+     * @param Doku_Event_Handler $controller DokuWiki's event controller object
+     * @return void
+     */
+    public function register(Doku_Event_Handler &$controller) {
+
+        $controller->register_hook('AJAX_CALL_UNKNOWN', 'BEFORE', $this, 'handle_ajax_call_acl');
+
+    }
+
+    /**
+     * AJAX call handler for ACL plugin
+     *
+     * @param Doku_Event $event  event object by reference
+     * @param mixed $param  empty
+     * @return void
+     */
+
+    public function handle_ajax_call_acl(Doku_Event &$event, $param) {
+        if($event->data !== 'plugin_acl') {
+            return;
+        }
+        $event->stopPropagation();
+        $event->preventDefault();
+
+        global $ID;
+        global $INPUT;
+
+        if(!auth_isadmin()) {
+            echo 'for admins only';
+            return;
+        }
+        if(!checkSecurityToken()) {
+            echo 'CRSF Attack';
+            return;
+        }
+
+        $ID = getID();
+
+        /** @var $acl admin_plugin_acl */
+        $acl = plugin_load('admin', 'acl');
+        $acl->handle();
+
+        $ajax = $INPUT->str('ajax');
+        header('Content-Type: text/html; charset=utf-8');
+
+        if($ajax == 'info') {
+            $acl->_html_info();
+        } elseif($ajax == 'tree') {
+
+            $ns = $INPUT->str('ns');
+            if($ns == '*') {
+                $ns = '';
+            }
+            $ns = cleanID($ns);
+            $lvl = count(explode(':', $ns));
+            $ns = utf8_encodeFN(str_replace(':', '/', $ns));
+
+            $data = $acl->_get_tree($ns, $ns);
+
+            foreach(array_keys($data) as $item) {
+                $data[$item]['level'] = $lvl + 1;
+            }
+            echo html_buildlist(
+                $data, 'acl', array($acl, '_html_list_acl'),
+                array($acl, '_html_li_acl')
+            );
+        }
+    }
+}
diff --git a/lib/plugins/acl/ajax.php b/lib/plugins/acl/ajax.php
deleted file mode 100644
index 10e18af9719bd1dcd0880c1390a771dda87703dc..0000000000000000000000000000000000000000
--- a/lib/plugins/acl/ajax.php
+++ /dev/null
@@ -1,57 +0,0 @@
-<?php
-/**
- * AJAX call handler for ACL plugin
- *
- * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
- * @author     Andreas Gohr <andi@splitbrain.org>
- */
-
-if(!defined('DOKU_INC')) define('DOKU_INC',dirname(__FILE__).'/../../../');
-require_once(DOKU_INC.'inc/init.php');
-//close session
-session_write_close();
-
-global $conf;
-global $ID;
-global $INPUT;
-
-//fix for Opera XMLHttpRequests
-$postData = http_get_raw_post_data();
-if(!count($_POST) && !empty($postData)){
-    parse_str($postData, $_POST);
-}
-
-if(!auth_isadmin()) die('for admins only');
-if(!checkSecurityToken()) die('CRSF Attack');
-
-$ID    = getID();
-
-/** @var $acl admin_plugin_acl */
-$acl = plugin_load('admin','acl');
-$acl->handle();
-
-$ajax = $INPUT->str('ajax');
-header('Content-Type: text/html; charset=utf-8');
-
-if($ajax == 'info'){
-    $acl->_html_info();
-}elseif($ajax == 'tree'){
-
-    $dir = $conf['datadir'];
-    $ns  = $INPUT->str('ns');
-    if($ns == '*'){
-        $ns ='';
-    }
-    $ns  = cleanID($ns);
-    $lvl = count(explode(':',$ns));
-    $ns  = utf8_encodeFN(str_replace(':','/',$ns));
-
-    $data = $acl->_get_tree($ns,$ns);
-
-    foreach(array_keys($data) as $item){
-        $data[$item]['level'] = $lvl+1;
-    }
-    echo html_buildlist($data, 'acl', array($acl, '_html_list_acl'),
-                        array($acl, '_html_li_acl'));
-}
-
diff --git a/lib/plugins/acl/script.js b/lib/plugins/acl/script.js
index 0abb80d671d7ac3afdfcefcd573dd29620d3a692..58598b1e04d382cd395194007d93b3e0e248b875 100644
--- a/lib/plugins/acl/script.js
+++ b/lib/plugins/acl/script.js
@@ -25,9 +25,10 @@ var dw_acl = {
                            var $frm = jQuery('#acl__detail form');
 
                            jQuery.post(
-                               DOKU_BASE + 'lib/plugins/acl/ajax.php',
+                               DOKU_BASE + 'lib/exe/ajax.php',
                                jQuery.extend(dw_acl.parseatt($clicky.parent().find('a')[0].search),
-                                             {ajax: 'tree',
+                                             {call: 'plugin_acl',
+                                              ajax: 'tree',
                                               current_ns: $frm.find('input[name=ns]').val(),
                                               current_id: $frm.find('input[name=id]').val()}),
                                show_sublist,
@@ -64,8 +65,8 @@ var dw_acl = {
             .attr('role', 'alert')
             .html('<img src="'+DOKU_BASE+'lib/images/throbber.gif" alt="..." />')
             .load(
-                DOKU_BASE + 'lib/plugins/acl/ajax.php',
-                jQuery('#acl__detail form').serialize() + '&ajax=info'
+                DOKU_BASE + 'lib/exe/ajax.php',
+                jQuery('#acl__detail form').serialize() + '&call=plugin_acl&ajax=info'
             );
         return false;
     },