* @license BSD */ require_once ("domframework/route.php"); require_once ("domframework/form.php"); require_once ("domframework/renderer.php"); /** Automatic Routing for SQL database * Allow to do CRUD on data with only one line in index.php */ class routeSQL { /** Activate the debug */ public $debug=0; /** Display the Actions column in list of entries */ public $displayActions = true; /** Do a confirmation in javascript before deleting entry */ public $deleteConfirm = true; /** Push the actions buttons at end of line */ public $actionsAtEnd = false; /** The Text to Delete */ public $textDelete = ""; /** The Text to Edit */ public $textEdit = ""; /** enable internal CSS */ public $enableInternalCSS = true; /** Definition of the position in top bar at left Allowed : addNew numberEntryByDisplay search information paginator */ public $topBarLeft = array ("addNew", "numberEntryByDisplay"); /** Definition of the position in top bar at right Allowed : addNew numberEntryByDisplay search information paginator */ public $topBarRight = array ("search"); /** Definition of the position in bottom bar at left Allowed : addNew numberEntryByDisplay search information paginator */ public $bottomBarLeft = array ("information"); /** Definition of the position in bottom bar at right Allowed : addNew numberEntryByDisplay search information paginator */ public $bottomBarRight = array ("paginator"); /** The cookie path used to determine the old parameters It is automatically generated with the URL */ public $path = ""; /** Authentication for HTML part */ public $authHTML = array ("email"=>"anonymous"); /** Authentication for REST part */ public $authREST = array ("email"=>"anonymous"); /** Authorization object. Should allow a method named "allow ($module, $user, $object)" which return - NO if the object is not defined - RO if the object is in read-only mode - RW if the object is in read-write mode */ public $authorization = null; /** Module name for authorization */ public $module = null; /** Chain multiple routeSQL. Wait for an routeSQL object */ public $chained = null; /** Chain multiple routeSQL. Wait for the foreign key */ public $chainedForeign = null; /** Allow one or multiple links by entry in the list The array must be of the form array ("linkname"=>, "icon"=>) icon is optional and the linkname is used if it is not provided */ public $internalLinks = array (); /** The renderer class to use for HTML pages */ public $rendererHTMLclass = false; /** The renderer method to use for HTML pages */ public $rendererHTMLmethod = false; /** The layout HTML to use for HTML pages */ public $rendererHTMLlayout = false; /** The extensions allowed in REST */ public $extensionsAllowed = array ("json", "xml"); /** The model file containing the database description */ private $model_file = ""; /** The model class included in the model file */ private $model_class = ""; /** The prefix to be used in the URL. Should be the end of $model_file Ex : if $model_file = models/model_zone.php, the url_prefix should be zone */ private $url_prefix = ""; /** The SQL object created */ private $objectDB = null; /** The DSN to connect to the database */ private $dsn = null; /** The Username to connect to the database */ private $username = null; /** The Password to connect to the database */ private $password = null; /** The Options to the PDO driver if needed */ private $driver_options = null; /** The Datas are protected in read-only */ private $readwriteAllowed = true; /** Connect to the database * @param string $model_file The model file containing the database * description * @param string $model_class The model class included in the model file * @param string $url_prefix The prefix to be used in the URL. Should be the * end of $model_file * @param string $dsn The DSN to connect to the database * @param string $username The username to connect to the database * @param string $password The password to connect to the database * @param array|null $driver_options The PDO driver options */ public function __construct ($model_file, $model_class, $url_prefix, $dsn, $username = null, $password = null, $driver_options = null) { $this->model_file = $model_file; $this->model_class = $model_class; $this->url_prefix = $url_prefix; $this->dsn = $dsn; $this->username = $username; $this->password = $password; $this->driver_options = $driver_options; $this->textDelete = "❌"; $this->textEdit = "✎"; $pos = strpos ($_SERVER["REQUEST_URI"], "?"); if ($pos === false) $this->path = $_SERVER["REQUEST_URI"]; else $this->path = substr ($_SERVER["REQUEST_URI"], 0, $pos); } /** Connect to the database */ private function connect () { include "models/$this->model_file"; $this->objectDB = new $this->model_class ($this->dsn, $this->username, $this->password, $this->driver_options); } /** Display the flash information if no flash view is available */ private function showflash () { $dataflash = ""; if (file_exists ("views/flash.php")) require ("views/flash.php"); else { if (isset ($_SESSION["renderer"]["flash"])) { foreach ($_SESSION["renderer"]["flash"] as $flash) { $dataflash .= "
\n"; $dataflash .= "$alert ".$flash[1]."\n"; $dataflash .= "
\n"; } unset ($_SESSION["renderer"]["flash"]); } } return $dataflash; } /** Display a paginator * $nbentries is the total number of elements * num is the number of elements displayed by page * page is the page to display * @param integer $nbentries The number of entries to display * @param integer $page The page number to display * @param integer $num ??? * @param string $search The search query */ private function paginatorArea ($nbentries, $page, $num, $search) { // The maximum of links available in the paginator $maxClickPaginator = 10; $route = new route (); $prePage = false; $postPage = false; $content = "
\n"; $content .= "\n"; $content .= "
\n"; return $content; } /** Display the actions buttons outside of the table (actually, juste the * 'Add new entry' button * @param integer $nbentries The number of entries * @param integer $page The page number * @param integer $num ??? * @param string $search The search query */ private function addNewArea ($nbentries, $page, $num, $search) { $content = ""; if ($this->displayActions && $this->readwriteAllowed) { $route = new route (); $content .= "
\n"; $content .= " " .dgettext ("domframework", "Add new entry")."\n"; $content .= "
\n"; } return $content; } /** Display the select list to choose the number of displayed entries * @param integer $nbentries The number of entries * @param integer $page The page number * @param integer $num ??? * @param string $search The search query */ private function numberEntryByDisplayArea ($nbentries, $page, $num, $search) { $route = new route (); $content = ""; $content .= "
\n"; $content .= "
\n"; $content .= " \n"; $content .= "
\n"; $content .= "
\n"; return $content; } /** Display the information * @param integer $nbentries The number of entries * @param integer $page The page number * @param integer $num ??? * @param string $search The search query */ private function informationArea ($nbentries, $page, $num, $search) { $content = ""; $content .= "
\n"; $message = dgettext ("domframework", "Display the element {FIRST} to {LAST} on {COUNT} elements"); if ($nbentries === 0) $message = str_replace ("{FIRST}", 0, $message); else $message = str_replace ("{FIRST}", 1+($num*($page-1)), $message); if ($nbentries < ($num*$page)) $message = str_replace ("{LAST}", $nbentries, $message); else $message = str_replace ("{LAST}", ($num*$page), $message); $message = str_replace ("{COUNT}", $nbentries, $message); $content .= $message; $content .= "
\n"; return $content; } /** Create the routes for REST pages and the associated actions */ public function routesREST () { $route = new route (); $route->debug = $this->debug;; $route->allowSlashes=false; $route->get ("rest/".$this->url_prefix."(\.{extension})?". "(\?({p1}=({v1})?)(&{p2}=({v2})?(&{p3}=({v3})?)?)?)?", function ($extension, $p1, $v1, $p2, $v2, $p3, $v3, $chain=null) { if ($this->chained !== null) { if ($this->chained->accessright ($this->authREST["email"], $chain) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedValues are the information associated to the $chain $chainedValues = $this->chained->keyexists ($chain); if ($chainedValues === false) throw new \Exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authREST["email"]) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($p1 === "search") $search = $v1; if ($p2 === "search") $search = $v2; if ($p3 === "search") $search = $v3; if (!isset ($search) || $search === null || $search === "") $search = ""; if (!isset ($extension) || $extension === null || $extension === "") $extension = reset ($this->extensionsAllowed); if (!in_array ($extension, $this->extensionsAllowed)) throw new \Exception (dgettext ("domframework", "Extension not allowed"), 403); $search = rawurldecode ($search); $this->connect(); $titles = $this->objectDB->titles (); unset ($titles[$this->chainedForeign]); $foreignSelect = null; if ($this->chained !== null) $foreignSelect = array (array ($this->chainedForeign, $chain)); if ($search === "") $data = $this->objectDB->read (null, array_keys($titles), null, null, $foreignSelect); else { $criteria = array (); foreach (array_keys($titles) as $column) { $s = $search; if ($search[0] === "^") $s = substr ($s, 1); else $s = "%$s"; if (substr ($search, -1) === "$") $s = substr ($s, 0, -1); else $s = "$s%"; $criteria[] = array ($column, "$s", "LIKE"); } $data = $this->objectDB->read ($criteria, array_keys ($titles), null, true, $foreignSelect); } // Limiting access to data only to data with read access right foreach ($data as $key=>$vals) { if ($this->accessright ($this->authHTML["email"], $vals[$this->objectDB->primary]) !== TRUE) unset ($data[$key]); } $this->renderrest ($extension, $data); }); $route->post ("rest/".$this->url_prefix."(\.{extension})?", function ($extension, $chain=null) { if ($this->chained !== null) { if ($this->chained->editright ($this->authREST["email"], $chain) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedvalues are the information associated to the $chain $chainedvalues = $this->chained->keyexists ($chain); if ($chainedvalues === false) throw new exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authREST["email"]) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->editright ($this->authREST["email"]) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->readonly ($this->authREST["email"]) === TRUE) throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); if (!isset ($extension) || $extension === null || $extension === "") $extension = reset ($this->extensionsAllowed); if (!in_array ($extension, $this->extensionsAllowed)) throw new \Exception (dgettext ("domframework", "Extension not allowed"), 403); $this->connect(); $values = $_POST; $errorsChain = array (); if ($this->chainedForeign !== null && isset ($values[$this->chainedForeign]) && $values[$this->chainedForeign] !== $chain) $errorsChain[$this->chainedForeign] = array ("error", dgettext ("domframework", "Can not change the external key")); if ($this->chainedForeign !== null) $values[$this->chainedForeign] = $chain; $errors = $this->objectDB->verify ($values); if (count ($errors) > 0 || count ($errorsChain) > 0) $this->renderrest ($extension, array_merge ($errors, $errorsChain), 400); try { $this->objectDB->insert ($values); $this->renderrest ($extension, "OK", 200); } catch (\Exception $e) { $this->renderrest ($extension, $e->getMessage(), 400); } }); $route->put ("rest/".$this->url_prefix."(\.{extension})?/{id}", function ($extension, $id, $chain=null) { if ($this->chained !== null) { if ($this->chained->editright ($this->authREST["email"], $chain) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedvalues are the information associated to the $chain $chainedvalues = $this->chained->keyexists ($chain); if ($chainedvalues === false) throw new exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authREST["email"]) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->editright ($this->authREST["email"]) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->readonly ($this->authREST["email"]) === TRUE) throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); if (!isset ($extension) || $extension === null || $extension === "") $extension = reset ($this->extensionsAllowed); if (!in_array ($extension, $this->extensionsAllowed)) throw new \Exception (dgettext ("domframework", "Extension not allowed"), 403); $this->connect(); parse_str (file_get_contents ("php://input"), $values); $errorsChain = array (); if ($this->chainedForeign !== null && isset ($values[$this->chainedForeign]) && $values[$this->chainedForeign] !== $chain) $errorsChain[$this->chainedForeign] = array ("error", dgettext ("domframework", "Can not change the external key")); $errors = $this->objectDB->verify ($values, $id); if (count ($errors) > 0 || count ($errorsChain) > 0) $this->renderrest ($extension, array_merge ($errors, $errorsChain), 400); try { $this->objectDB->update ($id, $values); $this->renderrest ($extension, "OK", 200); } catch (\Exception $e) { $this->renderrest ($extension, $e->getMessage(), 400); } }); $route->delete ("rest/".$this->url_prefix."(\.{extension})?/{id}", function ($extension, $id, $chain=null) { if ($this->chained !== null) { if ($this->chained->editright ($this->authREST["email"], $chain) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedValues are the information associated to the $chain $chainedValues = $this->chained->keyexists ($chain); if ($chainedValues === false) throw new \Exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authREST["email"]) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->editright ($this->authREST["email"]) !== TRUE) { if ($this->authREST["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->readonly ($this->authREST["email"]) === TRUE) throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); if (!isset ($extension) || $extension === null || $extension === "") $extension = reset ($this->extensionsAllowed); if (!in_array ($extension, $this->extensionsAllowed)) throw new \Exception (dgettext ("domframework", "Extension not allowed"), 403); $this->connect(); try { $this->objectDB->delete ($id); $this->renderrest ($extension, "OK", 200); } catch (\Exception $e) { $this->renderrest ($extension, $e->getMessage(), 400); } }); } /** Create the routes for HTML pages and the associated actions */ public function routesHTML () { // If chained routeSQL, the url_prefix must be adapted if ($this->chained !== null) { if (strpos ($this->chained->url_prefix, "/{chain}/") !== false) throw new \Exception (dgettext ("domframework", "Chained can not have an already chained object"), 500); $this->url_prefix = $this->chained->url_prefix."/{chain}/". $this->url_prefix; } /** Add HTML routes */ $route = new route (); $route->debug = $this->debug;; $route->allowSlashes=false; $route->get ($this->url_prefix."/", function ($chain=null) use ($route) { $route->redirect ("/".str_replace ("{chain}", $chain, $this->url_prefix), ""); }); $route->get ($this->url_prefix. "(\?({p1}=({v1})?)(&{p2}=({v2})?(&{p3}=({v3})?)?)?)?", function ($p1, $v1, $p2, $v2, $p3, $v3, $chain=null) use ($route) { // List all the objects of the table if ($this->chained !== null) { if ($this->chained->accessright ($this->authHTML["email"], $chain) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedValues are the information associated to the $chain $chainedValues = $this->chained->keyexists ($chain); if ($chainedValues === false) throw new \Exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authHTML["email"]) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->chained !== null && $this->chained->editright ($this->authHTML["email"], $chain) !== true) $this->readwriteAllowed = false; $this->url_prefix = str_replace ("{chain}", $chain, $this->url_prefix); $cookiePrefix = rawurlencode ($this->url_prefix); if (isset ($_COOKIE["$cookiePrefix-num"])) $num = $_COOKIE["$cookiePrefix-num"]; if (isset ($_COOKIE["$cookiePrefix-page"])) $page = $_COOKIE["$cookiePrefix-page"]; if (isset ($_COOKIE["$cookiePrefix-search"])) $search = $_COOKIE["$cookiePrefix-search"]; // num is the number of elements displayed by page // page is the page to display // Allow the parameters to be sent in any order if ($p1 === "num") $num = $v1; if ($p2 === "num") $num = $v2; if ($p3 === "num") $num = $v3; if ($p1 === "page") $page = $v1; if ($p2 === "page") $page = $v2; if ($p3 === "page") $page = $v3; if ($p1 === "search") $search = $v1; if ($p2 === "search") $search = $v2; if ($p3 === "search") $search = $v3; if (!isset ($num) || $num === null || $num === "") $num = 10; if (!isset ($page) || $page === null || $page === "") $page = 1; if (!isset ($search) || $search === null || $search === "") $search = ""; $page = intval ($page); $num = intval ($num); $search = rawurldecode ($search); setcookie ("$cookiePrefix-page", $page, time()+3600*24*30, $this->path); setcookie ("$cookiePrefix-num", $num, time()+3600*24*30, $this->path); setcookie ("$cookiePrefix-search", $search, time()+3600*24*30, $this->path); //echo "PAGE=$page\n"; //echo "NUM=$num\n"; //echo "SEARCH=$search\n"; //$route->debug=$this->debug; $this->connect(); $csrf = new csrf (); $token = $csrf->createToken (); $titles = $this->objectDB->titles (); unset ($titles[$this->chainedForeign]); $foreignSelect = null; if ($this->chained !== null) $foreignSelect = array (array ($this->chainedForeign, $chain)); if ($search === "") $data = $this->objectDB->read (null, array_keys($titles), null, null, $foreignSelect); else { $criteria = array (); foreach (array_keys($titles) as $column) { $s = $search; if ($search[0] === "^") $s = substr ($s, 1); else $s = "%$s"; if (substr ($search, -1) === "$") $s = substr ($s, 0, -1); else $s = "$s%"; $criteria[] = array ($column, "$s", "LIKE"); } $data = $this->objectDB->read ($criteria, array_keys ($titles), null, true, $foreignSelect); } // Limiting access to data only to data with read access right foreach ($data as $key=>$vals) { if ($this->accessright ($this->authHTML["email"], $vals[$this->objectDB->primary]) !== TRUE) unset ($data[$key]); } // Get the non mandatory foreign keys and display them instead of the // unusable id $foreignData = array (); if (isset($this->objectDB->foreign)) { foreach ($this->objectDB->foreign as $foreign=>$params) { if (! isset ($params[0])) throw new \Exception ("Undefined foreign key", 500); if (! isset ($params[1])) throw new \Exception ("Undefined foreign key column", 500); $class = $params[0]; $column = $params[1]; require_once ("models/model_$class.php"); $foreignObject = new $class ($this->dsn, $this->username, $this->password, $this->driver_options); if (isset ($foreignObject->unique)) { $tmpData = $foreignObject->read (null, array_merge (array ($column), $foreignObject->unique)); $associated = array (); $unique = reset ($foreignObject->unique); foreach ($tmpData as $vals) { // TODO : If $foreignObject->unique is not an array ? // or multidimensionnal array $associated[$vals[$column]] = $vals[$unique]; } // Add the unique text to the identifier foreach ($data as $line=>$vals) { foreach ($vals as $col=>$val) { if ($col === $foreign && isset ($associated[$val])) { $data[$line][$col] = $associated[$val]; } } } } } } $nbentries = count ($data); if ($num > 1000) $route->redirect ("/".str_replace ("{chain}", $chain, $this->url_prefix) ."?page=$page&num=1000&search=$search", ""); if ($page < 1) $route->redirect ("/".str_replace ("{chain}", $chain, $this->url_prefix) . "?page=1&num=$num&search=$search", ""); // Push on the last page if the values are too high if ($nbentries > 0 && ($page-1)*$num > $nbentries) { $maxPage = intval ($nbentries/$num)+1; $route->redirect ("/".str_replace("{chain}", $chain, $this->url_prefix). "?page=$maxPage&num=$num&search=$search", ""); } $content = ""; // Internal CSS if ($this->enableInternalCSS === true) { $content .= "\n"; } $content .= "
\n"; $content .= $this->showflash (); $content .= "
\n"; $content .= "
\n"; foreach ($this->topBarLeft as $area) { $areaName = $area."Area"; $content .= $this->$areaName ($nbentries, $page, $num, $search); } $content .= "
\n"; $content .= "
\n"; foreach ($this->topBarRight as $area) { $areaName = $area."Area"; $content .= $this->$areaName ($nbentries, $page, $num, $search); } $content .= "
\n"; $content .= "
\n"; // End of topBar $content .= " \n"; $content .= " \n"; $content .= " \n"; if ($this->readwriteAllowed && $this->displayActions && $this->actionsAtEnd === false) $content .= " \n"; foreach ($titles as $title) $content .= " \n"; if ($this->readwriteAllowed && $this->displayActions && $this->actionsAtEnd !== false) $content .= " \n"; $content .= " \n"; $content .= " \n"; $content .= " \n"; if ($nbentries === 0) { // Add one column more for actions $countTitles = count($titles); if ($this->readwriteAllowed && $this->displayActions) $countTitles++; $content .= " \n"; } else { $i = 1; $odd = "odd"; foreach ($data as $line) { if ($i <= (($page-1)*$num) || $i > (($page-1)*$num + $num)) { $i++; continue; } $content .= " "; if ($odd === "odd") $odd = "even"; else $odd = "odd"; if ($this->actionsAtEnd !== false) { foreach ($line as $col) $content .= ""; } if ($this->readwriteAllowed && $this->displayActions) { $content .= ""; } if ($this->actionsAtEnd === false) { foreach ($line as $col) $content .= ""; } $content .= "\n"; $i++; } } $content .= " \n"; $content .= "
".dgettext ("domframework", "Actions"). "".htmlentities ($title)."".dgettext ("domframework", "Actions"). "
"; $content .= dgettext ("domframework", "No entry available"); $content .= "
".htmlentities ($col).""; $content .= " ". $this->textEdit.""; $content .= " deleteConfirm) $content .= " onclick=\"return confirm('". dgettext ("domframework", "Are you sure to delete this entry?")."')\""; $content .= " class='delete'>".$this->textDelete.""; foreach ($this->internalLinks as $linkData) { if (! isset ($linkData["linkname"])) throw new \Exception ("No linkname defined !", 500); $content .= " "; if (isset ($linkData["icon"])) $content .= $linkData["icon"]; else $content .= $linkData["linkname"]; $content .= ""; } $content .= "".htmlentities ($col)."
\n"; $content .= "
\n"; $content .= "
\n"; foreach ($this->bottomBarLeft as $area) { $areaName = $area."Area"; $content .= $this->$areaName ($nbentries, $page, $num, $search); } $content .= "
\n"; $content .= "
\n"; foreach ($this->bottomBarRight as $area) { $areaName = $area."Area"; $content .= $this->$areaName ($nbentries, $page, $num, $search); } $content .= "
\n"; $content .= "
\n"; // End of bottomBar $content .= "
\n"; $this->rendererhtml ($content); }); $route->get ($this->url_prefix."/{id}/delete/{token}", function ($id, $token, $chain=null) { // Delete an existing object if the token is valid if ($this->chained !== null) { if ($this->chained->editright ($this->authHTML["email"], $chain) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedValues are the information associated to the $chain $chainedValues = $this->chained->keyexists ($chain); if ($chainedValues === false) throw new \Exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authHTML["email"], $id) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->editright ($this->authHTML["email"], $id) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->readonly ($this->authHTML["email"], $id) === TRUE) throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); $this->connect(); $csrf = new csrf (); $renderer = new renderer (); $route = new route (); try { $csrf->checkToken ($token); $this->objectDB->delete ($id); $route->redirect ("/". str_replace ("{chain}", $chain, $this->url_prefix), ""); } catch (\Exception $e) { $renderer->flash ("ERROR", $e->getMessage()); $route->redirect ("/". str_replace ("{chain}", $chain, $this->url_prefix), ""); } }); $route->get ($this->url_prefix."/add", function ($chain=null) { // Add a new entry : form to be filled by the user if ($this->chained !== null) { if ($this->chained->editright ($this->authHTML["email"], $chain) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedValues are the information associated to the $chain $chainedValues = $this->chained->keyexists ($chain); if ($chainedValues === false) throw new \Exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authHTML["email"]) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->editright ($this->authHTML["email"]) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->readonly ($this->authHTML["email"]) === TRUE) throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); $this->connect(); // Get the non mandatory foreign keys and display them instead of the // unusable id $foreignData = array (); if (isset($this->objectDB->foreign)) { foreach ($this->objectDB->foreign as $foreign=>$params) { if (! isset ($params[0])) throw new \Exception ("Undefined foreign key", 500); if (! isset ($params[1])) throw new \Exception ("Undefined foreign key column", 500); $class = $params[0]; $column = $params[1]; require_once ("models/model_$class.php"); $foreignObject = new $class ($this->dsn, $this->username, $this->password, $this->driver_options); if (isset ($foreignObject->unique)) { $tmpData = $foreignObject->read (null, array_merge (array ($column), $foreignObject->unique)); $associated = array (); $unique = reset ($foreignObject->unique); foreach ($tmpData as $vals) { // TODO : If $foreignObject->unique is not an array ? // or multidimensionnal array $associated[$vals[$column]] = $vals[$unique]; } $foreignData[$foreign] = $associated; } } } $content = $this->showflash (); $values = array (); $errors = array(); $titles = $this->objectDB->titles (); if (isset ($_SESSION["domframework"]["routeSQL"]["errors"])) { $errors = $_SESSION["domframework"]["routeSQL"]["errors"]; unset ($_SESSION["domframework"]["routeSQL"]["errors"]); } if (isset ($_SESSION["domframework"]["routeSQL"]["values"])) { $values = $_SESSION["domframework"]["routeSQL"]["values"]; unset ($_SESSION["domframework"]["routeSQL"]["values"]); } if ($this->enableInternalCSS === true) { // CSS is in edit an id too ! $content .= "\n"; // CSS is in edit an id too ! } $f = new form (); $fields = array (); foreach ($titles as $key=>$val) { $field = new formfield ($key, $val); if (! isset ($this->objectDB->fields[$key])) throw new \Exception (sprintf (dgettext ("domframework", "Field '%s' (defined in titles) not found in fields"), $key), 500); if (in_array ("not null", $this->objectDB->fields[$key])) $field->mandatory = true; if (in_array ("autoincrement", $this->objectDB->fields[$key])) $field->type = "hidden"; if ($key === $this->chainedForeign) { $field->defaults = $chain; $field->readonly = true; } if (isset ($foreignData[$key])) { // Add a select list to the foreign keys : the user can't add a value // that doesn't exists $field->type = "select"; $field->defaults = $foreignData[$key]; } $fields[] = $field; unset ($field); } $field = new formfield ("submit", dgettext ("domframework", "Save the data")); $field->defaults = dgettext ("domframework", "Save the data"); $field->type = "submit"; $fields[] = $field; unset ($field); $f->fields ($fields); $content .= $f->printHTML ("post", $values, $errors); $this->rendererhtml ($content); }); $route->post ($this->url_prefix."/add", function ($chain=null) use ($route) { // Add a new entry : effective save of the data if ($this->chained !== null) { if ($this->chained->editright ($this->authHTML["email"], $chain) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedvalues are the information associated to the $chain $chainedvalues = $this->chained->keyexists ($chain); if ($chainedvalues === false) throw new exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authHTML["email"]) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->editright ($this->authHTML["email"]) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->readonly ($this->authHTML["email"]) === TRUE) throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); $this->connect(); $f = new form (); $values = $f->values (); $errorsChain = array (); if ($this->chainedForeign !== null && isset ($values[$this->chainedForeign]) && $values[$this->chainedForeign] !== $chain) $errorsChain[$this->chainedForeign] = array ("error", dgettext ("domframework", "Can not change the external key")); $errors = $this->objectDB->verify ($values); if (count ($errors) == 0 && count ($errorsChain) == 0) { try { $this->objectDB->insert ($values); $renderer = new renderer (); $renderer->flash ("SUCCESS", dgettext ("domframework", "Creation done")); $route->redirect ("/". str_replace ("{chain}", $chain, $this->url_prefix), ""); } catch (\Exception $e) { $renderer = new renderer (); $renderer->flash ("ERROR", $e->getMessage ()); } } else { $renderer = new renderer (); foreach ($errorsChain as $error) $renderer->flash (strtoupper ($error[0]), $error[1]); } // If errors : save them and redirect to the page of editing to be // corrected $_SESSION["domframework"]["routeSQL"]["errors"] = $errors; $_SESSION["domframework"]["routeSQL"]["values"] = $values; $route->redirect ("/".str_replace("{chain}", $chain, $this->url_prefix). "/add", ""); }); $route->get ($this->url_prefix."/{id}", function ($id, $chain=null) { // List the details of one existing object if ($this->chained !== null) { if ($this->chained->accessright ($this->authHTML["email"], $chain) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedvalues are the information associated to the $chain $chainedvalues = $this->chained->keyexists ($chain); if ($chainedvalues === false) throw new exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authHTML["email"], $id) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->chained !== null && $this->chained->editright ($this->authHTML["email"], $chain) !== true) $this->readwriteAllowed = false; if ($this->readwriteAllowed === true) $this->readwriteAllowed = $this->editright ($this->authHTML["email"], $id); $readonly = $this->readonly ($this->authHTML["email"], $id); $this->connect(); // Get the non mandatory foreign keys and display them instead of the // unusable id $foreignData = array (); if (isset($this->objectDB->foreign)) { foreach ($this->objectDB->foreign as $foreign=>$params) { if (! isset ($params[0])) throw new \Exception ("Undefined foreign key", 500); if (! isset ($params[1])) throw new \Exception ("Undefined foreign key column", 500); $class = $params[0]; $column = $params[1]; require_once ("models/model_$class.php"); $foreignObject = new $class ($this->dsn, $this->username, $this->password, $this->driver_options); if (isset ($foreignObject->unique)) { $tmpData = $foreignObject->read (null, array_merge (array ($column), $foreignObject->unique)); $associated = array (); $unique = reset ($foreignObject->unique); foreach ($tmpData as $vals) { // TODO : If $foreignObject->unique is not an array ? // or multidimensionnal array $associated[$vals[$column]] = $vals[$unique]; } $foreignData[$foreign] = $associated; } } } $content = $this->showflash (); $values = array (); $errors = array(); $titles = $this->objectDB->titles (); $values = $this->objectDB->read (array (array ($this->objectDB->primary, $id))); if (count ($values) === 0) throw new \Exception (dgettext ("domframework", "Object not found"), 404); $values = $values[0]; if (isset ($_SESSION["domframework"]["routeSQL"]["errors"])) { $errors = $_SESSION["domframework"]["routeSQL"]["errors"]; unset ($_SESSION["domframework"]["routeSQL"]["errors"]); } if (isset ($_SESSION["domframework"]["routeSQL"]["values"])) { $values = $_SESSION["domframework"]["routeSQL"]["values"]; unset ($_SESSION["domframework"]["routeSQL"]["values"]); } if ($this->enableInternalCSS === true) { // CSS is in add too ! $content .= "\n"; // CSS is in add too ! } $f = new form (); $fields = array (); foreach ($titles as $key=>$val) { $field = new formfield ($key, $val); if (! isset ($this->objectDB->fields[$key])) throw new \Exception (sprintf (dgettext ("domframework", "Field '%s' (defined in titles) not found in fields"), $key), 500); if (in_array ("not null", $this->objectDB->fields[$key])) $field->mandatory = true; if (in_array ("autoincrement", $this->objectDB->fields[$key])) $field->type = "hidden"; if ($readonly === true || $this->readwriteAllowed === false) $field->readonly = true; if ($key === $this->chainedForeign) { $field->defaults = $chain; $field->readonly = true; } if (isset ($foreignData[$key])) { // Add a select list to the foreign keys : the user can't add a value // that doesn't exists $field->type = "select"; $field->defaults = $foreignData[$key]; } $fields[] = $field; unset ($field); } if ($readonly === false && $this->readwriteAllowed === true) { $field = new formfield ("submit", dgettext ("domframework", "Save the data")); $field->defaults = dgettext ("domframework", "Save the data"); $field->type = "submit"; $fields[] = $field; unset ($field); } $f->fields ($fields); $content .= $f->printHTML ("post", $values, $errors); $this->rendererhtml ($content); }); $route->post ($this->url_prefix."/{id}", function ($id, $chain=null) use ($route) { // Save the details of one existing object if ($this->chained !== null) { if ($this->chained->editright ($this->authHTML["email"], $chain) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } $this->chained->connect(); // $chainedvalues are the information associated to the $chain $chainedvalues = $this->chained->keyexists ($chain); if ($chainedvalues === false) throw new exception (dgettext ("domframework", "Object not found"), 404); } if ($this->accessright ($this->authHTML["email"], $id) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->editright ($this->authHTML["email"], $id) !== TRUE) { if ($this->authHTML["email"] === "anonymous") throw new \Exception (dgettext ("domframework", "Anonymous not allowed"), 401); throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); } if ($this->readonly ($this->authHTML["email"], $id) === TRUE) throw new \Exception (dgettext ("domframework", "Access forbidden"), 403); $this->connect(); $oldvalues = $this->objectDB->read (array (array ($this->objectDB->primary, $id))); if (count ($oldvalues) === 0) throw new \Exception (dgettext ("domframework", "Object not found"), 404); $oldvalues = $oldvalues[0]; $f = new form (); $values = $f->values (); if ($values[$this->objectDB->primary] !== $id) throw new \Exception (dgettext ("domframework", "Can not change the primary key"), 403); $errorsChain = array (); if ($this->chainedForeign !== null && isset ($values[$this->chainedForeign]) && $values[$this->chainedForeign] !== $chain) $errorsChain[$this->chainedForeign] = array ("error", dgettext ("domframework", "Can not change the external key")); if ($this->chainedForeign !== null) $values[$this->chainedForeign] = $chain; $errors = $this->objectDB->verify ($values, $id); if (count ($errors) == 0 && count ($errorsChain) == 0) { try { $this->objectDB->update ($id, $values); $renderer = new renderer (); $renderer->flash ("SUCCESS", dgettext ("domframework", "Update done")); $route->redirect ("/". str_replace ("{chain}", $chain, $this->url_prefix), ""); } catch (\Exception $e) { $renderer = new renderer (); $renderer->flash ("ERROR", $e->getMessage ()); } } else { $renderer = new renderer (); foreach ($errorsChain as $error) $renderer->flash (strtoupper ($error[0]), $error[1]); } // If errors : save them and redirect to the page of editing to be // corrected $_SESSION["domframework"]["routeSQL"]["errors"] = $errors; $_SESSION["domframework"]["routeSQL"]["values"] = $values; $route->redirect ("/".str_replace ("{chain}", $chain, $this->url_prefix). "/$id", ""); }); } /** Authorization : Return TRUE if the user right allow to see the data * Return FALSE else * @param array $auth The auth to authenticate * @param integer|null $id The id to examine */ public function accessright ($auth, $id=null) { // echo "accessright=".var_export ($id, TRUE)." for ". // var_export($this->model_class, TRUE)."\n"; if ($this->authorization !== null) { $result = $this->authorization->allow ($this->module, $auth, "/".$this->model_class."/$id"); // echo "RESULT=$result\n"; if ($result === "RO") return TRUE; if ($result === "RW") return TRUE; return FALSE; } return TRUE; } /** Authorization : Return TRUE if the user right allow to edit the data * Return FALSE else * @param array $auth The auth to authenticate * @param integer|null $id The id to examine */ public function editright ($auth, $id=null) { // echo "editright=".var_export ($id, TRUE)." for ". // var_export($this->model_class, TRUE)."\n"; if ($this->authorization !== null) { $result = $this->authorization->allow ($this->module, $auth, "/".$this->model_class."/$id"); // echo "RESULT=$result\n"; if ($result === "RW") return TRUE; return FALSE; } return TRUE; } /** Authorization : Return TRUE if the $id is in READONLY for the user or * FALSE if the user have the RW rights * @param array $auth The auth to authenticate * @param integer|null $id The id to examine */ public function readonly ($auth, $id=null) { // echo "readonly=".var_export ($id, TRUE)." for ". // var_export($this->model_class, TRUE)."\n"; if ($this->authorization !== null) { $result = $this->authorization->allow ($this->module, $auth, "/".$this->model_class."/$id"); // echo "RESULT=$result\n"; if ($result === "RO") return TRUE; return FALSE; } return FALSE; } /** Return the data of the row if the $id exists in the primary key of the * table * Return FALSE in the other cases * @param integer $id The id to examine */ public function keyexists ($id) { $data = $this->objectDB->read (array (array ($this->objectDB->primary, $id))); if (count ($data) > 0) return $data[0]; return FALSE; } /** Display the data in HTML with the view class/method if they are defined * @param array $data The data to display */ private function rendererhtml ($data) { require_once ("domframework/outputhtml.php"); $route = new route (); $html = new outputhtml (); $replacement = array ("{baseurl}"=>$route->baseURL ()); if ($this->rendererHTMLlayout === false) { $this->rendererHTMLlayout = " {title} {content} "; } // TODO : Test the $this->rendererHTMLclass, $this->rendererHTMLmethod ! echo $html->out ($data, FALSE, $this->rendererHTMLclass, $this->rendererHTMLmethod, $this->rendererHTMLlayout, $replacement); exit; } /** Return the result converted by JSON/XML, defined in REST * @param string $extension The display method * @param array $data The data to return * @param integer $getCode The HTTP code to return (200 OK by default) */ private function renderrest ($extension, $data, $getCode=200) { require_once ("domframework/output$extension.php"); $http = new http (); @header ($_SERVER["SERVER_PROTOCOL"]." $getCode ". $http->codetext ($getCode)); $class = "output$extension"; $result = new $class (); echo $result->out ($data)."\n"; exit; } }