* @license BSD */ namespace Domframework; /** * 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 = ["addNew", "numberEntryByDisplay"]; /** * Definition of the position in top bar at right * Allowed : addNew numberEntryByDisplay search information paginator */ public $topBarRight = ["search"]; /** * Definition of the position in bottom bar at left * Allowed : addNew numberEntryByDisplay search information paginator */ public $bottomBarLeft = ["information"]; /** * Definition of the position in bottom bar at right * Allowed : addNew numberEntryByDisplay search information paginator */ public $bottomBarRight = ["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 = ["email" => "anonymous"]; /** * Authentication for REST part */ public $authREST = ["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 = []; /** * 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 = ["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) { $alert = ""; $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, true)) { 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 = [[$this->chainedForeign, $chain]]; } if ($search === "") { $data = $this->objectDB->read( null, array_keys($titles), null, null, $foreignSelect ); } else { $criteria = []; 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[] = [$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, true)) { throw new \Exception(dgettext( "domframework", "Extension not allowed" ), 403); } $this->connect(); $values = $_POST; $errorsChain = []; if ( $this->chainedForeign !== null && isset($values[$this->chainedForeign]) && $values[$this->chainedForeign] !== $chain ) { $errorsChain[$this->chainedForeign] = ["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, true)) { throw new \Exception(dgettext( "domframework", "Extension not allowed" ), 403); } $this->connect(); parse_str(file_get_contents("php://input"), $values); $errorsChain = []; if ( $this->chainedForeign !== null && isset($values[$this->chainedForeign]) && $values[$this->chainedForeign] !== $chain ) { $errorsChain[$this->chainedForeign] = ["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, true)) { 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 = [[$this->chainedForeign, $chain]]; } if ($search === "") { $data = $this->objectDB->read( null, array_keys($titles), null, null, $foreignSelect ); } else { $criteria = []; 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[] = [$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 = []; 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( [$column], $foreignObject->unique )); $associated = []; $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 = []; 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( [$column], $foreignObject->unique )); $associated = []; $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 = []; $errors = []; $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 = []; 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], true)) { $field->mandatory = true; } if (in_array("autoincrement", $this->objectDB->fields[$key], true)) { $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 = []; if ( $this->chainedForeign !== null && isset($values[$this->chainedForeign]) && $values[$this->chainedForeign] !== $chain ) { $errorsChain[$this->chainedForeign] = ["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 = []; 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( [$column], $foreignObject->unique )); $associated = []; $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 = []; $errors = []; $titles = $this->objectDB->titles(); $values = $this->objectDB->read([[$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 = []; 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], true)) { $field->mandatory = true; } if (in_array("autoincrement", $this->objectDB->fields[$key], true)) { $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([[$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 = []; if ( $this->chainedForeign !== null && isset($values[$this->chainedForeign]) && $values[$this->chainedForeign] !== $chain ) { $errorsChain[$this->chainedForeign] = ["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([[$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) { $route = new Route(); $html = new Outputhtml(); $replacement = ["{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) { $http = new Http(); @header($_SERVER["SERVER_PROTOCOL"] . " $getCode " . $http->codetext($getCode)); $class = "Output$extension"; $result = new $class(); echo $result->out($data) . "\n"; exit; } }