Prevents warning in similar page search.
[platal.git] / classes / platal.php
index cbd9870..7e4476d 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /***************************************************************************
- *  Copyright (C) 2003-2010 Polytechnique.org                              *
+ *  Copyright (C) 2003-2011 Polytechnique.org                              *
  *  http://opensource.polytechnique.org/                                   *
  *                                                                         *
  *  This program is free software; you can redistribute it and/or modify   *
@@ -54,9 +54,9 @@ abstract class PlHook
         return ($this->type & $type) == $type;
     }
 
-    abstract protected function run(PlPage &$page, array $args);
+    abstract protected function run(PlPage $page, array $args);
 
-    public function call(PlPage &$page, array $args)
+    public function call(PlPage $page, array $args)
     {
         global $globals, $session, $platal;
         if (!$session->checkAuth($this->auth)) {
@@ -92,7 +92,7 @@ class PlStdHook extends PlHook
         $this->callback = $callback;
     }
 
-    protected function run(PlPage &$page, array $args)
+    protected function run(PlPage $page, array $args)
     {
         global $session, $platal;
 
@@ -108,6 +108,86 @@ class PlStdHook extends PlHook
     }
 }
 
+/** A specialized hook for API requests.
+ * It is intended to be used for passive API requests, authenticated either by
+ * an existing session (with a valid XSRF token), or by an alternative single
+ * request auth mechanism implemented by PlSession::apiAuth.
+ *
+ * This hook is suitable for read-write requests against the website, provided
+ * $auth is set appropriately. Note that the auth level is only checked for
+ * session-authenticated users, as "apiAuth" users are assumed to always have
+ * the requested level (use another hook otherwise).
+ *
+ * The callback will be passed as arguments the PlPage, the authenticated
+ * PlUser, the JSON decoded payload, and the remaining path components, as with
+ * any other hook.
+ *
+ * If the callback intends to JSON-encode its returned value, it is advised to
+ * use PlPage::jsonAssign, and return PL_JSON to enable automatic encoding.
+ */
+class PlApiHook extends PlHook
+{
+    private $actualAuth;
+    private $callback;
+
+    public function __construct($callback, $auth = AUTH_PUBLIC, $perms = 'user', $type = NO_AUTH)
+    {
+        // As mentioned above, $auth is only applied for session-based auth
+        // (as opposed to token-based). PlHook is initialized to AUTH_PUBLIC to
+        // avoid it refusing to approve requests; this is important as the user
+        // is not yet authenticated at that point (see below for the actual
+        // permissions check).
+        parent::__construct(AUTH_PUBLIC, $perms, $type);
+        $this->actualAuth = $auth;
+        $this->callback = $callback;
+    }
+
+    private function getEncodedPayload($method)
+    {
+        return $method == "GET" ? "" : file_get_contents("php://input");
+    }
+
+    private function decodePayload($encodedPayload)
+    {
+        return empty($encodedPayload) ? array() : json_decode($encodedPayload, true);
+    }
+
+    protected function run(PlPage $page, array $args)
+    {
+        $method = $_SERVER['REQUEST_METHOD'];
+        $encodedPayload = $this->getEncodedPayload($method);
+        $jsonPayload = $this->decodePayload($encodedPayload);
+        $resource = '/' . implode('/', $args);
+
+        // If the payload wasn't a valid JSON encoded object, bail out early.
+        if (is_null($jsonPayload)) {
+            $page->trigError("Could not decode the JSON-encoded payload sent with the request.");
+            return PL_BAD_REQUEST;
+        }
+
+        // Authenticate the request. Try first with the existing session (which
+        // is less expensive to check), by veryfing that the XSRF token is
+        // valid; otherwise fallbacks to API-type authentication from PlSession.
+        if (S::logged() && S::has_xsrf_token() && Platal::session()->checkAuth($this->actualAuth)) {
+            $user = S::user();
+        } else {
+            $user = Platal::session()->apiAuth($method, $resource, $encodedPayload);
+        }
+
+        // Check the permissions, unless the handler is fully public.
+        if ($this->actualAuth > AUTH_PUBLIC) {
+            if (is_null($user) || !$user->checkPerms($this->perms)) {
+                return PL_FORBIDDEN;
+            }
+        }
+
+        // Invoke the callback, whose signature is (PlPage, PlUser, jsonPayload).
+        array_shift($args);
+        array_unshift($args, $page, $user, $jsonPayload);
+        return call_user_func_array($this->callback, $args);
+    }
+}
+
 /** A specialized hook for token-based requests.
  * It is intended for purely passive requests (typically for serving CSV or RSS
  * content outside the browser), and can fallback to regular session-based
@@ -129,17 +209,13 @@ class PlTokenHook extends PlHook
 
     public function __construct($callback, $auth = AUTH_PUBLIC, $perms = 'user', $type = NO_AUTH)
     {
-        // As mentioned above, $auth is only applied for session-based auth
-        // (as opposed to token-based). PlHook is initialized to AUTH_PUBLIC to
-        // avoid it refusing to approve requests; this is important as the user
-        // is not yet authenticated at that point (see below for the actual
-        // permissions check).
+        // See PlApiHook::__construct.
         parent::__construct(AUTH_PUBLIC, $perms, $type);
         $this->actualAuth = $auth;
         $this->callback = $callback;
     }
 
-    protected function run(PlPage &$page, array $args)
+    protected function run(PlPage $page, array $args)
     {
         // Retrieve the user, either from the session (less expensive, as it is
         // already there), or from the in-path (user, token) pair.
@@ -174,7 +250,7 @@ class PlWikiHook extends PlHook
         parent::__construct($auth, $perms, $type);
     }
 
-    protected function run(PlPage &$page, array $args)
+    protected function run(PlPage $page, array $args)
     {
         return PL_WIKI;
     }
@@ -186,51 +262,51 @@ class PlHookTree
     public $aliased  = null;
     public $children = array();
 
-    public function addChild(array $path, PlHook $hook)
+    public function addChildren(array $hooks)
     {
         global $platal;
-        $next = array_shift($path);
-        $alias = null;
-        if ($next && $next{0} == '%') {
-            $alias = $next;
-            $next = $platal->hook_map(substr($next, 1));
-        }
-        if (!$next) {
-            return;
-        }
-        @$child =& $this->children[$next];
-        if (!$child) {
-            $child = new PlHookTree();
-            $this->children[$next] =& $child;
-            $child->aliased = $alias;
-        }
-        if (empty($path)) {
-            $child->hook = $hook;
-        } else {
-            $child->addChild($path, $hook);
-        }
-    }
-
-    private function findChildAux(array $remain, array $matched, array $aliased)
-    {
-        $next = @$remain[0];
-        if ($this->aliased) {
-            $aliased = $matched;
-        }
-        if (!empty($next)) {
-            $child = @$this->children[$next];
-            if ($child) {
-                array_shift($remain);
-                $matched[] = $next;
-                return $child->findChildAux($remain, $matched, $aliased);
+        foreach ($hooks as $path=>$hook) {
+            $path = explode('/', $path);
+            $element  = $this;
+            foreach ($path as $next) {
+                $alias = null;
+                if ($next{0} == '%') {
+                    $alias = $next;
+                    $next = $platal->hook_map(substr($next, 1));
+                }
+                if (!isset($element->children[$next])) {
+                    $child = new PlHookTree();
+                    $child->aliased = $alias;
+                    $element->children[$next] = $child;
+                } else {
+                    $child = $element->children[$next];
+                }
+                $element = $child;
             }
+            $element->hook = $hook;
         }
-        return array($this->hook, $matched, $remain, $aliased);
     }
 
     public function findChild(array $path)
     {
-        return $this->findChildAux($path, array(), array());
+        $remain  = $path;
+        $matched = array();
+        $aliased = array();
+        $element = $this;
+        while (true)
+        {
+            $next = @$remain[0];
+            if ($element->aliased) {
+                $aliased = $matched;
+            }
+            if (empty($next) || !isset($element->children[$next])) {
+                break;
+            }
+            $element = $element->children[$next];
+            array_shift($remain);
+            $matched[] = $next;
+        }
+        return array($element->hook, $matched, $remain, $aliased);
     }
 
     private function findNearestChildAux(array $remain, array $matched, array $aliased)
@@ -246,16 +322,18 @@ class PlHookTree
                 $nearest_sdx = 50;
                 $match = null;
                 foreach ($this->children as $path=>$hook) {
-                    $lev = levenshtein($next, $path);
-                    if ($lev <= $nearest_lev
-                        && ($lev < strlen($next) / 2 || strpos($next, $path) !== false
-                            || strpos($path, $next) !== false)) {
-                        $sdx = levenshtein(soundex($next), soundex($path));
-                        if ($lev == $nearest_lev || $sdx < $nearest_sdx) {
-                            $child = $hook;
-                            $nearest_lev = $lev;
-                            $nearest_sdx = $sdx;
-                            $match = $path;
+                    if ($path) {
+                        $lev = levenshtein($next, $path);
+                        if ($lev <= $nearest_lev
+                            && ($lev < strlen($next) / 2 || strpos($next, $path) !== false
+                                || strpos($path, $next) !== false)) {
+                            $sdx = levenshtein(soundex($next), soundex($path));
+                            if ($lev == $nearest_lev || $sdx < $nearest_sdx) {
+                                $child = $hook;
+                                $nearest_lev = $lev;
+                                $nearest_sdx = $sdx;
+                                $match = $path;
+                            }
                         }
                     }
                 }
@@ -325,10 +403,7 @@ abstract class Platal
         foreach ($modules as $module) {
             $module = strtolower($module);
             $this->mods[$module] = $m = PLModule::factory($module);
-            $hooks = $m->handlers();
-            foreach ($hooks as $path=>$hook) {
-                $this->hooks->addChild(explode('/', $path), $hook);
-            }
+            $this->hooks->addChildren($m->handlers());
         }
 
         if ($globals->mode == '') {
@@ -394,7 +469,7 @@ abstract class Platal
         return $url;
     }
 
-    private function call_hook(PlPage &$page)
+    private function call_hook(PlPage $page)
     {
         $hook = $this->find_hook();
         if (empty($hook)) {
@@ -410,7 +485,7 @@ abstract class Platal
 
     /** Show the authentication form.
      */
-    abstract public function force_login(PlPage& $page);
+    abstract public function force_login(PlPage $page);
 
     public function run()
     {