*/ require_once ("domframework/route.php"); require_once ("domframework/form.php"); require_once ("domframework/renderer.php"); error_reporting (E_ALL); /** Automatic Routing for SQL database Allow to do CRUD on datas with only one line in index.php */ class routeSQL { /** 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 informations paginator */ public $topBarLeft = array ("addNew", "numberEntryByDisplay"); /** Definition of the position in top bar at right Allowed : addNew numberEntryByDisplay search informations paginator */ public $topBarRight = array ("search"); /** Definition of the position in bottom bar at left Allowed : addNew numberEntryByDisplay search informations paginator */ public $bottomBarLeft = array ("informations"); /** Definition of the position in bottom bar at right Allowed : addNew numberEntryByDisplay search informations paginator */ public $bottomBarRight = array ("paginator"); /** The cookie path used to determine the old parameters It is automatically generated with the URL */ public $path = ""; /** Authentication */ public $auth = array ("email"=>"anonymous"); /** 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; /** Connect to the database */ 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 informations 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 */ private function paginatorArea ($nbentries, $page, $num, $search) { // The maximum of links available in the paginator $maxClickPaginator = 10; $prePage = false; $postPage = false; $content = "
\n"; $displayedNumbers = 0; for ($i = 1 ; $i < (1+$nbentries/($num)) ; $i++) { if ($i < ($page - $maxClickPaginator/2)) { if ($prePage === false) $content .= " ...\n"; $prePage = true; continue; } if ($displayedNumbers >= $maxClickPaginator) { if ($postPage === false) $content .= " ...\n"; $postPage = true; continue; } $displayedNumbers++; $content .= " url_prefix. "?page=$i&num=$num&search=".urlencode($search)."'"; if ($page == $i) $content .= " class='selected'"; $content .= ">$i\n"; } $content .= "
\n"; return $content; } /** Display the actions buttons outside of the table (actually, juste the 'Add new entry' button */ private function addNewArea ($nbentries, $page, $num, $search) { $content = ""; if ($this->displayActions) { $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 */ 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 informations */ private function informationsArea ($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 and the associated actions */ public function routesHTML () { /** Add HTML routes */ $route = new route (); $route->get ($this->url_prefix."/", function () use ($route) { $route->redirect ("/".$this->url_prefix, ""); }); $route->get ($this->url_prefix. "(\?({p1}=({v1})?)(&{p2}=({v2})?(&{p3}=({v3})?)?)?)?", function ($p1, $v1, $p2, $v2, $p3, $v3) use ($route) { // List all the objects of the table if ($this->accessright () !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } // 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 (isset ($_COOKIE["num"])) $num = $_COOKIE["num"]; if (isset ($_COOKIE["page"])) $page = $_COOKIE["page"]; if (isset ($_COOKIE["search"])) $search = $_COOKIE["search"]; 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 ("page", $page, time()+3600*24*30, $this->path); setcookie ("num", $num, time()+3600*24*30, $this->path); setcookie ("search", $search, time()+3600*24*30, $this->path); //echo "PAGE=$page\n"; //echo "NUM=$num\n"; //echo "SEARCH=$search\n"; //$route->debug=1; $this->connect(); $csrf = new csrf (); $token = $csrf->createToken (); $titles = $this->objectDB->titles (); if ($search === "") $datas = $this->objectDB->read (); 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"); } $datas = $this->objectDB->read ($criteria, null, null, true); } $nbentries = count ($datas); if ($num > 1000) $route->redirect ("/".$this->url_prefix. "?page=$page&num=1000&search=$search", ""); if ($page < 1) $route->redirect ("/".$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 ("/".$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->displayActions && $this->actionsAtEnd === false) $content .= " \n"; foreach ($titles as $title) $content .= " \n"; if ($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->displayActions) $countTitles++; $content .= " \n"; } else { $i = 1; $odd = "odd"; foreach ($datas 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->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.""; $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"; echo $content; }); $route->get ($this->url_prefix."/{id}/delete/{token}", function ($id, $token) { // Delete an existing object if the token is valid if ($this->accessright ($id) !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } if ($this->editright ($id) !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } if ($this->readonly ($id) === TRUE) throw new Exception (_("Access forbidden"), 403); $this->connect(); $csrf = new csrf (); $renderer = new renderer (); $route = new route (); try { $csrf->checkToken ($token); $this->objectDB->delete ($id); $route->redirect ("/".$this->url_prefix, ""); } catch (Exception $e) { $renderer->flash ("ERROR", $e->getMessage()); $route->redirect ("/".$this->url_prefix, ""); } }); $route->get ($this->url_prefix."/add", function () { // Add a new entry : form to be filled by the user if ($this->accessright () !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } if ($this->editright () !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } if ($this->readonly () === TRUE) throw new Exception (_("Access forbidden"), 403); $this->connect(); $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"; $fields[] = $field; unset ($field); } $field = new formfield ("submit", _("Save the datas")); $field->defaults = _("Save the datas"); $field->type = "submit"; $fields[] = $field; unset ($field); $f->fields ($fields); $content .= $f->printHTML ("post", $values, $errors); echo $content; }); $route->post ($this->url_prefix."/add", function () use ($route) { // Add a new entry : effective save of the datas if ($this->accessright () !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } if ($this->editright () !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } if ($this->readonly () === TRUE) throw new Exception (_("Access forbidden"), 403); $this->connect(); $f = new form (); $values = $f->values (); $errors = $this->objectDB->verify ($values); if (count ($errors) == 0) { try { $this->objectDB->insert ($values); $renderer = new renderer (); $renderer->flash ("SUCCESS", _("Creation done")); $route->redirect ("/".$this->url_prefix, ""); } catch (Exception $e) { $renderer = new renderer (); $renderer->flash ("ERROR", $e->getMessage ()); } } else { $renderer = new renderer (); foreach ($errors 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 ("/".$this->url_prefix."/add", ""); }); $route->get ($this->url_prefix."/{id}", function ($id) { // List the details of one existing object if ($this->accessright ($id) !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } $editright = $this->editright ($id); $readonly = $this->readonly ($id); $this->connect(); $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 || $editright === false) $field->readonly = true; $fields[] = $field; unset ($field); } if ($readonly === false && $editright === true) { $field = new formfield ("submit", _("Save the datas")); $field->defaults = _("Save the datas"); $field->type = "submit"; $fields[] = $field; unset ($field); } $f->fields ($fields); $content .= $f->printHTML ("post", $values, $errors); echo $content; }); $route->post ($this->url_prefix."/{id}", function ($id) use ($route) { // Save the details of one existing object if ($this->accessright ($id) !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } if ($this->editright ($id) !== TRUE) { if ($this->auth["email"] === "anonymous") throw new Exception (_("Anonymous not allowed"), 401); throw new Exception (_("Access forbidden"), 403); } if ($this->readonly ($id) === TRUE) throw new Exception (_("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); $errors = $this->objectDB->verify ($values, $id); if (count ($errors) == 0) { try { $this->objectDB->update ($id, $values); $renderer = new renderer (); $renderer->flash ("SUCCESS", _("Update done")); $route->redirect ("/".$this->url_prefix, ""); } catch (Exception $e) { $renderer = new renderer (); $renderer->flash ("ERROR", $e->getMessage ()); } } else { $renderer = new renderer (); foreach ($errors 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 ("/".$this->url_prefix."/$id", ""); }); } /** Authorization : Return TRUE if the user right allow to see the datas Return FALSE else */ public function accessright () { return TRUE; } /** Authorization : Return TRUE if the user right allow to edit the datas Return FALSE else */ public function editright () { return TRUE; } /** Authorization : Return TRUE if the $id is in READONLY for the user or FALSE if the user have the RW rights */ public function readonly () { return FALSE; } }